feat: add s3 localstack, upload api
This commit is contained in:
@@ -56,6 +56,7 @@ import {
|
||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
export type Machine = {
|
||||
@@ -159,9 +160,8 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={() => {
|
||||
deleteMachine(workflow.id);
|
||||
// navigator.clipboard.writeText(payment.id)
|
||||
onClick={async () => {
|
||||
callServerWithToast(await deleteMachine(workflow.id));
|
||||
}}
|
||||
>
|
||||
Delete Machine
|
||||
@@ -176,6 +176,17 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
async function callServerWithToast(result: {
|
||||
message: string;
|
||||
error?: boolean;
|
||||
}) {
|
||||
if (result.error) {
|
||||
toast.error(result.message);
|
||||
} else {
|
||||
toast.success(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function MachineList({ data }: { data: Machine[] }) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
@@ -333,8 +344,8 @@ function AddMachinesDialog() {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
endpoint: "",
|
||||
name: "My Local Machine",
|
||||
endpoint: "http://127.0.0.1:8188",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { create } from "zustand";
|
||||
type State = {
|
||||
data: {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
json: {
|
||||
event: string;
|
||||
data: any;
|
||||
@@ -27,7 +28,7 @@ export const useStore = create<State>((set) => ({
|
||||
addData: (id, json) =>
|
||||
set((state) => ({
|
||||
...state,
|
||||
data: [...state.data, { id, json }],
|
||||
data: [...state.data, { id, json, timestamp: Date.now() }],
|
||||
})),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,27 +1,123 @@
|
||||
"use client";
|
||||
|
||||
import type { findAllRuns } from "../app/[workflow_id]/page";
|
||||
import { StatusBadge } from "../app/[workflow_id]/page";
|
||||
import { useStore } from "@/components/MachinesWS";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import { StatusBadge } from "@/components/StatusBadge";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { getRelativeTime } from "@/lib/getRelativeTime";
|
||||
import { type findAllRuns } from "@/server/findAllRuns";
|
||||
import { getRunsOutput } from "@/server/getRunsOutput";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function RunDisplay({
|
||||
run,
|
||||
}: {
|
||||
run: Awaited<ReturnType<typeof findAllRuns>>[0];
|
||||
}) {
|
||||
const data = useStore((state) => state.data.find((x) => x.id === run.id));
|
||||
const data = useStore(
|
||||
(state) =>
|
||||
state.data
|
||||
.filter((x) => x.id === run.id)
|
||||
.sort((a, b) => b.timestamp - a.timestamp)?.[0]
|
||||
);
|
||||
|
||||
let status = run.status;
|
||||
|
||||
if (data?.json.event == "executing" && data.json.data.node == undefined) {
|
||||
status = "success";
|
||||
} else if (data?.json.event == "executing") {
|
||||
status = "running";
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>{run.version.version}</TableCell>
|
||||
<TableCell className="font-medium">{run.machine.name}</TableCell>
|
||||
<TableCell>{getRelativeTime(run.created_at)}</TableCell>
|
||||
<TableCell>{data ? data.json.event : "-"}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<StatusBadge run={run} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
||||
<TableRow>
|
||||
<TableCell>{run.version?.version}</TableCell>
|
||||
<TableCell className="font-medium">{run.machine?.name}</TableCell>
|
||||
<TableCell>{getRelativeTime(run.created_at)}</TableCell>
|
||||
<TableCell>
|
||||
{data && status != "success"
|
||||
? `${data.json.event} - ${data.json.data.node}`
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<StatusBadge status={status} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Run outputs</DialogTitle>
|
||||
<DialogDescription>
|
||||
You can view your run's outputs here
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<RunOutputs run_id={run.id} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
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 }) {
|
||||
if (props.filename.endsWith(".png")) {
|
||||
return (
|
||||
<img
|
||||
alt={props.filename}
|
||||
src={`api/view?file=${encodeURIComponent(
|
||||
`outputs/runs/${props.run_id}/${props.filename}`
|
||||
)}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { findAllRuns } from "../server/findAllRuns";
|
||||
import { RunDisplay } from "./RunDisplay";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
export async function RunsTable(props: { workflow_id: string }) {
|
||||
const allRuns = await findAllRuns(props.workflow_id);
|
||||
return (
|
||||
<div className="overflow-auto h-[400px] w-full">
|
||||
<Table className="">
|
||||
<TableCaption>A list of your recent runs.</TableCaption>
|
||||
<TableHeader className="bg-background top-0 sticky">
|
||||
<TableRow>
|
||||
<TableHead className=" w-[100px]">Version</TableHead>
|
||||
<TableHead className="">Machine</TableHead>
|
||||
<TableHead className="">Time</TableHead>
|
||||
<TableHead className="">Live Status</TableHead>
|
||||
<TableHead className=" text-right">Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{allRuns.map((run) => (
|
||||
<RunDisplay run={run} key={run.id} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { findAllRuns } from "../server/findAllRuns";
|
||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export function StatusBadge({
|
||||
status,
|
||||
}: {
|
||||
status: Awaited<ReturnType<typeof findAllRuns>>[0]["status"];
|
||||
}) {
|
||||
switch (status) {
|
||||
case "running":
|
||||
return (
|
||||
<Badge variant="secondary">
|
||||
{status} <LoadingIcon />
|
||||
</Badge>
|
||||
);
|
||||
case "success":
|
||||
return <Badge variant="success">{status}</Badge>;
|
||||
case "failed":
|
||||
return <Badge variant="destructive">{status}</Badge>;
|
||||
}
|
||||
return <Badge variant="secondary">{status}</Badge>;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type { findFirstTableWithVersion } from "@/app/[workflow_id]/page";
|
||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { createRun } from "@/server/createRun";
|
||||
import type { getMachines } from "@/server/curdMachine";
|
||||
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
|
||||
import { Play } from "lucide-react";
|
||||
import { parseAsInteger, useQueryState } from "next-usequerystate";
|
||||
import { useState } from "react";
|
||||
@@ -56,7 +56,7 @@ export function MachineSelect({
|
||||
machines: Awaited<ReturnType<typeof getMachines>>;
|
||||
}) {
|
||||
const [machine, setMachine] = useQueryState("machine", {
|
||||
defaultValue: machines[0].id ?? "",
|
||||
defaultValue: machines?.[0].id ?? "",
|
||||
});
|
||||
return (
|
||||
<Select
|
||||
@@ -111,6 +111,7 @@ export function RunWorkflowButton({
|
||||
try {
|
||||
const origin = window.location.origin;
|
||||
await createRun(origin, workflow_version_id, machine);
|
||||
// console.log(res.json());
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as React from "react";
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
Table.displayName = "Table"
|
||||
// <div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
// </div>
|
||||
));
|
||||
Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
@@ -33,8 +32,8 @@ const TableBody = React.forwardRef<
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
@@ -48,8 +47,8 @@ const TableFooter = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
));
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
@@ -63,8 +62,8 @@ const TableRow = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
));
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
@@ -78,8 +77,8 @@ const TableHead = React.forwardRef<
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
@@ -90,8 +89,8 @@ const TableCell = React.forwardRef<
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
@@ -102,8 +101,8 @@ const TableCaption = React.forwardRef<
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
export {
|
||||
Table,
|
||||
@@ -114,4 +113,4 @@ export {
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user