feat: add preview image builder
This commit is contained in:
parent
314eb9fd16
commit
9937252777
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
||||
__pycache__
|
||||
__pycache__
|
||||
.DS_Store
|
@ -13,5 +13,7 @@ SPACES_SECRET="aaa"
|
||||
SPACES_CDN_DONT_INCLUDE_BUCKET="false"
|
||||
SPACES_CDN_FORCE_PATH_STYLE="true"
|
||||
|
||||
MODAL_BUILDER_URL=
|
||||
|
||||
JWT_SECRET="openssl rand -hex 32"
|
||||
PLAUSIBLE_DOMAIN=
|
9
web/drizzle/0020_complete_black_tom.sql
Normal file
9
web/drizzle/0020_complete_black_tom.sql
Normal file
@ -0,0 +1,9 @@
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "machine_status" AS ENUM('ready', 'building', 'error');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "status" "machine_status" DEFAULT 'ready' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "snapshot" jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "build_log" text;
|
703
web/drizzle/meta/0020_snapshot.json
Normal file
703
web/drizzle/meta/0020_snapshot.json
Normal file
@ -0,0 +1,703 @@
|
||||
{
|
||||
"id": "60fd6aaf-0bd4-4b77-b707-fea7c44e829d",
|
||||
"prevId": "4c2423cd-420e-49fc-a6fe-2e6c56ce8c61",
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"tables": {
|
||||
"api_keys": {
|
||||
"name": "api_keys",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"org_id": {
|
||||
"name": "org_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"revoked": {
|
||||
"name": "revoked",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"api_keys_user_id_users_id_fk": {
|
||||
"name": "api_keys_user_id_users_id_fk",
|
||||
"tableFrom": "api_keys",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"api_keys_key_unique": {
|
||||
"name": "api_keys_key_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"key"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"deployments": {
|
||||
"name": "deployments",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workflow_version_id": {
|
||||
"name": "workflow_version_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workflow_id": {
|
||||
"name": "workflow_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"machine_id": {
|
||||
"name": "machine_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"environment": {
|
||||
"name": "environment",
|
||||
"type": "deployment_environment",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"deployments_user_id_users_id_fk": {
|
||||
"name": "deployments_user_id_users_id_fk",
|
||||
"tableFrom": "deployments",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"deployments_workflow_version_id_workflow_versions_id_fk": {
|
||||
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
|
||||
"tableFrom": "deployments",
|
||||
"tableTo": "workflow_versions",
|
||||
"columnsFrom": [
|
||||
"workflow_version_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"deployments_workflow_id_workflows_id_fk": {
|
||||
"name": "deployments_workflow_id_workflows_id_fk",
|
||||
"tableFrom": "deployments",
|
||||
"tableTo": "workflows",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"deployments_machine_id_machines_id_fk": {
|
||||
"name": "deployments_machine_id_machines_id_fk",
|
||||
"tableFrom": "deployments",
|
||||
"tableTo": "machines",
|
||||
"columnsFrom": [
|
||||
"machine_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"machines": {
|
||||
"name": "machines",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"org_id": {
|
||||
"name": "org_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"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()"
|
||||
},
|
||||
"disabled": {
|
||||
"name": "disabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"auth_token": {
|
||||
"name": "auth_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "machine_type",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'classic'"
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "machine_status",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'ready'"
|
||||
},
|
||||
"snapshot": {
|
||||
"name": "snapshot",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"build_log": {
|
||||
"name": "build_log",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"machines_user_id_users_id_fk": {
|
||||
"name": "machines_user_id_users_id_fk",
|
||||
"tableFrom": "machines",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflow_run_outputs": {
|
||||
"name": "workflow_run_outputs",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"run_id": {
|
||||
"name": "run_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
|
||||
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
|
||||
"tableFrom": "workflow_run_outputs",
|
||||
"tableTo": "workflow_runs",
|
||||
"columnsFrom": [
|
||||
"run_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflow_runs": {
|
||||
"name": "workflow_runs",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"workflow_version_id": {
|
||||
"name": "workflow_version_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"workflow_inputs": {
|
||||
"name": "workflow_inputs",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"workflow_id": {
|
||||
"name": "workflow_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"machine_id": {
|
||||
"name": "machine_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"origin": {
|
||||
"name": "origin",
|
||||
"type": "workflow_run_origin",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'api'"
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "workflow_run_status",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'not-started'"
|
||||
},
|
||||
"ended_at": {
|
||||
"name": "ended_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
|
||||
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "workflow_versions",
|
||||
"columnsFrom": [
|
||||
"workflow_version_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"workflow_runs_workflow_id_workflows_id_fk": {
|
||||
"name": "workflow_runs_workflow_id_workflows_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "workflows",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"workflow_runs_machine_id_machines_id_fk": {
|
||||
"name": "workflow_runs_machine_id_machines_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "machines",
|
||||
"columnsFrom": [
|
||||
"machine_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflows": {
|
||||
"name": "workflows",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"org_id": {
|
||||
"name": "org_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflows_user_id_users_id_fk": {
|
||||
"name": "workflows_user_id_users_id_fk",
|
||||
"tableFrom": "workflows",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflow_versions": {
|
||||
"name": "workflow_versions",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"workflow_id": {
|
||||
"name": "workflow_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"workflow": {
|
||||
"name": "workflow",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"workflow_api": {
|
||||
"name": "workflow_api",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflow_versions_workflow_id_workflows_id_fk": {
|
||||
"name": "workflow_versions_workflow_id_workflows_id_fk",
|
||||
"tableFrom": "workflow_versions",
|
||||
"tableTo": "workflows",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"deployment_environment": {
|
||||
"name": "deployment_environment",
|
||||
"values": {
|
||||
"staging": "staging",
|
||||
"production": "production"
|
||||
}
|
||||
},
|
||||
"machine_status": {
|
||||
"name": "machine_status",
|
||||
"values": {
|
||||
"ready": "ready",
|
||||
"building": "building",
|
||||
"error": "error"
|
||||
}
|
||||
},
|
||||
"machine_type": {
|
||||
"name": "machine_type",
|
||||
"values": {
|
||||
"classic": "classic",
|
||||
"runpod-serverless": "runpod-serverless",
|
||||
"modal-serverless": "modal-serverless"
|
||||
}
|
||||
},
|
||||
"workflow_run_origin": {
|
||||
"name": "workflow_run_origin",
|
||||
"values": {
|
||||
"manual": "manual",
|
||||
"api": "api"
|
||||
}
|
||||
},
|
||||
"workflow_run_status": {
|
||||
"name": "workflow_run_status",
|
||||
"values": {
|
||||
"not-started": "not-started",
|
||||
"running": "running",
|
||||
"uploading": "uploading",
|
||||
"success": "success",
|
||||
"failed": "failed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"comfyui_deploy": "comfyui_deploy"
|
||||
},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
@ -141,6 +141,13 @@
|
||||
"when": 1704174903117,
|
||||
"tag": "0019_damp_stick",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 20,
|
||||
"version": "5",
|
||||
"when": 1704350033885,
|
||||
"tag": "0020_complete_black_tom",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
50
web/src/app/(app)/api/machine-built/route.ts
Normal file
50
web/src/app/(app)/api/machine-built/route.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { parseDataSafe } from "../../../../lib/parseDataSafe";
|
||||
import { db } from "@/db/db";
|
||||
import { machinesTable } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
const Request = z.object({
|
||||
machine_id: z.string(),
|
||||
endpoint: z.string().optional(),
|
||||
build_log: z.string().optional(),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const [data, error] = await parseDataSafe(Request, request);
|
||||
if (!data || error) return error;
|
||||
|
||||
console.log(data);
|
||||
|
||||
const { machine_id, endpoint, build_log } = data;
|
||||
|
||||
if (endpoint) {
|
||||
await db
|
||||
.update(machinesTable)
|
||||
.set({
|
||||
status: "ready",
|
||||
endpoint: endpoint,
|
||||
build_log: build_log,
|
||||
})
|
||||
.where(eq(machinesTable.id, machine_id));
|
||||
} else {
|
||||
// console.log(data);
|
||||
await db
|
||||
.update(machinesTable)
|
||||
.set({
|
||||
status: "error",
|
||||
build_log: build_log,
|
||||
})
|
||||
.where(eq(machinesTable.id, machine_id));
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: "success",
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
}
|
9
web/src/app/(app)/machines/[machine_id]/loading.tsx
Normal file
9
web/src/app/(app)/machines/[machine_id]/loading.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export default function Loading() {
|
||||
const pathName = usePathname();
|
||||
return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
|
||||
}
|
43
web/src/app/(app)/machines/[machine_id]/page.tsx
Normal file
43
web/src/app/(app)/machines/[machine_id]/page.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { MachineBuildLog } from "../../../../components/MachineBuildLog";
|
||||
import { LogsViewer } from "@/components/LogsViewer";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { getRelativeTime } from "@/lib/getRelativeTime";
|
||||
import { getMachineById } from "@/server/curdMachine";
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: { machine_id: string };
|
||||
}) {
|
||||
const machine = await getMachineById(params.machine_id);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card className="w-full h-fit mt-4">
|
||||
<CardHeader>
|
||||
<CardTitle>{machine.name}</CardTitle>
|
||||
<CardDescription suppressHydrationWarning={true}>
|
||||
{getRelativeTime(machine?.updated_at)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{machine.status == "building" && (
|
||||
<MachineBuildLog
|
||||
machine_id={params.machine_id}
|
||||
endpoint={process.env.MODAL_BUILDER_URL!}
|
||||
/>
|
||||
)}
|
||||
{machine.build_log && (
|
||||
<LogsViewer logs={JSON.parse(machine.build_log)} />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
|
||||
|
||||
export default function Loading() {
|
||||
// You can add any UI inside Loading, including a Skeleton.
|
||||
return <LoadingPageWrapper tag="workflow" />;
|
||||
}
|
46
web/src/components/LogsViewer.tsx
Normal file
46
web/src/components/LogsViewer.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
export type LogsType = {
|
||||
machine_id?: string;
|
||||
logs: string;
|
||||
timestamp: number;
|
||||
}[];
|
||||
|
||||
export function LogsViewer({ logs }: { logs: LogsType }) {
|
||||
const container = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(logs.length, container.current);
|
||||
if (container.current) {
|
||||
const scrollHeight = container.current.scrollHeight;
|
||||
|
||||
container.current.scrollTo({
|
||||
top: scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, [logs.length]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(ref) => {
|
||||
if (!container.current && ref) {
|
||||
const scrollHeight = ref.scrollHeight;
|
||||
|
||||
ref.scrollTo({
|
||||
top: scrollHeight,
|
||||
behavior: "instant",
|
||||
});
|
||||
}
|
||||
container.current = ref;
|
||||
}}
|
||||
className="flex flex-col text-xs p-2 overflow-y-scroll max-h-[400px] whitespace-break-spaces"
|
||||
>
|
||||
{logs.map((x, i) => (
|
||||
<div key={i}>{x.logs}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
48
web/src/components/MachineBuildLog.tsx
Normal file
48
web/src/components/MachineBuildLog.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import type { LogsType } from "@/components/LogsViewer";
|
||||
import { LogsViewer } from "@/components/LogsViewer";
|
||||
import { getConnectionStatus } from "@/components/getConnectionStatus";
|
||||
import { useEffect, useState } from "react";
|
||||
import useWebSocket from "react-use-websocket";
|
||||
|
||||
export function MachineBuildLog({
|
||||
machine_id,
|
||||
endpoint,
|
||||
}: {
|
||||
machine_id: string;
|
||||
endpoint: string;
|
||||
}) {
|
||||
const [logs, setLogs] = useState<LogsType>([]);
|
||||
|
||||
const wsEndpoint = endpoint.replace(/^http/, "ws");
|
||||
const { lastMessage, readyState } = useWebSocket(
|
||||
`${wsEndpoint}/ws/${machine_id}`,
|
||||
{
|
||||
shouldReconnect: () => true,
|
||||
reconnectAttempts: 20,
|
||||
reconnectInterval: 1000,
|
||||
}
|
||||
);
|
||||
|
||||
const connectionStatus = getConnectionStatus(readyState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastMessage?.data) return;
|
||||
|
||||
const message = JSON.parse(lastMessage.data);
|
||||
|
||||
console.log(message);
|
||||
|
||||
if (message?.event === "LOGS") {
|
||||
setLogs((logs) => [...(logs ?? []), message.data]);
|
||||
}
|
||||
}, [lastMessage]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{connectionStatus}
|
||||
<LogsViewer logs={logs} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -23,8 +23,12 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { type MachineType } from "@/db/schema";
|
||||
import { addMachineSchema } from "@/server/addMachineSchema";
|
||||
import {
|
||||
addCustomMachineSchema,
|
||||
addMachineSchema,
|
||||
} from "@/server/addMachineSchema";
|
||||
import {
|
||||
addCustomMachine,
|
||||
addMachine,
|
||||
deleteMachine,
|
||||
disableMachine,
|
||||
@ -92,10 +96,15 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
return (
|
||||
// <a className="hover:underline" href={`/${row.original.id}`}>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div>{row.getValue("name")}</div>
|
||||
<a href={`/machines/${row.original.id}`} className="hover:underline">
|
||||
{row.getValue("name")}
|
||||
</a>
|
||||
{row.original.disabled && (
|
||||
<Badge variant="destructive">Disabled</Badge>
|
||||
)}
|
||||
{!row.original.disabled && row.original.status && (
|
||||
<Badge variant="outline">{row.original.status}</Badge>
|
||||
)}
|
||||
</div>
|
||||
// </a>
|
||||
);
|
||||
@ -254,6 +263,20 @@ export function MachineList({ data }: { data: Machine[] }) {
|
||||
serverAction={addMachine}
|
||||
formSchema={addMachineSchema}
|
||||
/>
|
||||
<InsertModal
|
||||
title="Custom Machine"
|
||||
description="Add custom Comfyui machines to your account."
|
||||
serverAction={addCustomMachine}
|
||||
formSchema={addCustomMachineSchema}
|
||||
fieldConfig={{
|
||||
type: {
|
||||
fieldType: "fallback",
|
||||
inputProps: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border overflow-x-auto w-full">
|
||||
|
@ -1,5 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { LogsViewer } from "./LogsViewer";
|
||||
import { getConnectionStatus } from "./getConnectionStatus";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Dialog,
|
||||
@ -10,9 +12,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import type { getMachines } from "@/server/curdMachine";
|
||||
import { Check, CircleOff, SatelliteDish } from "lucide-react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import useWebSocket, { ReadyState } from "react-use-websocket";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import useWebSocket from "react-use-websocket";
|
||||
import { create } from "zustand";
|
||||
|
||||
type State = {
|
||||
@ -98,17 +99,7 @@ function MachineWS({
|
||||
}
|
||||
);
|
||||
|
||||
const connectionStatus = {
|
||||
[ReadyState.CONNECTING]: (
|
||||
<SatelliteDish size={14} className="text-orange-500" />
|
||||
),
|
||||
[ReadyState.OPEN]: <Check size={14} className="text-green-500" />,
|
||||
[ReadyState.CLOSING]: <CircleOff size={14} className="text-orange-500" />,
|
||||
[ReadyState.CLOSED]: <CircleOff size={14} className="text-red-500" />,
|
||||
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
|
||||
}[readyState];
|
||||
|
||||
const container = useRef<HTMLDivElement | null>(null);
|
||||
const connectionStatus = getConnectionStatus(readyState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lastMessage?.data) return;
|
||||
@ -130,18 +121,6 @@ function MachineWS({
|
||||
}
|
||||
}, [lastMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(logs.length, container.current);
|
||||
if (container.current) {
|
||||
const scrollHeight = container.current.scrollHeight;
|
||||
|
||||
container.current.scrollTo({
|
||||
top: scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, [logs.length]);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild className="">
|
||||
@ -156,24 +135,7 @@ function MachineWS({
|
||||
You can view your run's outputs here
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div
|
||||
ref={(ref) => {
|
||||
if (!container.current && ref) {
|
||||
const scrollHeight = ref.scrollHeight;
|
||||
|
||||
ref.scrollTo({
|
||||
top: scrollHeight,
|
||||
behavior: "instant",
|
||||
});
|
||||
}
|
||||
container.current = ref;
|
||||
}}
|
||||
className="flex flex-col text-xs p-2 overflow-y-scroll max-h-[400px] whitespace-break-spaces"
|
||||
>
|
||||
{logs.map((x, i) => (
|
||||
<div key={i}>{x.logs}</div>
|
||||
))}
|
||||
</div>
|
||||
<LogsViewer logs={logs} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
17
web/src/components/getConnectionStatus.tsx
Normal file
17
web/src/components/getConnectionStatus.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { Check, CircleOff, SatelliteDish } from "lucide-react";
|
||||
import React from "react";
|
||||
import { ReadyState } from "react-use-websocket";
|
||||
|
||||
export function getConnectionStatus(readyState: ReadyState) {
|
||||
const connectionStatus = {
|
||||
[ReadyState.CONNECTING]: (
|
||||
<SatelliteDish size={14} className="text-orange-500" />
|
||||
),
|
||||
[ReadyState.OPEN]: <Check size={14} className="text-green-500" />,
|
||||
[ReadyState.CLOSING]: <CircleOff size={14} className="text-orange-500" />,
|
||||
[ReadyState.CLOSED]: <CircleOff size={14} className="text-red-500" />,
|
||||
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
|
||||
}[readyState];
|
||||
|
||||
return connectionStatus;
|
||||
}
|
@ -108,6 +108,12 @@ export const machinesType = pgEnum("machine_type", [
|
||||
"modal-serverless",
|
||||
]);
|
||||
|
||||
export const machinesStatus = pgEnum("machine_status", [
|
||||
"ready",
|
||||
"building",
|
||||
"error",
|
||||
]);
|
||||
|
||||
// We still want to keep the workflow run record.
|
||||
export const workflowRunsTable = dbSchema.table("workflow_runs", {
|
||||
id: uuid("id").primaryKey().defaultRandom().notNull(),
|
||||
@ -193,6 +199,9 @@ export const machinesTable = dbSchema.table("machines", {
|
||||
disabled: boolean("disabled").default(false).notNull(),
|
||||
auth_token: text("auth_token"),
|
||||
type: machinesType("type").notNull().default("classic"),
|
||||
status: machinesStatus("status").notNull().default("ready"),
|
||||
snapshot: jsonb("snapshot").$type<any>(),
|
||||
build_log: text("build_log"),
|
||||
});
|
||||
|
||||
export const insertMachineSchema = createInsertSchema(machinesTable, {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { insertMachineSchema } from "@/db/schema";
|
||||
import { insertMachineSchema, machinesTable } from "@/db/schema";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
|
||||
export const addMachineSchema = insertMachineSchema.pick({
|
||||
name: true,
|
||||
@ -6,3 +7,14 @@ export const addMachineSchema = insertMachineSchema.pick({
|
||||
type: true,
|
||||
auth_token: true,
|
||||
});
|
||||
|
||||
export const insertCustomMachineSchema = createInsertSchema(machinesTable, {
|
||||
name: (schema) => schema.name.default("My Machine"),
|
||||
type: (schema) => schema.type.default("modal-serverless"),
|
||||
});
|
||||
|
||||
export const addCustomMachineSchema = insertCustomMachineSchema.pick({
|
||||
name: true,
|
||||
type: true,
|
||||
snapshot: true,
|
||||
});
|
||||
|
@ -1,12 +1,17 @@
|
||||
"use server";
|
||||
|
||||
import type { addMachineSchema } from "./addMachineSchema";
|
||||
import type {
|
||||
addCustomMachineSchema,
|
||||
addMachineSchema,
|
||||
} from "./addMachineSchema";
|
||||
import { withServerPromise } from "./withServerPromise";
|
||||
import { db } from "@/db/db";
|
||||
import { machinesTable } from "@/db/schema";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import "server-only";
|
||||
import type { z } from "zod";
|
||||
|
||||
@ -27,6 +32,26 @@ export async function getMachines() {
|
||||
return machines;
|
||||
}
|
||||
|
||||
export async function getMachineById(id: string) {
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) throw new Error("No user id");
|
||||
const machines = await db
|
||||
.select()
|
||||
.from(machinesTable)
|
||||
.where(
|
||||
and(
|
||||
orgId
|
||||
? eq(machinesTable.org_id, orgId)
|
||||
: and(
|
||||
eq(machinesTable.user_id, userId),
|
||||
isNull(machinesTable.org_id)
|
||||
),
|
||||
eq(machinesTable.id, id)
|
||||
)
|
||||
);
|
||||
return machines[0];
|
||||
}
|
||||
|
||||
export const addMachine = withServerPromise(
|
||||
async (data: z.infer<typeof addMachineSchema>) => {
|
||||
const { userId, orgId } = auth();
|
||||
@ -42,6 +67,72 @@ export const addMachine = withServerPromise(
|
||||
}
|
||||
);
|
||||
|
||||
export const addCustomMachine = withServerPromise(
|
||||
async (data: z.infer<typeof addCustomMachineSchema>) => {
|
||||
const { userId, orgId } = auth();
|
||||
const headersList = headers();
|
||||
|
||||
if (!userId) return { error: "No user id" };
|
||||
|
||||
// Insert to our db
|
||||
const a = await db
|
||||
.insert(machinesTable)
|
||||
.values({
|
||||
...data,
|
||||
org_id: orgId,
|
||||
user_id: userId,
|
||||
status: "building",
|
||||
endpoint: "not-ready",
|
||||
})
|
||||
.returning();
|
||||
|
||||
const b = a[0];
|
||||
|
||||
// const origin = new URL(request.url).origin;
|
||||
const domain = headersList.get("x-forwarded-host") || "";
|
||||
const protocol = headersList.get("x-forwarded-proto") || "";
|
||||
// console.log("domain", domain);
|
||||
// console.log("domain", `${protocol}://${domain}/api/machine-built`);
|
||||
// return { message: "Machine Building" };
|
||||
|
||||
if (domain === "") {
|
||||
throw new Error("No domain");
|
||||
}
|
||||
|
||||
// Call remote builder
|
||||
const result = await fetch(`${process.env.MODAL_BUILDER_URL!}/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
machine_id: b.id,
|
||||
name: b.id,
|
||||
snapshot: JSON.parse(data.snapshot as string),
|
||||
callback_url: `${protocol}://${domain}/api/machine-built`,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const error_log = await result.text();
|
||||
await db
|
||||
.update(machinesTable)
|
||||
.set({
|
||||
...data,
|
||||
status: "error",
|
||||
build_log: error_log,
|
||||
})
|
||||
.where(eq(machinesTable.id, b.id));
|
||||
throw new Error(`Error: ${result.statusText} ${error_log}`);
|
||||
}
|
||||
|
||||
redirect(`/machines/${b.id}`);
|
||||
|
||||
// revalidatePath("/machines");
|
||||
return { message: "Machine Building" };
|
||||
}
|
||||
);
|
||||
|
||||
export const updateMachine = withServerPromise(
|
||||
async ({
|
||||
id,
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { isRedirectError } from "next/dist/client/components/redirect";
|
||||
|
||||
export async function wrapServerPromise<T>(result: Promise<T>) {
|
||||
return result.catch((error) => {
|
||||
if (isRedirectError(error)) throw error;
|
||||
return {
|
||||
error: error.message,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user