💄 blueprint data table

This commit is contained in:
Fred KISSIE
2025-10-23 22:14:09 +02:00
parent fa6b7ca3ed
commit e30fde5237
4 changed files with 191 additions and 15 deletions

View File

@@ -1154,6 +1154,9 @@
"sidebarBluePrints": "Blueprints",
"blueprints": "Blueprints",
"blueprintsDescription": "Blueprints are declarative YAML configurations that define your resources and their settings",
"blueprintAdd": "Add Blueprint",
"searchBlueprintProgress": "Search blueprints...",
"source": "Source",
"enableDockerSocket": "Enable Docker Blueprint",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to Newt.",
"enableDockerSocketLink": "Learn More",

View File

@@ -39,7 +39,8 @@ async function queryBlueprints(orgId: string, limit: number, offset: number) {
blueprintId: blueprints.blueprintId,
name: blueprints.name,
source: blueprints.source,
succeeded: blueprints.succeeded
succeeded: blueprints.succeeded,
orgId: blueprints.orgId
})
.from(blueprints)
.leftJoin(orgs, eq(blueprints.orgId, orgs.orgId))
@@ -48,8 +49,15 @@ async function queryBlueprints(orgId: string, limit: number, offset: number) {
return res;
}
type BlueprintData = Omit<
Awaited<ReturnType<typeof queryBlueprints>>[number],
"source"
> & {
source: "API" | "WEB" | "CLI";
};
export type ListBlueprintsResponse = {
blueprints: NonNullable<Awaited<ReturnType<typeof queryBlueprints>>>;
blueprints: NonNullable<BlueprintData[]>;
pagination: { total: number; limit: number; offset: number };
};
@@ -108,7 +116,7 @@ export async function listBlueprints(
return response<ListBlueprintsResponse>(res, {
data: {
blueprints: blueprintsList,
blueprints: blueprintsList as BlueprintData[],
pagination: {
total: count,
limit,

View File

@@ -24,13 +24,17 @@ export default async function BluePrintsPage(props: BluePrintsPageProps) {
try {
const res = await internal.get<
AxiosResponse<ListBlueprintsResponse>
>(`/org/${params.orgId}/domains`, await authCookieHeader());
>(`/org/${params.orgId}/blueprints`, await authCookieHeader());
blueprints = res.data.data.blueprints
console.log({
...res.data.data
})
} catch (e) {
console.error(e);
}
let org = null;
try {
const getOrg = cache(async () =>
@@ -49,6 +53,8 @@ export default async function BluePrintsPage(props: BluePrintsPageProps) {
}
const t = await getTranslations();
return (
<>
<OrgProvider org={org}>
@@ -56,7 +62,7 @@ export default async function BluePrintsPage(props: BluePrintsPageProps) {
title={t("blueprints")}
description={t("blueprintsDescription")}
/>
<BlueprintsTable blueprints={blueprints} />
<BlueprintsTable blueprints={blueprints} orgId={params.orgId} />
</OrgProvider>
</>
);

View File

@@ -3,8 +3,8 @@
import { ColumnDef } from "@tanstack/react-table";
import { DomainsDataTable } from "@app/components/DomainsDataTable";
import { Button } from "@app/components/ui/button";
import { ArrowUpDown } from "lucide-react";
import { useState } from "react";
import { ArrowRight, ArrowUpDown, MoreHorizontal } from "lucide-react";
import { useState, useTransition } from "react";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { formatAxiosError } from "@app/lib/api";
import { createApiClient } from "@app/lib/api";
@@ -15,18 +15,177 @@ import { useTranslations } from "next-intl";
import CreateDomainForm from "@app/components/CreateDomainForm";
import { useToast } from "@app/hooks/useToast";
import { useOrgContext } from "@app/hooks/useOrgContext";
import { DataTable } from "./ui/data-table";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./ui/dropdown-menu";
import Link from "next/link";
import { ListBlueprintsResponse } from "@server/routers/blueprints";
export type BlueprintRow = {
blueprintId: number;
source: string;
succeeded: boolean;
name: string;
};
export type BlueprintRow = ListBlueprintsResponse['blueprints'][number]
type Props = {
blueprints: BlueprintRow[];
orgId: string;
};
export default function BlueprintsTable({ blueprints }: Props) {
return <></>
export default function BlueprintsTable({ blueprints, orgId }: Props) {
const t = useTranslations();
const [isRefreshing, startTransition] = useTransition()
const router = useRouter()
const columns: ColumnDef<BlueprintRow>[] = [
{
accessorKey: "name",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("name")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
},
{
accessorKey: "source",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("source")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
// cell: ({ row }) => {
// const originalRow = row.original;
// if (
// originalRow.type == "newt" ||
// originalRow.type == "wireguard"
// ) {
// if (originalRow.online) {
// return (
// <span className="text-green-500 flex items-center space-x-2">
// <div className="w-2 h-2 bg-green-500 rounded-full"></div>
// <span>{t("online")}</span>
// </span>
// );
// } else {
// return (
// <span className="text-neutral-500 flex items-center space-x-2">
// <div className="w-2 h-2 bg-gray-500 rounded-full"></div>
// <span>{t("offline")}</span>
// </span>
// );
// }
// } else {
// return <span>-</span>;
// }
// }
},
// {
// accessorKey: "nice",
// header: ({ column }) => {
// return (
// <Button
// variant="ghost"
// onClick={() =>
// column.toggleSorting(column.getIsSorted() === "asc")
// }
// className="hidden md:flex whitespace-nowrap"
// >
// {t("site")}
// <ArrowUpDown className="ml-2 h-4 w-4" />
// </Button>
// );
// },
// // cell: ({ row }) => {
// // return (
// // <div className="hidden md:block whitespace-nowrap">
// // {row.original.nice}
// // </div>
// // );
// // }
// },
// {
// id: "actions",
// cell: ({ row }) => {
// const siteRow = row.original;
// return (
// <div className="flex items-center justify-end gap-2">
// <DropdownMenu>
// <DropdownMenuTrigger asChild>
// <Button variant="ghost" className="h-8 w-8 p-0">
// <span className="sr-only">Open menu</span>
// <MoreHorizontal className="h-4 w-4" />
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent align="end">
// <Link
// className="block w-full"
// href="#"
// // href={`/${siteRow.orgId}/settings/sites/${siteRow.nice}`}
// >
// <DropdownMenuItem>
// {t("viewSettings")}
// </DropdownMenuItem>
// </Link>
// <DropdownMenuItem
// onClick={() => {
// // setSelectedSite(siteRow);
// // setIsDeleteModalOpen(true);
// }}
// >
// <span className="text-red-500">
// {t("delete")}
// </span>
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// <Link
// href="#"
// // href={`/${siteRow.orgId}/settings/sites/${siteRow.nice}`}
// >
// <Button variant={"secondary"} size="sm">
// {t("edit")}
// <ArrowRight className="ml-2 w-4 h-4" />
// </Button>
// </Link>
// </div>
// );
// }
// }
];
return <DataTable
columns={columns}
data={blueprints}
persistPageSize="blueprint-table"
title={t('blueprints')}
searchPlaceholder={t('searchBlueprintProgress')}
searchColumn="name"
onAdd={() => {
router.push(`/${orgId}/settings/blueprints/create`);
}}
addButtonText={t('blueprintAdd')}
onRefresh={() => {
startTransition(() => router.refresh())
}}
isRefreshing={isRefreshing}
defaultSort={{
id: "name",
desc: false
}}
/>
}