From 6e3140d7a3be8d6b842f7aa54e107c753798b55a Mon Sep 17 00:00:00 2001 From: Ilyich Vismara Date: Fri, 2 Feb 2024 17:58:16 +0100 Subject: [PATCH 1/4] feat: connect permission page to api --- components/ArkeSearch/ArkeSearch.tsx | 47 +++++++ .../Permissions/AddPermissionDialog.tsx | 115 ++++++++++++++++++ components/Permissions/PermissionInput.tsx | 68 +++++++---- components/Permissions/PermissionSwitch.tsx | 38 ++++-- components/Permissions/PermissionTable.tsx | 30 ++--- crud/permission/columns.tsx | 27 ++-- package.json | 2 +- pages/[project]/permissions/index.tsx | 22 ++-- pnpm-lock.yaml | 10 +- 9 files changed, 283 insertions(+), 76 deletions(-) create mode 100644 components/ArkeSearch/ArkeSearch.tsx create mode 100644 components/Permissions/AddPermissionDialog.tsx diff --git a/components/ArkeSearch/ArkeSearch.tsx b/components/ArkeSearch/ArkeSearch.tsx new file mode 100644 index 0000000..bb48fc6 --- /dev/null +++ b/components/ArkeSearch/ArkeSearch.tsx @@ -0,0 +1,47 @@ +import { Autocomplete } from "@arkejs/ui"; +import { TUnit } from "@arkejs/client"; +import React, { useEffect, useState } from "react"; +import useDebounce from "@/hooks/useDebounce"; +import useClient from "@/arke/useClient"; + +export default function ArkeSearch({ + value, + onChange, +}: { + value: TUnit; + onChange: (value: TUnit) => void; +}) { + const client = useClient(); + const [inputValue, setInputValue] = useState(""); + const [values, setValues] = useState([]); + const debouncedInputValue = useDebounce(inputValue, 500); + + useEffect(() => { + if (debouncedInputValue) { + client.arke + .getAll({ + params: { + offset: 0, + limit: 5, + filter: `and(contains(id,${debouncedInputValue}))`, + order: `label;asc`, + }, + }) + .then((res) => { + setValues(res.data.content.items); + }); + } + }, [debouncedInputValue]); + + return ( + setInputValue(event.target.value)} + values={values} + value={value} + renderChips={false} + placeholder="Search an Arke or Group" + renderValue={(value) => (value as TUnit).label ?? (value as TUnit).id} + /> + ); +} diff --git a/components/Permissions/AddPermissionDialog.tsx b/components/Permissions/AddPermissionDialog.tsx new file mode 100644 index 0000000..32d3278 --- /dev/null +++ b/components/Permissions/AddPermissionDialog.tsx @@ -0,0 +1,115 @@ +import { Button, Dialog, Switch } from "@arkejs/ui"; +import { Field, Form, useForm } from "@arkejs/form"; +import ArkeSearch from "@/components/ArkeSearch/ArkeSearch"; +import toast from "react-hot-toast"; +import useClient from "@/arke/useClient"; +import { TUnit } from "@arkejs/client"; + +type Schema = { + parent?: TUnit; + get?: string; + post?: string; + put?: string; + delete?: string; + filter?: string; +}; + +const fields: Field[] = [ + { id: "parent" }, + { id: "get", type: "boolean", label: "GET", value: false }, + { id: "post", type: "boolean", label: "POST", value: false }, + { id: "put", type: "boolean", label: "PUT", value: false }, + { id: "delete", type: "boolean", label: "DELETE", value: false }, + { id: "filter", type: "string", label: "Filter" }, +]; + +export default function AddPermissionDialog({ + roleID, + open, + onClose, + ...props +}: { + roleID: string | null; + open: boolean; + onClose: () => void; + onSubmit: (roleID: string) => void; +}) { + const client = useClient(); + const { + formProps, + methods: { reset }, + } = useForm({ fields }); + + const onSubmit = async (data: Schema) => { + if (!data?.parent?.id || !roleID) return; + + try { + await client.unit.topology.addLink( + { arkeId: "arke", id: roleID }, + "permission", + { arkeId: "arke", id: data.parent.id }, + { + metadata: { + get: data.get, + post: data.post, + put: data.put, + delete: data.delete, + filter: data.filter || null, + }, + } + ); + + // reset on close + reset(); + props.onSubmit(roleID); + toast.success(`Permissions added to ${roleID}`); + } catch (err) { + toast.error("Something went wrong"); + } + }; + + const handleClose = () => { + onClose(); + reset(); + }; + + return ( + +
( + + ), + }} + > +
+ } + /> +
+ + + + +
+ +
+
+ + +
+ +
+ ); +} diff --git a/components/Permissions/PermissionInput.tsx b/components/Permissions/PermissionInput.tsx index 806a84d..551c247 100644 --- a/components/Permissions/PermissionInput.tsx +++ b/components/Permissions/PermissionInput.tsx @@ -14,34 +14,56 @@ * limitations under the License. */ import useClient from "@/arke/useClient"; -import { Input } from "@arkejs/ui"; -import { useRef, useState } from "react"; -import useOnClickOutside from "@/hooks/useOnClickOutside"; +import { Form, useForm } from "@arkejs/form"; +import { Button } from "@arkejs/ui"; +import { CheckIcon } from "@heroicons/react/20/solid"; +import toast from "react-hot-toast"; +import { TUnit } from "@arkejs/client"; -interface PermissionInputProps { - role: string; +type PermissionInputProps = { + roleID: string; value: string; -} -export function PermissionInput(props: PermissionInputProps) { + unitID: string; +} & TUnit; + +export function PermissionInput({ + roleID, + unitID, + value, + link: { metadata }, +}: PermissionInputProps) { const client = useClient(); - const [value, setValue] = useState(props.value); - const inputRef = useRef(null); + const { + formProps, + methods: { handleSubmit }, + } = useForm({ + fields: [{ id: "filter", type: "string", value: value }], + }); - function onUpdateData() { - console.log(value); - } + const onSubmit = async (data: Record) => { + try { + await client.unit.topology.editLink( + { arkeId: "arke", id: roleID }, + "permission", + { arkeId: "arke", id: unitID }, + { metadata: { ...metadata, filter: data.filter } } + ); + toast.success(`Filter updated`); + } catch (err) { + toast.error("Something went wrong"); + } + }; - useOnClickOutside(inputRef, onUpdateData); return ( - setValue(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && onUpdateData()} - onBlur={onUpdateData} - /> +
+ + + ); } diff --git a/components/Permissions/PermissionSwitch.tsx b/components/Permissions/PermissionSwitch.tsx index 60e43d5..c52f264 100644 --- a/components/Permissions/PermissionSwitch.tsx +++ b/components/Permissions/PermissionSwitch.tsx @@ -17,23 +17,35 @@ import useClient from "@/arke/useClient"; import { Switch } from "@arkejs/ui"; import { useState } from "react"; import toast from "react-hot-toast"; +import { TUnit } from "@arkejs/client"; -interface PermissionSwitchProps { - role: string; +type PermissionSwitchProps = { + roleID: string; + unitID: string; method: string; - checked: boolean; -} +} & TUnit; -export function PermissionSwitch(props: PermissionSwitchProps) { +export function PermissionSwitch({ + roleID, + unitID, + method, + link: { metadata }, +}: PermissionSwitchProps) { + const [checked, setChecked] = useState(!!metadata[method]); const client = useClient(); - const { role, method } = props; - const [checked, setChecked] = useState(props.checked); - function onChange(status: boolean) { - console.log(role, method, status); - /*client.unit.edit("arke", role, {}).then((res) => console.log(res.data)); - setChecked(!checked); - toast.success(`Permission ${role} (${method.toUpperCase()}) updated`);*/ - // TODO: reset check if api call failed + async function onChange(value: boolean) { + try { + await client.unit.topology.editLink( + { arkeId: "arke", id: roleID }, + "permission", + { arkeId: "arke", id: unitID }, + { metadata: { ...metadata, [method]: value } } + ); + setChecked(value); + toast.success(`Permission ${roleID} (${method.toUpperCase()}) updated`); + } catch (err) { + toast.error("Something went wrong"); + } } return ( diff --git a/components/Permissions/PermissionTable.tsx b/components/Permissions/PermissionTable.tsx index 5524cb4..3f11c15 100644 --- a/components/Permissions/PermissionTable.tsx +++ b/components/Permissions/PermissionTable.tsx @@ -19,7 +19,7 @@ import { Client, LinkDirection, TUnit } from "@arkejs/client"; import useClient from "@/arke/useClient"; import { CrudState } from "@/types/crud"; import { Filter, Sort, useTable } from "@arkejs/table"; -import { columns } from "@/crud/permission/columns"; +import { getColumns } from "@/crud/permission/columns"; import { Table } from "@/components/Table"; import { AddIcon } from "@/components/Icon"; import { Button } from "@arkejs/ui"; @@ -53,13 +53,25 @@ const fetchArkePermission = async ( ); }; -export function PermissionTable(props: { role: string }) { - const { role } = props; +export function PermissionTable({ role }: { role: string }) { const [isLoading, setIsLoading] = useState(false); const [data, setData] = useState([]); const [count, setCount] = useState(0); const client = useClient(); + const loadData = useCallback( + (page?: number, filters?: Filter[], sort?: Sort[]) => { + setIsLoading(true); + fetchArkePermission(role, client, page, filters, sort).then((res) => { + setData(res.data.content.items); + setCount(res.data.content.count); + setIsLoading(false); + }); + }, + [] + ); + const columns = getColumns(role); + const [crud, setCrud] = useState({ add: false, edit: false, @@ -92,18 +104,6 @@ export function PermissionTable(props: { role: string }) { : null ); - const loadData = useCallback( - (page?: number, filters?: Filter[], sort?: Sort[]) => { - setIsLoading(true); - fetchArkePermission(role, client, page, filters, sort).then((res) => { - setData(res.data.content.items); - setCount(res.data.content.count); - setIsLoading(false); - }); - }, - [] - ); - useEffect(() => { loadData(); }, []); diff --git a/crud/permission/columns.tsx b/crud/permission/columns.tsx index f52e2ec..e09d3d9 100644 --- a/crud/permission/columns.tsx +++ b/crud/permission/columns.tsx @@ -19,7 +19,7 @@ import { PermissionSwitch } from "@/components/Permissions/PermissionSwitch"; import { PermissionInput } from "@/components/Permissions/PermissionInput"; import { TUnit } from "@arkejs/client"; -export const columns: Column[] = [ +export const getColumns = (roleID: string): Column[] => [ { id: "id", label: "Arke ID", @@ -33,9 +33,10 @@ export const columns: Column[] = [ label: "Get", render: (rowData: any) => ( ), }, @@ -44,9 +45,10 @@ export const columns: Column[] = [ label: "Post", render: (rowData: any) => ( ), }, @@ -55,9 +57,10 @@ export const columns: Column[] = [ label: "Put", render: (rowData: any) => ( ), }, @@ -66,9 +69,10 @@ export const columns: Column[] = [ label: "Delete", render: (rowData: any) => ( ), }, @@ -78,8 +82,9 @@ export const columns: Column[] = [ render: (rowData: any) => ( ), }, diff --git a/package.json b/package.json index 18a55b0..bd8f0da 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test": "jest --silent" }, "dependencies": { - "@arkejs/client": "2.9.4", + "@arkejs/client": "2.9.5", "@arkejs/form": "1.6.2", "@arkejs/table": "0.8.0", "@arkejs/ui": "0.29.0", diff --git a/pages/[project]/permissions/index.tsx b/pages/[project]/permissions/index.tsx index c1d87ab..3fd0ca3 100644 --- a/pages/[project]/permissions/index.tsx +++ b/pages/[project]/permissions/index.tsx @@ -27,20 +27,22 @@ import { acceptedRoles } from "@/arke/config"; import { ExpandIcon } from "@/components/Icon/ExpandIcon"; import { PermissionTable } from "@/components/Permissions/PermissionTable"; import serverErrorRedirect from "@/server/serverErrorRedirect"; +import AddPermissionDialog from "@/components/Permissions/AddPermissionDialog"; function Permissions(props: { data: TUnit[]; count: number }) { const { data } = props; - const [crud, setCrud] = useState({ - add: false, - edit: false, - delete: false, - }); + const [activeRole, setActiveRole] = useState(null); const [expanded, setExpanded] = useState>( data.reduce((accumulator, value) => { return { ...accumulator, [value.id]: false }; }, {}) ); + const onAddPermission = (role: string) => { + setExpanded((prevState) => ({ ...prevState, [role]: false })); + setActiveRole(null); + }; + return ( @@ -60,9 +62,7 @@ function Permissions(props: { data: TUnit[]; count: number }) { @@ -75,6 +75,12 @@ function Permissions(props: { data: TUnit[]; count: number }) {
))} + setActiveRole(null)} + onSubmit={onAddPermission} + />
); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee1b2b4..dca22a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -6,8 +6,8 @@ settings: dependencies: '@arkejs/client': - specifier: 2.9.4 - version: 2.9.4 + specifier: 2.9.5 + version: 2.9.5 '@arkejs/form': specifier: 1.6.2 version: 1.6.2(react-dom@18.2.0)(react@18.2.0) @@ -138,8 +138,8 @@ packages: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 - /@arkejs/client@2.9.4: - resolution: {integrity: sha512-VOHOC7/Vc0k36j/+SeZBscQRZnAfKi8PZ4sDJL6YsvUg9s3gmsU8xmTjiwi75v089EcIV6YH5ZnAggjQn+PXfg==} + /@arkejs/client@2.9.5: + resolution: {integrity: sha512-2TmaE5wCMuBLue0ew+4/IuETRF/EfKwT3JfEHyiOXas4b/o72smqRFX83kameqVqhOpOG+LkrLCyKCn/olYgTA==} dependencies: axios: 1.6.0 transitivePeerDependencies: From b37d2aa59b736a71ca260dc9c6d44d2a77d7a241 Mon Sep 17 00:00:00 2001 From: Manolo Battista <81776297+manolo-battista@users.noreply.github.com> Date: Fri, 2 Feb 2024 18:19:10 +0100 Subject: [PATCH 2/4] Update components/ArkeSearch/ArkeSearch.tsx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/ArkeSearch/ArkeSearch.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/components/ArkeSearch/ArkeSearch.tsx b/components/ArkeSearch/ArkeSearch.tsx index bb48fc6..68332c4 100644 --- a/components/ArkeSearch/ArkeSearch.tsx +++ b/components/ArkeSearch/ArkeSearch.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2024 Arkemis S.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { Autocomplete } from "@arkejs/ui"; import { TUnit } from "@arkejs/client"; import React, { useEffect, useState } from "react"; From 7f46617e15b5a4ba4e00b17abd08e88f5c7157d9 Mon Sep 17 00:00:00 2001 From: Manolo Battista <81776297+manolo-battista@users.noreply.github.com> Date: Fri, 2 Feb 2024 18:19:16 +0100 Subject: [PATCH 3/4] Update components/Permissions/AddPermissionDialog.tsx Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/Permissions/AddPermissionDialog.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/components/Permissions/AddPermissionDialog.tsx b/components/Permissions/AddPermissionDialog.tsx index 32d3278..936b750 100644 --- a/components/Permissions/AddPermissionDialog.tsx +++ b/components/Permissions/AddPermissionDialog.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2024 Arkemis S.r.l. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { Button, Dialog, Switch } from "@arkejs/ui"; import { Field, Form, useForm } from "@arkejs/form"; import ArkeSearch from "@/components/ArkeSearch/ArkeSearch"; From d497ccb6976e612df5bae7df850c327adc6f0e04 Mon Sep 17 00:00:00 2001 From: Manolo Battista Date: Tue, 19 Mar 2024 19:50:05 +0100 Subject: [PATCH 4/4] fix: add check for metadata and fix boolean method permission --- components/Permissions/PermissionInput.tsx | 4 ++-- components/Permissions/PermissionSwitch.tsx | 8 ++++---- crud/permission/columns.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/Permissions/PermissionInput.tsx b/components/Permissions/PermissionInput.tsx index 551c247..ab38d62 100644 --- a/components/Permissions/PermissionInput.tsx +++ b/components/Permissions/PermissionInput.tsx @@ -30,7 +30,7 @@ export function PermissionInput({ roleID, unitID, value, - link: { metadata }, + link, }: PermissionInputProps) { const client = useClient(); const { @@ -46,7 +46,7 @@ export function PermissionInput({ { arkeId: "arke", id: roleID }, "permission", { arkeId: "arke", id: unitID }, - { metadata: { ...metadata, filter: data.filter } } + { metadata: { ...link?.metadata, filter: data.filter } } ); toast.success(`Filter updated`); } catch (err) { diff --git a/components/Permissions/PermissionSwitch.tsx b/components/Permissions/PermissionSwitch.tsx index c52f264..2f9c47f 100644 --- a/components/Permissions/PermissionSwitch.tsx +++ b/components/Permissions/PermissionSwitch.tsx @@ -29,9 +29,9 @@ export function PermissionSwitch({ roleID, unitID, method, - link: { metadata }, + link, }: PermissionSwitchProps) { - const [checked, setChecked] = useState(!!metadata[method]); + const [checked, setChecked] = useState(link?.metadata[method]); const client = useClient(); async function onChange(value: boolean) { try { @@ -39,7 +39,7 @@ export function PermissionSwitch({ { arkeId: "arke", id: roleID }, "permission", { arkeId: "arke", id: unitID }, - { metadata: { ...metadata, [method]: value } } + { metadata: { ...link?.metadata, [method]: value } } ); setChecked(value); toast.success(`Permission ${roleID} (${method.toUpperCase()}) updated`); @@ -50,7 +50,7 @@ export function PermissionSwitch({ return ( onChange(!checked)} color="primary" /> diff --git a/crud/permission/columns.tsx b/crud/permission/columns.tsx index e09d3d9..1f43655 100644 --- a/crud/permission/columns.tsx +++ b/crud/permission/columns.tsx @@ -84,7 +84,7 @@ export const getColumns = (roleID: string): Column[] => [ {...rowData} unitID={rowData.id as string} roleID={roleID} - value={rowData.link.metadata?.filter} + value={rowData.link?.metadata?.filter} /> ), },