Skip to content

Commit

Permalink
Admin settings config hoarder-app#282
Browse files Browse the repository at this point in the history
created the basic structure
  • Loading branch information
kamtschatka committed Oct 1, 2024
1 parent 8b69cdd commit 33f49ef
Show file tree
Hide file tree
Showing 6 changed files with 467 additions and 7 deletions.
137 changes: 130 additions & 7 deletions apps/web/app/dashboard/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,117 @@ import { redirect } from "next/navigation";
import AdminActions from "@/components/dashboard/admin/AdminActions";
import ServerStats from "@/components/dashboard/admin/ServerStats";
import UserList from "@/components/dashboard/admin/UserList";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { getServerAuthSession } from "@/server/auth";
import { Save, Undo } from "lucide-react";

import {
ConfigSubSection,
SectionSymbol,
serverConfig,
} from "@hoarder/db/config/config";
import {
ConfigType,
ConfigValue,
InferenceProviderEnum,
} from "@hoarder/db/config/configValue";

function createTabsTrigger(sectionName: string, section: ConfigSubSection) {
return (
<TabsTrigger value={sectionName}>{section[SectionSymbol].name}</TabsTrigger>
);
}

function createTab(sectionName: string, section: ConfigSubSection) {
return (
<TabsContent value={sectionName}>
<div className="flex flex-col gap-5 rounded-md border bg-background p-4">
<Table className="lg:w-1/2">
<TableBody>
{Object.values(section).map((value) => createConfigValueUI(value))}
<TableRow>
<TableCell></TableCell>
<TableCell>
<Undo className="size-7 pr-2" />
Reset
<Save className="size-7 pr-2" />
Save
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</TabsContent>
);
}

const LOOKUP_TABLE = {
[ConfigType.BOOLEAN]: createBooleanRow,
[ConfigType.STRING]: createStringRow,
[ConfigType.NUMBER]: createNumberRow,
[ConfigType.INFERENCE_PROVIDER_ENUM]: createInferenceProviderRow,
};

function createConfigValueUI(configValue: ConfigValue<ConfigType>) {
return (
<TableRow>
<TableCell>{configValue.name}</TableCell>
<TableCell>{LOOKUP_TABLE[configValue.type](configValue)}</TableCell>
</TableRow>
);
}

function createBooleanRow(configValue: ConfigValue<ConfigType>) {
return <Switch checked={configValue.defaultValue as boolean} />;
}
function createNumberRow(configValue: ConfigValue<ConfigType>) {
return (
<Input
type="number"
value={configValue.defaultValue as number}
className="w-100"
/>
);
}

function createStringRow(configValue: ConfigValue<ConfigType>) {
return (
<Input
type="text"
value={configValue.defaultValue?.toString()}
className="w-100"
/>
);
}

function createInferenceProviderRow(configValue: ConfigValue<ConfigType>) {
return (
<>
<Select value={configValue.defaultValue as string}>
<SelectTrigger className="w-fit">
<SelectValue placeholder={configValue.defaultValue} />
</SelectTrigger>
<SelectContent>
{Object.values(InferenceProviderEnum).map((option) => (
<SelectItem key={option} value={option}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
</>
);
}

export default async function AdminPage() {
const session = await getServerAuthSession();
Expand All @@ -11,13 +121,26 @@ export default async function AdminPage() {
}
return (
<>
<div className="rounded-md border bg-background p-4">
<ServerStats />
<AdminActions />
</div>
<div className="mt-4 rounded-md border bg-background p-4">
<UserList />
</div>
<Tabs defaultValue="information" className="w-full">
<TabsList className="grid w-full grid-cols-6 p-5">
<TabsTrigger value="information">Information</TabsTrigger>
{Object.entries(serverConfig).map((entry) =>
createTabsTrigger(entry[0], entry[1]),
)}
</TabsList>
<TabsContent value="information">
<div className="rounded-md border bg-background p-4">
<ServerStats />
<AdminActions />
</div>
<div className="mt-4 rounded-md border bg-background p-4">
<UserList />
</div>
</TabsContent>
{Object.entries(serverConfig).map((entry) =>
createTab(entry[0], entry[1]),
)}
</Tabs>
</>
);
}
28 changes: 28 additions & 0 deletions apps/web/components/ui/switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";
import * as SwitchPrimitives from "@radix-ui/react-switch";

const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className,
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
)}
/>
</SwitchPrimitives.Root>
));
Switch.displayName = SwitchPrimitives.Root.displayName;

export { Switch };
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-toggle": "^1.0.3",
Expand Down
151 changes: 151 additions & 0 deletions packages/db/config/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { z } from "zod";

