feat: add comftui plugin, initial db

This commit is contained in:
BennyKok
2023-12-09 00:10:59 +08:00
parent d6ac12a7ef
commit 6b431426ff
72 changed files with 4602 additions and 250 deletions
+64
View File
@@ -0,0 +1,64 @@
import { MachineSelect, VersionSelect } from "@/components/VersionSelect";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { db } from "@/db/db";
import { workflowTable, workflowVersionTable } from "@/db/schema";
import { getRelativeTime } from "@/lib/getRelativeTime";
import { getMachines } from "@/server/curdMachine";
import { desc, eq, sql } from "drizzle-orm";
import { Play } from "lucide-react";
export async function findFirstTableWithVersion(workflow_id: string) {
return await db.query.workflowTable.findFirst({
with: { versions: { orderBy: desc(workflowVersionTable.version) } },
where: eq(workflowTable.id, workflow_id),
});
}
export default async function Page({
params,
}: {
params: { workflow_id: string };
}) {
const workflow_id = params.workflow_id;
const workflow = await findFirstTableWithVersion(workflow_id);
const machines = await getMachines();
return (
<div className="mt-4 w-full flex flex-col lg:flex-row gap-4">
<Card className="w-full lg:w-fit lg:min-w-[500px]">
<CardHeader>
<CardTitle>{workflow?.name}</CardTitle>
<CardDescription suppressHydrationWarning={true}>
{getRelativeTime(workflow?.updated_at)}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex gap-2 ">
<VersionSelect workflow={workflow} />
<MachineSelect machines={machines} />
<Button className="gap-2">
Run <Play size={14} />
</Button>
</div>
</CardContent>
</Card>
<Card className="w-full ">
<CardHeader>
<CardTitle>Run</CardTitle>
</CardHeader>
<CardContent></CardContent>
</Card>
</div>
);
}
+56
View File
@@ -0,0 +1,56 @@
import { parseDataSafe } from "../../../lib/parseDataSafe";
import { db } from "@/db/db";
import {
workflowRunStatus,
workflowRunsTable,
workflowTable,
workflowVersionTable,
} from "@/db/schema";
import { eq, sql } from "drizzle-orm";
import { NextResponse } from "next/server";
import { ZodFormattedError, z } from "zod";
const Request = z.object({
workflow_version_id: z.string(),
machine_id: z.string(),
});
export async function OPTIONS(request: Request) {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
export async function POST(request: Request) {
const [data, error] = await parseDataSafe(Request, request);
if (!data || error) return error;
let { workflow_version_id, machine_id } = data;
const workflow_run = await db
.insert(workflowRunsTable)
.values({
workflow_version_id,
machine_id,
})
.returning();
return NextResponse.json(
{
workflow_run_id: workflow_run[0].id,
},
{
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
},
);
}
+55
View File
@@ -0,0 +1,55 @@
import { parseDataSafe } from "../../../lib/parseDataSafe";
import { db } from "@/db/db";
import {
workflowRunStatus,
workflowRunsTable,
workflowTable,
workflowVersionTable,
} from "@/db/schema";
import { eq, sql } from "drizzle-orm";
import { NextResponse } from "next/server";
import { ZodFormattedError, z } from "zod";
const Request = z.object({
run_id: z.string(),
status: z.enum(["not-started", "running", "success", "failed"]),
});
export async function OPTIONS(request: Request) {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
export async function POST(request: Request) {
const [data, error] = await parseDataSafe(Request, request);
if (!data || error) return error;
let { run_id, status } = data;
const workflow_run = await db
.update(workflowRunsTable)
.set({
status: status,
})
.where(eq(workflowRunsTable.id, run_id));
return NextResponse.json(
{
message: "success",
},
{
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
},
);
}
+125
View File
@@ -0,0 +1,125 @@
import { parseDataSafe } from "../../../lib/parseDataSafe";
import { db } from "@/db/db";
import { workflowTable, workflowVersionTable } from "@/db/schema";
import { eq, sql } from "drizzle-orm";
import { NextResponse } from "next/server";
import { ZodFormattedError, z } from "zod";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
const UploadRequest = z.object({
user_id: z.string(),
workflow_id: z.string().optional(),
workflow_name: z.string().optional(),
workflow: z.any(),
workflow_api: z.any(),
});
export async function OPTIONS(request: Request) {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
export async function POST(request: Request) {
console.log("hi");
const [data, error] = await parseDataSafe(
UploadRequest,
request,
corsHeaders,
);
if (!data || error) return error;
let { user_id, workflow, workflow_api, workflow_id, workflow_name } = data;
let version = -1;
// Case 1 new workflow
try {
if ((!workflow_id || workflow_id.length == 0) && workflow_name) {
// Create a new parent workflow
const workflow = await db
.insert(workflowTable)
.values({
user_id,
name: workflow_name,
})
.returning();
workflow_id = workflow[0].id;
// Create a new version
const data = await db
.insert(workflowVersionTable)
.values({
workflow_id: workflow_id,
workflow,
workflow_api,
version: 1,
})
.returning();
version = data[0].version;
} else if (workflow_id) {
// Case 2 update workflow
const data = await db
.insert(workflowVersionTable)
.values({
workflow_id,
workflow,
workflow_api,
// version: sql`${workflowVersionTable.version} + 1`,
version: sql`(
SELECT COALESCE(MAX(version), 0) + 1
FROM ${workflowVersionTable}
WHERE workflow_id = ${workflow_id}
)`,
})
.returning();
version = data[0].version;
} else {
return NextResponse.json(
{
error: "Invalid request, missing either workflow_id or name",
},
{
status: 500,
statusText: "Invalid request",
headers: corsHeaders,
},
);
}
} catch (error: any) {
return NextResponse.json(
{
error: error.toString(),
},
{
status: 500,
statusText: "Invalid request",
headers: corsHeaders,
},
);
}
return NextResponse.json(
{
workflow_id: workflow_id,
version: version,
},
{
status: 200,
headers: corsHeaders,
},
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+76
View File
@@ -0,0 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
+36
View File
@@ -0,0 +1,36 @@
import "./globals.css";
import { NavbarRight } from "@/components/NavbarRight";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Comfy Deploy",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<main className="flex min-h-screen flex-col items-center justify-start">
<div className="w-full h-18 flex items-center gap-4 p-4 border-b border-gray-200">
<a className="font-bold text-lg hover:underline" href="/">
Comfy Deploy
</a>
<NavbarRight />
{/* <div></div> */}
</div>
<div className="md:px-10 px-6 w-full flex items-start">
{children}
</div>
</main>
</body>
</html>
);
}
+67
View File
@@ -0,0 +1,67 @@
import { MachineList } from "@/components/MachineList";
import { WorkflowList } from "@/components/WorkflowList";
import { db } from "@/db/db";
import {
machinesTable,
usersTable,
workflowTable,
workflowVersionTable,
} from "@/db/schema";
import { auth, clerkClient } from "@clerk/nextjs";
import { desc, eq, sql } from "drizzle-orm";
export default function Page() {
return <MachineListServer />;
}
async function MachineListServer() {
const { userId } = await auth();
if (!userId) {
return <div>No auth</div>;
}
const workflow = await db.query.machinesTable.findMany({
orderBy: desc(machinesTable.updated_at),
where: eq(machinesTable.user_id, userId),
});
return (
<div className="w-full">
{/* <div>Machines</div> */}
<MachineList
data={workflow.map((x) => {
return {
id: x.id,
name: x.name,
date: x.updated_at,
endpoint: x.endpoint,
};
})}
/>
</div>
);
}
async function setInitialUserData(userId: string) {
const user = await clerkClient.users.getUser(userId);
// incase we dont have username such as google login, fallback to first name + last name
const usernameFallback =
user.username ?? (user.firstName ?? "") + (user.lastName ?? "");
// For the display name, if it for some reason is empty, fallback to username
let nameFallback = (user.firstName ?? "") + (user.lastName ?? "");
if (nameFallback === "") {
nameFallback = usernameFallback;
}
const result = await db.insert(usersTable).values({
id: userId,
// this is used for path, make sure this is unique
username: usernameFallback,
// this is for display name, maybe different from username
name: nameFallback,
});
}
+77
View File
@@ -0,0 +1,77 @@
import { WorkflowList } from "@/components/WorkflowList";
import { db } from "@/db/db";
import { usersTable, workflowTable, workflowVersionTable } from "@/db/schema";
import { auth, clerkClient } from "@clerk/nextjs";
import { desc, eq, sql } from "drizzle-orm";
export default function Home() {
return <WorkflowServer />;
}
async function WorkflowServer() {
const { userId } = await auth();
if (!userId) {
return <div>No auth</div>;
}
const user = await db.query.usersTable.findFirst({
where: eq(usersTable.id, userId),
});
if (!user) {
await setInitialUserData(userId);
}
const workflow = await db.query.workflowTable.findMany({
// extras: {
// count: sql<number>`(select count(*) from ${workflowVersionTable})`.as(
// "count",
// ),
// },
with: {
versions: {
limit: 1,
orderBy: desc(workflowVersionTable.version),
},
},
orderBy: desc(workflowTable.updated_at),
where: eq(workflowTable.user_id, userId),
});
return (
<WorkflowList
data={workflow.map((x) => {
return {
id: x.id,
email: x.name,
amount: x.versions[0]?.version ?? 0,
date: x.updated_at,
};
})}
/>
);
}
async function setInitialUserData(userId: string) {
const user = await clerkClient.users.getUser(userId);
// incase we dont have username such as google login, fallback to first name + last name
const usernameFallback =
user.username ?? (user.firstName ?? "") + (user.lastName ?? "");
// For the display name, if it for some reason is empty, fallback to username
let nameFallback = (user.firstName ?? "") + (user.lastName ?? "");
if (nameFallback === "") {
nameFallback = usernameFallback;
}
const result = await db.insert(usersTable).values({
id: userId,
// this is used for path, make sure this is unique
username: usernameFallback,
// this is for display name, maybe different from username
name: nameFallback,
});
}