From 30e20b8e20bb35bf227b3a4a47a3b171ace2e883 Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Fri, 10 Oct 2025 12:20:21 +0545 Subject: [PATCH] feat: add scope selector tab to permission form - Add Scope tab to permission resource selector - Create multi-select dropdown for scopes with namespace display - Store selected scopes in object_selector.scopes as {namespace?, name} - Update TypeScript types to support scope selectors - Display scopes in permission resource view --- src/api/types/permissions.ts | 26 ++++--- .../FormikPermissionSelectResourceFields.tsx | 20 ++++- .../Forms/FormikScopeMultiSelect.tsx | 78 +++++++++++++++++++ .../Forms/PermissionForm.tsx | 2 + .../Forms/PermissionResource.tsx | 13 +++- 5 files changed, 125 insertions(+), 14 deletions(-) create mode 100644 src/components/Permissions/ManagePermissions/Forms/FormikScopeMultiSelect.tsx diff --git a/src/api/types/permissions.ts b/src/api/types/permissions.ts index d12cabee3..fc3d3a541 100644 --- a/src/api/types/permissions.ts +++ b/src/api/types/permissions.ts @@ -13,6 +13,21 @@ export type PermissionGlobalObject = | "playbook" | "topology"; +type PermissionObjectSelector = { + playbooks?: Selectors[]; + connections?: Selectors[]; + configs?: Selectors[]; + components?: Selectors[]; + scopes?: ScopeSelector[]; +}; + +interface Selectors {} + +interface ScopeSelector { + namespace?: string; + name: string; +} + export type PermissionTable = { id: string; description: string; @@ -35,7 +50,7 @@ export type PermissionTable = { // Resources object?: PermissionGlobalObject; - object_selector?: Record[]; + object_selector?: PermissionObjectSelector; component_id?: string; canary_id?: string; config_id?: string; @@ -81,12 +96,3 @@ export type PermissionsSummary = PermissionTable & { connection_object: Pick; component_object: Pick; }; - -type PermissionObjectSelector = { - playbooks: Selectors[]; - connections: Selectors[]; - configs: Selectors[]; - components: Selectors[]; -}; - -interface Selectors {} diff --git a/src/components/Permissions/ManagePermissions/Forms/FormikPermissionSelectResourceFields.tsx b/src/components/Permissions/ManagePermissions/Forms/FormikPermissionSelectResourceFields.tsx index 63d2e357f..9a7f3abe8 100644 --- a/src/components/Permissions/ManagePermissions/Forms/FormikPermissionSelectResourceFields.tsx +++ b/src/components/Permissions/ManagePermissions/Forms/FormikPermissionSelectResourceFields.tsx @@ -4,6 +4,7 @@ import FormikSelectDropdown from "@flanksource-ui/components/Forms/Formik/Formik import { Switch } from "@flanksource-ui/ui/FormControls/Switch"; import { useFormikContext } from "formik"; import { useState } from "react"; +import FormikScopeMultiSelect from "./FormikScopeMultiSelect"; export const permissionObjectList = [ { label: "Canaries", value: "canaries" }, @@ -30,18 +31,26 @@ export default function FormikPermissionSelectResourceFields() { | "Canary" | "Playbook" | "Connection" - | "Global" => { + | "Global" + | "Scope" => { if (values.playbook_id) return "Playbook"; if (values.config_id) return "Catalog"; if (values.component_id) return "Component"; if (values.connection_id) return "Connection"; if (values.canary_id) return "Canary"; if (values.object) return "Global"; + if (values.object_selector?.scopes) return "Scope"; return "Catalog"; }; const [switchOption, setSwitchOption] = useState< - "Component" | "Catalog" | "Canary" | "Playbook" | "Connection" | "Global" + | "Component" + | "Catalog" + | "Canary" + | "Playbook" + | "Connection" + | "Global" + | "Scope" >(getInitialTab()); return ( @@ -55,7 +64,8 @@ export default function FormikPermissionSelectResourceFields() { "Component", "Connection", "Playbook", - "Global" + "Global", + "Scope" ]} className="w-auto" itemsClassName="" @@ -69,6 +79,8 @@ export default function FormikPermissionSelectResourceFields() { setFieldValue("canary_id", undefined); setFieldValue("component_id", undefined); setFieldValue("playbook_id", undefined); + setFieldValue("connection_id", undefined); + setFieldValue("object_selector", undefined); }} /> @@ -116,6 +128,8 @@ export default function FormikPermissionSelectResourceFields() { options={permissionObjectList} /> )} + + {switchOption === "Scope" && } ); diff --git a/src/components/Permissions/ManagePermissions/Forms/FormikScopeMultiSelect.tsx b/src/components/Permissions/ManagePermissions/Forms/FormikScopeMultiSelect.tsx new file mode 100644 index 000000000..49b6d8a1e --- /dev/null +++ b/src/components/Permissions/ManagePermissions/Forms/FormikScopeMultiSelect.tsx @@ -0,0 +1,78 @@ +import { useScopesQuery } from "@flanksource-ui/api/query-hooks/useScopesQuery"; +import { useField, useFormikContext } from "formik"; +import { useMemo } from "react"; +import Select from "react-select"; + +type ScopeRef = { + namespace?: string; + name: string; +}; + +type ScopeOption = { + value: string; + label: string; + namespace?: string; + name: string; +}; + +export default function FormikScopeMultiSelect() { + const { setFieldValue } = useFormikContext>(); + const [field] = useField<{ scopes?: ScopeRef[] }>({ + name: "object_selector" + }); + + const { data: scopes, isLoading } = useScopesQuery(); + + const scopeOptions = useMemo(() => { + if (!scopes) return []; + return scopes.map((scope) => ({ + value: JSON.stringify({ + namespace: scope.namespace, + name: scope.name + }), + label: scope.name, + namespace: scope.namespace, + name: scope.name + })); + }, [scopes]); + + const selectedScopes = useMemo(() => { + const scopeRefs = field.value?.scopes || []; + return scopeRefs.map((scopeRef) => ({ + value: JSON.stringify(scopeRef), + label: scopeRef.name, + namespace: scopeRef.namespace, + name: scopeRef.name + })); + }, [field.value]); + + const formatOptionLabel = (option: ScopeOption) => ( +
+ {option.name} + {option.namespace && ( + {option.namespace} + )} +
+ ); + + return ( +
+