mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
525fb11b25 | ||
|
|
5a5ee14a0e | ||
|
|
994b526b9b | ||
|
|
b5922f4fb4 | ||
|
|
42bdaaf82d | ||
|
|
1d75c9a35c | ||
|
|
c7775ade8c | ||
|
|
cd3e75b640 |
No files matched your search
@@ -297,29 +297,12 @@ function PeerOverview() {
|
||||
/>
|
||||
</FullTooltip>
|
||||
|
||||
<div>
|
||||
<Label>Assigned Groups</Label>
|
||||
<HelpText>
|
||||
Use groups to control what this peer can access.
|
||||
</HelpText>
|
||||
<FullTooltip
|
||||
content={
|
||||
<div
|
||||
className={
|
||||
"flex gap-2 items-center !text-nb-gray-300 text-xs"
|
||||
}
|
||||
>
|
||||
<LockIcon size={14} />
|
||||
<span>
|
||||
{`You don't have the required permissions to update this
|
||||
setting.`}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
interactive={false}
|
||||
className={"w-full block"}
|
||||
disabled={!isUser}
|
||||
>
|
||||
{!isUser && (
|
||||
<div>
|
||||
<Label>Assigned Groups</Label>
|
||||
<HelpText>
|
||||
Use groups to control what this peer can access.
|
||||
</HelpText>
|
||||
<PeerGroupSelector
|
||||
disabled={isUser}
|
||||
onChange={setSelectedGroups}
|
||||
@@ -327,8 +310,8 @@ function PeerOverview() {
|
||||
hideAllGroup={true}
|
||||
peer={peer}
|
||||
/>
|
||||
</FullTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ function NetBirdIcon({ size = 16, className }: Props) {
|
||||
src={NetBirdLogo}
|
||||
alt={"Netbird Icon"}
|
||||
width={size}
|
||||
priority={true}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const DropdownInput = forwardRef<HTMLInputElement, Props>(
|
||||
"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",
|
||||
"bg-transparent text-sm outline-none focus-visible:outline-none ring-0 focus-visible:ring-0",
|
||||
"dark:placeholder:text-nb-gray-400 font-light placeholder:text-neutral-500 pl-10",
|
||||
"dark:placeholder:text-nb-gray-400 font-light placeholder:text-neutral-500 pl-10 select-none",
|
||||
)}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
|
||||
@@ -11,7 +11,7 @@ import useSortedDropdownOptions from "@hooks/useSortedDropdownOptions";
|
||||
import { IconArrowBack } from "@tabler/icons-react";
|
||||
import { cn } from "@utils/helpers";
|
||||
import { Command, CommandGroup, CommandInput, CommandList } from "cmdk";
|
||||
import { sortBy, trim, unionBy } from "lodash";
|
||||
import { sortBy, trim, unionBy, uniqBy } from "lodash";
|
||||
import {
|
||||
ChevronsUpDown,
|
||||
FolderGit2,
|
||||
@@ -55,8 +55,13 @@ export function PeerGroupSelector({
|
||||
disabledGroups,
|
||||
dataCy = "group-selector-dropdown",
|
||||
}: Readonly<MultiSelectProps>) {
|
||||
const { groups, dropdownOptions, setDropdownOptions, addDropdownOptions } =
|
||||
useGroups();
|
||||
const {
|
||||
groups,
|
||||
dropdownOptions,
|
||||
setDropdownOptions,
|
||||
addDropdownOptions,
|
||||
updateGroupDropdown,
|
||||
} = useGroups();
|
||||
const searchRef = React.useRef<HTMLInputElement>(null);
|
||||
const [inputRef, { width }] = useElementSize<HTMLButtonElement>();
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -93,10 +98,18 @@ export function PeerGroupSelector({
|
||||
const selectGroup = (name: string) => {
|
||||
const group = groups?.find((group) => group.name == name);
|
||||
const option = dropdownOptions.find((option) => option.name == name);
|
||||
const groupPeers: GroupPeer[] | undefined =
|
||||
(group?.peers as GroupPeer[]) || [];
|
||||
let groupPeers: GroupPeer[] = [...((group?.peers as GroupPeer[]) || [])];
|
||||
|
||||
if (peer) groupPeers?.push({ id: peer?.id as string, name: peer?.name });
|
||||
if (peer) {
|
||||
groupPeers.push({ id: peer.id as string, name: peer.name });
|
||||
groupPeers = uniqBy(groupPeers, "id");
|
||||
updateGroupDropdown(name, {
|
||||
...group,
|
||||
name: name,
|
||||
peers: groupPeers,
|
||||
peers_count: groupPeers.length,
|
||||
});
|
||||
}
|
||||
|
||||
if (!group && !option) {
|
||||
addDropdownOptions([{ name: name, peers: groupPeers }]);
|
||||
@@ -117,6 +130,18 @@ export function PeerGroupSelector({
|
||||
// Remove group from the groupOptions if it does not have an id
|
||||
const deselectGroup = (name: string) => {
|
||||
onChange((previous) => {
|
||||
if (peer) {
|
||||
const option = dropdownOptions?.find((group) => group.name === name);
|
||||
const groupPeers: GroupPeer[] = [
|
||||
...((option?.peers as GroupPeer[]) || []),
|
||||
].filter((p) => p?.id !== peer?.id);
|
||||
updateGroupDropdown(name, {
|
||||
...option,
|
||||
name,
|
||||
peers: groupPeers,
|
||||
peers_count: groupPeers.length,
|
||||
});
|
||||
}
|
||||
return previous.filter((group) => group.name != name);
|
||||
});
|
||||
};
|
||||
@@ -162,6 +187,7 @@ export function PeerGroupSelector({
|
||||
dropdownOptions,
|
||||
values,
|
||||
open,
|
||||
hideAllGroup,
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -276,7 +302,7 @@ export function PeerGroupSelector({
|
||||
"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",
|
||||
"bg-transparent text-sm outline-none focus-visible:outline-none ring-0 focus-visible:ring-0",
|
||||
"dark:placeholder:text-nb-gray-400 font-light placeholder:text-neutral-500 pl-10",
|
||||
"dark:placeholder:text-nb-gray-400 font-light placeholder:text-neutral-500 pl-10 select-none",
|
||||
)}
|
||||
ref={searchRef}
|
||||
value={search}
|
||||
@@ -384,7 +410,7 @@ export function PeerGroupSelector({
|
||||
|
||||
<div
|
||||
className={
|
||||
"text-neutral-500 dark:text-nb-gray-300 font-medium flex items-center gap-2"
|
||||
"text-neutral-500 dark:text-nb-gray-300 font-medium flex items-center gap-2 select-none"
|
||||
}
|
||||
>
|
||||
{peerIcon}
|
||||
|
||||
@@ -166,7 +166,7 @@ export function PeerSelector({
|
||||
placeholder={"Search for peers by name or ip..."}
|
||||
/>
|
||||
|
||||
{unfilteredItems.length == 0 && (
|
||||
{unfilteredItems.length == 0 && search == "" && (
|
||||
<DropdownInfoText>
|
||||
{
|
||||
"Seems like you don't have any linux peers to assign as a routing peer."
|
||||
@@ -174,7 +174,7 @@ export function PeerSelector({
|
||||
</DropdownInfoText>
|
||||
)}
|
||||
|
||||
{filteredItems.length == 0 && (
|
||||
{filteredItems.length == 0 && search != "" && (
|
||||
<DropdownInfoText>
|
||||
There are no peers matching your search.
|
||||
</DropdownInfoText>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import useFetchApi, { useApiCall } from "@utils/api";
|
||||
import { merge, sortBy, unionBy } from "lodash";
|
||||
import { usePathname } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useLoggedInUser } from "@/contexts/UsersProvider";
|
||||
import { Group } from "@/interfaces/Group";
|
||||
@@ -25,18 +24,29 @@ const GroupContext = React.createContext(
|
||||
);
|
||||
|
||||
export default function GroupsProvider({ children }: Props) {
|
||||
const path = usePathname();
|
||||
const { permission } = useLoggedInUser();
|
||||
const { permission, isUser } = useLoggedInUser();
|
||||
|
||||
return path === "/peers" && permission.dashboard_view == "blocked" ? (
|
||||
return permission.dashboard_view == "blocked" ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<GroupsProviderContent>{children}</GroupsProviderContent>
|
||||
<GroupsProviderContent isUser={isUser}>{children}</GroupsProviderContent>
|
||||
);
|
||||
}
|
||||
|
||||
export function GroupsProviderContent({ children }: Props) {
|
||||
const { data: groups, mutate, isLoading } = useFetchApi<Group[]>("/groups");
|
||||
type ProviderContentProps = {
|
||||
children: React.ReactNode;
|
||||
isUser: boolean;
|
||||
};
|
||||
|
||||
export function GroupsProviderContent({
|
||||
children,
|
||||
isUser,
|
||||
}: Readonly<ProviderContentProps>) {
|
||||
const {
|
||||
data: groups,
|
||||
mutate,
|
||||
isLoading,
|
||||
} = useFetchApi<Group[]>("/groups", false, true, !isUser);
|
||||
const groupRequest = useApiCall<Group>("/groups", true);
|
||||
const [dropdownOptions, setDropdownOptions] = useState<Group[]>([]);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ const useSortedDropdownOptions = (
|
||||
dropdownOptions: Group[],
|
||||
values: Group[],
|
||||
isPopupOpen: boolean,
|
||||
hideAllGroupOption = false,
|
||||
): Group[] => {
|
||||
const sortOrderRef = useRef<Map<string, number>>(new Map());
|
||||
const prevValuesRef = useRef<Group[]>([]);
|
||||
@@ -25,12 +26,15 @@ const useSortedDropdownOptions = (
|
||||
// Sort the dropdown options based on the current sort order
|
||||
return useMemo(() => {
|
||||
const sortOrder = sortOrderRef.current;
|
||||
return [...dropdownOptions].sort((a, b) => {
|
||||
const indexA = sortOrder.get(a.name) ?? Infinity;
|
||||
const indexB = sortOrder.get(b.name) ?? Infinity;
|
||||
return indexA - indexB;
|
||||
});
|
||||
}, [dropdownOptions, sortOrderRef.current]);
|
||||
return [...dropdownOptions]
|
||||
.sort((a, b) => {
|
||||
const indexA = sortOrder.get(a.name) ?? Infinity;
|
||||
const indexB = sortOrder.get(b.name) ?? Infinity;
|
||||
if (a.name === "All") return -1; // Move "All" to the top
|
||||
return indexA - indexB;
|
||||
})
|
||||
.filter((group) => !hideAllGroupOption || group.name !== "All");
|
||||
}, [dropdownOptions, sortOrderRef.current, hideAllGroupOption]);
|
||||
};
|
||||
|
||||
export default useSortedDropdownOptions;
|
||||
@@ -26,12 +26,14 @@ export default function NavbarWithDropdown() {
|
||||
src={NetBirdLogoFull}
|
||||
height={22}
|
||||
alt={"NetBird Logo"}
|
||||
priority={true}
|
||||
className={"hidden md:block"}
|
||||
/>
|
||||
<Image
|
||||
src={NetBirdLogo}
|
||||
width={30}
|
||||
alt={"NetBird Logo"}
|
||||
priority={true}
|
||||
className={"md:hidden"}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -47,6 +47,14 @@ export default function ActivityDescription({ event }: Props) {
|
||||
</div>
|
||||
);
|
||||
|
||||
if (event.activity_code == "setupkey.delete")
|
||||
return (
|
||||
<div className={"inline"}>
|
||||
Setup-Key <Value> {m.name}</Value> with key <Value>{m.key}</Value> was
|
||||
deleted
|
||||
</div>
|
||||
);
|
||||
|
||||
if (event.activity_code == "setupkey.add")
|
||||
return (
|
||||
<div className={"inline"}>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
export default function EmptyRow() {
|
||||
return <div className={"text-nb-gray-600"}>-</div>;
|
||||
import { cn } from "@utils/helpers";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function EmptyRow({ className }: Readonly<Props>) {
|
||||
return <div className={cn("text-nb-gray-600", className)}>-</div>;
|
||||
}
|
||||
@@ -246,6 +246,7 @@ export default function PeersTable({ peers, isLoading, headingTarget }: Props) {
|
||||
user_name: false,
|
||||
user_email: false,
|
||||
actions: !isUser,
|
||||
groups: !isUser,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
getStartedCard={
|
||||
@@ -423,30 +424,32 @@ export default function PeersTable({ peers, isLoading, headingTarget }: Props) {
|
||||
|
||||
<DataTableRowsPerPage table={table} disabled={peers?.length == 0} />
|
||||
|
||||
<GroupSelector
|
||||
disabled={peers?.length == 0}
|
||||
values={
|
||||
(table
|
||||
.getColumn("group_names")
|
||||
?.getFilterValue() as string[]) || []
|
||||
}
|
||||
onChange={(groups) => {
|
||||
table.setPageIndex(0);
|
||||
if (groups.length == 0) {
|
||||
table.getColumn("group_names")?.setFilterValue(undefined);
|
||||
return;
|
||||
} else {
|
||||
table.getColumn("group_names")?.setFilterValue(groups);
|
||||
{!isUser && (
|
||||
<GroupSelector
|
||||
disabled={peers?.length == 0}
|
||||
values={
|
||||
(table
|
||||
.getColumn("group_names")
|
||||
?.getFilterValue() as string[]) || []
|
||||
}
|
||||
resetSelectedRows();
|
||||
}}
|
||||
groups={tableGroups}
|
||||
/>
|
||||
onChange={(groups) => {
|
||||
table.setPageIndex(0);
|
||||
if (groups.length == 0) {
|
||||
table.getColumn("group_names")?.setFilterValue(undefined);
|
||||
return;
|
||||
} else {
|
||||
table.getColumn("group_names")?.setFilterValue(groups);
|
||||
}
|
||||
resetSelectedRows();
|
||||
}}
|
||||
groups={tableGroups}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DataTableRefreshButton
|
||||
isDisabled={peers?.length == 0}
|
||||
onClick={() => {
|
||||
mutate("/groups").then();
|
||||
if (!isUser) mutate("/groups").then();
|
||||
mutate("/users").then();
|
||||
mutate("/peers").then();
|
||||
}}
|
||||
|
||||
@@ -68,7 +68,6 @@ export default function RouteModal({ children, open, setOpen }: Props) {
|
||||
const [newPolicy, setNewPolicy] = useState<Policy>();
|
||||
|
||||
const handleCreatePolicyPrompt = async (r: Route) => {
|
||||
console.log(r);
|
||||
if (!r?.access_control_groups) return;
|
||||
|
||||
const choice = await confirm({
|
||||
@@ -101,7 +100,6 @@ export default function RouteModal({ children, open, setOpen }: Props) {
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log(newPolicy);
|
||||
setNewPolicy(newPolicy);
|
||||
setRoutePolicyModal(true);
|
||||
};
|
||||
|
||||
@@ -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<Props>) {
|
||||
const { confirm } = useDialog();
|
||||
const deleteRequest = useApiCall<SetupKey>("/setup-keys/" + setupKey.id);
|
||||
const request = useApiCall<SetupKey>("/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) {
|
||||
<Button
|
||||
variant={"danger-outline"}
|
||||
size={"sm"}
|
||||
onClick={handleConfirm}
|
||||
onClick={handleRevoke}
|
||||
disabled={setupKey.revoked || !setupKey.valid}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
<Undo2Icon size={16} />
|
||||
Revoke
|
||||
</Button>
|
||||
<Button variant={"danger-outline"} size={"sm"} onClick={handleDelete}>
|
||||
<Trash2 size={16} />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<Props>) {
|
||||
const [modal, setModal] = useState(false);
|
||||
const request = useApiCall<SetupKey>("/setup-keys/" + setupKey.id);
|
||||
const { mutate } = useSWRConfig();
|
||||
|
||||
@@ -5,7 +5,7 @@ type Props = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
export default function SetupKeyKeyCell({ text }: Props) {
|
||||
export default function SetupKeyKeyCell({ text }: Readonly<Props>) {
|
||||
return (
|
||||
<div className={"flex"}>
|
||||
<Badge variant={"gray"} className={"text-xs font-mono"}>
|
||||
|
||||
@@ -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<Props>) {
|
||||
const [successModal, setSuccessModal] = useState(false);
|
||||
const [setupKey, setSetupKey] = useState<SetupKey>();
|
||||
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<ModalProps>) {
|
||||
const setupKeyRequest = useApiCall<SetupKey>("/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) {
|
||||
<div className={"flex justify-between"}>
|
||||
<div>
|
||||
<Label>Expires in</Label>
|
||||
<HelpText>Should be between 1 and 365 days.</HelpText>
|
||||
<HelpText>
|
||||
Days until the key expires. <br />
|
||||
Leave empty for no expiration.
|
||||
</HelpText>
|
||||
</div>
|
||||
<Input
|
||||
maxWidthClass={"max-w-[200px]"}
|
||||
placeholder={"7"}
|
||||
maxWidthClass={"max-w-[202px]"}
|
||||
placeholder={"Unlimited"}
|
||||
min={1}
|
||||
max={365}
|
||||
value={expiresIn}
|
||||
error={expiresInError}
|
||||
errorTooltip={true}
|
||||
type={"number"}
|
||||
data-cy={"setup-key-expire-in-days"}
|
||||
|
||||
@@ -5,7 +5,11 @@ type Props = {
|
||||
valid: boolean;
|
||||
secret?: string;
|
||||
};
|
||||
export default function SetupKeyNameCell({ name, valid, secret }: Props) {
|
||||
export default function SetupKeyNameCell({
|
||||
name,
|
||||
valid,
|
||||
secret,
|
||||
}: Readonly<Props>) {
|
||||
return (
|
||||
<ActiveInactiveRow
|
||||
active={valid || false}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Repeat1 } from "lucide-react";
|
||||
type Props = {
|
||||
reusable: boolean;
|
||||
};
|
||||
export default function SetupKeyTypeCell({ reusable }: Props) {
|
||||
export default function SetupKeyTypeCell({ reusable }: Readonly<Props>) {
|
||||
return (
|
||||
<div className={"flex"}>
|
||||
<Badge className={"text-xs"} variant={"gray"}>
|
||||
|
||||
@@ -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<SetupKey>[] = [
|
||||
header: ({ column }) => {
|
||||
return <DataTableHeader column={column}>Expires</DataTableHeader>;
|
||||
},
|
||||
cell: ({ row }) => <ExpirationDateRow date={row.original.expires} />,
|
||||
cell: ({ row }) => {
|
||||
let expires = dayjs(row.original.expires);
|
||||
let isNeverExpiring = expires?.year() == 1 || false;
|
||||
return !isNeverExpiring ? (
|
||||
<ExpirationDateRow date={row.original.expires} />
|
||||
) : (
|
||||
<EmptyRow className={"px-3"} />
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
@@ -116,7 +126,7 @@ export default function SetupKeysTable({
|
||||
setupKeys,
|
||||
isLoading,
|
||||
headingTarget,
|
||||
}: Props) {
|
||||
}: Readonly<Props>) {
|
||||
const { mutate } = useSWRConfig();
|
||||
const path = usePathname();
|
||||
|
||||
@@ -216,6 +226,20 @@ export default function SetupKeysTable({
|
||||
{(table) => (
|
||||
<>
|
||||
<ButtonGroup disabled={setupKeys?.length == 0}>
|
||||
<ButtonGroup.Button
|
||||
onClick={() => {
|
||||
table.setPageIndex(0);
|
||||
table.getColumn("valid")?.setFilterValue(undefined);
|
||||
}}
|
||||
disabled={setupKeys?.length == 0}
|
||||
variant={
|
||||
table.getColumn("valid")?.getFilterValue() == undefined
|
||||
? "tertiary"
|
||||
: "secondary"
|
||||
}
|
||||
>
|
||||
All
|
||||
</ButtonGroup.Button>
|
||||
<ButtonGroup.Button
|
||||
onClick={() => {
|
||||
table.setPageIndex(0);
|
||||
@@ -233,16 +257,16 @@ export default function SetupKeysTable({
|
||||
<ButtonGroup.Button
|
||||
onClick={() => {
|
||||
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
|
||||
</ButtonGroup.Button>
|
||||
</ButtonGroup>
|
||||
<DataTableRowsPerPage
|
||||
|
||||
Reference in new issue
Block a user