feat: add deployment endpoint

This commit is contained in:
BennyKok 2023-12-14 22:36:57 +08:00
parent 0835d966f1
commit 7e05fae7b3
29 changed files with 1776 additions and 190 deletions

View File

@ -213,7 +213,7 @@ async def upload_file(prompt_id, filename, subfolder=None):
filename = os.path.basename(filename) filename = os.path.basename(filename)
file = os.path.join(output_dir, 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'] file_upload_endpoint = prompt_metadata[prompt_id]['file_upload_endpoint']

Binary file not shown.

View File

@ -0,0 +1,40 @@
DO $$ BEGIN
CREATE TYPE "deployment_environment" AS ENUM('staging', 'production');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> 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 $$;

View File

@ -0,0 +1 @@
ALTER TABLE "comfy_deploy"."deployments" ALTER COLUMN "machine_id" SET NOT NULL;

View File

@ -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": {}
}
}

View File

@ -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": {}
}
}

View File

@ -36,6 +36,20 @@
"when": 1702357291227, "when": 1702357291227,
"tag": "0004_zippy_freak", "tag": "0004_zippy_freak",
"breakpoints": true "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
} }
] ]
} }

View File

@ -39,6 +39,7 @@
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.48.2", "react-hook-form": "^7.48.2",
"react-use-websocket": "^4.5.0", "react-use-websocket": "^4.5.0",
"shiki": "^0.14.6",
"sonner": "^1.2.4", "sonner": "^1.2.4",
"tailwind-merge": "^2.1.0", "tailwind-merge": "^2.1.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

View File

@ -1,7 +1,8 @@
import { RunsTable } from "../../components/RunsTable"; import { DeploymentsTable, RunsTable } from "../../components/RunsTable";
import { findFirstTableWithVersion } from "../../server/findFirstTableWithVersion"; import { findFirstTableWithVersion } from "../../server/findFirstTableWithVersion";
import { MachinesWSMain } from "@/components/MachinesWS"; import { MachinesWSMain } from "@/components/MachinesWS";
import { import {
CreateDeploymentButton,
MachineSelect, MachineSelect,
RunWorkflowButton, RunWorkflowButton,
VersionSelect, VersionSelect,
@ -28,6 +29,7 @@ export default async function Page({
return ( return (
<div className="mt-4 w-full flex flex-col lg:flex-row gap-4 max-h-[calc(100dvh-100px)]"> <div className="mt-4 w-full flex flex-col lg:flex-row gap-4 max-h-[calc(100dvh-100px)]">
<div className="flex gap-4 flex-col">
<Card className="w-full lg:w-fit lg:min-w-[500px] h-fit"> <Card className="w-full lg:w-fit lg:min-w-[500px] h-fit">
<CardHeader> <CardHeader>
<CardTitle>{workflow?.name}</CardTitle> <CardTitle>{workflow?.name}</CardTitle>
@ -41,11 +43,22 @@ export default async function Page({
<VersionSelect workflow={workflow} /> <VersionSelect workflow={workflow} />
<MachineSelect machines={machines} /> <MachineSelect machines={machines} />
<RunWorkflowButton workflow={workflow} machines={machines} /> <RunWorkflowButton workflow={workflow} machines={machines} />
<CreateDeploymentButton workflow={workflow} machines={machines} />
</div> </div>
<MachinesWSMain machines={machines} /> <MachinesWSMain machines={machines} />
</CardContent> </CardContent>
</Card> </Card>
<Card className="w-full ">
<CardHeader>
<CardTitle>Deployments</CardTitle>
</CardHeader>
<CardContent>
<DeploymentsTable workflow_id={workflow_id} />
</CardContent>
</Card>
</div>
<Card className="w-full "> <Card className="w-full ">
<CardHeader> <CardHeader>

View File

@ -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,
}
);
}
}

View File

@ -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,
}
);
}
}

View File

