Skip to content

Commit 6cf8e50

Browse files
committed
feat: add view for permission based on resource/team/person
Fixes #2246
1 parent f8de4a8 commit 6cf8e50

File tree

6 files changed

+266
-2
lines changed

6 files changed

+266
-2
lines changed

src/api/services/permissions.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { AVATAR_INFO } from "@flanksource-ui/constants";
2+
import { IncidentCommander } from "../axios";
3+
import { resolvePostGrestRequestWithPagination } from "../resolve";
4+
import { PermissionAPIResponse } from "../types/permissions";
5+
6+
export type FetchPermissionsInput = {
7+
componentId?: string;
8+
personId?: string;
9+
teamId?: string;
10+
configId?: string;
11+
checkId?: string;
12+
canaryId?: string;
13+
playbookId?: string;
14+
};
15+
16+
function composeQueryParamForFetchPermissions({
17+
componentId,
18+
personId,
19+
teamId,
20+
configId,
21+
checkId,
22+
canaryId,
23+
playbookId
24+
}: FetchPermissionsInput) {
25+
if (componentId) {
26+
return `component_id=eq.${componentId}`;
27+
}
28+
if (personId) {
29+
return `person_id=eq.${personId}`;
30+
}
31+
if (teamId) {
32+
return `team_id=eq.${teamId}`;
33+
}
34+
if (configId) {
35+
return `config_id=eq.${configId}`;
36+
}
37+
if (checkId) {
38+
return `check_id=eq.${checkId}`;
39+
}
40+
if (canaryId) {
41+
return `canary_id=eq.${canaryId}`;
42+
}
43+
if (playbookId) {
44+
return `playbook_id=eq.${playbookId}`;
45+
}
46+
return undefined;
47+
}
48+
49+
export function fetchPermissions(
50+
input: FetchPermissionsInput,
51+
pagination: {
52+
pageSize: number;
53+
pageIndex: number;
54+
}
55+
) {
56+
const queryParam = composeQueryParamForFetchPermissions(input);
57+
const selectFields = [
58+
"*",
59+
"checks:check_id(id, name, status, type)",
60+
"catalog:config_id(id, name, type, config_class)",
61+
"component:component_id(id, name, icon)",
62+
"canary:canary_id(id, name, icon)",
63+
"playbook:playbook_id(id, title, name, icon)",
64+
"team:team_id(id, name, icon)",
65+
`person:person_id(${AVATAR_INFO})`,
66+
`createdBy:created_by(${AVATAR_INFO})`
67+
];
68+
69+
const { pageSize, pageIndex } = pagination;
70+
71+
const url = `/permissions?${queryParam}&select=${selectFields.join(",")}&limit=${pageSize}&offset=${pageIndex * pageSize}`;
72+
return resolvePostGrestRequestWithPagination(
73+
IncidentCommander.get<PermissionAPIResponse[]>(url)
74+
);
75+
}

