diff --git a/.gitignore b/.gitignore
index ed8ebf5..c34d28b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-__pycache__
\ No newline at end of file
+__pycache__
+.DS_Store
\ No newline at end of file
diff --git a/web/.env.example b/web/.env.example
index ded4e58..8eefbb5 100644
--- a/web/.env.example
+++ b/web/.env.example
@@ -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=
\ No newline at end of file
diff --git a/web/drizzle/0020_complete_black_tom.sql b/web/drizzle/0020_complete_black_tom.sql
new file mode 100644
index 0000000..4928db9
--- /dev/null
+++ b/web/drizzle/0020_complete_black_tom.sql
@@ -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;
\ No newline at end of file
diff --git a/web/drizzle/meta/0020_snapshot.json b/web/drizzle/meta/0020_snapshot.json
new file mode 100644
index 0000000..68cc8d2
--- /dev/null
+++ b/web/drizzle/meta/0020_snapshot.json
@@ -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": {}
+ }
+}
\ No newline at end of file
diff --git a/web/drizzle/meta/_journal.json b/web/drizzle/meta/_journal.json
index 029c90f..3ae00ed 100644
--- a/web/drizzle/meta/_journal.json
+++ b/web/drizzle/meta/_journal.json
@@ -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
}
]
}
\ No newline at end of file
diff --git a/web/src/app/(app)/api/machine-built/route.ts b/web/src/app/(app)/api/machine-built/route.ts
new file mode 100644
index 0000000..4dea5ef
--- /dev/null
+++ b/web/src/app/(app)/api/machine-built/route.ts
@@ -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,
+ }
+ );
+}
diff --git a/web/src/app/(app)/machines/[machine_id]/loading.tsx b/web/src/app/(app)/machines/[machine_id]/loading.tsx
new file mode 100644
index 0000000..9ff4783
--- /dev/null
+++ b/web/src/app/(app)/machines/[machine_id]/loading.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import { LoadingPageWrapper } from "@/components/LoadingWrapper";
+import { usePathname } from "next/navigation";
+
+export default function Loading() {
+ const pathName = usePathname();
+ return