@ -66,6 +66,18 @@
} }
} }
.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 { @layer base {
* { * {
@apply border-border; @apply border-border;

View File

@ -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 (
<div className="relative w-full max-w-full text-sm">
{/* max-w-[calc(32rem-1.5rem-1.5rem)] */}
{/* <div className=""> */}
<p
// tabIndex={1}
className="[&>pre]:p-4 rounded-sm "
style={{
overflowWrap: "break-word",
}}
dangerouslySetInnerHTML={{
__html: highlighter.codeToHtml(props.code.trim(), {
lang: props.lang,
}),
}}
/>
{/* </div> */}
<CopyButton className="absolute right-2 top-2" text={props.code} />
</div>
);
}

View File

@ -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 (
<Button
onClick={() => {
navigator.clipboard.writeText(props.text);
}}
className={cn(" p-2 min-h-0 aspect-square", className)}
>
<Copy size={14} />
</Button>
);
}

View File

@ -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 <URL> \
--header 'Content-Type: application/json' \
--data '{
"deployment_id": "<ID>"
}'
`;
const jsTemplate = `
const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: '{"deployment_id":"<ID>"}'
};
fetch('<URL>', 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 = '<RUN_ID>';
fetch('<URL>?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<ReturnType<typeof findAllDeployments>>[0];
}) {
return (
<Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow>
<TableCell className="capitalize">{deployment.environment}</TableCell>
<TableCell className="font-medium">
{deployment.version?.version}
</TableCell>
<TableCell className="font-medium">
{deployment.machine?.name}
</TableCell>
<TableCell className="text-right">
{getRelativeTime(deployment.updated_at)}
</TableCell>
</TableRow>
</DialogTrigger>
<DialogContent className="max-w-xl">
<DialogHeader>
<DialogTitle className="capitalize">
{deployment.environment} Deployment
</DialogTitle>
<DialogDescription>Code for your deployment client</DialogDescription>
</DialogHeader>
<Tabs defaultValue="js" className="w-full">
<TabsList className="grid w-fit grid-cols-2">
<TabsTrigger value="js">js</TabsTrigger>
<TabsTrigger value="curl">curl</TabsTrigger>
</TabsList>
<TabsContent className="flex flex-col gap-2" value="js">
<CodeBlock lang="js" code={formatCode(jsTemplate, deployment)} />
<CodeBlock
lang="js"
code={formatCode(jsTemplate_checkStatus, deployment)}
/>
</TabsContent>
<TabsContent value="curl">
<CodeBlock
lang="bash"
code={formatCode(curlTemplate, deployment)}
/>
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
);
}
function formatCode(
codeTemplate: string,
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0]
) {
return codeTemplate
.replace(
"<URL>",
`${process.env.VERCEL_URL ?? "http://localhost:3000"}/api/run`
)
.replace("<ID>", deployment.id);
}

View File

