-
- {props.code}
+
+ {props.title && (
+
+ {props.title}
+
+ )}
+
+
+ {props.code}
+
);
diff --git a/src/components/wrappers/common/dialog.tsx b/src/components/wrappers/common/dialog.tsx
index 13b28e4a..c9f2cf5a 100644
--- a/src/components/wrappers/common/dialog.tsx
+++ b/src/components/wrappers/common/dialog.tsx
@@ -23,7 +23,7 @@ export const AgentRegistrationDialog = (props: agentRegistrationDialogProps) =>
diff --git a/src/components/wrappers/common/table/data-table.tsx b/src/components/wrappers/common/table/data-table.tsx
index ad83010f..61e1bcc0 100644
--- a/src/components/wrappers/common/table/data-table.tsx
+++ b/src/components/wrappers/common/table/data-table.tsx
@@ -14,7 +14,7 @@ import {
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table";
import {Input} from "@/components/ui/input";
-import {ReactNode, useEffect, useState} from "react";
+import {ReactNode, useMemo, useState} from "react";
import {TablePagination} from "./table-pagination";
import {Checkbox} from "@/components/ui/checkbox";
import {Button} from "@/components/ui/button";
@@ -63,35 +63,38 @@ export function DataTable
({
const [columnFilters, setColumnFilters] = useState([]);
const [rowSelection, setRowSelection] = useState({});
- useEffect(() => {
- setRowSelection({});
- }, [data]);
-
- if (enableSelect && data.length > 0) {
+ const finalColumns = useMemo(() => {
const selectColumnExists = columns.some((column) => column.id === "select");
-
- if (!selectColumnExists) {
- columns.unshift({
- id: "select",
- header: ({table}) => (
- table.toggleAllPageRowsSelected(!!value)}
- aria-label="Select all"
- />
- ),
- cell: ({row}) => row.toggleSelected(!!value)}
- aria-label="Select row"/>,
- enableSorting: false,
- enableHiding: false,
- });
+ if (enableSelect && data.length > 0 && !selectColumnExists) {
+ return [
+ {
+ id: "select",
+ header: ({table}: {table: any}) => (
+ table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ />
+ ),
+ cell: ({row}: {row: any}) => (
+ row.toggleSelected(!!value)}
+ aria-label="Select row"
+ />
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ },
+ ...columns,
+ ];
}
- }
+ return columns;
+ }, [columns, enableSelect, data.length]);
const table = useReactTable({
data,
- columns,
+ columns: finalColumns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
@@ -99,6 +102,10 @@ export function DataTable({
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onRowSelectionChange: setRowSelection,
+ getRowId: (row: any) => row.id || row.uuid,
+ autoResetPageIndex: false,
+ autoResetExpanded: false,
+ enableRowSelection: true,
state: {
sorting,
columnFilters,
diff --git a/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx b/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx
index 8d4eed19..8936d7ab 100644
--- a/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx
+++ b/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx
@@ -1,23 +1,88 @@
"use client";
-import {PasswordInput} from "@/components/ui/password-input";
+
+import {Input} from "@/components/ui/input";
+import {Label} from "@/components/ui/label";
+import {Button} from "@/components/ui/button";
+import {Copy, Check} from "lucide-react";
import {useState} from "react";
-import {CopyButton} from "@/components/wrappers/common/button/copy-button";
+import {copyToClipboardWithMeta} from "@/components/wrappers/common/button/copy-button";
export type AgentCardKeyProps = {
edgeKey: string;
+ agentName: string;
};
-export const AgentCardKey = ({edgeKey}: AgentCardKeyProps) => {
- const [code, setCode] = useState(`${edgeKey}`);
+export const AgentCardKey = ({edgeKey, agentName}: AgentCardKeyProps) => {
+ const [isCopiedKey, setIsCopiedKey] = useState(false);
+ const [isCopiedCommand, setIsCopiedCommand] = useState(false);
+
+ const command = `portabase agent "${agentName}" --key ${edgeKey}`;
+
+ const handleCopy = async (text: string, setter: (v: boolean) => void) => {
+ await copyToClipboardWithMeta(text);
+ setter(true);
+ setTimeout(() => setter(false), 2000);
+ };
+
+ const handleFocus = (event: React.FocusEvent) => {
+ event.target.select();
+ };
+
return (
- <>
- {
- setCode(edgeKey);
- }}
- />
-
- >
+
+
+
+
+
+
+
+
+
+ Use this key for manual configuration of your agent.
+
+
+
+
+
+
+
+
+
+
+ Run this command on your server to automatically register the agent.
+
+
+
+
);
};
diff --git a/src/components/wrappers/dashboard/agent/agent-content.tsx b/src/components/wrappers/dashboard/agent/agent-content.tsx
index 3c6f6cc5..5ad5c655 100644
--- a/src/components/wrappers/dashboard/agent/agent-content.tsx
+++ b/src/components/wrappers/dashboard/agent/agent-content.tsx
@@ -4,76 +4,109 @@ import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
import {Server} from "lucide-react";
import {formatDateLastContact} from "@/utils/date-formatting";
import {AgentCardKey} from "@/components/wrappers/dashboard/agent/agent-card-key/agent-card-key";
+import {AgentWithDatabases} from "@/db/schema/08_agent";
+import {useQuery} from "@tanstack/react-query";
+import {getAgentAction} from "@/features/agents/agents.action";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/components/ui/accordion";
+import {Separator} from "@/components/ui/separator";
+import {Badge} from "@/components/ui/badge";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {AgentDatabaseCard} from "@/components/wrappers/dashboard/agent/agent-database-card";
-import {AgentWithDatabases} from "@/db/schema/08_agent";
-import {eventUpdate} from "@/types/events";
-import {useAutoRefresh} from "@/hooks/use-auto-refresh";
type AgentContentPageProps = {
edgeKey: string;
agent: AgentWithDatabases
-
}
-export const AgentContentPage = ({edgeKey, agent}: AgentContentPageProps) => {
+export const AgentContentPage = ({edgeKey, agent: initialAgent}: AgentContentPageProps) => {
- useAutoRefresh({
- poll: {
- enabled: true,
- intervalMs: 5000,
+ const {data} = useQuery({
+ queryKey: ["agent-data", initialAgent.id],
+ queryFn: async () => {
+ const result = await getAgentAction(initialAgent.id);
+ return result?.data;
},
- sse: {
- enabled: true,
- url: "/api/events",
- eventName: "modification",
- shouldRefresh: (data) => {
- const update = data as eventUpdate;
- return Boolean(update?.update);
- },
+ initialData: {
+ data: initialAgent
},
+ staleTime: 0,
+ gcTime: 0,
+ refetchInterval: 5000,
});
+ const agent = data?.data ?? initialAgent;
return (
- <>
+
-
+
- Databases
-
+ Databases
+
- {agent.databases.length}
- Databases linked to this agent
+ {agent.databases.length}
+ Linked resources
-
+
- Last contact
-
+ Last contact
+
- {formatDateLastContact(agent.lastContact)}
- Last contact with agent
+ {formatDateLastContact(agent.lastContact)}
+ Status heartbeat
-
-
-
- Edge Key
-
-
-
-
-
-
- >
+
+
+
+
+
+ Registration & Setup
+ {!agent.lastContact && (
+
+ Action Required
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
Managed Databases
+
+ Resources currently connected to this agent.
+
+
+
+
+
+
+
)
}
\ No newline at end of file
diff --git a/src/components/wrappers/dashboard/agent/agent-database-columns.tsx b/src/components/wrappers/dashboard/agent/agent-database-columns.tsx
new file mode 100644
index 00000000..c0fd3341
--- /dev/null
+++ b/src/components/wrappers/dashboard/agent/agent-database-columns.tsx
@@ -0,0 +1,79 @@
+"use client"
+
+import {ColumnDef} from "@tanstack/react-table"
+import {Database} from "@/db/schema/07_database"
+import Image from "next/image"
+import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator"
+import {formatDateLastContact} from "@/utils/date-formatting"
+import Link from "next/link"
+import {Button} from "@/components/ui/button"
+import {ChevronRight} from "lucide-react"
+
+export const agentDatabaseColumns: ColumnDef[] = [
+ {
+ accessorKey: "dbms",
+ header: "Type",
+ cell: ({row}) => (
+
+
+
+ )
+ },
+ {
+ accessorKey: "name",
+ header: "Name",
+ cell: ({row}) => (
+
+ {row.original.name}
+
+ {row.original.agentDatabaseId}
+
+
+ )
+ },
+ {
+ accessorKey: "lastContact",
+ header: "Last Contact",
+ cell: ({row}) => (
+
+
+ {formatDateLastContact(row.original.lastContact)}
+
+
+ )
+ },
+ {
+ id: "status",
+ header: "Status",
+ cell: ({row}) => (
+
+
+
+ )
+ },
+ {
+ id: "actions",
+ header: () => null,
+ cell: ({row}) => {
+ const href = row.original.projectId
+ ? `/dashboard/projects/${row.original.projectId}/database/${row.original.id}`
+ : "#";
+
+ return (
+
+
+
+ )
+ }
+ }
+]
diff --git a/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx b/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx
index 5ccbef18..7966ddd9 100644
--- a/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx
+++ b/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx
@@ -1,7 +1,7 @@
"use client";
import { Button } from "@/components/ui/button";
-import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
+import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DialogClose } from "@/components/ui/dialog";
import { generateEdgeKey } from "@/utils/edge_key";
import { PropsWithChildren } from "react";
@@ -16,26 +16,29 @@ export type agentRegistrationDialogProps = PropsWithChildren<{
export function AgentModalKey(props: agentRegistrationDialogProps) {
const edge_key = generateEdgeKey(getServerUrl(), props.agent.id);
- const code = `EDGE_KEY = ${edge_key}`;
+ const command = `portabase agent "${props.agent.name}" --key ${edge_key}`;
return (