From cd3e75b6401f1275cf7dac068cdca71e414ed4ed Mon Sep 17 00:00:00 2001 From: Maycon Santos Date: Fri, 1 Nov 2024 16:04:43 +0100 Subject: [PATCH] Add setup-key improvements (#420) - Add support to key deletion - Add custom and unlimited expiration --- src/modules/activity/ActivityDescription.tsx | 8 ++++ src/modules/common-table-rows/EmptyRow.tsx | 10 ++++- src/modules/setup-keys/SetupKeyActionCell.tsx | 45 ++++++++++++++----- src/modules/setup-keys/SetupKeyGroupsCell.tsx | 2 +- src/modules/setup-keys/SetupKeyKeyCell.tsx | 2 +- src/modules/setup-keys/SetupKeyModal.tsx | 33 +++++++------- src/modules/setup-keys/SetupKeyNameCell.tsx | 6 ++- src/modules/setup-keys/SetupKeyTypeCell.tsx | 2 +- src/modules/setup-keys/SetupKeysTable.tsx | 34 +++++++++++--- 9 files changed, 102 insertions(+), 40 deletions(-) diff --git a/src/modules/activity/ActivityDescription.tsx b/src/modules/activity/ActivityDescription.tsx index f845dbd6..1838ba97 100644 --- a/src/modules/activity/ActivityDescription.tsx +++ b/src/modules/activity/ActivityDescription.tsx @@ -47,6 +47,14 @@ export default function ActivityDescription({ event }: Props) { ); + if (event.activity_code == "setupkey.delete") + return ( +
+ Setup-Key {m.name} with key {m.key} was + deleted +
+ ); + if (event.activity_code == "setupkey.add") return (
diff --git a/src/modules/common-table-rows/EmptyRow.tsx b/src/modules/common-table-rows/EmptyRow.tsx index 260fb8e9..e5617271 100644 --- a/src/modules/common-table-rows/EmptyRow.tsx +++ b/src/modules/common-table-rows/EmptyRow.tsx @@ -1,3 +1,9 @@ -export default function EmptyRow() { - return
-
; +import { cn } from "@utils/helpers"; + +type Props = { + className?: string; +}; + +export default function EmptyRow({ className }: Readonly) { + return
-
; } diff --git a/src/modules/setup-keys/SetupKeyActionCell.tsx b/src/modules/setup-keys/SetupKeyActionCell.tsx index da9ccbed..cc213eb7 100644 --- a/src/modules/setup-keys/SetupKeyActionCell.tsx +++ b/src/modules/setup-keys/SetupKeyActionCell.tsx @@ -1,7 +1,7 @@ import Button from "@components/Button"; import { notify } from "@components/Notification"; import { useApiCall } from "@utils/api"; -import { Trash2 } from "lucide-react"; +import { Trash2, Undo2Icon } from "lucide-react"; import * as React from "react"; import { useSWRConfig } from "swr"; import { useDialog } from "@/contexts/DialogProvider"; @@ -10,16 +10,26 @@ import { SetupKey } from "@/interfaces/SetupKey"; type Props = { setupKey: SetupKey; }; -export default function SetupKeyActionCell({ setupKey }: Props) { +export default function SetupKeyActionCell({ setupKey }: Readonly) { const { confirm } = useDialog(); - const deleteRequest = useApiCall("/setup-keys/" + setupKey.id); + const request = useApiCall("/setup-keys/" + setupKey.id); const { mutate } = useSWRConfig(); const handleRevoke = async () => { + const choice = await confirm({ + title: `Revoke '${setupKey?.name || "Setup Key"}'?`, + description: + "Are you sure you want to revoke the setup key? This action cannot be undone.", + confirmText: "Revoke", + cancelText: "Cancel", + type: "danger", + }); + if (!choice) return; + notify({ title: setupKey?.name || "Setup Key", description: "Setup key was successfully revoked", - promise: deleteRequest + promise: request .put({ name: setupKey?.name || "Setup Key", type: setupKey.type, @@ -37,17 +47,26 @@ export default function SetupKeyActionCell({ setupKey }: Props) { }); }; - const handleConfirm = async () => { + const handleDelete = async () => { const choice = await confirm({ - title: `Revoke '${setupKey?.name || "Setup Key"}'?`, + title: `Delete '${setupKey?.name || "Setup Key"}'?`, description: - "Are you sure you want to revoke the setup key? This action cannot be undone.", - confirmText: "Revoke", + "Are you sure you want to delete the setup key? This action cannot be undone.", + confirmText: "Delete", cancelText: "Cancel", type: "danger", }); if (!choice) return; - handleRevoke().then(); + + notify({ + title: setupKey?.name || "Setup Key", + description: "Setup key was successfully deleted", + promise: request.del().then(() => { + mutate("/setup-keys"); + mutate("/groups"); + }), + loadingMessage: "Deleting the setup key...", + }); }; return ( @@ -55,12 +74,16 @@ export default function SetupKeyActionCell({ setupKey }: Props) { +
); } diff --git a/src/modules/setup-keys/SetupKeyGroupsCell.tsx b/src/modules/setup-keys/SetupKeyGroupsCell.tsx index b35abe14..25e94292 100644 --- a/src/modules/setup-keys/SetupKeyGroupsCell.tsx +++ b/src/modules/setup-keys/SetupKeyGroupsCell.tsx @@ -9,7 +9,7 @@ import GroupsRow from "@/modules/common-table-rows/GroupsRow"; type Props = { setupKey: SetupKey; }; -export default function SetupKeyGroupsCell({ setupKey }: Props) { +export default function SetupKeyGroupsCell({ setupKey }: Readonly) { const [modal, setModal] = useState(false); const request = useApiCall("/setup-keys/" + setupKey.id); const { mutate } = useSWRConfig(); diff --git a/src/modules/setup-keys/SetupKeyKeyCell.tsx b/src/modules/setup-keys/SetupKeyKeyCell.tsx index bb9fbd1d..2f903b8a 100644 --- a/src/modules/setup-keys/SetupKeyKeyCell.tsx +++ b/src/modules/setup-keys/SetupKeyKeyCell.tsx @@ -5,7 +5,7 @@ type Props = { text: string; }; -export default function SetupKeyKeyCell({ text }: Props) { +export default function SetupKeyKeyCell({ text }: Readonly) { return (
diff --git a/src/modules/setup-keys/SetupKeyModal.tsx b/src/modules/setup-keys/SetupKeyModal.tsx index 1dc3c6ea..3192cff1 100644 --- a/src/modules/setup-keys/SetupKeyModal.tsx +++ b/src/modules/setup-keys/SetupKeyModal.tsx @@ -42,7 +42,11 @@ type Props = { setOpen: (open: boolean) => void; }; const copyMessage = "Setup-Key was copied to your clipboard!"; -export default function SetupKeyModal({ children, open, setOpen }: Props) { +export default function SetupKeyModal({ + children, + open, + setOpen, +}: Readonly) { const [successModal, setSuccessModal] = useState(false); const [setupKey, setSetupKey] = useState(); const [, copy] = useCopyToClipboard(setupKey?.key); @@ -131,7 +135,7 @@ type ModalProps = { onSuccess?: (setupKey: SetupKey) => void; }; -export function SetupKeyModalContent({ onSuccess }: ModalProps) { +export function SetupKeyModalContent({ onSuccess }: Readonly) { const setupKeyRequest = useApiCall("/setup-keys", true); const { mutate } = useSWRConfig(); @@ -149,18 +153,10 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) { return reusable ? "Unlimited" : "1"; }, [reusable]); - const expiresInError = useMemo(() => { - const expires = parseInt(expiresIn); - if (expires < 1 || expires > 365) { - return "Days should be between 1 and 365"; - } - return ""; - }, [expiresIn]); - const isDisabled = useMemo(() => { const trimmedName = trim(name); - return trimmedName.length === 0 || expiresInError.length > 0; - }, [name, expiresInError]); + return trimmedName.length === 0; + }, [name]); const submit = () => { if (!selectedGroups) return; @@ -174,7 +170,7 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) { .post({ name, type: reusable ? "reusable" : "one-off", - expires_in: parseInt(expiresIn ? expiresIn : "7") * 24 * 60 * 60, // Days to seconds, defaults to 7 days + expires_in: parseInt(expiresIn || "0") * 24 * 60 * 60, // Days to seconds, defaults to 7 days revoked: false, auto_groups: groups.map((group) => group.id), usage_limit: reusable ? parseInt(usageLimit) : 1, @@ -253,15 +249,16 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
- Should be between 1 and 365 days. + + Days until the key expires.
+ Leave empty for no expiration. +
) { return ( ) { return (
diff --git a/src/modules/setup-keys/SetupKeysTable.tsx b/src/modules/setup-keys/SetupKeysTable.tsx index 6ade8036..120c31ae 100644 --- a/src/modules/setup-keys/SetupKeysTable.tsx +++ b/src/modules/setup-keys/SetupKeysTable.tsx @@ -8,6 +8,7 @@ import DataTableRefreshButton from "@components/table/DataTableRefreshButton"; import { DataTableRowsPerPage } from "@components/table/DataTableRowsPerPage"; import GetStartedTest from "@components/ui/GetStartedTest"; import { ColumnDef, SortingState } from "@tanstack/react-table"; +import dayjs from "dayjs"; import { ExternalLinkIcon, PlusCircle } from "lucide-react"; import { usePathname } from "next/navigation"; import React, { useState } from "react"; @@ -15,6 +16,7 @@ import { useSWRConfig } from "swr"; import SetupKeysIcon from "@/assets/icons/SetupKeysIcon"; import { useLocalStorage } from "@/hooks/useLocalStorage"; import { SetupKey } from "@/interfaces/SetupKey"; +import EmptyRow from "@/modules/common-table-rows/EmptyRow"; import ExpirationDateRow from "@/modules/common-table-rows/ExpirationDateRow"; import LastTimeRow from "@/modules/common-table-rows/LastTimeRow"; import SetupKeyActionCell from "@/modules/setup-keys/SetupKeyActionCell"; @@ -94,7 +96,15 @@ export const SetupKeysTableColumns: ColumnDef[] = [ header: ({ column }) => { return Expires; }, - cell: ({ row }) => , + cell: ({ row }) => { + let expires = dayjs(row.original.expires); + let isNeverExpiring = expires?.year() == 1 || false; + return !isNeverExpiring ? ( + + ) : ( + + ); + }, }, { @@ -116,7 +126,7 @@ export default function SetupKeysTable({ setupKeys, isLoading, headingTarget, -}: Props) { +}: Readonly) { const { mutate } = useSWRConfig(); const path = usePathname(); @@ -216,6 +226,20 @@ export default function SetupKeysTable({ {(table) => ( <> + { + table.setPageIndex(0); + table.getColumn("valid")?.setFilterValue(undefined); + }} + disabled={setupKeys?.length == 0} + variant={ + table.getColumn("valid")?.getFilterValue() == undefined + ? "tertiary" + : "secondary" + } + > + All + { table.setPageIndex(0); @@ -233,16 +257,16 @@ export default function SetupKeysTable({ { table.setPageIndex(0); - table.getColumn("valid")?.setFilterValue(""); + table.getColumn("valid")?.setFilterValue(false); }} disabled={setupKeys?.length == 0} variant={ - table.getColumn("valid")?.getFilterValue() != true + table.getColumn("valid")?.getFilterValue() == false ? "tertiary" : "secondary" } > - All + Expired