@ -161,7 +161,7 @@ export const columns: ColumnDef<Machine>[] = [
<DropdownMenuItem <DropdownMenuItem
className="text-destructive" className="text-destructive"
onClick={async () => { onClick={async () => {
callServerWithToast(await deleteMachine(workflow.id)); callServerPromise(deleteMachine(workflow.id));
}} }}
> >
Delete Machine Delete Machine
@ -176,15 +176,16 @@ export const columns: ColumnDef<Machine>[] = [
}, },
]; ];
async function callServerWithToast(result: { export async function callServerPromise<T>(result: Promise<T>) {
message: string; return result
error?: boolean; .then((x) => {
}) { if ((x as { message: string })?.message !== undefined) {
if (result.error) { toast.success((x as { message: string }).message);
toast.error(result.message);
} else {
toast.success(result.message);
} }
})
.catch((error) => {
toast.error(error.message);
});
} }
export function MachineList({ data }: { data: Machine[] }) { export function MachineList({ data }: { data: Machine[] }) {

View File

@ -1,5 +1,6 @@
"use client"; "use client";
import { RunOutputs } from "./RunOutputs";
import { useStore } from "@/components/MachinesWS"; import { useStore } from "@/components/MachinesWS";
import { StatusBadge } from "@/components/StatusBadge"; import { StatusBadge } from "@/components/StatusBadge";
import { import {
@ -10,18 +11,9 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { import { TableCell, TableRow } from "@/components/ui/table";
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getRelativeTime } from "@/lib/getRelativeTime"; import { getRelativeTime } from "@/lib/getRelativeTime";
import { type findAllRuns } from "@/server/findAllRuns"; import { type findAllRuns } from "@/server/findAllRuns";
import { getRunsOutput } from "@/server/getRunsOutput";
import { useEffect, useState } from "react";
export function RunDisplay({ export function RunDisplay({
run, run,
@ -73,42 +65,6 @@ export function RunDisplay({
); );
} }
export function RunOutputs({ run_id }: { run_id: string }) {
const [outputs, setOutputs] = useState<
Awaited<ReturnType<typeof getRunsOutput>>
>([]);
useEffect(() => {
getRunsOutput(run_id).then((x) => setOutputs(x));
}, [run_id]);
return (
<Table>
{/* <TableCaption>A list of your recent runs.</TableCaption> */}
<TableHeader className="bg-background top-0 sticky">
<TableRow>
<TableHead className="w-[100px]">File</TableHead>
<TableHead className="">Output</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{outputs?.map((run) => {
const fileName = run.data.images[0].filename;
// const filePath
return (
<TableRow key={run.id}>
<TableCell>{fileName}</TableCell>
<TableCell>
<OutputRender run_id={run_id} filename={fileName} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
}
export function OutputRender(props: { run_id: string; filename: string }) { export function OutputRender(props: { run_id: string; filename: string }) {
if (props.filename.endsWith(".png")) { if (props.filename.endsWith(".png")) {
return ( return (

View File

@ -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<Awaited<ReturnType<typeof getRunsOutput>>>();
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 (
<Table>
{/* <TableCaption>A list of your recent runs.</TableCaption> */}
<TableHeader className="bg-background top-0 sticky">
<TableRow>
<TableHead className="w-[100px]">File</TableHead>
<TableHead className="">Output</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{outputs?.map((run) => {
const fileName = run.data.images[0].filename;
// const filePath
return (
<TableRow key={run.id}>
<TableCell>{fileName}</TableCell>
<TableCell>
<OutputRender run_id={run_id} filename={fileName} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
}

View File

@ -1,4 +1,5 @@
import { findAllRuns } from "../server/findAllRuns"; import { findAllDeployments, findAllRuns } from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
import { RunDisplay } from "./RunDisplay"; import { RunDisplay } from "./RunDisplay";
import { import {
Table, Table,
@ -33,3 +34,27 @@ export async function RunsTable(props: { workflow_id: string }) {
</div> </div>
); );
} }
export async function DeploymentsTable(props: { workflow_id: string }) {
const allRuns = await findAllDeployments(props.workflow_id);
return (
<div className="overflow-auto h-[400px] w-full">
<Table className="">
<TableCaption>A list of your deployments</TableCaption>
<TableHeader className="bg-background top-0 sticky">
<TableRow>
<TableHead className=" w-[100px]">Environment</TableHead>
<TableHead className=" w-[100px]">Version</TableHead>
<TableHead className="">Machine</TableHead>
<TableHead className=" text-right">Updated At</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{allRuns.map((run) => (
<DeploymentDisplay deployment={run} key={run.id} />
))}
</TableBody>
</Table>
</div>
);
}

View File

@ -1,7 +1,14 @@
"use client"; "use client";
import { LoadingIcon } from "@/components/LoadingIcon"; import { LoadingIcon } from "@/components/LoadingIcon";
import { callServerPromise } from "@/components/MachineList";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { import {
Select, Select,
SelectContent, SelectContent,
@ -12,9 +19,10 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { createRun } from "@/server/createRun"; import { createRun } from "@/server/createRun";
import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine"; import type { getMachines } from "@/server/curdMachine";
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion"; import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
import { Play } from "lucide-react"; import { MoreVertical, Play } from "lucide-react";
import { parseAsInteger, useQueryState } from "next-usequerystate"; import { parseAsInteger, useQueryState } from "next-usequerystate";
import { useState } from "react"; import { useState } from "react";
@ -122,3 +130,70 @@ export function RunWorkflowButton({
</Button> </Button>
); );
} }
export function CreateDeploymentButton({
workflow,
machines,
}: {
workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
machines: Awaited<ReturnType<typeof getMachines>>;
}) {
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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-2" disabled={isLoading} variant="outline">
Deploy <MoreVertical size={14} /> {isLoading && <LoadingIcon />}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem
onClick={async () => {
if (!workflow_version_id) return;
setIsLoading(true);
await callServerPromise(
createDeployments(
workflow.id,
workflow_version_id,
machine,
"production"
)
);
setIsLoading(false);
}}
>
Production
</DropdownMenuItem>
<DropdownMenuItem
onClick={async () => {
if (!workflow_version_id) return;
setIsLoading(true);
await callServerPromise(
createDeployments(
workflow.id,
workflow_version_id,
machine,
"staging"
)
);
setIsLoading(false);
}}
>
Staging
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -1,6 +1,5 @@
import * as React from "react" import { cn } from "@/lib/utils";
import * as React from "react";
import { cn } from "@/lib/utils"
const Card = React.forwardRef< const Card = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -14,8 +13,8 @@ const Card = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
Card.displayName = "Card" Card.displayName = "Card";
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -26,8 +25,8 @@ const CardHeader = React.forwardRef<
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} {...props}
/> />
)) ));
CardHeader.displayName = "CardHeader" CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -41,8 +40,8 @@ const CardTitle = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
CardTitle.displayName = "CardTitle" CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -53,16 +52,16 @@ const CardDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
CardDescription.displayName = "CardDescription" CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef< const CardContent = React.forwardRef<
HTMLDivElement, HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
)) ));
CardContent.displayName = "CardContent" CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@ -73,7 +72,14 @@ const CardFooter = React.forwardRef<
className={cn("flex items-center p-6 pt-0", className)} className={cn("flex items-center p-6 pt-0", className)}
{...props} {...props}
/> />
)) ));
CardFooter.displayName = "CardFooter" CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@ -1,18 +1,17 @@
"use client" "use client";
import * as React from "react" import { cn } from "@/lib/utils";
import * as DialogPrimitive from "@radix-ui/react-dialog" import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react" 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< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
@ -26,8 +25,8 @@ const DialogOverlay = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
@ -37,8 +36,9 @@ const DialogContent = React.forwardRef<
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
// grid tuning off grid for styling issue
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] grid grid-cols-1 translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className
)} )}
{...props} {...props}
@ -50,8 +50,8 @@ const DialogContent = React.forwardRef<
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)) ));
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({
className, className,
@ -64,8 +64,8 @@ const DialogHeader = ({
)} )}
{...props} {...props}
/> />
) );
DialogHeader.displayName = "DialogHeader" DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({ const DialogFooter = ({
className, className,
@ -78,8 +78,8 @@ const DialogFooter = ({
)} )}
{...props} {...props}
/> />
) );
DialogFooter.displayName = "DialogFooter" DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef< const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>, React.ElementRef<typeof DialogPrimitive.Title>,
@ -93,8 +93,8 @@ const DialogTitle = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
DialogTitle.displayName = DialogPrimitive.Title.displayName DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef< const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>, React.ElementRef<typeof DialogPrimitive.Description>,
@ -105,8 +105,8 @@ const DialogDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
DialogDescription.displayName = DialogPrimitive.Description.displayName DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
@ -119,4 +119,4 @@ export {
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogDescription, DialogDescription,
} };

