diff --git a/src/components/SetupKeyEdit.tsx b/src/components/SetupKeyEdit.tsx new file mode 100644 index 0000000..900c0af --- /dev/null +++ b/src/components/SetupKeyEdit.tsx @@ -0,0 +1,505 @@ +import React, { useEffect, useRef, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { actions as setupKeyActions } from "../store/setup-key"; +import { + Button, + Col, + Divider, + Form, + Input, + InputNumber, + Row, + Select, + Breadcrumb, + Switch, + Tag, + Typography, + Card, +} from "antd"; +import { RootState } from "typesafe-actions"; +import { + FormSetupKey, + SetupKey, + SetupKeyToSave, +} from "../store/setup-key/types"; +import { formatDate, timeAgo } from "../utils/common"; +import { RuleObject } from "antd/lib/form"; +import { CustomTagProps } from "rc-select/lib/BaseSelect"; +import { Group } from "../store/group/types"; +import { useGetTokenSilently } from "../utils/token"; +import { expiresInToSeconds, ExpiresInValue } from "../views/ExpiresInInput"; +import moment from "moment"; +import { Container } from "./Container"; +import Paragraph from "antd/es/typography/Paragraph"; +import { EditOutlined, LockOutlined } from "@ant-design/icons"; +import { actions as personalAccessTokenActions } from "../store/personal-access-token"; + +const { Option } = Select; +const { Text } = Typography; +const ExpiresInDefault: ExpiresInValue = { number: 30, interval: "day" }; + +const customExpiresFormat = (value: Date): string | null => { + return formatDate(value); +}; + +const SetupKeyNew = () => { + const { getTokenSilently } = useGetTokenSilently(); + const dispatch = useDispatch(); + + const setupKey = useSelector((state: RootState) => state.setupKey.setupKey); + const savedSetupKey = useSelector( + (state: RootState) => state.setupKey.savedSetupKey + ); + const groups = useSelector((state: RootState) => state.group.data); + + const [form] = Form.useForm(); + const [editName, setEditName] = useState(false); + const [tagGroups, setTagGroups] = useState([] as string[]); + const [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey); + const inputNameRef = useRef(null); + + useEffect(() => { + //Unmounting component clean + return () => { + setVisibleNewSetupKey(false); + }; + }, []); + + useEffect(() => { + if (!editName) return; + + inputNameRef.current!.focus({ cursor: "end" }); + }, [editName]); + + useEffect(() => { + setTagGroups( + groups?.filter((g) => g.name !== "All").map((g) => g.name) || [] + ); + }, [groups]); + + useEffect(() => { + if (!setupKey) return; + + const allGroups = new Map(); + let formKeyGroups: string[] = []; + groups.forEach((g) => allGroups.set(g.id!, g)); + + if (setupKey.auto_groups) { + formKeyGroups = setupKey.auto_groups + .filter((g) => allGroups.get(g)) + .map((g) => allGroups.get(g)!.name); + } + + const fSetupKey = { + ...setupKey, + autoGroupNames: setupKey.auto_groups ? formKeyGroups : [], + expiresInFormatted: ExpiresInDefault, + exp: moment(setupKey.expires), + last: moment(setupKey.last_used), + } as FormSetupKey; + + form.setFieldsValue(fSetupKey); + setFormSetupKey(fSetupKey); + }, [setupKey]); + + const createSetupKeyToSave = (): SetupKeyToSave => { + const autoGroups = + groups + ?.filter((g) => formSetupKey.autoGroupNames.includes(g.name)) + .map((g) => g.id || "") || []; + // find groups that do not yet exist (newly added by the user) + const allGroupsNames: string[] = groups?.map((g) => g.name); + const groupsToCreate = formSetupKey.autoGroupNames.filter( + (s) => !allGroupsNames.includes(s) + ); + + const expiresIn = expiresInToSeconds(formSetupKey.expiresInFormatted); + return { + id: formSetupKey.id, + name: formSetupKey.name, + type: formSetupKey.type, + auto_groups: autoGroups, + revoked: formSetupKey.revoked, + groupsToCreate: groupsToCreate, + expires_in: expiresIn, + usage_limit: formSetupKey.usage_limit, + } as SetupKeyToSave; + }; + + const handleFormSubmit = async () => { + try { + await form.validateFields(); + } catch (e) { + const errorFields = (e as any).errorFields; + return console.log("errorInfo", errorFields); + } + + const setupKeyToSave = createSetupKeyToSave(); + dispatch( + setupKeyActions.saveSetupKey.request({ + getAccessTokenSilently: getTokenSilently, + payload: setupKeyToSave, + }) + ); + }; + + const setVisibleNewSetupKey = (status: boolean) => { + form.resetFields(); + dispatch(setupKeyActions.setSetupEditKeyVisible(status)); + }; + + const onCancel = () => { + if (savedSetupKey.loading) return; + + dispatch( + setupKeyActions.setSetupKey({ + name: "", + type: "one-off", + key: "", + last_used: "", + expires: "", + state: "valid", + auto_groups: [] as string[], + usage_limit: 0, + used_times: 0, + expires_in: 0, + } as SetupKey) + ); + setFormSetupKey({} as FormSetupKey); + setVisibleNewSetupKey(false); + }; + + const onChange = (data: any) => { + setFormSetupKey({ ...formSetupKey, ...data }); + }; + + const toggleEditName = (status: boolean) => { + setEditName(status); + }; + + const selectValidator = (_: RuleObject, value: string[]) => { + let hasSpaceNamed = []; + + value.forEach(function (v: string) { + if (!v.trim().length) { + hasSpaceNamed.push(v); + } + }); + + if (hasSpaceNamed.length) { + return Promise.reject( + new Error("Group names with just spaces are not allowed") + ); + } + + return Promise.resolve(); + }; + + const tagRender = (props: CustomTagProps) => { + const { value, closable, onClose } = props; + const onPreventMouseDown = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + + return ( + + {value} + + ); + }; + + const optionRender = (label: string) => { + let peersCount = ""; + const g = groups.find((_g) => _g.name === label); + + if (g) { + peersCount = ` - ${g.peers_count || 0} ${ + !g.peers_count || parseInt(g.peers_count) !== 1 ? "peers" : "peer" + } `; + } + + return ( + <> + + {label} + + {peersCount} + + ); + }; + + const dropDownRender = (menu: React.ReactElement) => ( + <> + {menu} + + + + + Add new group by pressing "Enter" + + + + + + + + + + ); + + const changesDetected = (): boolean => { + return ( + formSetupKey.name == null || + formSetupKey.name !== setupKey.name || + groupsChanged() || + formSetupKey.usage_limit !== setupKey.usage_limit + ); + }; + + const groupsChanged = (): boolean => { + if ( + setupKey && + setupKey.auto_groups && + formSetupKey.autoGroupNames.length !== setupKey.auto_groups.length + ) { + return true; + } + const formGroupIds = + groups + ?.filter((g) => formSetupKey.autoGroupNames.includes(g.name)) + .map((g) => g.id || "") || []; + + return ( + setupKey.auto_groups?.filter((g) => !formGroupIds.includes(g)).length > 0 + ); + }; + + const getFormKey = (key: string) => { + if (key) return key.split("-")[0].concat("*****"); + }; + + const onBreadcrumbUsersClick = () => { + if (savedSetupKey.loading) return; + // dispatch(userActions.setUser(null as unknown as User)); + dispatch(personalAccessTokenActions.resetPersonalAccessTokens(null)); + setVisibleNewSetupKey(false); + }; + + return ( + <> + Setup Keys, + }, + { + title: setupKey.name, + }, + ]} + /> + +
+
+ + + + Key + + {formSetupKey.state} + + + } + /> + + + + + + {formSetupKey.type === "one-off" ? "One-off" : "Reusable"}, + available uses + + + } + style={{ marginTop: "8px" }} + /> + + + + + + + + Auto-assigned groups + + + + + + + + + + + + Expires + + + } + value={customExpiresFormat(new Date(formSetupKey.expires))!} + /> + + + + + + + Learn more about + + {" "} + setup keys + + + +
+
+ + + + +
+ + ); +}; + +export default SetupKeyNew; diff --git a/src/components/SetupKeyNew.tsx b/src/components/SetupKeyNew.tsx index 9c7db32..c33128b 100644 --- a/src/components/SetupKeyNew.tsx +++ b/src/components/SetupKeyNew.tsx @@ -1,571 +1,552 @@ -import React, {useEffect, useRef, useState} from "react"; -import {useDispatch, useSelector} from "react-redux"; -import {actions as setupKeyActions} from "../store/setup-key"; -import {Button, Col, Divider, Form, Input, InputNumber, Modal, Row, Select, Space, Switch, Tag, Typography} from "antd"; -import {RootState} from "typesafe-actions"; -import {FormSetupKey, SetupKey, SetupKeyToSave} from "../store/setup-key/types"; -import {formatDate, timeAgo} from "../utils/common"; -import {RuleObject} from "antd/lib/form"; -import {CustomTagProps} from "rc-select/lib/BaseSelect"; -import {Group} from "../store/group/types"; -import {useGetTokenSilently} from "../utils/token"; -import {expiresInToSeconds, ExpiresInValue} from "../views/ExpiresInInput"; +import React, { useEffect, useRef, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { actions as setupKeyActions } from "../store/setup-key"; +import { + Button, + Col, + Divider, + Form, + Input, + InputNumber, + Modal, + Row, + Select, + Space, + Switch, + Tag, + Typography, +} from "antd"; +import { RootState } from "typesafe-actions"; +import { + FormSetupKey, + SetupKey, + SetupKeyToSave, +} from "../store/setup-key/types"; +import { RuleObject } from "antd/lib/form"; +import { CustomTagProps } from "rc-select/lib/BaseSelect"; +import { Group } from "../store/group/types"; +import { useGetTokenSilently } from "../utils/token"; +import { expiresInToSeconds, ExpiresInValue } from "../views/ExpiresInInput"; import moment from "moment"; -import {Container} from "./Container"; +import { Container } from "./Container"; import Paragraph from "antd/es/typography/Paragraph"; -import {EditOutlined, LockOutlined} from "@ant-design/icons"; -const {Option} = Select; -const {Text} = Typography; -const ExpiresInDefault: ExpiresInValue = {number: 30, interval: "day"}; - -const customExpiresFormat = (value: Date): string | null => { - return formatDate(value); -}; - -const customLastUsedFormat = (value: Date): string | null => { - if (value.getFullYear() === 1) { - // 1st of Jan 0001 - return "never"; - } - - let ago = timeAgo(value.toString()); - if (!ago) return "unused"; - - return ago; -}; +const { Option } = Select; +const { Text } = Typography; +const ExpiresInDefault: ExpiresInValue = { number: 30, interval: "day" }; const SetupKeyNew = () => { - const {getTokenSilently} = useGetTokenSilently(); - const dispatch = useDispatch(); - const setupNewKeyVisible = useSelector((state: RootState) => state.setupKey.setupNewKeyVisible); - const setupKey = useSelector((state: RootState) => state.setupKey.setupKey); - const savedSetupKey = useSelector((state: RootState) => state.setupKey.savedSetupKey); - const groups = useSelector((state: RootState) => state.group.data); + const { getTokenSilently } = useGetTokenSilently(); + const dispatch = useDispatch(); + const setupNewKeyVisible = useSelector( + (state: RootState) => state.setupKey.setupNewKeyVisible + ); + const setupKey = useSelector((state: RootState) => state.setupKey.setupKey); + const savedSetupKey = useSelector( + (state: RootState) => state.setupKey.savedSetupKey + ); + const groups = useSelector((state: RootState) => state.group.data); - const [form] = Form.useForm(); - const [editName, setEditName] = useState(false); - const [tagGroups, setTagGroups] = useState([] as string[]); - const [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey); - const inputNameRef = useRef(null); - const isEditMode: boolean = !!formSetupKey.id; + const [form] = Form.useForm(); + const [editName, setEditName] = useState(false); + const [tagGroups, setTagGroups] = useState([] as string[]); + const [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey); + const inputNameRef = useRef(null); - useEffect(() => { - if (!editName) return; + useEffect(() => { + if (!editName) return; - inputNameRef.current!.focus({cursor: "end"}); - }, [editName]); + inputNameRef.current!.focus({ cursor: "end" }); + }, [editName]); - useEffect(() => { - setTagGroups(groups?.filter((g) => g.name !== "All").map((g) => g.name) || []); - }, [groups]); + useEffect(() => { + setTagGroups( + groups?.filter((g) => g.name !== "All").map((g) => g.name) || [] + ); + }, [groups]); - useEffect(() => { - if (!setupKey) return; + useEffect(() => { + if (!setupKey) return; - const allGroups = new Map(); - let formKeyGroups: string[] = []; - groups.forEach((g) => allGroups.set(g.id!, g)); + const allGroups = new Map(); + let formKeyGroups: string[] = []; + groups.forEach((g) => allGroups.set(g.id!, g)); - if (setupKey.auto_groups) { - formKeyGroups = setupKey.auto_groups.filter((g) => allGroups.get(g)).map((g) => allGroups.get(g)!.name); - } + if (setupKey.auto_groups) { + formKeyGroups = setupKey.auto_groups + .filter((g) => allGroups.get(g)) + .map((g) => allGroups.get(g)!.name); + } - const fSetupKey = { - ...setupKey, - autoGroupNames: setupKey.auto_groups ? formKeyGroups : [], - expiresInFormatted: ExpiresInDefault, - exp: moment(setupKey.expires), - last: moment(setupKey.last_used), - } as FormSetupKey; + const fSetupKey = { + ...setupKey, + autoGroupNames: setupKey.auto_groups ? formKeyGroups : [], + expiresInFormatted: ExpiresInDefault, + exp: moment(setupKey.expires), + last: moment(setupKey.last_used), + } as FormSetupKey; - form.setFieldsValue(fSetupKey); - setFormSetupKey(fSetupKey); - }, [setupKey]); + form.setFieldsValue(fSetupKey); + setFormSetupKey(fSetupKey); + }, [setupKey]); - const createSetupKeyToSave = (): SetupKeyToSave => { - const autoGroups = - groups?.filter((g) => formSetupKey.autoGroupNames.includes(g.name)).map((g) => g.id || "") || []; - // find groups that do not yet exist (newly added by the user) - const allGroupsNames: string[] = groups?.map((g) => g.name); - const groupsToCreate = formSetupKey.autoGroupNames.filter((s) => !allGroupsNames.includes(s)); - - const expiresIn = expiresInToSeconds(formSetupKey.expiresInFormatted); - return { - id: formSetupKey.id, - name: formSetupKey.name, - type: formSetupKey.type, - auto_groups: autoGroups, - revoked: formSetupKey.revoked, - groupsToCreate: groupsToCreate, - expires_in: expiresIn, - usage_limit: formSetupKey.usage_limit, - } as SetupKeyToSave; - }; - - const handleFormSubmit = async () => { - try { - await form.validateFields(); - } catch (e) { - const errorFields = (e as any).errorFields; - return console.log("errorInfo", errorFields); - } - - const setupKeyToSave = createSetupKeyToSave(); - dispatch( - setupKeyActions.saveSetupKey.request({ - getAccessTokenSilently: getTokenSilently, - payload: setupKeyToSave, - }) - ); - }; - - const setVisibleNewSetupKey = (status: boolean) => { - form.resetFields(); - dispatch(setupKeyActions.setSetupNewKeyVisible(status)); - }; - - const onCancel = () => { - if (savedSetupKey.loading) return; - - dispatch( - setupKeyActions.setSetupKey({ - name: "", - type: "one-off", - key: "", - last_used: "", - expires: "", - state: "valid", - auto_groups: [] as string[], - usage_limit: 0, - used_times: 0, - expires_in: 0, - } as SetupKey) - ); - setFormSetupKey({} as FormSetupKey); - setVisibleNewSetupKey(false); - }; - - const onChange = (data: any) => { - setFormSetupKey({...formSetupKey, ...data}); - }; - - const toggleEditName = (status: boolean) => { - setEditName(status); - }; - - const selectValidator = (_: RuleObject, value: string[]) => { - let hasSpaceNamed = []; - - value.forEach(function (v: string) { - if (!v.trim().length) { - hasSpaceNamed.push(v); - } - }); - - if (hasSpaceNamed.length) { - return Promise.reject(new Error("Group names with just spaces are not allowed")); - } - - return Promise.resolve(); - }; - - const tagRender = (props: CustomTagProps) => { - const {value, closable, onClose} = props; - const onPreventMouseDown = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - - return ( - - {value} - - ); - }; - - const optionRender = (label: string) => { - let peersCount = ""; - const g = groups.find((_g) => _g.name === label); - - if (g) { - peersCount = ` - ${g.peers_count || 0} ${ - !g.peers_count || parseInt(g.peers_count) !== 1 ? "peers" : "peer" - } `; - } - - return ( - <> - - {label} - - {peersCount} - - ); - }; - - const dropDownRender = (menu: React.ReactElement) => ( - <> - {menu} - - - - Add new group by pressing "Enter" - - - - - - - - + const createSetupKeyToSave = (): SetupKeyToSave => { + const autoGroups = + groups + ?.filter((g) => formSetupKey.autoGroupNames.includes(g.name)) + .map((g) => g.id || "") || []; + // find groups that do not yet exist (newly added by the user) + const allGroupsNames: string[] = groups?.map((g) => g.name); + const groupsToCreate = formSetupKey.autoGroupNames.filter( + (s) => !allGroupsNames.includes(s) ); - const changesDetected = (): boolean => { - return ( - formSetupKey.name == null || - formSetupKey.name !== setupKey.name || - groupsChanged() || - formSetupKey.usage_limit !== setupKey.usage_limit - ); - }; + const expiresIn = expiresInToSeconds(formSetupKey.expiresInFormatted); + return { + id: formSetupKey.id, + name: formSetupKey.name, + type: formSetupKey.type, + auto_groups: autoGroups, + revoked: formSetupKey.revoked, + groupsToCreate: groupsToCreate, + expires_in: expiresIn, + usage_limit: formSetupKey.usage_limit, + } as SetupKeyToSave; + }; - const groupsChanged = (): boolean => { - if (setupKey && setupKey.auto_groups && formSetupKey.autoGroupNames.length !== setupKey.auto_groups.length) { - return true; - } - const formGroupIds = - groups?.filter((g) => formSetupKey.autoGroupNames.includes(g.name)).map((g) => g.id || "") || []; + const handleFormSubmit = async () => { + try { + await form.validateFields(); + } catch (e) { + const errorFields = (e as any).errorFields; + return console.log("errorInfo", errorFields); + } - return setupKey.auto_groups?.filter((g) => !formGroupIds.includes(g)).length > 0; + let setupKeyToSave = createSetupKeyToSave(); + if (setupKeyToSave.type === "reusable") { + const updateUsageLimit = + setupKeyToSave.usage_limit === "unlimited" || + setupKeyToSave.usage_limit === undefined || + setupKeyToSave.usage_limit === " " + ? 0 + : Number(setupKeyToSave.usage_limit); + + setupKeyToSave = { + ...setupKeyToSave, + usage_limit: updateUsageLimit, + }; + } else { + setupKeyToSave = { + ...setupKeyToSave, + usage_limit: 1, + }; + } + + dispatch( + setupKeyActions.saveSetupKey.request({ + getAccessTokenSilently: getTokenSilently, + payload: setupKeyToSave, + }) + ); + }; + + const setVisibleNewSetupKey = (status: boolean) => { + form.resetFields(); + dispatch(setupKeyActions.setSetupNewKeyVisible(status)); + }; + + const onCancel = () => { + if (savedSetupKey.loading) return; + + dispatch( + setupKeyActions.setSetupKey({ + name: "", + type: "one-off", + key: "", + last_used: "", + expires: "", + state: "valid", + auto_groups: [] as string[], + usage_limit: 0, + used_times: 0, + expires_in: 0, + } as SetupKey) + ); + setFormSetupKey({} as FormSetupKey); + setVisibleNewSetupKey(false); + }; + + const onChange = (data: any) => { + if (data.reusable) { + form.setFieldValue("usage_limit", "unlimited"); + } else { + form.setFieldValue("usage_limit", "1"); + } + setFormSetupKey({ ...formSetupKey, ...data }); + }; + + const selectValidator = (_: RuleObject, value: string[]) => { + let hasSpaceNamed = []; + + value.forEach(function (v: string) { + if (!v.trim().length) { + hasSpaceNamed.push(v); + } + }); + + if (hasSpaceNamed.length) { + return Promise.reject( + new Error("Group names with just spaces are not allowed") + ); + } + + return Promise.resolve(); + }; + + const tagRender = (props: CustomTagProps) => { + const { value, closable, onClose } = props; + const onPreventMouseDown = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); }; return ( - - - - , - ]} + + {value} + + ); + }; + + const optionRender = (label: string) => { + let peersCount = ""; + const g = groups.find((_g) => _g.name === label); + + if (g) { + peersCount = ` - ${g.peers_count || 0} ${ + !g.peers_count || parseInt(g.peers_count) !== 1 ? "peers" : "peer" + } `; + } + + return ( + <> + + {label} + + {peersCount} + + ); + }; + + const dropDownRender = (menu: React.ReactElement) => ( + <> + {menu} + + + + + Add new group by pressing "Enter" + + + + + + + + + + ); + + const changesDetected = (): boolean => { + return ( + formSetupKey.name == null || + formSetupKey.name !== setupKey.name || + groupsChanged() || + formSetupKey.usage_limit !== setupKey.usage_limit + ); + }; + + const groupsChanged = (): boolean => { + if ( + setupKey && + setupKey.auto_groups && + formSetupKey.autoGroupNames.length !== setupKey.auto_groups.length + ) { + return true; + } + const formGroupIds = + groups + ?.filter((g) => formSetupKey.autoGroupNames.includes(g.name)) + .map((g) => g.id || "") || []; + + return ( + setupKey.auto_groups?.filter((g) => !formGroupIds.includes(g)).length > 0 + ); + }; + + return ( + - - - {isEditMode ? "Setup key overview" : "Create setup key"} - - - {"Use this key to register new machines in your network"} - -
Cancel + + , + ]} + > + + + Create setup key + + + {"Use this key to register new machines in your network"} + + + + + + Name + + + Set an easily identifiable name for your key + + + + + + + + + + + + + + Reusable + + + Use this type to enroll multiple peers + + + + + + { + setFormSetupKey({ + ...formSetupKey, + type: checked ? "reusable" : "one-off", + }); }} + /> + + + + + + + + + Usage limit + + + + + + + + + For example, set to 30 if you want to enroll 30 peers + + + + + + + + Expires in + + + + + + + + Should be between 1 and 180 days + + + + + + + + Auto-assigned groups + + + These groups will be automatically assigned to peers enrolled + with this key + + + + + toggleEditName(true)}/>} - onPressEnter={() => toggleEditName(false)} - onBlur={() => toggleEditName(false)} - /> - ) : ( - - )} - - - - - {isEditMode && ( - - - - Key - - {formSetupKey.state} - - - } - /> - - - )} - {!isEditMode && ( - - - - Reusable - - - Use this type to enroll multiple peers - - - - - - { - setFormSetupKey({ - ...formSetupKey, - type: checked ? "reusable" : "one-off", - }); - }} - /> - - - - )} - - {isEditMode && ( - - - {formSetupKey.type === "one-off" ? "One-off" : "Reusable"} - - key - )} - - - - - {isEditMode ? "Available uses" : "Usage limit"} - - - - - {isEditMode && ( - - } - style={{width: "104px", marginTop: "5px"}} - /> - - )} - - - {!isEditMode && ( - - - - - - For example, set to 30 if you want to enroll 30 peers - - - - )} - - - {!isEditMode && ( - - - - Expires in - - - - - - - - Should be between 1 and 180 days - - - )} - - {isEditMode && ( - - - - - - - Expires - - - } - value={customExpiresFormat(new Date(formSetupKey.expires))!} - /> - - - - - Last used - - - } - value={customLastUsedFormat(new Date(formSetupKey.last_used))!} - /> - - - - - - )} - - - - - Auto-assigned groups - - {isEditMode ? ( - <> - ) : ( - - These groups will be automatically assigned to peers enrolled with this key - - )} - - - - - - - - - - Learn more about - - {" "} - setup keys - - - - -
-
- ) - ; + {tagGroups.map((m) => ( + + ))} + + + + + + + Learn more about + + {" "} + setup keys + + + + + +
+ ); }; -export default SetupKeyNew; \ No newline at end of file +export default SetupKeyNew; diff --git a/src/components/UserEdit.tsx b/src/components/UserEdit.tsx index b19818e..b77c3ea 100644 --- a/src/components/UserEdit.tsx +++ b/src/components/UserEdit.tsx @@ -297,238 +297,395 @@ const UserEdit = () => { } return ( - <> -
- onBreadcrumbUsersClick("Users")}>All Users, - }, - { - title: onBreadcrumbUsersClick(tab)}>{tab}, - // menu: { items: menuItems }, - }, - { - title: user.name, - }, - ]} - /> + <> +
+ onBreadcrumbUsersClick("Users")}> + All Users + + ), + }, + { + title: onBreadcrumbUsersClick(tab)}>{tab}, + // menu: { items: menuItems }, + }, + { + title: user.name, + }, + ]} + /> - -
-
+
+ + + {!user.is_service_user && ( + + Email} + style={{ marginRight: "70px", fontWeight: "bold" }} + > + + + + )} + + Role} + style={{ marginRight: "50px", fontWeight: "bold" }} + > + + + + + + {!user.is_service_user && ( + + + Auto-assigned groups} + tooltip="Every peer enrolled with this user will be automatically added to these groups" + rules={[{ validator: selectValidator }]} + style={{ marginRight: "70px", fontWeight: "bold" }} + > + - - } - - Role} - style={{marginRight: "50px"}} - > - - - - + {tagGroups.map((m) => ( + + ))} + + + - {!user.is_service_user && + {!user.is_current && isAdmin && ( + + + + + + )} + + )} - - Auto-assigned groups} - tooltip="Every peer enrolled with this user will be automatically added to these groups" - rules={[{validator: selectValidator}]} - style={{marginRight: "70px"}} - > - - - - - {!user.is_current && isAdmin && ( - - - - - )} - } - - - - - - -
- - - {user && (user.is_current || user.is_service_user) && -
- Access - tokens - - - - Access tokens give access to NetBird API - - - {personalAccessTokens && personalAccessTokens.length > 0 && - } - - - {personalAccessTokens && personalAccessTokens.length > 0 && - - ((a as TokenDataTable).created_at.localeCompare((b as TokenDataTable).created_at))} - defaultSortOrder='descend' - render={(text, record, index) => { - return (<> - - - - - - {(record as TokenDataTable).name} - {"Created" + ((record as TokenDataTable).created_by_email && user.is_service_user ? " by " + (record as TokenDataTable).created_by_email : "") + " on " + fullDate((record as TokenDataTable).created_at)} - - - ) - }}/> - { - return <> - Expires - on - {fullDate((record as TokenDataTable).expiration_date)} - - }} - /> - { - return <> - Last - used - {(record as TokenDataTable).last_used ? fullDate((record as TokenDataTable).last_used) : "Never"} - - }} - /> - { - return ( - - ) - }} - /> -
} - - {(personalAccessTokens === null || personalAccessTokens.length === 0) && - - - You don’t have any access tokens yet - - - } -
- -
} + + + + +
- - {confirmModalContextHolder} - - ) +
+ + {user && (user.is_current || user.is_service_user) && ( + +
+ + Access tokens + + + + + Access tokens give access to NetBird API + + + + {personalAccessTokens && + personalAccessTokens.length > 0 && ( + + )} + + + {personalAccessTokens && personalAccessTokens.length > 0 && ( + + + (a as TokenDataTable).created_at.localeCompare( + (b as TokenDataTable).created_at + ) + } + defaultSortOrder="descend" + render={(text, record, index) => { + return ( + <> + + + + + + + {(record as TokenDataTable).name} + + + {"Created" + + ((record as TokenDataTable) + .created_by_email && user.is_service_user + ? " by " + + (record as TokenDataTable) + .created_by_email + : "") + + " on " + + fullDate( + (record as TokenDataTable).created_at + )} + + + + + ); + }} + /> + { + return ( + <> + + Expires on + + + {fullDate( + (record as TokenDataTable).expiration_date + )} + + + ); + }} + /> + { + return ( + <> + + Last used + + + {(record as TokenDataTable).last_used + ? fullDate((record as TokenDataTable).last_used) + : "Never"} + + + ); + }} + /> + { + return ( + + ); + }} + /> +
+ )} + + {(personalAccessTokens === null || + personalAccessTokens.length === 0) && ( + + + You don’t have any access tokens yet + + + + )} +
+
+ )} +
+ + {confirmModalContextHolder} + + ); } diff --git a/src/store/setup-key/actions.ts b/src/store/setup-key/actions.ts index 214159e..228658f 100644 --- a/src/store/setup-key/actions.ts +++ b/src/store/setup-key/actions.ts @@ -32,7 +32,8 @@ const actions = { removeSetupKey: createAction('REMOVE_SETUP_KEY')(), setSetupKey: createAction('SET_SETUP_KEY')(), - setSetupNewKeyVisible: createAction('SET_SETUP_NEW_KEY_VISIBLE')() + setSetupNewKeyVisible: createAction('SET_SETUP_NEW_KEY_VISIBLE')(), + setSetupEditKeyVisible: createAction('SET_SETUP_EDIT_KEY_VISIBLE')() }; export type ActionTypes = ActionType; diff --git a/src/store/setup-key/reducer.ts b/src/store/setup-key/reducer.ts index 1dcf35f..96664ff 100644 --- a/src/store/setup-key/reducer.ts +++ b/src/store/setup-key/reducer.ts @@ -5,15 +5,16 @@ import actions, { ActionTypes } from './actions'; import {ApiError, DeleteResponse, CreateResponse, ChangeResponse} from "../../services/api-client/types"; type StateType = Readonly<{ - data: SetupKey[] | null; - setupKey: SetupKey | null; - loading: boolean; - failed: ApiError | null; - saving: boolean; - deletedSetupKey: DeleteResponse; - revokedSetupKey: ChangeResponse; - savedSetupKey: CreateResponse; - setupNewKeyVisible: boolean + data: SetupKey[] | null; + setupKey: SetupKey | null; + loading: boolean; + failed: ApiError | null; + saving: boolean; + deletedSetupKey: DeleteResponse; + revokedSetupKey: ChangeResponse; + savedSetupKey: CreateResponse; + setupNewKeyVisible: boolean; + setupEditKeyVisible: boolean; }>; const initialState: StateType = { @@ -43,7 +44,8 @@ const initialState: StateType = { error: null, data : null }, - setupNewKeyVisible: false + setupNewKeyVisible: false, + setupEditKeyVisible: false }; const data = createReducer(initialState.data as SetupKey[]) @@ -85,13 +87,17 @@ const savedSetupKey = createReducer, ActionTypes const setupNewKeyVisible = createReducer(initialState.setupNewKeyVisible) .handleAction(actions.setSetupNewKeyVisible, (store, action) => action.payload) + const setupEditKeyVisible = createReducer(initialState.setupEditKeyVisible) + .handleAction(actions.setSetupEditKeyVisible, (store, action) => action.payload) + export default combineReducers({ - data, - setupKey, - loading, - failed, - saving, - deletedSetupKey, - savedSetupKey: savedSetupKey, - setupNewKeyVisible + data, + setupKey, + loading, + failed, + saving, + deletedSetupKey, + savedSetupKey: savedSetupKey, + setupNewKeyVisible, + setupEditKeyVisible, }); diff --git a/src/store/setup-key/types.ts b/src/store/setup-key/types.ts index 5a65754..f556615 100644 --- a/src/store/setup-key/types.ts +++ b/src/store/setup-key/types.ts @@ -14,7 +14,7 @@ export interface SetupKey { valid: boolean; auto_groups: string[] expires_in: number; - usage_limit: number; + usage_limit: any; } export interface FormSetupKey extends SetupKey { diff --git a/src/views/SetupKeys.tsx b/src/views/SetupKeys.tsx index 67fc94f..f850c9f 100644 --- a/src/views/SetupKeys.tsx +++ b/src/views/SetupKeys.tsx @@ -4,28 +4,29 @@ import { RootState } from "typesafe-actions"; import { actions as setupKeyActions } from "../store/setup-key"; import { Container } from "../components/Container"; import { - Alert, - Button, - Card, - Col, - Input, - message, - Modal, - Popover, - Radio, - RadioChangeEvent, - Row, - Select, - Space, - Table, - Tag, - Typography, + Alert, + Button, + Card, + Col, + Input, + message, + Modal, + Popover, + Radio, + RadioChangeEvent, + Row, + Select, + Space, + Table, + Tag, + Typography, } from "antd"; import { SetupKey, SetupKeyToSave } from "../store/setup-key/types"; import { filter } from "lodash"; import { formatDate, timeAgo } from "../utils/common"; import { ExclamationCircleOutlined } from "@ant-design/icons"; import SetupKeyNew from "../components/SetupKeyNew"; +import SetupKeyEdit from "../components/SetupKeyEdit"; import ButtonCopyMessage from "../components/ButtonCopyMessage"; import tableSpin from "../components/Spin"; import { actions as groupActions } from "../store/group"; @@ -38,558 +39,654 @@ const { Title, Text, Paragraph } = Typography; const { Column } = Table; interface SetupKeyDataTable extends SetupKey { - key: string; - groupsCount: number; + key: string; + groupsCount: number; } export const SetupKeys = () => { - const { onChangePageSize, pageSizeOptions, pageSize } = usePageSizeHelpers(); - const { getTokenSilently } = useGetTokenSilently(); - const dispatch = useDispatch(); + const { onChangePageSize, pageSizeOptions, pageSize } = usePageSizeHelpers(); + const { getTokenSilently } = useGetTokenSilently(); + const dispatch = useDispatch(); - const setupKeys = useSelector((state: RootState) => state.setupKey.data); - const failed = useSelector((state: RootState) => state.setupKey.failed); - const loading = useSelector((state: RootState) => state.setupKey.loading); - const deletedSetupKey = useSelector((state: RootState) => state.setupKey.deletedSetupKey); - const savedSetupKey = useSelector((state: RootState) => state.setupKey.savedSetupKey); - const groups = useSelector((state: RootState) => state.group.data); + const setupKeys = useSelector((state: RootState) => state.setupKey.data); + const failed = useSelector((state: RootState) => state.setupKey.failed); + const loading = useSelector((state: RootState) => state.setupKey.loading); + const deletedSetupKey = useSelector( + (state: RootState) => state.setupKey.deletedSetupKey + ); + const savedSetupKey = useSelector( + (state: RootState) => state.setupKey.savedSetupKey + ); + const groups = useSelector((state: RootState) => state.group.data); - const [textToSearch, setTextToSearch] = useState(""); - const [optionValidAll, setOptionValidAll] = useState("valid"); - const [dataTable, setDataTable] = useState([] as SetupKeyDataTable[]); - const setupNewKeyVisible = useSelector((state: RootState) => state.setupKey.setupNewKeyVisible); - const [groupPopupVisible, setGroupPopupVisible] = useState(""); + const [textToSearch, setTextToSearch] = useState(""); + const [optionValidAll, setOptionValidAll] = useState("valid"); + const [dataTable, setDataTable] = useState([] as SetupKeyDataTable[]); + const setupNewKeyVisible = useSelector( + (state: RootState) => state.setupKey.setupNewKeyVisible + ); + const setupEditKeyVisible = useSelector( + (state: RootState) => state.setupKey.setupEditKeyVisible + ); + const [groupPopupVisible, setGroupPopupVisible] = useState(""); - const styleNotification = { marginTop: 85 }; - const showTutorial = !dataTable.length; + const styleNotification = { marginTop: 85 }; + const showTutorial = !dataTable.length; - const optionsValidAll = [ - { label: "Valid", value: "valid" }, - { label: "All", value: "all" }, - ]; + const optionsValidAll = [ + { label: "Valid", value: "valid" }, + { label: "All", value: "all" }, + ]; - const [confirmModal, confirmModalContextHolder] = Modal.useModal(); + const [confirmModal, confirmModalContextHolder] = Modal.useModal(); - const transformDataTable = (d: SetupKey[]): SetupKeyDataTable[] => { - return d.map((p) => ({ ...p, groupsCount: p.auto_groups ? p.auto_groups.length : 0 } as SetupKeyDataTable)); - }; + const transformDataTable = (d: SetupKey[]): SetupKeyDataTable[] => { + return d.map( + (p) => + ({ + ...p, + groupsCount: p.auto_groups ? p.auto_groups.length : 0, + } as SetupKeyDataTable) + ); + }; - useEffect(() => { - dispatch(setupKeyActions.getSetupKeys.request({ getAccessTokenSilently: getTokenSilently, payload: null })); - dispatch(groupActions.getGroups.request({ getAccessTokenSilently: getTokenSilently, payload: null })); - }, []); + useEffect(() => { + dispatch( + setupKeyActions.getSetupKeys.request({ + getAccessTokenSilently: getTokenSilently, + payload: null, + }) + ); + dispatch( + groupActions.getGroups.request({ + getAccessTokenSilently: getTokenSilently, + payload: null, + }) + ); + }, []); - useEffect(() => { - setDataTable(transformDataTable(filterDataTable())); - }, [setupKeys]); + useEffect(() => { + setDataTable(transformDataTable(filterDataTable())); + }, [setupKeys]); - useEffect(() => { - setDataTable(transformDataTable(filterDataTable())); - }, [textToSearch, optionValidAll]); + useEffect(() => { + setDataTable(transformDataTable(filterDataTable())); + }, [textToSearch, optionValidAll]); - const deleteKey = "deleting"; - useEffect(() => { - if (deletedSetupKey.loading) { - message.loading({ content: "Deleting...", key: deleteKey, style: styleNotification }); - } else if (deletedSetupKey.success) { - message.success({ - content: "Setup key has been successfully removed.", - key: deleteKey, - duration: 2, - style: styleNotification, - }); - dispatch(setupKeyActions.setDeleteSetupKey({ ...deletedSetupKey, success: false })); - dispatch(setupKeyActions.resetDeletedSetupKey(null)); - } else if (deletedSetupKey.error) { - message.error({ - content: "Failed to delete setup key. You might not have enough permissions.", - key: deleteKey, - duration: 2, - style: styleNotification, - }); - dispatch(setupKeyActions.setDeleteSetupKey({ ...deletedSetupKey, error: null })); - dispatch(setupKeyActions.resetDeletedSetupKey(null)); - } - }, [deletedSetupKey]); + const deleteKey = "deleting"; + useEffect(() => { + if (deletedSetupKey.loading) { + message.loading({ + content: "Deleting...", + key: deleteKey, + style: styleNotification, + }); + } else if (deletedSetupKey.success) { + message.success({ + content: "Setup key has been successfully removed.", + key: deleteKey, + duration: 2, + style: styleNotification, + }); + dispatch( + setupKeyActions.setDeleteSetupKey({ + ...deletedSetupKey, + success: false, + }) + ); + dispatch(setupKeyActions.resetDeletedSetupKey(null)); + } else if (deletedSetupKey.error) { + message.error({ + content: + "Failed to delete setup key. You might not have enough permissions.", + key: deleteKey, + duration: 2, + style: styleNotification, + }); + dispatch( + setupKeyActions.setDeleteSetupKey({ ...deletedSetupKey, error: null }) + ); + dispatch(setupKeyActions.resetDeletedSetupKey(null)); + } + }, [deletedSetupKey]); - const createKey = "saving"; - useEffect(() => { - if (savedSetupKey.loading) { - message.loading({ content: "Saving...", key: createKey, duration: 0, style: styleNotification }); - } else if (savedSetupKey.success) { - message.success({ - content: "Setup key has been successfully saved.", - key: createKey, - duration: 2, - style: styleNotification, - }); - dispatch(setupKeyActions.setSetupNewKeyVisible(false)); - dispatch(setupKeyActions.setSavedSetupKey({ ...savedSetupKey, success: false })); - dispatch(setupKeyActions.resetSavedSetupKey(null)); - } else if (savedSetupKey.error) { - message.error({ - content: "Failed to update setup key. You might not have enough permissions.", - key: createKey, - duration: 2, - style: styleNotification, - }); - dispatch(setupKeyActions.setSavedSetupKey({ ...savedSetupKey, error: null })); - dispatch(setupKeyActions.resetSavedSetupKey(null)); - } - }, [savedSetupKey]); + const createKey = "saving"; + useEffect(() => { + if (savedSetupKey.loading) { + message.loading({ + content: "Saving...", + key: createKey, + duration: 0, + style: styleNotification, + }); + } else if (savedSetupKey.success) { + message.success({ + content: "Setup key has been successfully saved.", + key: createKey, + duration: 2, + style: styleNotification, + }); + dispatch(setupKeyActions.setSetupNewKeyVisible(false)); + dispatch( + setupKeyActions.setSavedSetupKey({ ...savedSetupKey, success: false }) + ); + dispatch(setupKeyActions.resetSavedSetupKey(null)); + dispatch(setupKeyActions.setSetupEditKeyVisible(false)); + } else if (savedSetupKey.error) { + message.error({ + content: + "Failed to update setup key. You might not have enough permissions.", + key: createKey, + duration: 2, + style: styleNotification, + }); + dispatch( + setupKeyActions.setSavedSetupKey({ ...savedSetupKey, error: null }) + ); + dispatch(setupKeyActions.resetSavedSetupKey(null)); + } + }, [savedSetupKey]); - const filterDataTable = (): SetupKey[] => { - const t = textToSearch.toLowerCase().trim(); - let f: SetupKey[] = [...setupKeys]; - if (optionValidAll === "valid") { - f = filter(setupKeys, (_f: SetupKey) => _f.valid && !_f.revoked); - } - f = filter( - f, - (_f: SetupKey) => - _f.name.toLowerCase().includes(t) || - _f.state.includes(t) || - _f.type.toLowerCase().includes(t) || - _f.key.toLowerCase().includes(t) || - t === "" - ) as SetupKey[]; - return f; - }; + const filterDataTable = (): SetupKey[] => { + const t = textToSearch.toLowerCase().trim(); + let f: SetupKey[] = [...setupKeys]; + if (optionValidAll === "valid") { + f = filter(setupKeys, (_f: SetupKey) => _f.valid && !_f.revoked); + } + f = filter( + f, + (_f: SetupKey) => + _f.name.toLowerCase().includes(t) || + _f.state.includes(t) || + _f.type.toLowerCase().includes(t) || + _f.key.toLowerCase().includes(t) || + t === "" + ) as SetupKey[]; + return f; + }; - const onChangeTextToSearch = (e: React.ChangeEvent) => { - setTextToSearch(e.target.value); - }; + const onChangeTextToSearch = ( + e: React.ChangeEvent + ) => { + setTextToSearch(e.target.value); + }; - const searchDataTable = () => { - const data = filterDataTable(); - setDataTable(transformDataTable(data)); - }; + const searchDataTable = () => { + const data = filterDataTable(); + setDataTable(transformDataTable(data)); + }; - const onChangeValidAll = ({ target: { value } }: RadioChangeEvent) => { - setOptionValidAll(value); - }; + const onChangeValidAll = ({ target: { value } }: RadioChangeEvent) => { + setOptionValidAll(value); + }; - const showConfirmRevoke = (setupKeyToAction: SetupKeyDataTable) => { - let name = setupKeyToAction ? setupKeyToAction.name : ""; - confirmModal.confirm({ - icon: , - title: 'Revoke setupKey "' + name + '"', - width: 600, - content: ( - - Are you sure you want to revoke key? - - ), - onOk() { - dispatch( - setupKeyActions.saveSetupKey.request({ - getAccessTokenSilently: getTokenSilently, - payload: { - id: setupKeyToAction ? setupKeyToAction.id : null, - revoked: true, - name: setupKeyToAction ? setupKeyToAction.name : null, - auto_groups: - setupKeyToAction && setupKeyToAction.auto_groups ? setupKeyToAction.auto_groups : [], - } as SetupKeyToSave, - }) - ); - }, - }); - }; - - const onClickAddNewSetupKey = () => { - const autoGroups: string[] = []; - dispatch(setupKeyActions.setSetupNewKeyVisible(true)); + const showConfirmRevoke = (setupKeyToAction: SetupKeyDataTable) => { + let name = setupKeyToAction ? setupKeyToAction.name : ""; + confirmModal.confirm({ + icon: , + title: 'Revoke setupKey "' + name + '"', + width: 600, + content: ( + + Are you sure you want to revoke key? + + ), + onOk() { dispatch( - setupKeyActions.setSetupKey({ - name: "", - type: "one-off", - auto_groups: autoGroups, - } as SetupKey) + setupKeyActions.saveSetupKey.request({ + getAccessTokenSilently: getTokenSilently, + payload: { + id: setupKeyToAction ? setupKeyToAction.id : null, + revoked: true, + name: setupKeyToAction ? setupKeyToAction.name : null, + auto_groups: + setupKeyToAction && setupKeyToAction.auto_groups + ? setupKeyToAction.auto_groups + : [], + } as SetupKeyToSave, + }) ); - }; + }, + }); + }; - const setKeyAndView = (key: SetupKeyDataTable) => { - dispatch(setupKeyActions.setSetupNewKeyVisible(true)); - dispatch( - setupKeyActions.setSetupKey({ - id: key?.id || null, - key: key?.key, - name: key?.name, - revoked: key?.revoked, - expires: key?.expires, - state: key?.state, - type: key?.type, - used_times: key?.used_times, - valid: key?.valid, - auto_groups: key?.auto_groups, - last_used: key?.last_used, - usage_limit: key?.usage_limit, - } as SetupKey) - ); - }; + const onClickAddNewSetupKey = () => { + const autoGroups: string[] = []; + dispatch(setupKeyActions.setSetupNewKeyVisible(true)); + dispatch( + setupKeyActions.setSetupKey({ + name: "", + type: "one-off", + auto_groups: autoGroups, + } as SetupKey) + ); + }; - useEffect(() => { - if (setupNewKeyVisible) { - setGroupPopupVisible(""); - } - }, [setupNewKeyVisible]); + const setKeyAndView = (key: SetupKeyDataTable) => { + dispatch(setupKeyActions.setSetupEditKeyVisible(true)); + dispatch( + setupKeyActions.setSetupKey({ + id: key?.id || null, + key: key?.key, + name: key?.name, + revoked: key?.revoked, + expires: key?.expires, + state: key?.state, + type: key?.type, + used_times: key?.used_times, + valid: key?.valid, + auto_groups: key?.auto_groups, + last_used: key?.last_used, + usage_limit: key?.usage_limit, + } as SetupKey) + ); + }; - const onPopoverVisibleChange = (b: boolean, key: string) => { - if (setupNewKeyVisible) { - setGroupPopupVisible(""); - } else { - if (b) { - setGroupPopupVisible(key); - } else { - setGroupPopupVisible(""); - } - } - }; + useEffect(() => { + if (setupNewKeyVisible) { + setGroupPopupVisible(""); + } + }, [setupNewKeyVisible]); - const renderPopoverGroups = ( - label: string, - rowGroups: string[] | string[] | null, - setupKeyToAction: SetupKeyDataTable - ) => { - let groupsMap = new Map(); - groups.forEach((g) => { - groupsMap.set(g.id!, g); - }); + const onPopoverVisibleChange = (b: boolean, key: string) => { + if (setupNewKeyVisible) { + setGroupPopupVisible(""); + } else { + if (b) { + setGroupPopupVisible(key); + } else { + setGroupPopupVisible(""); + } + } + }; - let displayGroups: Group[] = []; - if (rowGroups) { - displayGroups = rowGroups.filter((g) => groupsMap.get(g)).map((g) => groupsMap.get(g)!); - } + const renderPopoverGroups = ( + label: string, + rowGroups: string[] | string[] | null, + setupKeyToAction: SetupKeyDataTable + ) => { + let groupsMap = new Map(); + groups.forEach((g) => { + groupsMap.set(g.id!, g); + }); - let btn = ( - - ); - if (!displayGroups || displayGroups!.length < 1) { - return btn; - } + let displayGroups: Group[] = []; + if (rowGroups) { + displayGroups = rowGroups + .filter((g) => groupsMap.get(g)) + .map((g) => groupsMap.get(g)!); + } - const content = displayGroups?.map((g, i) => { - const _g = g as Group; - const peersCount = ` - ${_g.peers_count || 0} ${ - !_g.peers_count || parseInt(_g.peers_count) !== 1 ? "peers" : "peer" - } `; - return ( -
- - {_g.name} - - {peersCount} -
- ); - }); - const mainContent = {content}; - let popoverPlacement = "top"; - if (content && content.length > 5) { - popoverPlacement = "rightTop"; - } + let btn = ( + + ); + if (!displayGroups || displayGroups!.length < 1) { + return btn; + } - return ( - onPopoverVisibleChange(b, setupKeyToAction.key)} - open={groupPopupVisible === setupKeyToAction.key} - content={mainContent} - title={null} - > - {btn} - - ); - }; - - const setUpdateGroupsVisible = (setupKeyToAction: SetupKey, status: boolean) => { - if (status) { - dispatch(setupKeyActions.setSetupKey({ ...setupKeyToAction })); - dispatch(setupKeyActions.setSetupNewKeyVisible(true)); - return; - } - const autoGroups: string[] = []; - dispatch( - setupKeyActions.setSetupKey({ - name: "", - type: "one-off", - auto_groups: autoGroups, - } as SetupKey) - ); - dispatch(setupKeyActions.setSetupNewKeyVisible(false)); - }; + const content = displayGroups?.map((g, i) => { + const _g = g as Group; + const peersCount = ` - ${_g.peers_count || 0} ${ + !_g.peers_count || parseInt(_g.peers_count) !== 1 ? "peers" : "peer" + } `; + return ( +
+ + {_g.name} + + {peersCount} +
+ ); + }); + const mainContent = {content}; + let popoverPlacement = "top"; + if (content && content.length > 5) { + popoverPlacement = "rightTop"; + } return ( - <> - - - - Setup Keys - - A list of all the setup keys in your account including their name, state, type and - expiration. - - - - - {/**/} - - - - - - + + + + +