From bbb7f398ab411bed7daaa80f8c400e0f49c0d0ac Mon Sep 17 00:00:00 2001 From: BennyKok Date: Fri, 15 Dec 2023 17:58:19 +0800 Subject: [PATCH] feat: add api generation --- web/bun.lockb | Bin 329462 -> 329863 bytes web/drizzle/0009_easy_banshee.sql | 16 + web/drizzle/meta/0009_snapshot.json | 614 +++++++++++++++++++++++ web/drizzle/meta/_journal.json | 7 + web/package.json | 1 + web/src/app/api-keys/page.tsx | 32 ++ web/src/app/machines/page.tsx | 27 +- web/src/components/APIKeyList.tsx | 417 +++++++++++++++ web/src/components/MachineList.tsx | 16 +- web/src/components/NavbarRight.tsx | 15 +- web/src/components/RunDisplay.tsx | 2 +- web/src/components/VersionSelect.tsx | 2 +- web/src/components/callServerPromise.tsx | 17 + web/src/db/schema.ts | 14 + web/src/server/curdApiKeys.ts | 78 +++ 15 files changed, 1213 insertions(+), 45 deletions(-) create mode 100644 web/drizzle/0009_easy_banshee.sql create mode 100644 web/drizzle/meta/0009_snapshot.json create mode 100644 web/src/app/api-keys/page.tsx create mode 100644 web/src/components/APIKeyList.tsx create mode 100644 web/src/components/callServerPromise.tsx create mode 100644 web/src/server/curdApiKeys.ts diff --git a/web/bun.lockb b/web/bun.lockb index db0303910883c437a5a1ec48a0962566b94d9870..44a53d613cd9c2d0fb5b2a886f6a93967a969519 100755 GIT binary patch delta 54685 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 delta 53760 zcmeFad7O^r-~WFtF6NR6NlcQ^V${&sW*FuoDUxJghXxaav5jqL%qWUVr86Ct?1>?v z4IxPj71Bzj(rVOHTB&^cJzwW}Tx0a{>F)l1@B8=r{n7RCn%D7u9q(gb&gHt!(;c(V z{dv~8_tlFZkb3y@^;K#R5`ZoTm0pD!={i6jHZY~}2=~Hob zj~amvF&idUDCOK&>Y~p1@jhSJ=gS{8IALtgsEq6n>*9kwacstjLHO^-j>X;ypAA1{ z8UM@?W0QSju+KtI#8>>u6;2>j&p*sb3|*Et=}Mok0%9htOdqT7^IZyW!22Tje(Ve2 z95@cH7x4Mcg%>yQ^p5D~U>7#@`D(&VtX~bd3ifpT&WFokSAx^ws&G~EInU?I%Rf8G zi&z7zc0a;u#R6CryB)3!mu~9y!pUThN1|6j55h|KSrebH2D}Sag&&70AparDn^L@d zm%ysP442#c^0Nu3fHYVME^px#dTE7uKd;qQUSW;la>NbH&eqWR>R@ZAvL}pTFnqpgt-Z!nLRTH;U+ty)8kS#58;|dV z%VKAxk4hLhV5skI>(}KPFa33vC!y!=YndJ!40n=msgBVo`0Uw7>CWE9`lD=0lX zXT-o^nPanTh6(Mw(U=dbS7*TLgp1)YTng5-c=b9jKI*p#Tf_T3`JUs;^X0$M-YcNL z&A89?p8Z2BkE1Q^-@(hc8?1^B8aXy$V0w1Oh>grvV{dB!rIMDC&RfpGi z_PTrlEWh4eyo@jJ>Q&%ZY|Wp&*qVRmz*-nzTED$;o`lBTz4@{dmgDUf32X}2({pa zeZ3|7E3As$oaVJ;Z1(WX0d(I}*jj0g`gsN2hMx+o(BCWQIJz?KL=T+CN8c$)Z&C$ZP1T;DC`6vNOi`eCx0^ z1oy(4WD{T&JZ@YjIr)6uv1?+th1I}%uqt|<^$Wq8BVXrHfLhvom{&keSY5jpU0pbC zxX)J`UV$ACPlJ_lFIbyfb66F*09HjpuqymTy4_=7<+C1E1?RxZe*}CPoY$3rYL;My zv*8-pM{e?V*Sl%0y6Q!2Rb-jvsc=nfXRKGTZ1^JV<>S19t|wlTvH`68-yiQ4ycJgd z%V6|o`S+1PV=@-UShyn@X-MkZ49i<_Iz zCS!2M#8IPsUs`=$jtT~AYFOv*WhySO863Z zXm&zQde(5~+Hw{1=Amhj?}5*PZ?(Q#r+I1C!D`uc)4g?`Ie5g#F>DB<@zW$2JbdJU zbk=)xgSk0##Gr(6qkP+M_wswv@~DL2BL}9BrDDS~vogo}p1i}2FPooEMp_naoWtd6 zRID-6OLp_{^s!?z2KhQW6=SQkyYWu1p&8ku#$-@XC-F^3nLq ztetna=U2%Y9b3a6VA*EyS=4Y5^f=HBad!;tjs1j-&f23y_H=030a4>*Of zH8N|@_9ku^*1R~H>rK#ouqNhD_j^luJGR=?lR8}lPlVkq;Q?>{#LV&L*+sCX%RT5C zpzMsS@mitVoY7}rmiMvoYCQ~AT`J0&lsMd&tZ019bK3B5c^HQa`9)sZ+hN7cTH@LFFZF8tA*|_h z4XkO>1y)_Nmw9>C#8zGFE%(wdhuu1l95smIeD`9jPE+yIbUO5~XAi@sj(PcYZNx__ zyo~!{tDqEE1?8{u5`F+{uHQUzOhQKbz#+X>dhs7T;x%@(jlX`ix3mwzs@R?QiT7Y@ z$hN|&$P;cnN7z;bR8W61Q13b

b7QYqLtUT*JnPV9kvp{M6C|u=bMOuqv3Do#3wP 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 bN)nol`6U?-8NBaP9N>7x^w4$&{kV{ diff --git a/web/drizzle/0009_easy_banshee.sql b/web/drizzle/0009_easy_banshee.sql new file mode 100644 index 0000000..67d2eef --- /dev/null +++ b/web/drizzle/0009_easy_banshee.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS "comfyui_deploy"."api_keys" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "key" text NOT NULL, + "name" text NOT NULL, + "user_id" text NOT NULL, + "org_id" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "api_keys_key_unique" UNIQUE("key") +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "comfyui_deploy"."api_keys" ADD CONSTRAINT "api_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/web/drizzle/meta/0009_snapshot.json b/web/drizzle/meta/0009_snapshot.json new file mode 100644 index 0000000..fc4084a --- /dev/null +++ b/web/drizzle/meta/0009_snapshot.json @@ -0,0 +1,614 @@ +{ + "id": "a5d542ea-484f-431a-b31f-170e789a03a7", + "prevId": "bba0fc3e-5729-44b8-bf45-d3f194cc79c0", + "version": "5", + "dialect": "pg", + "tables": { + "api_keys": { + "name": "api_keys", + "schema": "comfyui_deploy", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_key_unique": { + "name": "api_keys_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + } + }, + "deployments": { + "name": "deployments", + "schema": "comfyui_deploy", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_version_id": { + "name": "workflow_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment": { + "name": "environment", + "type": "deployment_environment", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "deployments_user_id_users_id_fk": { + "name": "deployments_user_id_users_id_fk", + "tableFrom": "deployments", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployments_workflow_version_id_workflow_versions_id_fk": { + "name": "deployments_workflow_version_id_workflow_versions_id_fk", + "tableFrom": "deployments", + "tableTo": "workflow_versions", + "columnsFrom": [ + "workflow_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployments_workflow_id_workflows_id_fk": { + "name": "deployments_workflow_id_workflows_id_fk", + "tableFrom": "deployments", + "tableTo": "workflows", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployments_machine_id_machines_id_fk": { + "name": "deployments_machine_id_machines_id_fk", + "tableFrom": "deployments", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "machines": { + "name": "machines", + "schema": "comfyui_deploy", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "machines_user_id_users_id_fk": { + "name": "machines_user_id_users_id_fk", + "tableFrom": "machines", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "schema": "comfyui_deploy", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "workflow_run_outputs": { + "name": "workflow_run_outputs", + "schema": "comfyui_deploy", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_run_outputs_run_id_workflow_runs_id_fk": { + "name": "workflow_run_outputs_run_id_workflow_runs_id_fk", + "tableFrom": "workflow_run_outputs", + "tableTo": "workflow_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "workflow_runs": { + "name": "workflow_runs", + "schema": "comfyui_deploy", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workflow_version_id": { + "name": "workflow_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workflow_inputs": { + "name": "workflow_inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "workflow_run_status", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_runs_workflow_version_id_workflow_versions_id_fk": { + "name": "workflow_runs_workflow_version_id_workflow_versions_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "workflow_versions", + "columnsFrom": [ + "workflow_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_runs_workflow_id_workflows_id_fk": { + "name": "workflow_runs_workflow_id_workflows_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "workflows", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_runs_machine_id_machines_id_fk": { + "name": "workflow_runs_machine_id_machines_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "machines", + "columnsFrom": [ + "machine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "workflows": { + "name": "workflows", + "schema": "comfyui_deploy", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflows_user_id_users_id_fk": { + "name": "workflows_user_id_users_id_fk", + "tableFrom": "workflows", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "workflow_versions": { + "name": "workflow_versions", + "schema": "comfyui_deploy", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workflow": { + "name": "workflow", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "workflow_api": { + "name": "workflow_api", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_versions_workflow_id_workflows_id_fk": { + "name": "workflow_versions_workflow_id_workflows_id_fk", + "tableFrom": "workflow_versions", + "tableTo": "workflows", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "deployment_environment": { + "name": "deployment_environment", + "values": { + "staging": "staging", + "production": "production" + } + }, + "workflow_run_status": { + "name": "workflow_run_status", + "values": { + "not-started": "not-started", + "running": "running", + "success": "success", + "failed": "failed" + } + } + }, + "schemas": { + "comfyui_deploy": "comfyui_deploy" + }, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/web/drizzle/meta/_journal.json b/web/drizzle/meta/_journal.json index 862d636..97faadb 100644 --- a/web/drizzle/meta/_journal.json +++ b/web/drizzle/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1702615888467, "tag": "0008_futuristic_adam_destine", "breakpoints": true + }, + { + "idx": 9, + "version": "5", + "when": 1702632471725, + "tag": "0009_easy_banshee", + "breakpoints": true } ] } \ No newline at end of file diff --git a/web/package.json b/web/package.json index 7823595..9f6f6d3 100644 --- a/web/package.json +++ b/web/package.json @@ -34,6 +34,7 @@ "dayjs": "^1.11.10", "drizzle-orm": "^0.29.1", "lucide-react": "^0.294.0", + "nanoid": "^5.0.4", "next": "14.0.3", "next-usequerystate": "^1.13.2", "react": "^18", diff --git a/web/src/app/api-keys/page.tsx b/web/src/app/api-keys/page.tsx new file mode 100644 index 0000000..13a43a0 --- /dev/null +++ b/web/src/app/api-keys/page.tsx @@ -0,0 +1,32 @@ +import { APIKeyList } from "@/components/APIKeyList"; +import { getAPIKeys } from "@/server/curdApiKeys"; +import { auth } from "@clerk/nextjs"; + +export default function Page() { + return ; +} + +async function Component() { + const { userId } = await auth(); + + if (!userId) { + return

; + } + + const workflow = await getAPIKeys(); + + return ( +
+ { + return { + id: x.id, + name: x.name, + date: x.updated_at, + endpoint: `****${x.key.slice(-4)}`, + }; + })} + /> +
+ ); +} diff --git a/web/src/app/machines/page.tsx b/web/src/app/machines/page.tsx index 6cc64a3..4d0bf7f 100644 --- a/web/src/app/machines/page.tsx +++ b/web/src/app/machines/page.tsx @@ -1,7 +1,7 @@ import { MachineList } from "@/components/MachineList"; import { db } from "@/db/db"; -import { machinesTable, usersTable } from "@/db/schema"; -import { auth, clerkClient } from "@clerk/nextjs"; +import { machinesTable } from "@/db/schema"; +import { auth } from "@clerk/nextjs"; import { desc, eq } from "drizzle-orm"; export default function Page() { @@ -36,26 +36,3 @@ async function MachineListServer() { ); } - -async function setInitialUserData(userId: string) { - const user = await clerkClient.users.getUser(userId); - - // incase we dont have username such as google login, fallback to first name + last name - const usernameFallback = - user.username ?? (user.firstName ?? "") + (user.lastName ?? ""); - - // For the display name, if it for some reason is empty, fallback to username - let nameFallback = (user.firstName ?? "") + (user.lastName ?? ""); - if (nameFallback === "") { - nameFallback = usernameFallback; - } - - const result = await db.insert(usersTable).values({ - id: userId, - // this is used for path, make sure this is unique - username: usernameFallback, - - // this is for display name, maybe different from username - name: nameFallback, - }); -} diff --git a/web/src/components/APIKeyList.tsx b/web/src/components/APIKeyList.tsx new file mode 100644 index 0000000..7cf7ccc --- /dev/null +++ b/web/src/components/APIKeyList.tsx @@ -0,0 +1,417 @@ +"use client"; + +import { getRelativeTime } from "../lib/getRelativeTime"; +import { LoadingIcon } from "./LoadingIcon"; +import { callServerPromise } from "./callServerPromise"; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Form, +} from "./ui/form"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { addNewAPIKey, deleteAPIKey } from "@/server/curdApiKeys"; +import { zodResolver } from "@hookform/resolvers/zod"; +import type { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, +} from "@tanstack/react-table"; +import { + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { ArrowUpDown, MoreHorizontal } from "lucide-react"; +import * as React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +export type APIKey = { + id: string; + name: string; + endpoint: string; + date: Date; +}; + +export const columns: ColumnDef[] = [ + { + accessorKey: "id", + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "name", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return ( + //
+ row.getValue("name") + // + ); + }, + }, + { + accessorKey: "endpoint", + header: () =>
Endpoint
, + cell: ({ row }) => { + return ( +
{row.original.endpoint}
+ ); + }, + }, + { + accessorKey: "date", + sortingFn: "datetime", + enableSorting: true, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => ( +
+ {getRelativeTime(row.original.date)} +
+ ), + }, + + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + const workflow = row.original; + + return ( + + + + + + Actions + { + callServerPromise(deleteAPIKey(workflow.id)); + }} + > + Delete API Key + + {/* + View customer + View payment details */} + + + ); + }, + }, +]; + +export function APIKeyList({ data }: { data: APIKey[] }) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + + const table = useReactTable({ + data, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }); + + return ( +
+
+ + table.getColumn("name")?.setFilterValue(event.target.value) + } + className="max-w-sm" + /> +
+ + {/* + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + */} +
+
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. +
+
+ + +
+
+
+ ); +} + +const formSchema = z.object({ + name: z.string().min(1), +}); + +function AddMachinesDialog() { + const [open, setOpen] = React.useState(false); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "My API Key", + }, + }); + + const [apiKey, setAPIKey] = React.useState + > | null>(); + + return ( + { + setOpen(open); + if (!open) setAPIKey(null); + }} + > + + + + +
+ { + const apiKey = await callServerPromise(addNewAPIKey(data.name)); + if (apiKey) setAPIKey(apiKey); + // setOpen(false); + })} + > + + Create API Key + + Create API Key for workflow upload + + +
+ {/*
*/} + ( + + Name + + + + + + )} + /> + {apiKey && ( + + API Key (Copy the API key now) + + + + {/* */} + + )} +
+ + {apiKey ? ( + + ) : ( + + )} + + + + +
+ ); +} diff --git a/web/src/components/MachineList.tsx b/web/src/components/MachineList.tsx index 3783977..eedaf29 100644 --- a/web/src/components/MachineList.tsx +++ b/web/src/components/MachineList.tsx @@ -2,6 +2,7 @@ import { getRelativeTime } from "../lib/getRelativeTime"; import { LoadingIcon } from "./LoadingIcon"; +import { callServerPromise } from "./callServerPromise"; import { FormControl, FormField, @@ -56,7 +57,6 @@ import { import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import * as React from "react"; import { useForm } from "react-hook-form"; -import { toast } from "sonner"; import { z } from "zod"; export type Machine = { @@ -176,20 +176,6 @@ export const columns: ColumnDef[] = [ }, ]; -export async function callServerPromise(result: Promise) { - return result - .then((x) => { - if ((x as { message: string })?.message !== undefined) { - toast.success((x as { message: string }).message); - } - return x; - }) - .catch((error) => { - toast.error(error.message); - return null; - }); -} - export function MachineList({ data }: { data: Machine[] }) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState( diff --git a/web/src/components/NavbarRight.tsx b/web/src/components/NavbarRight.tsx index 14364ca..f64a853 100644 --- a/web/src/components/NavbarRight.tsx +++ b/web/src/components/NavbarRight.tsx @@ -10,19 +10,28 @@ export function NavbarRight() { return ( { if (value === "machines") { router.push("/machines"); + } else if (value === "api-keys") { + router.push("/api-keys"); } else { router.push("/"); } }} > - + Workflow Machines + API Keys ); diff --git a/web/src/components/RunDisplay.tsx b/web/src/components/RunDisplay.tsx index 3273035..d9b035d 100644 --- a/web/src/components/RunDisplay.tsx +++ b/web/src/components/RunDisplay.tsx @@ -1,7 +1,7 @@ "use client"; import { LiveStatus } from "./LiveStatus"; -import { callServerPromise } from "@/components/MachineList"; +import { callServerPromise } from "./callServerPromise"; import { Dialog, DialogContent, diff --git a/web/src/components/VersionSelect.tsx b/web/src/components/VersionSelect.tsx index 9e45c0e..bca9b08 100644 --- a/web/src/components/VersionSelect.tsx +++ b/web/src/components/VersionSelect.tsx @@ -1,7 +1,7 @@ "use client"; +import { callServerPromise } from "./callServerPromise"; import { LoadingIcon } from "@/components/LoadingIcon"; -import { callServerPromise } from "@/components/MachineList"; import { Button } from "@/components/ui/button"; import { DropdownMenu, diff --git a/web/src/components/callServerPromise.tsx b/web/src/components/callServerPromise.tsx new file mode 100644 index 0000000..ac5d06a --- /dev/null +++ b/web/src/components/callServerPromise.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { toast } from "sonner"; + +export async function callServerPromise(result: Promise) { + return result + .then((x) => { + if ((x as { message: string })?.message !== undefined) { + toast.success((x as { message: string }).message); + } + return x; + }) + .catch((error) => { + toast.error(error.message); + return null; + }); +} diff --git a/web/src/db/schema.ts b/web/src/db/schema.ts index 1d999be..e832462 100644 --- a/web/src/db/schema.ts +++ b/web/src/db/schema.ts @@ -203,5 +203,19 @@ export const deploymentsRelations = relations(deploymentsTable, ({ one }) => ({ }), })); +export const apiKeyTable = dbSchema.table("api_keys", { + id: uuid("id").primaryKey().defaultRandom().notNull(), + key: text("key").notNull().unique(), + name: text("name").notNull(), + user_id: text("user_id") + .references(() => usersTable.id, { + onDelete: "cascade", + }) + .notNull(), + org_id: text("org_id"), + created_at: timestamp("created_at").defaultNow().notNull(), + updated_at: timestamp("updated_at").defaultNow().notNull(), +}); + export type UserType = InferSelectModel; export type WorkflowType = InferSelectModel; diff --git a/web/src/server/curdApiKeys.ts b/web/src/server/curdApiKeys.ts new file mode 100644 index 0000000..d55bc47 --- /dev/null +++ b/web/src/server/curdApiKeys.ts @@ -0,0 +1,78 @@ +"use server"; + +import { db } from "@/db/db"; +import { apiKeyTable } from "@/db/schema"; +import { auth } from "@clerk/nextjs"; +import { and, desc, eq } from "drizzle-orm"; +import { customAlphabet } from "nanoid"; +import { revalidatePath } from "next/cache"; + +export const nanoid = customAlphabet( + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +); + +const prefixes = { + cd: "cd", +} as const; + +function newId(prefix: keyof typeof prefixes): string { + return [prefixes[prefix], nanoid(16)].join("_"); +} + +export async function addNewAPIKey(name: string) { + const { userId, orgId } = auth(); + + if (!userId) throw new Error("No user id"); + + const key = await db + .insert(apiKeyTable) + .values({ + name: name, + key: newId("cd"), + user_id: userId, + org_id: orgId, + }) + .returning(); + + revalidatePath("/api-keys"); + + return key[0]; +} + +export async function deleteAPIKey(id: string) { + const { userId, orgId } = auth(); + + if (!userId) throw new Error("No user id"); + + if (orgId) { + await db + .delete(apiKeyTable) + .where(and(eq(apiKeyTable.id, id), eq(apiKeyTable.org_id, orgId))) + .execute(); + } else { + await db + .delete(apiKeyTable) + .where(and(eq(apiKeyTable.id, id), eq(apiKeyTable.user_id, userId))) + .execute(); + } + + revalidatePath("/api-keys"); +} + +export async function getAPIKeys() { + const { userId, orgId } = auth(); + + if (!userId) throw new Error("No user id"); + + if (orgId) { + return await db.query.apiKeyTable.findMany({ + where: eq(apiKeyTable.org_id, orgId), + orderBy: desc(apiKeyTable.created_at), + }); + } else { + return await db.query.apiKeyTable.findMany({ + where: eq(apiKeyTable.user_id, userId), + orderBy: desc(apiKeyTable.created_at), + }); + } +}