import {
ConfigType,
ConfigValue,
InferenceProviderEnum,
InferenceProviderEnumValidator,
} from "./configValue";

export const SectionSymbol = Symbol("section");

export interface SectionInformation {
name: string;
}

export interface ConfigSubSection {
[SectionSymbol]: SectionInformation;
[configValue: string]: ConfigValue<ConfigType>;
}

export type ServerConfig = Record<string, ConfigSubSection>;

export const serverConfig: ServerConfig = {
generalConfig: {
[SectionSymbol]: {
name: "General",
},
disableSignups: new ConfigValue({
key: "DISABLE_SIGNUPS",
name: "Disable Signups",
type: ConfigType.BOOLEAN,
defaultValue: false,
validator: z.boolean(),
}),
maxAssetSize: new ConfigValue({
key: "MAX_ASSET_SIZE_MB",
name: "Maximum Asset size(MB)",
type: ConfigType.NUMBER,
defaultValue: 4,
validator: z.number().positive(),
}),
disableNewReleaseCheck: new ConfigValue({
key: "DISABLE_NEW_RELEASE_CHECK",
name: "Disable new release check",
type: ConfigType.BOOLEAN,
defaultValue: false,
validator: z.boolean(),
}),
},
crawlerConfig: {
[SectionSymbol]: {
name: "Crawler",
},
downloadBannerImage: new ConfigValue({
key: "CRAWLER_DOWNLOAD_BANNER_IMAGE",
name: "Download Banner Image",
type: ConfigType.BOOLEAN,
defaultValue: true,
validator: z.boolean(),
}),
storeScreenshot: new ConfigValue({
key: "CRAWLER_STORE_SCREENSHOT",
name: "Disable screenshots",
type: ConfigType.BOOLEAN,
defaultValue: true,
validator: z.boolean(),
}),
storeFullPageScreenshot: new ConfigValue({
key: "CRAWLER_FULL_PAGE_SCREENSHOT",
name: "Store full page screenshots",
type: ConfigType.BOOLEAN,
defaultValue: false,
validator: z.boolean(),
}),
fullPageArchive: new ConfigValue({
key: "CRAWLER_FULL_PAGE_ARCHIVE",
name: "Store full page archive",
type: ConfigType.BOOLEAN,
defaultValue: false,
validator: z.boolean(),
}),
jobTimeout: new ConfigValue({
key: "CRAWLER_JOB_TIMEOUT_SEC",
name: "Job Timeout (sec)",
type: ConfigType.NUMBER,
defaultValue: 60,
validator: z.number().positive(),
}),
navigateTimeout: new ConfigValue({
key: "CRAWLER_NAVIGATE_TIMEOUT_SEC",
name: "Navigate Timeout (sec)",
type: ConfigType.NUMBER,
defaultValue: 30,
validator: z.number().positive(),
}),
},
inferenceConfig: {
[SectionSymbol]: {
name: "Inference Config",
},
inferenceProvider: new ConfigValue({
key: "INFERENCE_PROVIDER",
name: "Inference Provider",
type: ConfigType.INFERENCE_PROVIDER_ENUM,
defaultValue: InferenceProviderEnum.DISABLED,
validator: InferenceProviderEnumValidator,
}),
openApiKey: new ConfigValue({
key: "OPENAI_API_KEY",
name: "OpenAPI Key",
type: ConfigType.STRING,
defaultValue: "",
validator: z.string(),
}),
openAiBaseUrl: new ConfigValue({
key: "OPENAI_BASE_URL",
name: "OpenAPI base URL",
type: ConfigType.STRING,
defaultValue: "",
validator: z.string(),
}),
ollamaBaseUrl: new ConfigValue({
key: "OLLAMA_BASE_URL",
name: "Ollama base URL",
type: ConfigType.STRING,
defaultValue: "",
validator: z.string(),
}),
inferenceTextModel: new ConfigValue({
key: "INFERENCE_TEXT_MODEL",
name: "Inference text model",
type: ConfigType.STRING,
defaultValue: "gpt-3.5-turbo-0125",
validator: z.string(),
}),
inferenceImageModel: new ConfigValue({
key: "INFERENCE_IMAGE_MODEL",
name: "Inference image model",
type: ConfigType.STRING,
defaultValue: "gpt-4o-2024-05-13",
validator: z.string(),
}),
inferenceLanguage: new ConfigValue({
key: "INFERENCE_LANG",
name: "Inference language",
type: ConfigType.STRING,
defaultValue: "english",
validator: z.string(),
}),
},
};
Loading

0 comments on commit 33f49ef

Please sign in to comment.