diff --git a/frontend/src/components/updates/resource.tsx b/frontend/src/components/updates/resource.tsx
new file mode 100644
index 000000000..651b320dc
--- /dev/null
+++ b/frontend/src/components/updates/resource.tsx
@@ -0,0 +1 @@
+export const ResourceUpdates = () => {};
diff --git a/frontend/src/layouts/layout.tsx b/frontend/src/layouts/layout.tsx
index 6f687827d..837f30b84 100644
--- a/frontend/src/layouts/layout.tsx
+++ b/frontend/src/layouts/layout.tsx
@@ -13,7 +13,7 @@ export const Layout = () => {
<>
-
diff --git a/frontend/src/layouts/resource.tsx b/frontend/src/layouts/resource.tsx
index 68ac3505f..3ea2d107a 100644
--- a/frontend/src/layouts/resource.tsx
+++ b/frontend/src/layouts/resource.tsx
@@ -1,36 +1,18 @@
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
import { ReactNode } from "react";
import { Page } from "./page";
+import { Outlet } from "react-router-dom";
interface ResourceProps {
title: ReactNode;
info: ReactNode;
actions: ReactNode;
- tabs: { title: string; component: ReactNode }[];
}
-export const Resource = ({ title, info, actions, tabs }: ResourceProps) => (
-
- {title}}
- subtitle={{info}
}
- actions={actions}
- content={
-
-
- {tabs.map(({ title }) => (
-
- {title}
-
- ))}
-
- {tabs.map((t, i) => (
-
- {t.component}
-
- ))}
-
- }
- />
-
+export const Resource = ({ title, info, actions }: ResourceProps) => (
+
{title}}
+ subtitle={{info}
}
+ actions={actions}
+ content={}
+ />
);
diff --git a/frontend/src/resources/deployment/components/deployment-logs.tsx b/frontend/src/resources/deployment/components/deployment-logs.tsx
index 37a5b003e..ce73f573d 100644
--- a/frontend/src/resources/deployment/components/deployment-logs.tsx
+++ b/frontend/src/resources/deployment/components/deployment-logs.tsx
@@ -1,21 +1,23 @@
import { Button } from "@ui/button";
import { Card, CardHeader, CardContent } from "@ui/card";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@ui/tabs"; // import { useDeploymentLog } from "@hooks/deployments";
-import { AlertOctagon, ChevronDown } from "lucide-react";
+import { AlertOctagon, ChevronDown, TerminalSquare } from "lucide-react";
import { useEffect } from "react";
import { useRead } from "@hooks";
+import { useParams } from "react-router-dom";
const scroll_to_bottom = (id: string) => () =>
document
.getElementById(id)
?.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
-export const DeploymentLogs = ({
- deployment_id,
-}: {
- deployment_id: string;
-}) => {
- const { data, refetch } = useRead("GetLog", { deployment_id, tail: 200 });
+export const DeploymentLogs = () => {
+ const deployment_id = useParams().deploymentId;
+ const { data, refetch } = useRead(
+ "GetLog",
+ { deployment_id, tail: 200 },
+ { enabled: !!deployment_id }
+ );
useEffect(() => {
const handle = setInterval(() => refetch(), 30000);
@@ -29,38 +31,36 @@ export const DeploymentLogs = ({
return (
-
-
-
-
- Out
-
-
- Err
- {data?.stderr && (
-
- )}
-
-
-
-
- {["stdout", "stderr"].map((t) => (
-
-
-
- {data?.[t as keyof typeof data] || `no ${t} logs`}
-
-
-
-
- ))}
-
-
+
+
+
+
Logs
+
+
+
+ Out
+
+
+ Err
+ {data?.stderr && (
+
+ )}
+
+
+
+ {["stdout", "stderr"].map((t) => (
+
+
+
{data?.[t as keyof typeof data] || `no ${t} logs`}
+
+
+
+ ))}
);
};
diff --git a/frontend/src/resources/deployment/layout.tsx b/frontend/src/resources/deployment/layout.tsx
new file mode 100644
index 000000000..3e91e7d24
--- /dev/null
+++ b/frontend/src/resources/deployment/layout.tsx
@@ -0,0 +1,69 @@
+import { Link, useParams } from "react-router-dom";
+import { useSetRecentlyViewed } from "@hooks";
+import { Resource } from "@layouts/resource";
+import { CardDescription } from "@ui/card";
+import {
+ DeploymentBuild,
+ DeploymentName,
+ DeploymentServer,
+ DeploymentStatus,
+ DeploymentStatusIcon,
+} from "./util";
+import {
+ RedeployContainer,
+ RemoveContainer,
+ StartOrStopContainer,
+} from "./components/actions";
+import { Button } from "@ui/button";
+import { Bell, Settings } from "lucide-react";
+
+export const DeploymentLayout = () => {
+ const { deploymentId } = useParams();
+ const push = useSetRecentlyViewed();
+
+ if (!deploymentId) return null;
+ push("Deployment", deploymentId);
+
+ return (
+
+
+
+ }
+ info={
+
+ }
+ actions={
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ />
+ );
+};
diff --git a/frontend/src/resources/deployment/page.tsx b/frontend/src/resources/deployment/page.tsx
index a261ec5e6..b053520e7 100644
--- a/frontend/src/resources/deployment/page.tsx
+++ b/frontend/src/resources/deployment/page.tsx
@@ -1,22 +1,32 @@
import { useParams } from "react-router-dom";
-import { useSetRecentlyViewed } from "@hooks";
-import { Resource } from "@layouts/resource";
-import { CardDescription } from "@ui/card";
-import {
- DeploymentBuild,
- DeploymentName,
- DeploymentServer,
- DeploymentStatus,
- DeploymentStatusIcon,
-} from "./util";
-import {
- RedeployContainer,
- RemoveContainer,
- StartOrStopContainer,
-} from "./components/actions";
+import { useRead, useSetRecentlyViewed } from "@hooks";
import { DeploymentLogs } from "./components/deployment-logs";
+import { Card, CardHeader, CardTitle } from "@ui/card";
+import { Bell } from "lucide-react";
-export const Deployment = () => {
+const DeploymentUpdates = ({ id }: { id: string }) => {
+ const updates = useRead("ListUpdates", { target: { id } }).data;
+
+ return (
+
+
+
+
Updates
+
+
+ {updates?.slice(0, 3).map((u) => (
+
+
+ {u.operation}
+
+
+ ))}
+
+
+ );
+};
+
+export const DeploymentPage = () => {
const { deploymentId } = useParams();
const push = useSetRecentlyViewed();
@@ -24,41 +34,9 @@ export const Deployment = () => {
push("Deployment", deploymentId);
return (
- }
- info={
-
- }
- actions={
-
-
-
-
-
- }
- tabs={[
- {
- title: "Logs",
- component: ,
- },
- {
- title: "Config",
- component: <>Config>,
- },
- {
- title: "Updates",
- component: <>Updates>,
- },
- ]}
- />
+
+
+
+
);
};
diff --git a/frontend/src/resources/deployment/updates.tsx b/frontend/src/resources/deployment/updates.tsx
new file mode 100644
index 000000000..de2166d24
--- /dev/null
+++ b/frontend/src/resources/deployment/updates.tsx
@@ -0,0 +1,43 @@
+import { useRead } from "@hooks";
+import { Table, TableCell, TableHead, TableHeader, TableRow } from "@ui/table";
+import { fmt_update_date } from "@util/helpers";
+import { Check, X } from "lucide-react";
+import { useParams } from "react-router-dom";
+
+export const DeploymentUpdates = () => {
+ const deploymentId = useParams().deploymentId;
+ const updates = useRead("ListUpdates", { target: { id: deploymentId } }).data;
+
+ return (
+
+
+
+ Start
+ End
+ Operation
+ Operator
+ Success
+
+
+ {updates?.map((update) => (
+
+ {fmt_update_date(new Date(update.start_ts))}
+
+ {update.end_ts
+ ? fmt_update_date(new Date(update.end_ts))
+ : "ongoing..."}
+
+ {update.operation}
+ {update.operator}
+
+ {update.success ? (
+
+ ) : (
+
+ )}
+
+
+ ))}
+
+ );
+};
diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx
index 2548a0f48..566e118eb 100644
--- a/frontend/src/router.tsx
+++ b/frontend/src/router.tsx
@@ -3,10 +3,13 @@ import { Layout } from "@layouts/layout";
import { Login } from "@pages/auth/login";
import { Signup } from "@pages/auth/signup";
import { Dashboard } from "@pages/dashboard";
-import { Deployment } from "@resources/deployment/page";
import { Server } from "@resources/server/page";
import { Build } from "@resources/build/page";
import { Deployments, Builds, Servers, Builders } from "@resources/pages";
+import { DeploymentLogs } from "@resources/deployment/components/deployment-logs";
+import { DeploymentUpdates } from "@resources/deployment/updates";
+import { DeploymentLayout } from "@resources/deployment/layout";
+import { DeploymentPage } from "@resources/deployment/page";
const router = createBrowserRouter([
{
@@ -17,14 +20,24 @@ const router = createBrowserRouter([
{ path: "login", element: },
{ path: "signup", element: },
+ // Deployments
{
path: "deployments",
children: [
{ path: "", element: },
- { path: ":deploymentId", element: },
+ {
+ path: ":deploymentId",
+ element: ,
+ children: [
+ { path: "", element: },
+ { path: "updates", element: },
+ { path: "config", element: <>deployment config!> },
+ ],
+ },
],
},
+ // Servers
{
path: "servers",
children: [
@@ -32,6 +45,8 @@ const router = createBrowserRouter([
{ path: ":serverId", element: },
],
},
+
+ // Builds
{
path: "builds",
children: [
@@ -39,6 +54,8 @@ const router = createBrowserRouter([
{ path: ":buildId", element: },
],
},
+
+ // Builders
{
path: "builders",
children: [
diff --git a/frontend/src/ui/table.tsx b/frontend/src/ui/table.tsx
new file mode 100644
index 000000000..dba7337b4
--- /dev/null
+++ b/frontend/src/ui/table.tsx
@@ -0,0 +1,121 @@
+import * as React from "react";
+
+import { cn } from "@util/helpers";
+
+const Table = React.forwardRef<
+ HTMLTableElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+Table.displayName = "Table";
+
+const TableHeader = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableHeader.displayName = "TableHeader";
+
+const TableBody = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableBody.displayName = "TableBody";
+
+const TableFooter = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableFooter.displayName = "TableFooter";
+
+const TableRow = React.forwardRef<
+ HTMLTableRowElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableRow.displayName = "TableRow";
+
+const TableHead = React.forwardRef<
+ HTMLTableCellElement,
+ React.ThHTMLAttributes
+>(({ className, ...props }, ref) => (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+));
+TableHead.displayName = "TableHead";
+
+const TableCell = React.forwardRef<
+ HTMLTableCellElement,
+ React.TdHTMLAttributes
+>(({ className, ...props }, ref) => (
+ | [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+));
+TableCell.displayName = "TableCell";
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableCaption.displayName = "TableCaption";
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+};
diff --git a/frontend/src/util/helpers.ts b/frontend/src/util/helpers.ts
index 783ae0507..1ab99a543 100644
--- a/frontend/src/util/helpers.ts
+++ b/frontend/src/util/helpers.ts
@@ -298,3 +298,6 @@ export function readableImageNameTag(
return ["none", "none"];
}
}
+
+export const fmt_update_date = (d: Date) =>
+ `${d.getDate()}/${d.getMonth()} @ ${d.getHours()}:${d.getMinutes()}`;
| |