UX changes for modals and refactoring (#380)

This commit is contained in:
Eduard Gert
2024-05-08 14:42:04 +02:00
committed by GitHub
parent 3f943bb7d4
commit 5caeab118b
27 changed files with 454 additions and 256 deletions

View File

@@ -5,6 +5,9 @@ const nextConfig = {
unoptimized: true,
},
reactStrictMode: false,
env: {
APP_ENV: process.env.APP_ENV || "production",
},
};
module.exports = nextConfig;

View File

@@ -1,14 +1,40 @@
"use client";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useLocalStorage } from "@hooks/useLocalStorage";
import { useRedirect } from "@hooks/useRedirect";
import { useEffect, useState } from "react";
type Props = {
url: string;
queryParams?: string;
};
export default function NotFound() {
const router = useRouter();
useEffect(() => {
router.push("/peers");
});
const [mounted, setMounted] = useState(false);
const [tempQueryParams, setTempQueryParams] = useLocalStorage(
"netbird-query-params",
"",
);
const [queryParams, setQueryParams] = useState("");
return <FullScreenLoading />;
useEffect(() => {
setQueryParams(tempQueryParams);
setTempQueryParams("");
setMounted(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return mounted ? (
<Redirect
url={window?.location?.pathname || "/"}
queryParams={queryParams}
/>
) : (
<FullScreenLoading />
);
}
const Redirect = ({ url, queryParams }: Props) => {
useRedirect(url == "/" ? "/peers" : url + (queryParams && `?${queryParams}`));
return <FullScreenLoading />;
};

View File

@@ -16,6 +16,8 @@ export default function CircleIcon({
return (
<span
style={{ width: size + "px", height: size + "px" }}
data-cy="circle-icon"
data-cy-status={active ? "active" : "inactive"}
className={cn(
"rounded-full",
active

View File

@@ -5,14 +5,16 @@ import React, { forwardRef } from "react";
type Props = {
children: React.ReactNode;
disabled?: boolean;
className?: string;
};
function ButtonGroup({ children, disabled }: Props) {
function ButtonGroup({ children, disabled, className }: Props) {
return (
<div
className={cn(
"rounded-lg border-[1px] dark:border-nb-gray-900 border-neutral-200 overflow-hidden flex items-center justify-center shrink-0 border-separate",
disabled ? "opacity-100 !border-nb-gray-900/20" : "",
className,
)}
>
{children}
@@ -21,7 +23,10 @@ function ButtonGroup({ children, disabled }: Props) {
}
const ButtonGroupButton = forwardRef(
({ ...props }: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
(
{ className, ...props }: ButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>,
) => {
return (
<Button
ref={ref}
@@ -31,6 +36,7 @@ const ButtonGroupButton = forwardRef(
className={cn(
"first:border-l-0 last:border-r-0 border-t-0 border-b-0 h-[41px]",
"!py-2.5 !px-4",
className,
)}
/>
);

View File

@@ -198,6 +198,7 @@ export function PeerGroupSelector({
<CommandList className={"w-full"}>
<div className={"relative"}>
<CommandInput
data-cy={"group-search-input"}
className={cn(
"min-h-[42px] w-full relative",
"border-b-0 border-t-0 border-r-0 border-l-0 border-neutral-200 dark:border-nb-gray-700 items-center",

View File

@@ -21,12 +21,19 @@ function SegmentedTabs({ value, onChange, children }: Props) {
);
}
function List({ children }: { children: React.ReactNode }) {
function List({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<TabsList
className={
"bg-nb-gray-930/70 p-1.5 rounded-t-lg flex justify-center gap-1 border border-b-0 border-nb-gray-900"
}
className={cn(
"bg-nb-gray-930/70 p-1.5 rounded-t-lg flex justify-center gap-1 border border-b-0 border-nb-gray-900",
className,
)}
>
{children}
</TabsList>

View File

@@ -75,7 +75,10 @@ const ModalContent = React.forwardRef<
<>
{children}
{showClose && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400">
<DialogPrimitive.Close
data-cy={"modal-close"}
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400"
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>

View File

@@ -28,9 +28,10 @@ export function DataTableRowsPerPage<TData>({
role="combobox"
aria-expanded={open}
disabled={disabled}
data-cy={"rows-per-page"}
className="w-[200px] justify-between"
>
<RowsIcon size={15} className={"text-nb-gray-300"} />
<RowsIcon size={15} className={"text-nb-gray-300 shrink-0"} />
<div>
<span className={"text-white"}>
{table.getState().pagination.pageSize}
@@ -47,6 +48,7 @@ export function DataTableRowsPerPage<TData>({
<CommandItem
key={val}
value={val.toString()}
data-cy={`rows-per-page-value`}
onSelect={(currentValue) => {
table.setPageSize(Number(currentValue));
setOpen(false);

View File

@@ -94,6 +94,7 @@ export default function DialogProvider({ children }: Props) {
className={"w-full"}
size={"sm"}
tabIndex={-1}
data-cy={"confirmation.cancel"}
onClick={() => fn.current && fn.current(false)}
>
{dialogOptions.cancelText || "Cancel"}
@@ -109,6 +110,7 @@ export default function DialogProvider({ children }: Props) {
}
className={"w-full"}
size={"sm"}
data-cy={"confirmation.confirm"}
onClick={() => fn.current && fn.current(true)}
>
{dialogOptions.confirmText || "Confirm"}

View File

@@ -77,9 +77,7 @@ export default function PeerProvider({ children, peer }: Props) {
? loginExpiration
: peer.login_expiration_enabled,
approval_required:
approval_required != undefined
? approval_required
: peer.approval_required,
approval_required == undefined ? undefined : approval_required,
},
`/${peer.id}`,
);

View File

@@ -239,12 +239,6 @@ export function AccessControlModalContent({
const portAndDirectionDisabled = protocol == "icmp" || protocol == "all";
const buttonDisabled = useMemo(() => {
if (sourceGroups.length == 0 || destinationGroups.length == 0) return true;
if (name.length == 0) return true;
if (direction != "bi" && ports.length == 0) return true;
}, [sourceGroups, destinationGroups, direction, ports, name]);
const [postureChecks, setPostureChecks] = useState<PostureCheck[]>([]);
const postureChecksLoaded = useRef(false);
@@ -268,6 +262,16 @@ export function AccessControlModalContent({
}
}, [initialPostureChecks]);
const continuePostureChecksDisabled = useMemo(() => {
if (sourceGroups.length == 0 || destinationGroups.length == 0) return true;
if (direction != "bi" && ports.length == 0) return true;
}, [sourceGroups, destinationGroups, direction, ports]);
const submitDisabled = useMemo(() => {
if (name.length == 0) return true;
if (continuePostureChecksDisabled) return true;
}, [name, continuePostureChecksDisabled]);
return (
<ModalContent maxWidthClass={"max-w-2xl"}>
<ModalHeader
@@ -283,14 +287,17 @@ export function AccessControlModalContent({
color={"netbird"}
/>
<Tabs defaultValue={tab} onValueChange={(v) => setTab(v)}>
<Tabs defaultValue={tab} onValueChange={(v) => setTab(v)} value={tab}>
<TabsList justify={"start"} className={"px-8"}>
<TabsTrigger value={"policy"}>
<ArrowRightLeft size={16} />
Policy
</TabsTrigger>
<PostureCheckTabTrigger />
<TabsTrigger value={"general"}>
<PostureCheckTabTrigger disabled={continuePostureChecksDisabled} />
<TabsTrigger
value={"general"}
disabled={continuePostureChecksDisabled}
>
<Text
size={16}
className={
@@ -456,24 +463,74 @@ export function AccessControlModalContent({
</Paragraph>
</div>
<div className={"flex gap-3 w-full justify-end"}>
<ModalClose asChild={true}>
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
{!policy ? (
<>
{tab == "policy" && (
<ModalClose asChild={true}>
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
)}
<Button
variant={"primary"}
disabled={buttonDisabled}
onClick={submit}
>
{policy ? (
<>Save Changes</>
) : (
<>
<PlusCircle size={16} />
Add Policy
</>
)}
</Button>
{tab == "posture_checks" && (
<Button variant={"secondary"} onClick={() => setTab("policy")}>
Back
</Button>
)}
{tab == "policy" && (
<Button
variant={"primary"}
onClick={() => setTab("posture_checks")}
disabled={continuePostureChecksDisabled}
>
Continue
</Button>
)}
{tab == "posture_checks" && (
<Button
variant={"primary"}
onClick={() => setTab("general")}
disabled={continuePostureChecksDisabled}
>
Continue
</Button>
)}
{tab == "general" && (
<>
<Button
variant={"secondary"}
onClick={() => setTab("posture_checks")}
>
Back
</Button>
<Button
variant={"primary"}
disabled={submitDisabled}
onClick={submit}
>
<PlusCircle size={16} />
Add Policy
</Button>
</>
)}
</>
) : (
<>
<ModalClose asChild={true}>
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
<Button
variant={"primary"}
disabled={submitDisabled}
onClick={submit}
>
Save Changes
</Button>
</>
)}
</div>
</ModalFooter>
</ModalContent>

View File

@@ -1,3 +1,4 @@
import Badge from "@components/Badge";
import Button from "@components/Button";
import {
Modal,
@@ -10,6 +11,7 @@ import ModalHeader from "@components/modal/ModalHeader";
import { PeerGroupSelector } from "@components/PeerGroupSelector";
import Separator from "@components/Separator";
import MultipleGroups from "@components/ui/MultipleGroups";
import { IconCirclePlus } from "@tabler/icons-react";
import { FolderGit2 } from "lucide-react";
import * as React from "react";
import { useMemo } from "react";
@@ -27,6 +29,7 @@ type Props = {
label?: string;
description?: string;
peer?: Peer;
showAddGroupButton?: boolean;
};
export default function GroupsRow({
@@ -37,6 +40,7 @@ export default function GroupsRow({
label = "Assigned Groups",
description = "Use groups to control what this peer can access",
peer,
showAddGroupButton = false,
}: Props) {
const { groups: allGroups } = useGroups();
const { isUser } = useLoggedInUser();
@@ -59,7 +63,14 @@ export default function GroupsRow({
setModal && !isUser && setModal(true);
}}
>
<MultipleGroups groups={foundGroups} label={label} />
{foundGroups?.length == 0 && showAddGroupButton ? (
<Badge variant={"gray"} useHover={true}>
<IconCirclePlus size={14} />
Add Groups
</Badge>
) : (
<MultipleGroups groups={foundGroups} label={label} />
)}
</ModalTrigger>
<EditGroupsModal
groups={foundGroups}

View File

@@ -238,27 +238,22 @@ export function NameserverModalContent({
return "";
}, [name]);
const hasAnyError = useMemo(() => {
return (
const canContinueToDomains = useMemo(() => {
return !(
hasNSErrors ||
nsError ||
domainError ||
nameservers.length == 0 ||
hasDomainErrors ||
groups.length == 0 ||
nameLengthError !== "" ||
name == ""
groups.length == 0
);
}, [
nsError,
domainError,
nameservers,
groups,
hasNSErrors,
hasDomainErrors,
nameLengthError,
name,
]);
}, [hasNSErrors, nsError, nameservers.length, groups.length]);
const canContinueToGeneral = useMemo(() => {
return !(!canContinueToDomains || domainError || hasDomainErrors);
}, [canContinueToDomains, domainError, hasDomainErrors]);
const canSubmit = useMemo(() => {
return !(!canContinueToGeneral || nameLengthError !== "" || name == "");
}, [canContinueToGeneral, nameLengthError, name]);
return (
<ModalContent maxWidthClass={"max-w-xl"}>
@@ -269,7 +264,7 @@ export function NameserverModalContent({
color={"netbird"}
/>
<Tabs defaultValue={tab} onValueChange={(v) => setTab(v)}>
<Tabs defaultValue={tab} onValueChange={(v) => setTab(v)} value={tab}>
<TabsList justify={"start"} className={"px-8"}>
<TabsTrigger value={"nameserver"}>
<ServerIcon
@@ -280,7 +275,7 @@ export function NameserverModalContent({
/>
Nameserver
</TabsTrigger>
<TabsTrigger value={"domains"}>
<TabsTrigger value={"domains"} disabled={!canContinueToDomains}>
<GlobeIcon
size={16}
className={
@@ -289,7 +284,7 @@ export function NameserverModalContent({
/>
Domains
</TabsTrigger>
<TabsTrigger value={"general"}>
<TabsTrigger value={"general"} disabled={!canContinueToGeneral}>
<Text
size={16}
className={
@@ -473,20 +468,77 @@ export function NameserverModalContent({
</Paragraph>
</div>
<div className={"flex gap-3 w-full justify-end"}>
<ModalClose asChild={true}>
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
{!isUpdate ? (
<>
{tab == "nameserver" && (
<ModalClose asChild={true}>
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
)}
<Button variant={"primary"} disabled={hasAnyError} onClick={submit}>
{isUpdate ? (
<>Save Changes</>
) : (
<>
<PlusCircle size={16} />
Add Nameserver
</>
)}
</Button>
{tab == "domains" && (
<Button
variant={"secondary"}
onClick={() => setTab("nameserver")}
>
Back
</Button>
)}
{tab == "nameserver" && (
<Button
variant={"primary"}
onClick={() => setTab("domains")}
disabled={!canContinueToDomains}
>
Continue
</Button>
)}
{tab == "domains" && (
<Button
variant={"primary"}
onClick={() => setTab("general")}
disabled={!canContinueToGeneral}
>
Continue
</Button>
)}
{tab == "general" && (
<>
<Button
variant={"secondary"}
onClick={() => setTab("domains")}
>
Back
</Button>
<Button
variant={"primary"}
disabled={!canSubmit}
onClick={submit}
>
<PlusCircle size={16} />
Add Nameserver
</Button>
</>
)}
</>
) : (
<>
<ModalClose asChild={true}>
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
<Button
variant={"primary"}
disabled={!canSubmit}
onClick={submit}
>
Save Changes
</Button>
</>
)}
</div>
</ModalFooter>
</ModalContent>

View File

@@ -13,8 +13,7 @@ export const useHasExitNodes = (peer?: Peer) => {
);
return peer
? routes?.some(
(route) =>
route?.peer === peer.id && route?.network.includes("0.0.0.0"),
(route) => route?.peer === peer.id && route?.network === "0.0.0.0/0",
) || false
: false;
};

View File

@@ -163,7 +163,10 @@ export default function PostureCheckModal({
Checks
</TabsTrigger>
<TabsTrigger value={"general"}>
<TabsTrigger
value={"general"}
disabled={!isAtLeastOneCheckEnabled}
>
<Text
size={16}
className={
@@ -243,12 +246,23 @@ export default function PostureCheckModal({
</div>
<div className={"flex gap-3 w-full justify-end"}>
<>
<Button
variant={"secondary"}
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
{tab == "checks" && (
<Button
variant={"secondary"}
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
)}
{tab == "general" && (
<Button
variant={"secondary"}
onClick={() => setTab("checks")}
>
Back
</Button>
)}
{!postureCheck && tab == "checks" && (
<Button

View File

@@ -2,9 +2,13 @@ import { TabsTrigger } from "@components/Tabs";
import { ShieldCheck } from "lucide-react";
import * as React from "react";
export const PostureCheckTabTrigger = () => {
type Props = {
disabled?: boolean;
};
export const PostureCheckTabTrigger = ({ disabled = false }: Props) => {
return (
<TabsTrigger value={"posture_checks"}>
<TabsTrigger value={"posture_checks"} disabled={disabled}>
<ShieldCheck size={16} />
Posture Checks
</TabsTrigger>

View File

@@ -0,0 +1,37 @@
import Badge from "@components/Badge";
import FullTooltip from "@components/FullTooltip";
import { cn } from "@utils/helpers";
import { HelpCircle } from "lucide-react";
import * as React from "react";
import EmptyRow from "@/modules/common-table-rows/EmptyRow";
type Props = {
ephemeral: boolean;
};
export default function SetupKeyEphemeralCell({ ephemeral }: Props) {
return ephemeral ? (
<FullTooltip
interactive={false}
content={
<div className={"max-w-xs text-xs"}>
Peers that are offline for over 10 minutes will be removed
automatically.
</div>
}
disabled={!ephemeral}
>
<Badge variant={"gray"}>
<span
className={cn(
"h-2 w-2 rounded-full mr-0.5",
ephemeral ? "bg-yellow-500" : "bg-nb-gray-400",
)}
></span>
Ephemeral
<HelpCircle size={12} />
</Badge>
</FullTooltip>
) : (
<EmptyRow />
);
}

View File

@@ -46,6 +46,7 @@ export default function SetupKeyGroupsCell({ setupKey }: Props) {
}
groups={setupKey.auto_groups || []}
onSave={handleSave}
showAddGroupButton={true}
modal={modal}
setModal={setModal}
/>

View File

@@ -1,16 +0,0 @@
import Badge from "@components/Badge";
import React from "react";
type Props = {
text: string;
};
export default function SetupKeyKeyCell({ text }: Props) {
return (
<div className={"flex"}>
<Badge variant={"gray"} className={"text-xs font-mono"}>
{text.substring(0, 5) + "****"}
</Badge>
</div>
);
}

View File

@@ -87,7 +87,11 @@ export default function SetupKeyModal({ children, open, setOpen }: Props) {
</div>
</div>
<div className={"px-8 pb-6"}>
<div
className={"px-8 pb-6"}
data-cy={"setup-key-copy-input"}
data-cy-setup-key-value={setupKey?.key || ""}
>
<Code message={copyMessage}>
<Code.Line>
{setupKey?.key || "Setup key could not be created..."}
@@ -101,6 +105,7 @@ export default function SetupKeyModal({ children, open, setOpen }: Props) {
variant={"secondary"}
className={"w-full"}
tabIndex={-1}
data-cy={"setup-key-close"}
>
Close
</Button>
@@ -108,6 +113,7 @@ export default function SetupKeyModal({ children, open, setOpen }: Props) {
<Button
variant={"primary"}
className={"w-full"}
data-cy={"setup-key-copy"}
onClick={() => copy(copyMessage)}
>
<CopyIcon size={14} />
@@ -202,6 +208,7 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
<Input
placeholder={"e.g., AWS Servers"}
value={name}
data-cy={"setup-key-name"}
onChange={(e) => setName(e.target.value)}
/>
</div>
@@ -233,6 +240,7 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
disabled={!reusable}
value={usageLimit}
type={"number"}
data-cy={"setup-key-usage-limit"}
onChange={(e) => setUsageLimit(e.target.value)}
placeholder={usageLimitPlaceholder}
customPrefix={
@@ -256,6 +264,7 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
error={expiresInError}
errorTooltip={true}
type={"number"}
data-cy={"setup-key-expire-in-days"}
onChange={(e) => setExpiresIn(e.target.value)}
customPrefix={
<AlarmClock size={16} className={"text-nb-gray-300"} />
@@ -312,7 +321,12 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) {
<Button variant={"secondary"}>Cancel</Button>
</ModalClose>
<Button variant={"primary"} onClick={submit} disabled={isDisabled}>
<Button
variant={"primary"}
onClick={submit}
disabled={isDisabled}
data-cy={"create-setup-key"}
>
<PlusCircle size={16} />
Create Setup Key
</Button>

View File

@@ -3,7 +3,20 @@ import ActiveInactiveRow from "@/modules/common-table-rows/ActiveInactiveRow";
type Props = {
name: string;
valid: boolean;
secret?: string;
};
export default function SetupKeyNameCell({ valid, name }: Props) {
return <ActiveInactiveRow active={valid} inactiveDot={"red"} text={name} />;
export default function SetupKeyNameCell({ name, valid, secret }: Props) {
return (
<ActiveInactiveRow
active={valid || false}
inactiveDot={"red"}
text={name || ""}
>
{secret && (
<span className={"font-mono text-xs text-nb-gray-400 mt-1"}>
{secret.substring(0, 5) + "****"}
</span>
)}
</ActiveInactiveRow>
);
}

View File

@@ -1,24 +0,0 @@
import Badge from "@components/Badge";
import { IconRepeat } from "@tabler/icons-react";
import { Repeat1 } from "lucide-react";
type Props = {
reusable: boolean;
};
export default function SetupKeyTypeCell({ reusable }: Props) {
return (
<div className={"flex"}>
<Badge className={"text-xs"} variant={"gray"}>
{reusable ? (
<>
<IconRepeat size={14} className={"text-green-400"} /> Reusable
</>
) : (
<>
<Repeat1 size={14} /> One-off
</>
)}
</Badge>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import { MonitorSmartphoneIcon } from "lucide-react";
import { IconRepeat } from "@tabler/icons-react";
import { Repeat1 } from "lucide-react";
type Props = {
current: number;
@@ -7,14 +8,16 @@ type Props = {
};
export default function SetupKeyUsageCell({ current, limit, reusable }: Props) {
return reusable ? (
<div className={"flex gap-1 flex-col"}>
<div className={"flex items-center gap-2"}>
<MonitorSmartphoneIcon size={14} />
{current} of {limit} Peers
</div>
<div></div>
<div className={"flex items-center text-[13px] text-nb-gray-300 gap-2"}>
<IconRepeat size={14} className={"text-green-400"} />
<span>
<span className={"font-medium text-nb-gray-200"}> {current} </span> of{" "}
{limit == 0 ? <>Unlimited</> : limit} Peers
</span>
</div>
) : (
<div className={"text-nb-gray-800"}>-</div>
<div className={"flex items-center text-[13px] text-nb-gray-300 gap-2"}>
<Repeat1 size={14} /> One-off
</div>
);
}

View File

@@ -3,10 +3,11 @@ import ButtonGroup from "@components/ButtonGroup";
import InlineLink from "@components/InlineLink";
import SquareIcon from "@components/SquareIcon";
import { DataTable } from "@components/table/DataTable";
import DataTableHeader from "@components/table/DataTableHeader";
import DataTableRefreshButton from "@components/table/DataTableRefreshButton";
import { DataTableRowsPerPage } from "@components/table/DataTableRowsPerPage";
import GetStartedTest from "@components/ui/GetStartedTest";
import { SortingState } from "@tanstack/react-table";
import { ColumnDef, SortingState } from "@tanstack/react-table";
import { ExternalLinkIcon, PlusCircle } from "lucide-react";
import { usePathname } from "next/navigation";
import React, { useState } from "react";
@@ -14,8 +15,96 @@ import { useSWRConfig } from "swr";
import SetupKeysIcon from "@/assets/icons/SetupKeysIcon";
import { useLocalStorage } from "@/hooks/useLocalStorage";
import { SetupKey } from "@/interfaces/SetupKey";
import ExpirationDateRow from "@/modules/common-table-rows/ExpirationDateRow";
import LastTimeRow from "@/modules/common-table-rows/LastTimeRow";
import SetupKeyActionCell from "@/modules/setup-keys/SetupKeyActionCell";
import SetupKeyEphemeralCell from "@/modules/setup-keys/SetupKeyEphemeralCell";
import SetupKeyGroupsCell from "@/modules/setup-keys/SetupKeyGroupsCell";
import SetupKeyModal from "@/modules/setup-keys/SetupKeyModal";
import { SetupKeysTableColumns } from "@/modules/setup-keys/SetupKeysTableColumns";
import SetupKeyNameCell from "@/modules/setup-keys/SetupKeyNameCell";
import SetupKeyUsageCell from "@/modules/setup-keys/SetupKeyUsageCell";
export const SetupKeysTableColumns: ColumnDef<SetupKey>[] = [
{
accessorKey: "name",
header: ({ column }) => {
return <DataTableHeader column={column}>Name & Key</DataTableHeader>;
},
sortingFn: "text",
cell: ({ row }) => (
<SetupKeyNameCell
name={row.original.name}
valid={row.original.valid}
secret={row.original.key}
/>
),
},
{
id: "valid",
accessorKey: "valid",
sortingFn: "basic",
},
{
accessorKey: "usage_limit",
header: ({ column }) => {
return <DataTableHeader column={column}>Usage</DataTableHeader>;
},
cell: ({ row }) => (
<SetupKeyUsageCell
current={row.original.used_times}
limit={row.original.usage_limit || 0}
reusable={row.original.type == "reusable"}
/>
),
},
{
accessorKey: "last_used",
header: ({ column }) => {
return <DataTableHeader column={column}>Last used</DataTableHeader>;
},
sortingFn: "datetime",
cell: ({ row }) => (
<LastTimeRow date={row.original.last_used} text={"Last used on"} />
),
},
{
id: "group_strings",
accessorKey: "group_strings",
accessorFn: (s) => s.groups?.map((g) => g?.name || "").join(", "),
},
{
accessorFn: (item) => item.auto_groups?.length,
id: "groups",
header: ({ column }) => {
return <DataTableHeader column={column}>Groups</DataTableHeader>;
},
cell: ({ row }) => <SetupKeyGroupsCell setupKey={row.original} />,
},
{
accessorKey: "ephemeral",
header: ({ column }) => {
return <DataTableHeader column={column}>Ephemeral</DataTableHeader>;
},
cell: ({ row }) => (
<SetupKeyEphemeralCell ephemeral={row.original.ephemeral} />
),
},
{
accessorKey: "expires",
header: ({ column }) => {
return <DataTableHeader column={column}>Expires</DataTableHeader>;
},
cell: ({ row }) => <ExpirationDateRow date={row.original.expires} />,
},
{
accessorKey: "id",
header: "",
cell: ({ row }) => {
return <SetupKeyActionCell setupKey={row.original} />;
},
},
];
type Props = {
setupKeys?: SetupKey[];
@@ -33,10 +122,6 @@ export default function SetupKeysTable({ setupKeys, isLoading }: Props) {
id: "valid",
desc: true,
},
{
id: "type",
desc: true,
},
{
id: "last_used",
desc: true,

View File

@@ -1,107 +0,0 @@
"use client";
import DataTableHeader from "@components/table/DataTableHeader";
import { ColumnDef } from "@tanstack/react-table";
import * as React from "react";
import { SetupKey } from "@/interfaces/SetupKey";
import ExpirationDateRow from "@/modules/common-table-rows/ExpirationDateRow";
import LastTimeRow from "@/modules/common-table-rows/LastTimeRow";
import SetupKeyActionCell from "@/modules/setup-keys/SetupKeyActionCell";
import SetupKeyGroupsCell from "@/modules/setup-keys/SetupKeyGroupsCell";
import SetupKeyKeyCell from "@/modules/setup-keys/SetupKeyKeyCell";
import SetupKeyNameCell from "@/modules/setup-keys/SetupKeyNameCell";
import SetupKeyTypeCell from "@/modules/setup-keys/SetupKeyTypeCell";
export const SetupKeysTableColumns: ColumnDef<SetupKey>[] = [
/* {
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
onCheckedChange={(value) => table.toggleAllRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},*/
{
accessorKey: "name",
header: ({ column }) => {
return <DataTableHeader column={column}>Name</DataTableHeader>;
},
sortingFn: "text",
cell: ({ row }) => (
<SetupKeyNameCell
valid={row.original.valid}
name={row.original?.name || ""}
/>
),
},
{
id: "valid",
accessorKey: "valid",
sortingFn: "basic",
},
{
accessorKey: "type",
header: ({ column }) => {
return <DataTableHeader column={column}>Reusable</DataTableHeader>;
},
cell: ({ row }) => (
<SetupKeyTypeCell reusable={row.original.type === "reusable"} />
),
},
{
accessorKey: "key",
header: ({ column }) => {
return <DataTableHeader column={column}>Key</DataTableHeader>;
},
cell: ({ row }) => <SetupKeyKeyCell text={row.original.key} />,
},
{
id: "group_strings",
accessorKey: "group_strings",
accessorFn: (s) => s.groups?.map((g) => g?.name || "").join(", "),
},
{
accessorKey: "last_used",
header: ({ column }) => {
return <DataTableHeader column={column}>Last used</DataTableHeader>;
},
sortingFn: "datetime",
cell: ({ row }) => (
<LastTimeRow date={row.original.last_used} text={"Last used on"} />
),
},
{
accessorFn: (item) => item.auto_groups?.length,
id: "groups",
header: ({ column }) => {
return <DataTableHeader column={column}>Groups</DataTableHeader>;
},
cell: ({ row }) => <SetupKeyGroupsCell setupKey={row.original} />,
},
{
accessorKey: "expires",
header: ({ column }) => {
return <DataTableHeader column={column}>Expires</DataTableHeader>;
},
cell: ({ row }) => <ExpirationDateRow date={row.original.expires} />,
},
{
accessorKey: "id",
header: "",
cell: ({ row }) => {
return <SetupKeyActionCell setupKey={row.original} />;
},
},
];

View File

@@ -27,9 +27,11 @@ const loadConfig = (): Config => {
let silentRedirectURI = "/#silent-callback";
let tokenSource = "accessToken";
if (process.env.NODE_ENV !== "production") {
if (process.env.APP_ENV === "test") {
configJson = require("@/config/test");
} else if (process.env.NODE_ENV === "development") {
configJson = require("@/config/local");
} else {
} else if (process.env.NODE_ENV === "production") {
configJson = require("@/config/production");
}

View File

@@ -34,6 +34,9 @@
"@/config/local": [
"./.local-config.json"
],
"@/config/test": [
"./.test-config.json"
],
"@components/*": [
"./src/components/*"
],