Skip to content

Commit

Permalink
feat: add permissions form and permission table
Browse files Browse the repository at this point in the history
Fixes #2246
  • Loading branch information
mainawycliffe committed Sep 25, 2024
1 parent d650be1 commit 3745f16
Show file tree
Hide file tree
Showing 16 changed files with 619 additions and 27 deletions.
17 changes: 17 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MdOutlineIntegrationInstructions,
MdOutlineSupportAgent
} from "react-icons/md";
import { RiShieldUserFill } from "react-icons/ri";
import { VscJson } from "react-icons/vsc";
import {
BrowserRouter,
Expand Down Expand Up @@ -55,6 +56,7 @@ import { ConnectionsPage } from "./pages/Settings/ConnectionsPage";
import { EventQueueStatusPage } from "./pages/Settings/EventQueueStatus";
import { FeatureFlagsPage } from "./pages/Settings/FeatureFlagsPage";
import NotificationSilencePage from "./pages/Settings/NotificationSilencePage";
import { PermissionsPage } from "./pages/Settings/PermissionsPage";
import { TopologyCardPage } from "./pages/TopologyCard";
import { UsersPage } from "./pages/UsersPage";
import { ConfigInsightsPage } from "./pages/config/ConfigInsightsList";
Expand Down Expand Up @@ -162,6 +164,13 @@ const settingsNav: SettingsNavigationItems = {
featureName: features["settings.connections"],
resourceName: tables.connections
},
{
name: "Permissions",
href: "/settings/permissions",
icon: RiShieldUserFill,
featureName: features["settings.permissions"],
resourceName: tables.permissions
},
...(process.env.NEXT_PUBLIC_AUTH_IS_CLERK === "true"
? []
: [
Expand Down Expand Up @@ -358,6 +367,14 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
true
)}
/>
<Route
path="permissions"
element={withAuthorizationAccessCheck(
<PermissionsPage />,
tables.permissions,
"read"
)}
/>
<Route
path="users"
element={withAuthorizationAccessCheck(
Expand Down
21 changes: 19 additions & 2 deletions src/api/services/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AVATAR_INFO } from "@flanksource-ui/constants";
import { IncidentCommander } from "../axios";
import { resolvePostGrestRequestWithPagination } from "../resolve";
import { PermissionAPIResponse } from "../types/permissions";
import { PermissionAPIResponse, PermissionTable } from "../types/permissions";

export type FetchPermissionsInput = {
componentId?: string;
Expand Down Expand Up @@ -48,7 +48,7 @@ function composeQueryParamForFetchPermissions({
if (connectionId) {
return `connection_id=eq.${connectionId}`;
}
return undefined;
return "";
}

export function fetchPermissions(
Expand Down Expand Up @@ -79,3 +79,20 @@ export function fetchPermissions(
IncidentCommander.get<PermissionAPIResponse[]>(url)
);
}

export function addPermission(permission: PermissionTable) {
return IncidentCommander.post<PermissionTable>("/permissions", permission);
}

export function updatePermission(permission: PermissionTable) {
return IncidentCommander.patch<PermissionTable>(
`/permissions?id=eq.${permission.id}`,
permission
);
}

export function deletePermission(id: string) {
return IncidentCommander.patch(`/permissions?id=eq.${id}`, {
deleted_at: "now()"
});
}
2 changes: 2 additions & 0 deletions src/api/types/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ export type PermissionTable = {
canary_id?: string;
playbook_id?: string;
created_by: string;
connection_id?: string;
person_id?: string;
team_id?: string;
updated_by: string;
created_at: string;
updated_at: string;
until?: string;
source?: string;
};

export type PermissionAPIResponse = PermissionTable & {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Forms/Formik/FormikConnectionField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function FormikConnectionField({
return (
<FormikSelectDropdown
name={name}
className="h-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
className="h-full rounded-md border-gray-300 py-2 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
options={connections}
label={label}
isLoading={isLoading}
Expand Down
44 changes: 44 additions & 0 deletions src/components/Forms/Formik/FormikPlaybooksDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useGetPlaybookNames } from "@flanksource-ui/api/query-hooks/playbooks";
import PlaybookSpecIcon from "@flanksource-ui/components/Playbooks/Settings/PlaybookSpecIcon";
import { useMemo } from "react";
import FormikSelectDropdown from "./FormikSelectDropdown";

type FormikPlaybooksDropdownProps = {
name: string;
label?: string;
required?: boolean;
hint?: string;
className?: string;
};

export default function FormikPlaybooksDropdown({
name,
label,
required = false,
hint,
className = "flex flex-col space-y-2 py-2"
}: FormikPlaybooksDropdownProps) {
const { isLoading, data: checks } = useGetPlaybookNames();

const options = useMemo(
() =>
checks?.map((playbook) => ({
label: playbook.title || playbook.name,
value: playbook.id,
icon: <PlaybookSpecIcon playbook={playbook} />
})),
[checks]
);

return (
<FormikSelectDropdown
name={name}
className={className}
options={options}
label={label}
isLoading={isLoading}
required={required}
hint={hint}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useState } from "react";
import { AiFillPlusCircle } from "react-icons/ai";
import PermissionForm from "./PermissionForm";

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

return (
<>
<button type="button" className="" onClick={() => setIsOpen(true)}>
<AiFillPlusCircle size={32} className="text-blue-600" />
</button>
<PermissionForm isOpen={isOpen} onClose={() => setIsOpen(false)} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { deletePermission } from "@flanksource-ui/api/services/permissions";
import {
toastError,
toastSuccess
} from "@flanksource-ui/components/Toast/toast";
import { ConfirmationPromptDialog } from "@flanksource-ui/ui/AlertDialog/ConfirmationPromptDialog";
import { Button } from "@flanksource-ui/ui/Buttons/Button";
import { useMutation } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { useCallback, useState } from "react";
import { FaCircleNotch, FaTrash } from "react-icons/fa";

export default function DeletePermission({
permissionId,
onDeleted = () => {}
}: {
permissionId: string;
onDeleted: () => void;
}) {
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);

const { mutate: deleteResource, isLoading } = useMutation({
mutationFn: async (id: string) => {
const res = await deletePermission(id);
return res.data;
},
onSuccess: (_) => {
toastSuccess("Permission deleted");
onDeleted();
},
onError: (error: AxiosError) => {
toastError(error.message);
}
});

const onDeleteResource = useCallback(() => {
setIsConfirmDialogOpen(false);
deleteResource(permissionId);
}, [deleteResource, permissionId]);

return (
<>
<Button
text="Delete"
disabled={isLoading}
icon={
!isLoading ? <FaTrash /> : <FaCircleNotch className="animate-spin" />
}
className="btn-danger"
onClick={() => setIsConfirmDialogOpen(true)}
/>

{isConfirmDialogOpen && (
<ConfirmationPromptDialog
title="Delete Permission"
description="Are you sure you want to permission?"
onConfirm={onDeleteResource}
isOpen={isConfirmDialogOpen}
onClose={() => setIsConfirmDialogOpen(false)}
className="z-[9999]"
/>
)}
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import FormikCanaryDropdown from "@flanksource-ui/components/Forms/Formik/FormikCanaryDropdown";
import FormikConnectionField from "@flanksource-ui/components/Forms/Formik/FormikConnectionField";
import FormikPlaybooksDropdown from "@flanksource-ui/components/Forms/Formik/FormikPlaybooksDropdown";
import FormikResourceSelectorDropdown from "@flanksource-ui/components/Forms/Formik/FormikResourceSelectorDropdown";
import { Switch } from "@flanksource-ui/ui/FormControls/Switch";
import { useFormikContext } from "formik";
import { useState } from "react";

export default function FormikPermissionSelectResourceFields() {
const { setFieldValue } = useFormikContext<Record<string, any>>();

const [switchOption, setSwitchOption] = useState<
"Component" | "Catalog" | "Canary" | "Playbook" | "Connection"
>("Catalog");

return (
<div className="flex flex-col gap-2">
<label className={`form-label`}>Resource</label>
<div>
<div className="flex w-full flex-row">
<Switch
options={[
"Catalog",
"Component",
"Canary",
"Connection",
"Playbook"
]}
className="w-auto"
itemsClassName=""
defaultValue="Go Template"
value={switchOption}
onChange={(v) => {
setSwitchOption(v);
setFieldValue("config_id", undefined);
setFieldValue("check_id", undefined);
setFieldValue("canary_id", undefined);
setFieldValue("component_id", undefined);
setFieldValue("playbook_id", undefined);
}}
/>
</div>

{switchOption === "Catalog" && (
<FormikResourceSelectorDropdown
required
name="config_id"
configResourceSelector={[{}]}
/>
)}

{switchOption === "Component" && (
<FormikResourceSelectorDropdown
required
name="component_id"
componentResourceSelector={[{}]}
/>
)}

{switchOption === "Playbook" && (
<FormikPlaybooksDropdown required name="playbook_id" />
)}

{switchOption === "Canary" && (
<FormikCanaryDropdown required name="canary_id" />
)}

{switchOption === "Connection" && (
<FormikConnectionField required name="connection_id" />
)}
</div>
</div>
);
}
Loading

0 comments on commit 3745f16

Please sign in to comment.