Skip to content

Commit

Permalink
feat: add agents page with list of agents
Browse files Browse the repository at this point in the history
  • Loading branch information
mainawycliffe committed Sep 25, 2023
1 parent 43a0d14 commit e26266c
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 5 deletions.
16 changes: 16 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ import { ConfigInsightsPage } from "./pages/config/ConfigInsightsList";
import { HealthPage } from "./pages/health";
import { features } from "./services/permissions/features";
import { stringSortHelper } from "./utils/common";
import { MdOutlineSupportAgent } from "react-icons/md";
import AgentsPage from "./components/Agents/AgentPage";

export type NavigationItems = {
name: string;
Expand Down Expand Up @@ -108,6 +110,13 @@ const navigation: NavigationItems = [
icon: LogsIcon,
featureName: features.logs,
resourceName: tables.database
},
{
name: "Agents",
href: "/agents",
icon: MdOutlineSupportAgent,
featureName: features.agents,
resourceName: tables.database
}
];

Expand Down Expand Up @@ -258,6 +267,13 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
/>
</Route>

<Route path="agents" element={sidebar}>
<Route
index
element={withAccessCheck(<AgentsPage />, tables.agents, "read")}
/>
</Route>

<Route path="settings" element={sidebar}>
<Route
path="connections"
Expand Down
16 changes: 16 additions & 0 deletions src/api/query-hooks/useAgentsQuery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
import { DatabaseResponse } from "./useNotificationsQuery";
import { getAgentsList } from "../services/agents";
import { Agent } from "../../components/Agents/AgentPage";

export function useAgentsListQuery(
params: { sortBy?: string; sortOrder?: string } = {},
pagingParams: { pageIndex?: number; pageSize?: number } = {},
options?: UseQueryOptions<DatabaseResponse<Agent>, Error>
) {
return useQuery<DatabaseResponse<Agent>, Error>(
["agents", "list"],
() => getAgentsList(params, pagingParams),
options
);
}
8 changes: 4 additions & 4 deletions src/api/query-hooks/useNotificationsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import { createResource, updateResource } from "../schemaResources";
import { toastError, toastSuccess } from "../../components/Toast/toast";
import { useUser } from "../../context";

type Response =
export type DatabaseResponse<T extends Record<string, any>> =
| { error: Error; data: null; totalEntries: undefined }
| {
data: Notification[] | null;
data: T[] | null;
totalEntries?: number | undefined;
error: null;
};