src/api/types/permissions.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { ConfigItem } from "./configs";
2+
import { HealthCheck } from "./health";
3+
import { PlaybookSpec } from "./playbooks";
4+
import { Topology } from "./topology";
5+
import { Team, User } from "./users";
6+
7+
export type PermissionTable = {
8+
id: string;
9+
description: string;
10+
action: string;
11+
deny?: boolean;
12+
component_id?: string;
13+
config_id?: string;
14+
canary_id?: string;
15+
playbook_id?: string;
16+
created_by: string;
17+
person_id?: string;
18+
team_id?: string;
19+
updated_by: string;
20+
created_at: string;
21+
updated_at: string;
22+
until?: string;
23+
};
24+
25+
export type PermissionAPIResponse = PermissionTable & {
26+
checks: Pick<HealthCheck, "id" | "name" | "type" | "status">;
27+
catalog: Pick<ConfigItem, "id" | "name" | "type" | "config_class">;
28+
component: Pick<Topology, "id" | "name" | "icon">;
29+
canary: Pick<Topology, "id" | "name" | "icon">;
30+
playbook: Pick<PlaybookSpec, "id" | "name" | "icon" | "title">;
31+
team: Pick<Team, "id" | "name" | "icon">;
32+
person: User;
33+
createdBy: User;
34+
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { PermissionAPIResponse } from "@flanksource-ui/api/types/permissions";
2+
import { Avatar } from "@flanksource-ui/ui/Avatar";
3+
import { Badge } from "@flanksource-ui/ui/Badge/Badge";
4+
import { MRTDateCell } from "@flanksource-ui/ui/MRTDataTable/Cells/MRTDateCells";
5+
import MRTDataTable from "@flanksource-ui/ui/MRTDataTable/MRTDataTable";
6+
import { MRT_ColumnDef } from "mantine-react-table";
7+
import { CheckLink } from "../Canary/HealthChecks/CheckLink";
8+
import ConfigLink from "../Configs/ConfigLink/ConfigLink";
9+
import { TopologyLink } from "../Topology/TopologyLink";
10+
11+
const permissionsTableColumns: MRT_ColumnDef<PermissionAPIResponse>[] = [
12+
{
13+
id: "Resource",
14+
header: "Resource",
15+
Cell: ({ row }) => {
16+
const config = row.original.catalog;
17+
const check = row.original.checks;
18+
const playbook = row.original.playbook;
19+
const canary = row.original.canary;
20+
const component = row.original.component;
21+
22+
return (
23+
<div className="flex flex-col">
24+
{config && <ConfigLink config={config} />}
25+
{check && <CheckLink check={check} />}
26+
{playbook && <span>{playbook.title}</span>}
27+
{canary && <div>{canary.name}</div>}
28+
{component && <TopologyLink topology={component} />}
29+
</div>
30+
);
31+
}
32+
},
33+
{
34+
id: "action",
35+
header: "Action",
36+
Cell: ({ row }) => {
37+
const action = row.original.action;
38+
const deny = row.original.deny;
39+
40+
return (
41+
<div>
42+
<span>{action}</span>
43+
{deny && <Badge text="deny" />}
44+
</div>
45+
);
46+
}
47+
},
48+
{
49+
id: "updated",
50+
header: "Updated",
51+
accessorFn: (row) => row.updated_at,
52+
Cell: MRTDateCell
53+
},
54+
{
55+
id: "created",
56+
header: "Created",
57+
accessorFn: (row) => row.created_at
58+
},
59+
{
60+
id: "createdBy",
61+
header: "Created By",
62+
Cell: ({ row }) => {
63+
const createdBy = row.original.createdBy;
64+
return <Avatar user={createdBy} />;
65+
}
66+
}
67+
];
68+
69+
type PermissionsTableProps = {
70+
permissions: PermissionAPIResponse[];
71+
isLoading: boolean;
72+
pageCount: number;
73+
totalEntries: number;
74+
};
75+
76+
export default function PermissionsTable({
77+
permissions,
78+
isLoading,
79+
pageCount,
80+
totalEntries
81+
}: PermissionsTableProps) {
82+
return (
83+
<MRTDataTable<PermissionAPIResponse>
84+
columns={permissionsTableColumns}
85+
data={permissions}
86+
isLoading={isLoading}
87+
manualPageCount={pageCount}
88+
totalRowCount={totalEntries}
89+
enableServerSidePagination
90+
/>
91+
);
92+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {
2+
fetchPermissions,
3+
FetchPermissionsInput
4+
} from "@flanksource-ui/api/services/permissions";
5+
import useReactTablePaginationState from "@flanksource-ui/ui/DataTable/Hooks/useReactTablePaginationState";
6+
import { useQuery } from "@tanstack/react-query";
7+
import { useMemo } from "react";
8+
import PermissionsTable from "./PermissionsTable";
9+
10+
type PermissionsViewProps = {
11+
permissionRequest: FetchPermissionsInput;
12+
};
13+
14+
export default function PermissionsView({
15+
permissionRequest
16+
}: PermissionsViewProps) {
17+
const { pageSize, pageIndex } = useReactTablePaginationState();
18+
19+
const isEnabled = useMemo(() => {
20+
return Object.values(permissionRequest).some(
21+
(value) => value !== undefined
22+
);
23+
}, [permissionRequest]);
24+
25+
const { isLoading, data } = useQuery({
26+
queryKey: [
27+
"permissions",
28+
permissionRequest,
29+
{
30+
pageIndex,
31+
pageSize
32+
}
33+
],
34+
queryFn: () =>
35+
fetchPermissions(permissionRequest, {
36+
pageIndex,
37+
pageSize
38+
}),
39+
enabled: isEnabled
40+
});
41+
42+
const totalEntries = data?.totalEntries || 0;
43+
const pageCount = totalEntries ? Math.ceil(totalEntries / pageSize) : -1;
44+
const permissions = data?.data || [];
45+
46+
return (
47+
<PermissionsTable
48+
permissions={permissions}
49+
isLoading={isLoading}
50+
pageCount={pageCount}
51+
totalEntries={totalEntries}
52+
/>
53+
);
54+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { PlaybookSpec } from "@flanksource-ui/api/types/playbooks";
2+
3+
type PlaybookLinkProps = {
4+
playbook: Pick<PlaybookSpec, "id" | "name" | "icon">;
5+
};
6+
7+
export default function PlaybookLink() {}

src/ui/Badge/Badge.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type BadgeProps = {
44
text: React.ReactNode;
55
value?: string;
66
size?: "xs" | "sm" | "md";
7-
color?: "blue" | "gray";
7+
color?: "blue" | "gray" | "yellow";
88
dot?: string;
99
title?: string;
1010
className?: string;
@@ -29,7 +29,9 @@ export function Badge({
2929
const colorClass =
3030
color === "blue"
3131
? "bg-blue-100 text-blue-800"
32-
: "bg-gray-100 text-gray-700";
32+
: color === "yellow"
33+
? "bg-yellow-100 text-yellow-800"
34+
: "bg-gray-100 text-gray-700";
3335
const spanClassName =
3436
size === "sm" ? "text-sm px-1 py-0.5" : "text-xs px-1 py-0.5";
3537
const svgClassName =

0 commit comments

Comments
 (0)