From 7e05fae7b3fdb714cdea84906423164028ca6902 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Thu, 14 Dec 2023 22:36:57 +0800 Subject: [PATCH] feat: add deployment endpoint --- routes.py | 2 +- web/bun.lockb | Bin 326854 -> 328694 bytes web/drizzle/0005_worthless_dakota_north.sql | 40 ++ web/drizzle/0006_chief_mariko_yashida.sql | 1 + web/drizzle/meta/0005_snapshot.json | 531 ++++++++++++++++++++ web/drizzle/meta/0006_snapshot.json | 531 ++++++++++++++++++++ web/drizzle/meta/_journal.json | 14 + web/package.json | 1 + web/src/app/[workflow_id]/page.tsx | 47 +- web/src/app/api/create-run/route.ts | 45 -- web/src/app/api/run/route.ts | 84 ++++ web/src/app/globals.css | 12 + web/src/components/CodeBlock.tsx | 30 ++ web/src/components/CopyButton.tsx | 24 + web/src/components/DeploymentDisplay.tsx | 113 +++++ web/src/components/MachineList.tsx | 21 +- web/src/components/RunDisplay.tsx | 48 +- web/src/components/RunOutputs.tsx | 53 ++ web/src/components/RunsTable.tsx | 27 +- web/src/components/VersionSelect.tsx | 77 ++- web/src/components/ui/card.tsx | 38 +- web/src/components/ui/dialog.tsx | 48 +- web/src/db/schema.ts | 71 ++- web/src/middleware.ts | 9 +- web/src/server/curdDeploments.ts | 46 ++ web/src/server/curdMachine.ts | 13 +- web/src/server/findAllRuns.tsx | 21 +- web/src/server/getRunsOutput.tsx | 17 +- web/src/server/resource.ts | 2 +- 29 files changed, 1776 insertions(+), 190 deletions(-) create mode 100644 web/drizzle/0005_worthless_dakota_north.sql create mode 100644 web/drizzle/0006_chief_mariko_yashida.sql create mode 100644 web/drizzle/meta/0005_snapshot.json create mode 100644 web/drizzle/meta/0006_snapshot.json delete mode 100644 web/src/app/api/create-run/route.ts create mode 100644 web/src/app/api/run/route.ts create mode 100644 web/src/components/CodeBlock.tsx create mode 100644 web/src/components/CopyButton.tsx create mode 100644 web/src/components/DeploymentDisplay.tsx create mode 100644 web/src/components/RunOutputs.tsx create mode 100644 web/src/server/curdDeploments.ts diff --git a/routes.py b/routes.py index 6ce05db..68d5b89 100644 --- a/routes.py +++ b/routes.py @@ -213,7 +213,7 @@ async def upload_file(prompt_id, filename, subfolder=None): filename = os.path.basename(filename) file = os.path.join(output_dir, filename) - print("uploading file", file) + # print("uploading file", file) file_upload_endpoint = prompt_metadata[prompt_id]['file_upload_endpoint'] diff --git a/web/bun.lockb b/web/bun.lockb index 6229656203770127e890ca1e143455089ea2f978..350b7978eb9d98c2027a5cb14d9fe91da02060f5 100755 GIT binary patch delta 59538 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 z statement-breakpoint +CREATE TABLE IF NOT EXISTS "comfy_deploy"."deployments" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" text NOT NULL, + "workflow_version_id" uuid NOT NULL, + "workflow_id" uuid NOT NULL, + "machine_id" uuid, + "environment" "deployment_environment" NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "comfy_deploy"."deployments" ADD CONSTRAINT "deployments_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfy_deploy"."users"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "comfy_deploy"."deployments" ADD CONSTRAINT "deployments_workflow_version_id_workflow_versions_id_fk" FOREIGN KEY ("workflow_version_id") REFERENCES "comfy_deploy"."workflow_versions"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "comfy_deploy"."deployments" ADD CONSTRAINT "deployments_workflow_id_workflows_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "comfy_deploy"."workflows"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "comfy_deploy"."deployments" ADD CONSTRAINT "deployments_machine_id_machines_id_fk" FOREIGN KEY ("machine_id") REFERENCES "comfy_deploy"."machines"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/web/drizzle/0006_chief_mariko_yashida.sql b/web/drizzle/0006_chief_mariko_yashida.sql new file mode 100644 index 0000000..235cf36 --- /dev/null +++ b/web/drizzle/0006_chief_mariko_yashida.sql @@ -0,0 +1 @@ +ALTER TABLE "comfy_deploy"."deployments" ALTER COLUMN "machine_id" SET NOT NULL; \ No newline at end of file diff --git a/web/drizzle/meta/0005_snapshot.json b/web/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..c2b1475 --- /dev/null +++ b/web/drizzle/meta/0005_snapshot.json @@ -0,0 +1,531 @@ +{ + "id": "af90a047-fec9-4d35-be52-82f8e0ee1cf4", + "prevId": "07a389e2-3713-4047-93e7-bf1da2333b16", + "version": "5", + "dialect": "pg", + "tables": { + "deployments": { + "name": "deployments", + "schema": "comfy_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": false + }, + "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": "comfy_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": "comfy_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": "comfy_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": "comfy_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_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": "comfy_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": "comfy_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": { + "comfy_deploy": "comfy_deploy" + }, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/web/drizzle/meta/0006_snapshot.json b/web/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..e93fdf1 --- /dev/null +++ b/web/drizzle/meta/0006_snapshot.json @@ -0,0 +1,531 @@ +{ + "id": "c68b3727-5154-41eb-b44e-d5f26b72be44", + "prevId": "af90a047-fec9-4d35-be52-82f8e0ee1cf4", + "version": "5", + "dialect": "pg", + "tables": { + "deployments": { + "name": "deployments", + "schema": "comfy_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": "comfy_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": "comfy_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": "comfy_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": "comfy_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_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": "comfy_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": "comfy_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": { + "comfy_deploy": "comfy_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 c5c9eea..c11df69 100644 --- a/web/drizzle/meta/_journal.json +++ b/web/drizzle/meta/_journal.json @@ -36,6 +36,20 @@ "when": 1702357291227, "tag": "0004_zippy_freak", "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1702545286004, + "tag": "0005_worthless_dakota_north", + "breakpoints": true + }, + { + "idx": 6, + "version": "5", + "when": 1702556119574, + "tag": "0006_chief_mariko_yashida", + "breakpoints": true } ] } \ No newline at end of file diff --git a/web/package.json b/web/package.json index 0d25ba4..84eb503 100644 --- a/web/package.json +++ b/web/package.json @@ -39,6 +39,7 @@ "react-dom": "^18", "react-hook-form": "^7.48.2", "react-use-websocket": "^4.5.0", + "shiki": "^0.14.6", "sonner": "^1.2.4", "tailwind-merge": "^2.1.0", "tailwindcss-animate": "^1.0.7", diff --git a/web/src/app/[workflow_id]/page.tsx b/web/src/app/[workflow_id]/page.tsx index 2e651ed..e54ef39 100644 --- a/web/src/app/[workflow_id]/page.tsx +++ b/web/src/app/[workflow_id]/page.tsx @@ -1,7 +1,8 @@ -import { RunsTable } from "../../components/RunsTable"; +import { DeploymentsTable, RunsTable } from "../../components/RunsTable"; import { findFirstTableWithVersion } from "../../server/findFirstTableWithVersion"; import { MachinesWSMain } from "@/components/MachinesWS"; import { + CreateDeploymentButton, MachineSelect, RunWorkflowButton, VersionSelect, @@ -28,24 +29,36 @@ export default async function Page({ return (
- - - {workflow?.name} - - {getRelativeTime(workflow?.updated_at)} - - +
+ + + {workflow?.name} + + {getRelativeTime(workflow?.updated_at)} + + - -
- - - -
+ +
+ + + + +
- -
-
+ + + + + + Deployments + + + + + + +
diff --git a/web/src/app/api/create-run/route.ts b/web/src/app/api/create-run/route.ts deleted file mode 100644 index 5e62f60..0000000 --- a/web/src/app/api/create-run/route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { parseDataSafe } from "../../../lib/parseDataSafe"; -import { createRun } from "../../../server/createRun"; -import { NextResponse } from "next/server"; -import { z } from "zod"; - -const Request = z.object({ - workflow_version_id: z.string(), - // workflow_version: z.number().optional(), - machine_id: z.string(), -}); - -export async function POST(request: Request) { - const [data, error] = await parseDataSafe(Request, request); - if (!data || error) return error; - - const origin = new URL(request.url).origin; - - const { workflow_version_id, machine_id } = data; - - try { - const workflow_run_id = await createRun( - origin, - workflow_version_id, - machine_id - ); - - return NextResponse.json( - { - workflow_run_id: workflow_run_id, - }, - { - status: 200, - } - ); - } catch (error: any) { - return NextResponse.json( - { - error: error.message, - }, - { - status: 500, - } - ); - } -} diff --git a/web/src/app/api/run/route.ts b/web/src/app/api/run/route.ts new file mode 100644 index 0000000..2aa4cdd --- /dev/null +++ b/web/src/app/api/run/route.ts @@ -0,0 +1,84 @@ +import { parseDataSafe } from "../../../lib/parseDataSafe"; +import { createRun } from "../../../server/createRun"; +import { db } from "@/db/db"; +import { deploymentsTable } from "@/db/schema"; +import { getRunsData } from "@/server/getRunsOutput"; +import { replaceCDNUrl } from "@/server/resource"; +import { eq } from "drizzle-orm"; +import { NextResponse } from "next/server"; +import { z } from "zod"; + +const Request = z.object({ + deployment_id: z.string(), +}); + +const Request2 = z.object({ + run_id: z.string(), +}); + +export async function GET(request: Request) { + const [data, error] = await parseDataSafe(Request2, request); + if (!data || error) return error; + + const run = await getRunsData(data.run_id); + + if (run?.status === "success" && run?.outputs?.length > 0) { + for (let i = 0; i < run.outputs.length; i++) { + const output = run.outputs[i]; + + if (output.data?.images === undefined) continue; + + for (let j = 0; j < output.data?.images.length; j++) { + const element = output.data?.images[j]; + element.url = replaceCDNUrl( + `${process.env.SPACES_ENDPOINT}/comfyui-deploy/outputs/runs/${run.id}/${element.filename}` + ); + } + } + } + + return NextResponse.json(run, { + status: 200, + }); +} + +export async function POST(request: Request) { + const [data, error] = await parseDataSafe(Request, request); + if (!data || error) return error; + + const origin = new URL(request.url).origin; + + const { deployment_id } = data; + + try { + const deploymentData = await db.query.deploymentsTable.findFirst({ + where: eq(deploymentsTable.id, deployment_id), + }); + + if (!deploymentData) throw new Error("Deployment not found"); + + const run_id = await createRun( + origin, + deploymentData.workflow_version_id, + deploymentData.machine_id + ); + + return NextResponse.json( + { + run_id: run_id, + }, + { + status: 200, + } + ); + } catch (error: any) { + return NextResponse.json( + { + error: error.message, + }, + { + status: 500, + } + ); + } +} diff --git a/web/src/app/globals.css b/web/src/app/globals.css index 6a75725..d3f4ab8 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -65,6 +65,18 @@ --ring: 212.7 26.8% 83.9%; } } + +.shiki>code>span { + /* text-wrap: wrap; */ + /* word-wrap: break-word; */ + /* @apply break-all ; */ + text-wrap: wrap; +} + +.shiki { + /* @apply rounded-lg p-2 overflow-x-scroll */ + @apply rounded-lg p-2 overflow-hidden +} @layer base { * { diff --git a/web/src/components/CodeBlock.tsx b/web/src/components/CodeBlock.tsx new file mode 100644 index 0000000..9989f76 --- /dev/null +++ b/web/src/components/CodeBlock.tsx @@ -0,0 +1,30 @@ +import { CopyButton } from "@/components/CopyButton"; +import type { Lang } from "shiki"; +import shiki from "shiki"; + +export async function CodeBlock(props: { code: string; lang: Lang }) { + const highlighter = await shiki.getHighlighter({ + theme: "one-dark-pro", + }); + + return ( +
+ {/* max-w-[calc(32rem-1.5rem-1.5rem)] */} + {/*
*/} +

+ {/*

*/} + +
+ ); +} diff --git a/web/src/components/CopyButton.tsx b/web/src/components/CopyButton.tsx new file mode 100644 index 0000000..ca0ea8c --- /dev/null +++ b/web/src/components/CopyButton.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { Copy } from "lucide-react"; + +export function CopyButton({ + className, + ...props +}: { + text: string; + className?: string; +}) { + return ( + + ); +} diff --git a/web/src/components/DeploymentDisplay.tsx b/web/src/components/DeploymentDisplay.tsx new file mode 100644 index 0000000..0866099 --- /dev/null +++ b/web/src/components/DeploymentDisplay.tsx @@ -0,0 +1,113 @@ +import { CodeBlock } from "@/components/CodeBlock"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { TableCell, TableRow } from "@/components/ui/table"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { getRelativeTime } from "@/lib/getRelativeTime"; +import type { findAllDeployments } from "@/server/findAllRuns"; + +const curlTemplate = ` +curl --request POST \ + --url \ + --header 'Content-Type: application/json' \ + --data '{ + "deployment_id": "" +}' +`; + +const jsTemplate = ` +const options = { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: '{"deployment_id":""}' +}; + +fetch('', options) + .then(response => response.json()) + .then(response => console.log(response)) + .catch(err => console.error(err)); +`; + +const jsTemplate_checkStatus = ` +const options = { + method: 'GET', + headers: {'Content-Type': 'application/json'}, +}; + +const run_id = ''; + +fetch('?run_id=' + run_id, options) + .then(response => response.json()) + .then(response => console.log(response)) + .catch(err => console.error(err)); +`; + +export function DeploymentDisplay({ + deployment, +}: { + deployment: Awaited>[0]; +}) { + return ( + + + + {deployment.environment} + + {deployment.version?.version} + + + {deployment.machine?.name} + + + {getRelativeTime(deployment.updated_at)} + + + + + + + {deployment.environment} Deployment + + Code for your deployment client + + + + js + curl + + + + + + + + + + + + ); +} + +function formatCode( + codeTemplate: string, + deployment: Awaited>[0] +) { + return codeTemplate + .replace( + "", + `${process.env.VERCEL_URL ?? "http://localhost:3000"}/api/run` + ) + .replace("", deployment.id); +} diff --git a/web/src/components/MachineList.tsx b/web/src/components/MachineList.tsx index 858becd..9d6f2fb 100644 --- a/web/src/components/MachineList.tsx +++ b/web/src/components/MachineList.tsx @@ -161,7 +161,7 @@ export const columns: ColumnDef[] = [ { - callServerWithToast(await deleteMachine(workflow.id)); + callServerPromise(deleteMachine(workflow.id)); }} > Delete Machine @@ -176,15 +176,16 @@ export const columns: ColumnDef[] = [ }, ]; -async function callServerWithToast(result: { - message: string; - error?: boolean; -}) { - if (result.error) { - toast.error(result.message); - } else { - toast.success(result.message); - } +export async function callServerPromise(result: Promise) { + return result + .then((x) => { + if ((x as { message: string })?.message !== undefined) { + toast.success((x as { message: string }).message); + } + }) + .catch((error) => { + toast.error(error.message); + }); } export function MachineList({ data }: { data: Machine[] }) { diff --git a/web/src/components/RunDisplay.tsx b/web/src/components/RunDisplay.tsx index ce25594..a8ad2b3 100644 --- a/web/src/components/RunDisplay.tsx +++ b/web/src/components/RunDisplay.tsx @@ -1,5 +1,6 @@ "use client"; +import { RunOutputs } from "./RunOutputs"; import { useStore } from "@/components/MachinesWS"; import { StatusBadge } from "@/components/StatusBadge"; import { @@ -10,18 +11,9 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; +import { TableCell, TableRow } from "@/components/ui/table"; import { getRelativeTime } from "@/lib/getRelativeTime"; import { type findAllRuns } from "@/server/findAllRuns"; -import { getRunsOutput } from "@/server/getRunsOutput"; -import { useEffect, useState } from "react"; export function RunDisplay({ run, @@ -73,42 +65,6 @@ export function RunDisplay({ ); } -export function RunOutputs({ run_id }: { run_id: string }) { - const [outputs, setOutputs] = useState< - Awaited> - >([]); - - useEffect(() => { - getRunsOutput(run_id).then((x) => setOutputs(x)); - }, [run_id]); - - return ( - - {/* A list of your recent runs. */} - - - File - Output - - - - {outputs?.map((run) => { - const fileName = run.data.images[0].filename; - // const filePath - return ( - - {fileName} - - - - - ); - })} - -
- ); -} - export function OutputRender(props: { run_id: string; filename: string }) { if (props.filename.endsWith(".png")) { return ( diff --git a/web/src/components/RunOutputs.tsx b/web/src/components/RunOutputs.tsx new file mode 100644 index 0000000..f957f33 --- /dev/null +++ b/web/src/components/RunOutputs.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { OutputRender } from "./RunDisplay"; +import { callServerPromise } from "@/components/MachineList"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { getRunsOutput } from "@/server/getRunsOutput"; +import { useEffect, useState } from "react"; + +export function RunOutputs({ run_id }: { run_id: string }) { + const [outputs, setOutputs] = + useState>>(); + + useEffect(() => { + if (!run_id) return; + // fetch(`/api/run?run_id=${run_id}`) + // .then((x) => x.json()) + // .then((x) => setOutputs(x)); + callServerPromise(getRunsOutput(run_id).then((x) => setOutputs(x))); + }, [run_id, outputs]); + + return ( + + {/* A list of your recent runs. */} + + + File + Output + + + + {outputs?.map((run) => { + const fileName = run.data.images[0].filename; + // const filePath + return ( + + {fileName} + + + + + ); + })} + +
+ ); +} diff --git a/web/src/components/RunsTable.tsx b/web/src/components/RunsTable.tsx index f96a72b..fd0effd 100644 --- a/web/src/components/RunsTable.tsx +++ b/web/src/components/RunsTable.tsx @@ -1,4 +1,5 @@ -import { findAllRuns } from "../server/findAllRuns"; +import { findAllDeployments, findAllRuns } from "../server/findAllRuns"; +import { DeploymentDisplay } from "./DeploymentDisplay"; import { RunDisplay } from "./RunDisplay"; import { Table, @@ -33,3 +34,27 @@ export async function RunsTable(props: { workflow_id: string }) {
); } + +export async function DeploymentsTable(props: { workflow_id: string }) { + const allRuns = await findAllDeployments(props.workflow_id); + return ( +
+ + A list of your deployments + + + Environment + Version + Machine + Updated At + + + + {allRuns.map((run) => ( + + ))} + +
+
+ ); +} diff --git a/web/src/components/VersionSelect.tsx b/web/src/components/VersionSelect.tsx index c02b05f..9e45c0e 100644 --- a/web/src/components/VersionSelect.tsx +++ b/web/src/components/VersionSelect.tsx @@ -1,7 +1,14 @@ "use client"; import { LoadingIcon } from "@/components/LoadingIcon"; +import { callServerPromise } from "@/components/MachineList"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Select, SelectContent, @@ -12,9 +19,10 @@ import { SelectValue, } from "@/components/ui/select"; import { createRun } from "@/server/createRun"; +import { createDeployments } from "@/server/curdDeploments"; import type { getMachines } from "@/server/curdMachine"; import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion"; -import { Play } from "lucide-react"; +import { MoreVertical, Play } from "lucide-react"; import { parseAsInteger, useQueryState } from "next-usequerystate"; import { useState } from "react"; @@ -122,3 +130,70 @@ export function RunWorkflowButton({ ); } + +export function CreateDeploymentButton({ + workflow, + machines, +}: { + workflow: Awaited>; + machines: Awaited>; +}) { + const [version] = useQueryState("version", { + defaultValue: workflow?.versions[0].version ?? 1, + ...parseAsInteger, + }); + const [machine] = useQueryState("machine", { + defaultValue: machines[0].id ?? "", + }); + const [isLoading, setIsLoading] = useState(false); + const workflow_version_id = workflow?.versions.find( + (x) => x.version === version + )?.id; + return ( + + + + + + { + if (!workflow_version_id) return; + + setIsLoading(true); + await callServerPromise( + createDeployments( + workflow.id, + workflow_version_id, + machine, + "production" + ) + ); + setIsLoading(false); + }} + > + Production + + { + if (!workflow_version_id) return; + + setIsLoading(true); + await callServerPromise( + createDeployments( + workflow.id, + workflow_version_id, + machine, + "staging" + ) + ); + setIsLoading(false); + }} + > + Staging + + + + ); +} diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx index afa13ec..1460271 100644 --- a/web/src/components/ui/card.tsx +++ b/web/src/components/ui/card.tsx @@ -1,6 +1,5 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; +import * as React from "react"; const Card = React.forwardRef< HTMLDivElement, @@ -14,8 +13,8 @@ const Card = React.forwardRef< )} {...props} /> -)) -Card.displayName = "Card" +)); +Card.displayName = "Card"; const CardHeader = React.forwardRef< HTMLDivElement, @@ -26,8 +25,8 @@ const CardHeader = React.forwardRef< className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} /> -)) -CardHeader.displayName = "CardHeader" +)); +CardHeader.displayName = "CardHeader"; const CardTitle = React.forwardRef< HTMLParagraphElement, @@ -41,8 +40,8 @@ const CardTitle = React.forwardRef< )} {...props} /> -)) -CardTitle.displayName = "CardTitle" +)); +CardTitle.displayName = "CardTitle"; const CardDescription = React.forwardRef< HTMLParagraphElement, @@ -53,16 +52,16 @@ const CardDescription = React.forwardRef< className={cn("text-sm text-muted-foreground", className)} {...props} /> -)) -CardDescription.displayName = "CardDescription" +)); +CardDescription.displayName = "CardDescription"; const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
-)) -CardContent.displayName = "CardContent" +)); +CardContent.displayName = "CardContent"; const CardFooter = React.forwardRef< HTMLDivElement, @@ -73,7 +72,14 @@ const CardFooter = React.forwardRef< className={cn("flex items-center p-6 pt-0", className)} {...props} /> -)) -CardFooter.displayName = "CardFooter" +)); +CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx index cad6f58..78ca5d8 100644 --- a/web/src/components/ui/dialog.tsx +++ b/web/src/components/ui/dialog.tsx @@ -1,18 +1,17 @@ -"use client" +"use client"; -import * as React from "react" -import * as DialogPrimitive from "@radix-ui/react-dialog" -import { X } from "lucide-react" +import { cn } from "@/lib/utils"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; -import { cn } from "@/lib/utils" +const Dialog = DialogPrimitive.Root; -const Dialog = DialogPrimitive.Root +const DialogTrigger = DialogPrimitive.Trigger; -const DialogTrigger = DialogPrimitive.Trigger +const DialogPortal = DialogPrimitive.Portal; -const DialogPortal = DialogPrimitive.Portal - -const DialogClose = DialogPrimitive.Close +const DialogClose = DialogPrimitive.Close; const DialogOverlay = React.forwardRef< React.ElementRef, @@ -26,8 +25,8 @@ const DialogOverlay = React.forwardRef< )} {...props} /> -)) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< React.ElementRef, @@ -37,8 +36,9 @@ const DialogContent = React.forwardRef< -)) -DialogContent.displayName = DialogPrimitive.Content.displayName +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ className, @@ -64,8 +64,8 @@ const DialogHeader = ({ )} {...props} /> -) -DialogHeader.displayName = "DialogHeader" +); +DialogHeader.displayName = "DialogHeader"; const DialogFooter = ({ className, @@ -78,8 +78,8 @@ const DialogFooter = ({ )} {...props} /> -) -DialogFooter.displayName = "DialogFooter" +); +DialogFooter.displayName = "DialogFooter"; const DialogTitle = React.forwardRef< React.ElementRef, @@ -93,8 +93,8 @@ const DialogTitle = React.forwardRef< )} {...props} /> -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; const DialogDescription = React.forwardRef< React.ElementRef, @@ -105,8 +105,8 @@ const DialogDescription = React.forwardRef< className={cn("text-sm text-muted-foreground", className)} {...props} /> -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; export { Dialog, @@ -119,4 +119,4 @@ export { DialogFooter, DialogTitle, DialogDescription, -} +}; diff --git a/web/src/db/schema.ts b/web/src/db/schema.ts index e920396..62f80d2 100644 --- a/web/src/db/schema.ts +++ b/web/src/db/schema.ts @@ -67,6 +67,11 @@ export const workflowRunStatus = pgEnum("workflow_run_status", [ "failed", ]); +export const deploymentEnvironment = pgEnum("deployment_environment", [ + "staging", + "production", +]); + // We still want to keep the workflow run record. export const workflowRunsTable = dbSchema.table("workflow_runs", { id: uuid("id").primaryKey().defaultRandom().notNull(), @@ -91,16 +96,20 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", { created_at: timestamp("created_at").defaultNow().notNull(), }); -export const workflowRunRelations = relations(workflowRunsTable, ({ one }) => ({ - machine: one(machinesTable, { - fields: [workflowRunsTable.machine_id], - references: [machinesTable.id], - }), - version: one(workflowVersionTable, { - fields: [workflowRunsTable.workflow_version_id], - references: [workflowVersionTable.id], - }), -})); +export const workflowRunRelations = relations( + workflowRunsTable, + ({ one, many }) => ({ + machine: one(machinesTable, { + fields: [workflowRunsTable.machine_id], + references: [machinesTable.id], + }), + version: one(workflowVersionTable, { + fields: [workflowRunsTable.workflow_version_id], + references: [workflowVersionTable.id], + }), + outputs: many(workflowRunOutputs), + }) +); // We still want to keep the workflow run record. export const workflowRunOutputs = dbSchema.table("workflow_run_outputs", { @@ -116,6 +125,16 @@ export const workflowRunOutputs = dbSchema.table("workflow_run_outputs", { updated_at: timestamp("updated_at").defaultNow().notNull(), }); +export const workflowOutputRelations = relations( + workflowRunOutputs, + ({ one }) => ({ + run: one(workflowRunsTable, { + fields: [workflowRunOutputs.run_id], + references: [workflowRunsTable.id], + }), + }) +); + // when user delete, also delete all the workflow versions export const machinesTable = dbSchema.table("machines", { id: uuid("id").primaryKey().defaultRandom().notNull(), @@ -130,5 +149,37 @@ export const machinesTable = dbSchema.table("machines", { updated_at: timestamp("updated_at").defaultNow().notNull(), }); +export const deploymentsTable = dbSchema.table("deployments", { + id: uuid("id").primaryKey().defaultRandom().notNull(), + user_id: text("user_id") + .references(() => usersTable.id, { + onDelete: "cascade", + }) + .notNull(), + workflow_version_id: uuid("workflow_version_id") + .notNull() + .references(() => workflowVersionTable.id), + workflow_id: uuid("workflow_id") + .notNull() + .references(() => workflowTable.id), + machine_id: uuid("machine_id") + .notNull() + .references(() => machinesTable.id), + environment: deploymentEnvironment("environment").notNull(), + created_at: timestamp("created_at").defaultNow().notNull(), + updated_at: timestamp("updated_at").defaultNow().notNull(), +}); + +export const deploymentsRelations = relations(deploymentsTable, ({ one }) => ({ + machine: one(machinesTable, { + fields: [deploymentsTable.machine_id], + references: [machinesTable.id], + }), + version: one(workflowVersionTable, { + fields: [deploymentsTable.workflow_version_id], + references: [workflowVersionTable.id], + }), +})); + export type UserType = InferSelectModel; export type WorkflowType = InferSelectModel; diff --git a/web/src/middleware.ts b/web/src/middleware.ts index cfc83a6..f7375b8 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -1,13 +1,10 @@ -import { db } from "./db/db"; -import { usersTable } from "./db/schema"; -import { authMiddleware, redirectToSignIn, clerkClient } from "@clerk/nextjs"; -import { eq } from "drizzle-orm"; -import { NextResponse } from "next/server"; +import { authMiddleware, redirectToSignIn } from "@clerk/nextjs"; // This example protects all routes including api/trpc routes // Please edit this to allow other routes to be public as needed. // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware export default authMiddleware({ + // debug: true, publicRoutes: ["/api/(.*)"], // publicRoutes: ["/", "/(.*)"], async afterAuth(auth, req, evt) { @@ -33,6 +30,6 @@ export default authMiddleware({ }); export const config = { - matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/" , "/(api|trpc)(.*)"], + matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"], // matcher: ['/','/create', '/api/(twitter|generation|init|voice-cloning)'], }; diff --git a/web/src/server/curdDeploments.ts b/web/src/server/curdDeploments.ts new file mode 100644 index 0000000..03742c0 --- /dev/null +++ b/web/src/server/curdDeploments.ts @@ -0,0 +1,46 @@ +"use server"; + +import { db } from "@/db/db"; +import { deploymentsTable } from "@/db/schema"; +import { auth } from "@clerk/nextjs"; +import { and, eq } from "drizzle-orm"; +import { revalidatePath } from "next/cache"; +import "server-only"; + +export async function createDeployments( + workflow_id: string, + version_id: string, + machine_id: string, + environment: "production" | "staging" +) { + const { userId } = auth(); + if (!userId) throw new Error("No user id"); + + // Same environment and same workflow + const existingDeployment = await db.query.deploymentsTable.findFirst({ + where: and( + eq(deploymentsTable.workflow_id, workflow_id), + eq(deploymentsTable.environment, environment) + ), + }); + + if (existingDeployment) { + await db + .update(deploymentsTable) + .set({ + workflow_id, + workflow_version_id: version_id, + machine_id, + }) + .where(eq(deploymentsTable.id, existingDeployment.id)); + } else { + await db.insert(deploymentsTable).values({ + user_id: userId, + workflow_id, + workflow_version_id: version_id, + machine_id, + environment, + }); + } + revalidatePath(`/${workflow_id}`); +} diff --git a/web/src/server/curdMachine.ts b/web/src/server/curdMachine.ts index 571f152..03f9dae 100644 --- a/web/src/server/curdMachine.ts +++ b/web/src/server/curdMachine.ts @@ -43,14 +43,7 @@ export async function addMachine(name: string, endpoint: string) { export async function deleteMachine( machine_id: string ): Promise<{ message: string; error?: boolean }> { - try { - await db.delete(machinesTable).where(eq(machinesTable.id, machine_id)); - revalidatePath("/machines"); - return { message: "Machine Deleted" }; - } catch (error: unknown) { - return { - message: `Error: ${error.detail}`, - error: true, - }; - } + await db.delete(machinesTable).where(eq(machinesTable.id, machine_id)); + revalidatePath("/machines"); + return { message: "Machine Deleted" }; } diff --git a/web/src/server/findAllRuns.tsx b/web/src/server/findAllRuns.tsx index 49a10c4..af2da80 100644 --- a/web/src/server/findAllRuns.tsx +++ b/web/src/server/findAllRuns.tsx @@ -1,5 +1,5 @@ import { db } from "@/db/db"; -import { workflowRunsTable } from "@/db/schema"; +import { deploymentsTable, workflowRunsTable } from "@/db/schema"; import { desc, eq } from "drizzle-orm"; export async function findAllRuns(workflow_id: string) { @@ -21,3 +21,22 @@ export async function findAllRuns(workflow_id: string) { }, }); } + +export async function findAllDeployments(workflow_id: string) { + return await db.query.deploymentsTable.findMany({ + where: eq(deploymentsTable.workflow_id, workflow_id), + orderBy: desc(deploymentsTable.environment), + with: { + machine: { + columns: { + name: true, + }, + }, + version: { + columns: { + version: true, + }, + }, + }, + }); +} diff --git a/web/src/server/getRunsOutput.tsx b/web/src/server/getRunsOutput.tsx index 44bbae4..79d0210 100644 --- a/web/src/server/getRunsOutput.tsx +++ b/web/src/server/getRunsOutput.tsx @@ -1,12 +1,27 @@ "use server"; import { db } from "@/db/db"; -import { workflowRunOutputs } from "@/db/schema"; +import { workflowRunOutputs, workflowRunsTable } from "@/db/schema"; import { eq } from "drizzle-orm"; export async function getRunsOutput(run_id: string) { + // throw new Error("Not implemented"); return await db .select() .from(workflowRunOutputs) .where(eq(workflowRunOutputs.run_id, run_id)); } + +export async function getRunsData(run_id: string) { + // throw new Error("Not implemented"); + return await db.query.workflowRunsTable.findFirst({ + where: eq(workflowRunsTable.id, run_id), + with: { + outputs: { + columns: { + data: true, + }, + }, + }, + }); +} diff --git a/web/src/server/resource.ts b/web/src/server/resource.ts index 44b1d3e..fe194b8 100644 --- a/web/src/server/resource.ts +++ b/web/src/server/resource.ts @@ -17,7 +17,7 @@ const s3Client = new S3({ forcePathStyle: true, }); -function replaceCDNUrl(url: string) { +export function replaceCDNUrl(url: string) { url = url.replace( process.env.SPACES_ENDPOINT!, process.env.SPACES_ENDPOINT_CDN!