View File

@ -67,6 +67,11 @@ export const workflowRunStatus = pgEnum("workflow_run_status", [
"failed", "failed",
]); ]);
export const deploymentEnvironment = pgEnum("deployment_environment", [
"staging",
"production",
]);
// We still want to keep the workflow run record. // We still want to keep the workflow run record.
export const workflowRunsTable = dbSchema.table("workflow_runs", { export const workflowRunsTable = dbSchema.table("workflow_runs", {
id: uuid("id").primaryKey().defaultRandom().notNull(), id: uuid("id").primaryKey().defaultRandom().notNull(),
@ -91,7 +96,9 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
}); });
export const workflowRunRelations = relations(workflowRunsTable, ({ one }) => ({ export const workflowRunRelations = relations(
workflowRunsTable,
({ one, many }) => ({
machine: one(machinesTable, { machine: one(machinesTable, {
fields: [workflowRunsTable.machine_id], fields: [workflowRunsTable.machine_id],
references: [machinesTable.id], references: [machinesTable.id],
@ -100,7 +107,9 @@ export const workflowRunRelations = relations(workflowRunsTable, ({ one }) => ({
fields: [workflowRunsTable.workflow_version_id], fields: [workflowRunsTable.workflow_version_id],
references: [workflowVersionTable.id], references: [workflowVersionTable.id],
}), }),
})); outputs: many(workflowRunOutputs),
})
);
// We still want to keep the workflow run record. // We still want to keep the workflow run record.
export const workflowRunOutputs = dbSchema.table("workflow_run_outputs", { 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(), 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 // when user delete, also delete all the workflow versions
export const machinesTable = dbSchema.table("machines", { export const machinesTable = dbSchema.table("machines", {
id: uuid("id").primaryKey().defaultRandom().notNull(), id: uuid("id").primaryKey().defaultRandom().notNull(),
@ -130,5 +149,37 @@ export const machinesTable = dbSchema.table("machines", {
updated_at: timestamp("updated_at").defaultNow().notNull(), 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<typeof usersTable>; export type UserType = InferSelectModel<typeof usersTable>;
export type WorkflowType = InferSelectModel<typeof workflowTable>; export type WorkflowType = InferSelectModel<typeof workflowTable>;

View File

@ -1,13 +1,10 @@
import { db } from "./db/db"; import { authMiddleware, redirectToSignIn } from "@clerk/nextjs";
import { usersTable } from "./db/schema";
import { authMiddleware, redirectToSignIn, clerkClient } from "@clerk/nextjs";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";
// This example protects all routes including api/trpc routes // This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed. // 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 // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware
export default authMiddleware({ export default authMiddleware({
// debug: true,
publicRoutes: ["/api/(.*)"], publicRoutes: ["/api/(.*)"],
// publicRoutes: ["/", "/(.*)"], // publicRoutes: ["/", "/(.*)"],
async afterAuth(auth, req, evt) { async afterAuth(auth, req, evt) {
@ -33,6 +30,6 @@ export default authMiddleware({
}); });
export const config = { export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/" , "/(api|trpc)(.*)"], matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
// matcher: ['/','/create', '/api/(twitter|generation|init|voice-cloning)'], // matcher: ['/','/create', '/api/(twitter|generation|init|voice-cloning)'],
}; };

View File

@ -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}`);
}

View File

@ -43,14 +43,7 @@ export async function addMachine(name: string, endpoint: string) {
export async function deleteMachine( export async function deleteMachine(
machine_id: string machine_id: string
): Promise<{ message: string; error?: boolean }> { ): Promise<{ message: string; error?: boolean }> {
try {
await db.delete(machinesTable).where(eq(machinesTable.id, machine_id)); await db.delete(machinesTable).where(eq(machinesTable.id, machine_id));
revalidatePath("/machines"); revalidatePath("/machines");
return { message: "Machine Deleted" }; return { message: "Machine Deleted" };
} catch (error: unknown) {
return {
message: `Error: ${error.detail}`,
error: true,
};
}
} }

View File

@ -1,5 +1,5 @@
import { db } from "@/db/db"; import { db } from "@/db/db";
import { workflowRunsTable } from "@/db/schema"; import { deploymentsTable, workflowRunsTable } from "@/db/schema";
import { desc, eq } from "drizzle-orm"; import { desc, eq } from "drizzle-orm";
export async function findAllRuns(workflow_id: string) { 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,
},
},
},
});
}

View File

@ -1,12 +1,27 @@
"use server"; "use server";
import { db } from "@/db/db"; import { db } from "@/db/db";
import { workflowRunOutputs } from "@/db/schema"; import { workflowRunOutputs, workflowRunsTable } from "@/db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
export async function getRunsOutput(run_id: string) { export async function getRunsOutput(run_id: string) {
// throw new Error("Not implemented");
return await db return await db
.select() .select()
.from(workflowRunOutputs) .from(workflowRunOutputs)
.where(eq(workflowRunOutputs.run_id, run_id)); .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,
},
},
},
});
}

View File

@ -17,7 +17,7 @@ const s3Client = new S3({
forcePathStyle: true, forcePathStyle: true,
}); });
function replaceCDNUrl(url: string) { export function replaceCDNUrl(url: string) {
url = url.replace( url = url.replace(
process.env.SPACES_ENDPOINT!, process.env.SPACES_ENDPOINT!,
process.env.SPACES_ENDPOINT_CDN! process.env.SPACES_ENDPOINT_CDN!