export function useNotificationsSummaryQuery(
options?: UseQueryOptions<Response, Error>
options?: UseQueryOptions<DatabaseResponse<Notification>, Error>
) {
return useQuery<Response, Error>(
return useQuery<DatabaseResponse<Notification>, Error>(
["notifications", "settings"],
() => getNotificationsSummary(),
options
Expand Down
33 changes: 33 additions & 0 deletions src/api/services/agents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Agent } from "../../components/Agents/AgentPage";
import { AVATAR_INFO } from "../../constants";
import { IncidentCommander } from "../axios";
import { resolve } from "../resolve";

export const getAgentsList = async (
params: {
sortBy?: string;
sortOrder?: string;
},
pagingParams: { pageIndex?: number; pageSize?: number }
) => {
const { sortBy, sortOrder } = params;

const sortByParam = sortBy ? `&order=${sortBy}` : "&order=created_at";
const sortOrderParam = sortOrder ? `.${sortOrder}` : ".desc";

const { pageIndex, pageSize } = pagingParams;
const pagingParamsStr =
pageIndex || pageSize
? `&limit=${pageSize}&offset=${pageIndex! * pageSize!}`
: "";
return resolve(
IncidentCommander.get<Agent[] | null>(
`/agents?select=*,person:person_id(${AVATAR_INFO}),created_by(${AVATAR_INFO})&order=created_at.desc&${pagingParamsStr}${sortByParam}${sortOrderParam}`,
{
headers: {
Prefer: "count=exact"
}
}
)
);
};
108 changes: 108 additions & 0 deletions src/components/Agents/AgentPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useState } from "react";
import { AiFillPlusCircle } from "react-icons/ai";
import { useSearchParams } from "react-router-dom";
import { useAgentsListQuery } from "../../api/query-hooks/useAgentsQuery";
import { User } from "../../api/services/users";
import { BreadcrumbNav, BreadcrumbRoot } from "../BreadcrumbNav";
import { SearchLayout } from "../Layout";
import AgentsTable from "./List/AgentsTable";
import { Head } from "../Head/Head";

export type Agent = {
id?: string;
name: string;
hostname?: string;
description?: string;
ip?: string;
version?: string;
username?: string;
person_id?: string;
person?: User;
properties?: { [key: string]: any };
tls?: string;
created_by?: User;
created_at: Date;
updated_at: Date;
};

export default function AgentsPage() {
const [isOpen, setIsOpen] = useState(false);

const [{ pageIndex, pageSize }, setPageState] = useState({
pageIndex: 0,
pageSize: 150
});

const [searchParams, setSearchParams] = useSearchParams();

const sortBy = searchParams.get("sortBy") ?? "";
const sortOrder = searchParams.get("sortOrder") ?? "desc";

const { data, isLoading, refetch, isRefetching } = useAgentsListQuery(
{
sortBy,
sortOrder
},
{
pageIndex,
pageSize
},
{
keepPreviousData: true
}
);

const agent = data?.data;
const totalEntries = data?.totalEntries;
const pageCount = totalEntries ? Math.ceil(totalEntries / pageSize) : -1;

return (
<>
<Head prefix="Agents" />
<SearchLayout
title={
<BreadcrumbNav
list={[
<BreadcrumbRoot link="/agents">Agents</BreadcrumbRoot>,
<button
type="button"
className=""
onClick={() => setIsOpen(true)}
>
<AiFillPlusCircle size={32} className="text-blue-600" />
</button>
]}
/>
}
onRefresh={refetch}
contentClass="p-0 h-full"
loading={isLoading || isRefetching}
>
<div className="flex flex-col flex-1 p-6 pb-0 h-full w-full">
<AgentsTable
agents={agent ?? []}
isLoading={isLoading || isRefetching}
pageCount={pageCount}
pageIndex={pageIndex}
pageSize={pageSize}
setPageState={setPageState}
sortBy={sortBy}
sortOrder={sortOrder}
onSortByChanged={(sortBy) => {
const sort = typeof sortBy === "function" ? sortBy([]) : sortBy;
if (sort.length === 0) {
searchParams.delete("sortBy");
searchParams.delete("sortOrder");
} else {
searchParams.set("sortBy", sort[0]?.id);
searchParams.set("sortOrder", sort[0].desc ? "desc" : "asc");
}
setSearchParams(searchParams);
}}
refresh={refetch}
/>
</div>
</SearchLayout>
</>
);
}
54 changes: 54 additions & 0 deletions src/components/Agents/List/AgentsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { SortingState, Updater } from "@tanstack/react-table";
import { useMemo } from "react";
import { Agent } from "../AgentPage";
import { DataTable } from "../../DataTable";
import { agentsTableColumns } from "./AgentsTableColumns";

type AgentsTableProps = {
agents: Agent[];
isLoading?: boolean;
pageCount: number;
pageIndex: number;
pageSize: number;
sortBy: string;
sortOrder: string;
setPageState?: (state: { pageIndex: number; pageSize: number }) => void;
hiddenColumns?: string[];
onSortByChanged?: (sortByState: Updater<SortingState>) => void;
refresh?: () => void;
};

export default function AgentsTable({
agents,
isLoading,
hiddenColumns = [],
sortBy,
sortOrder,
onSortByChanged = () => {},
refresh = () => {}
}: AgentsTableProps) {
const tableSortByState = useMemo(() => {
return [
{
id: sortBy,
desc: sortOrder === "desc"
}
];
}, [sortBy, sortOrder]);

const columns = useMemo(() => agentsTableColumns, []);

return (
<DataTable
data={agents}
columns={columns}
isLoading={isLoading}
handleRowClick={() => {}}
stickyHead
hiddenColumns={hiddenColumns}
tableSortByState={tableSortByState}
onTableSortByChanged={onSortByChanged}
enableServerSideSorting
/>
);
}
73 changes: 73 additions & 0 deletions src/components/Agents/List/AgentsTableColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ColumnDef } from "@tanstack/react-table";
import { Agent } from "../AgentPage";
import { DateCell } from "../../ConfigViewer/columns";
import { User } from "../../../api/services/users";
import { Avatar } from "../../Avatar";

export const agentsTableColumns: ColumnDef<Agent>[] = [
{
header: "Name",
accessorKey: "name",
minSize: 150,
enableSorting: true
},
{
header: "Hostname",
accessorKey: "hostname",
minSize: 150,
enableSorting: true
},
{
header: "IP Address",
accessorKey: "ip",
minSize: 150,
enableSorting: true
},
{
header: "Person",
minSize: 150,
cell: ({ row }) => {
const person = row?.getValue<User>("person_id");
return <Avatar user={person} />;
}
},
{
header: "Version",
accessorKey: "version",
minSize: 80,
enableSorting: true
},

{
header: "Username",
accessorKey: "username",
minSize: 150,
enableSorting: true
},
{
header: "TLS",
accessorKey: "tls",
minSize: 80,
enableSorting: true
},
{
header: "Created At",
minSize: 150,
enableSorting: true,
cell: DateCell
},
{
header: "Updated At",
minSize: 150,
enableSorting: true,
cell: DateCell
},
{
header: "Created By",
minSize: 80,
cell: ({ row }) => {
const createdBy = row?.getValue<User>("created_by");
return <Avatar user={createdBy} />;
}
}
];
3 changes: 2 additions & 1 deletion src/context/UserAccessContext/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export const tables = {
config_scrapers: "config_scrapers",
identities: "identities",
connections: "connections",
kratos: "kratos"
kratos: "kratos",
agents: "agents"
};

export const permDefs = {
Expand Down
1 change: 1 addition & 0 deletions src/services/permissions/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const features = {
incidents: "incidents",
config: "config",
logs: "logs",
agents: "agents",
"settings.connections": "settings.connections",
"settings.users": "settings.users",
"settings.teams": "settings.teams",
Expand Down

0 comments on commit e26266c

Please sign in to comment.