mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Setup keys screen (#167)
This commit is contained in:
@@ -1,95 +1,74 @@
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {actions as setupKeyActions} from '../store/setup-key';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
DatePicker,
|
||||
DatePickerProps,
|
||||
Divider,
|
||||
Drawer,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
List, Modal,
|
||||
Radio,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Tag,
|
||||
Typography
|
||||
} from "antd";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {CloseOutlined, EditOutlined, QuestionCircleFilled} from "@ant-design/icons";
|
||||
import {FormSetupKey, SetupKey, SetupKeyToSave} from "../store/setup-key/types";
|
||||
import {Header} from "antd/es/layout/layout";
|
||||
import {checkExpiresIn, 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 ExpiresInInput, {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, 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 moment from "moment";
|
||||
import { Container } from "./Container";
|
||||
import Paragraph from "antd/es/typography/Paragraph";
|
||||
import { EditOutlined, LockOutlined } from "@ant-design/icons";
|
||||
|
||||
const {Option} = Select;
|
||||
const { Option } = Select;
|
||||
const { Text } = Typography;
|
||||
const ExpiresInDefault: ExpiresInValue = { number: 30, interval: "day" };
|
||||
|
||||
const {Text} = Typography;
|
||||
const customExpiresFormat = (value: Date): string | null => {
|
||||
return formatDate(value);
|
||||
};
|
||||
|
||||
const ExpiresInDefault: ExpiresInValue = {number: 30, interval: "day"}
|
||||
|
||||
const customExpiresFormat: DatePickerProps['format'] = value => {
|
||||
return formatDate(value)
|
||||
}
|
||||
|
||||
const customLastUsedFormat: DatePickerProps['format'] = value => {
|
||||
if (value.year() == 1) {
|
||||
const customLastUsedFormat = (value: Date): string | null => {
|
||||
if (value.getFullYear() === 1) {
|
||||
// 1st of Jan 0001
|
||||
return "never"
|
||||
return "never";
|
||||
}
|
||||
let ago = timeAgo(value.toString())
|
||||
if (!ago) {
|
||||
return "unused"
|
||||
}
|
||||
return ago
|
||||
}
|
||||
|
||||
let ago = timeAgo(value.toString());
|
||||
if (!ago) return "unused";
|
||||
|
||||
return ago;
|
||||
};
|
||||
|
||||
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 [editName, setEditName] = useState(false)
|
||||
const inputNameRef = useRef<any>(null)
|
||||
const [selectedTagGroups, setSelectedTagGroups] = useState([] as string[])
|
||||
const [tagGroups, setTagGroups] = useState([] as string[])
|
||||
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 [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey)
|
||||
const [form] = Form.useForm()
|
||||
const [form] = Form.useForm();
|
||||
const [editName, setEditName] = useState(false);
|
||||
const [tagGroups, setTagGroups] = useState([] as string[]);
|
||||
const [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey);
|
||||
const inputNameRef = useRef<any>(null);
|
||||
const isEditMode: boolean = !!formSetupKey.id;
|
||||
|
||||
useEffect(() => {
|
||||
if (editName) inputNameRef.current!.focus({
|
||||
cursor: 'end',
|
||||
});
|
||||
if (!editName) return;
|
||||
|
||||
inputNameRef.current!.focus({ cursor: "end" });
|
||||
}, [editName]);
|
||||
|
||||
useEffect(() => {
|
||||
setTagGroups(groups?.filter(g => g.name != "All").map(g => g.name) || [])
|
||||
}, [groups])
|
||||
setTagGroups(groups?.filter((g) => g.name !== "All").map((g) => g.name) || []);
|
||||
}, [groups]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!setupKey) return
|
||||
if (!setupKey) return;
|
||||
|
||||
let allGroups = new Map<string, Group>();
|
||||
groups.forEach(g => {
|
||||
allGroups.set(g.id!, g)
|
||||
})
|
||||
|
||||
let formKeyGroups: string[] = []
|
||||
const allGroups = new Map<string, Group>();
|
||||
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)
|
||||
formKeyGroups = setupKey.auto_groups.filter((g) => allGroups.get(g)).map((g) => allGroups.get(g)!.name);
|
||||
}
|
||||
|
||||
const fSetupKey = {
|
||||
@@ -97,19 +76,21 @@ const SetupKeyNew = () => {
|
||||
autoGroupNames: setupKey.auto_groups ? formKeyGroups : [],
|
||||
expiresInFormatted: ExpiresInDefault,
|
||||
exp: moment(setupKey.expires),
|
||||
last: moment(setupKey.last_used)
|
||||
} as FormSetupKey
|
||||
setFormSetupKey(fSetupKey)
|
||||
form.setFieldsValue(fSetupKey)
|
||||
}, [setupKey])
|
||||
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 || '') || []
|
||||
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 allGroupsNames: string[] = groups?.map((g) => g.name);
|
||||
const groupsToCreate = formSetupKey.autoGroupNames.filter((s) => !allGroupsNames.includes(s));
|
||||
|
||||
let expiresIn = expiresInToSeconds(formSetupKey.expiresInFormatted)
|
||||
const expiresIn = expiresInToSeconds(formSetupKey.expiresInFormatted);
|
||||
return {
|
||||
id: formSetupKey.id,
|
||||
name: formSetupKey.name,
|
||||
@@ -118,72 +99,79 @@ const SetupKeyNew = () => {
|
||||
revoked: formSetupKey.revoked,
|
||||
groupsToCreate: groupsToCreate,
|
||||
expires_in: expiresIn,
|
||||
usage_limit: formSetupKey.usage_limit
|
||||
} as SetupKeyToSave
|
||||
}
|
||||
usage_limit: formSetupKey.usage_limit,
|
||||
} as SetupKeyToSave;
|
||||
};
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
form.validateFields()
|
||||
.then((values) => {
|
||||
let setupKeyToSave = createSetupKeyToSave()
|
||||
dispatch(setupKeyActions.saveSetupKey.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: 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,
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
console.log('errorInfo', errorInfo)
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
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: new Array(),
|
||||
usage_limit: 0,
|
||||
used_times: 0,
|
||||
expires_in: 0
|
||||
} as SetupKey))
|
||||
setFormSetupKey({} as FormSetupKey)
|
||||
setVisibleNewSetupKey(false)
|
||||
}
|
||||
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})
|
||||
}
|
||||
setFormSetupKey({ ...formSetupKey, ...data });
|
||||
};
|
||||
|
||||
const toggleEditName = (status: boolean) => {
|
||||
setEditName(status);
|
||||
}
|
||||
};
|
||||
|
||||
const selectValidator = (_: RuleObject, value: string[]) => {
|
||||
let hasSpaceNamed = []
|
||||
let hasSpaceNamed = [];
|
||||
|
||||
value.forEach(function (v: string) {
|
||||
if (!v.trim().length) {
|
||||
hasSpaceNamed.push(v)
|
||||
hasSpaceNamed.push(v);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (hasSpaceNamed.length) {
|
||||
return Promise.reject(new Error("Group names with just spaces are not allowed"))
|
||||
return Promise.reject(new Error("Group names with just spaces are not allowed"));
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const tagRender = (props: CustomTagProps) => {
|
||||
const {label, value, closable, onClose} = props;
|
||||
const { value, closable, onClose } = props;
|
||||
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -195,294 +183,421 @@ const SetupKeyNew = () => {
|
||||
onMouseDown={onPreventMouseDown}
|
||||
closable={closable}
|
||||
onClose={onClose}
|
||||
style={{marginRight: 3}}
|
||||
style={{ marginRight: 3 }}
|
||||
>
|
||||
<strong>{value}</strong>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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'} `
|
||||
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 (
|
||||
<>
|
||||
<Tag
|
||||
color="blue"
|
||||
style={{marginRight: 3}}
|
||||
>
|
||||
<Tag color="blue" style={{ marginRight: 3 }}>
|
||||
<strong>{label}</strong>
|
||||
</Tag>
|
||||
<span style={{fontSize: ".85em"}}>{peersCount}</span>
|
||||
<span style={{ fontSize: ".85em" }}>{peersCount}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const dropDownRender = (menu: React.ReactElement) => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{margin: '8px 0'}}/>
|
||||
<Row style={{padding: '0 8px 4px'}}>
|
||||
<Divider style={{ margin: "8px 0" }} />
|
||||
<Row style={{ padding: "0 8px 4px" }}>
|
||||
<Col flex="auto">
|
||||
<span style={{color: "#9CA3AF"}}>Add new group by pressing "Enter"</span>
|
||||
<span style={{ color: "#9CA3AF" }}>Add new group by pressing "Enter"</span>
|
||||
</Col>
|
||||
<Col flex="none">
|
||||
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M1.70455 7.19176V5.89915H10.3949C10.7727 5.89915 11.1174 5.80634 11.429 5.62074C11.7405 5.43513 11.9875 5.18655 12.1697 4.875C12.3554 4.56345 12.4482 4.21875 12.4482 3.84091C12.4482 3.46307 12.3554 3.12003 12.1697 2.81179C11.9841 2.50024 11.7356 2.25166 11.424 2.06605C11.1158 1.88044 10.7727 1.78764 10.3949 1.78764H9.83807V0.5H10.3949C11.0114 0.5 11.5715 0.650805 12.0753 0.952414C12.5791 1.25402 12.9818 1.65672 13.2834 2.16051C13.585 2.6643 13.7358 3.22443 13.7358 3.84091C13.7358 4.30161 13.648 4.73414 13.4723 5.13849C13.3 5.54285 13.0613 5.89915 12.7564 6.20739C12.4515 6.51562 12.0968 6.75758 11.6925 6.93324C11.2881 7.10559 10.8556 7.19176 10.3949 7.19176H1.70455ZM4.90128 11.0646L0.382102 6.54545L4.90128 2.02628L5.79119 2.91619L2.15696 6.54545L5.79119 10.1747L4.90128 11.0646Z"
|
||||
fill="#9CA3AF"/>
|
||||
fill="#9CA3AF"
|
||||
/>
|
||||
</svg>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
|
||||
const handleChangeTags = (value: string[]) => {
|
||||
let validatedValues: string[] = []
|
||||
value.forEach(function (v) {
|
||||
if (v.trim().length) {
|
||||
validatedValues.push(v)
|
||||
}
|
||||
})
|
||||
setSelectedTagGroups(validatedValues)
|
||||
};
|
||||
|
||||
const inputLabel = (text: any) => (
|
||||
<>
|
||||
<span>{text}</span>
|
||||
<Tag color="red">{formSetupKey.state}</Tag>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
const changesDetected = (): boolean => {
|
||||
return formSetupKey.name == null || formSetupKey.name !== setupKey.name || groupsChanged()
|
||||
|| formSetupKey.usage_limit !== setupKey.usage_limit
|
||||
}
|
||||
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
|
||||
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 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 setupKey.auto_groups?.filter((g) => !formGroupIds.includes(g)).length > 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{setupKey &&
|
||||
<Drawer
|
||||
forceRender={true}
|
||||
headerStyle={{display: "none"}}
|
||||
open={setupNewKeyVisible}
|
||||
bodyStyle={{paddingBottom: 80}}
|
||||
onClose={onCancel}
|
||||
footer={
|
||||
<Space style={{display: 'flex', justifyContent: 'end'}}>
|
||||
<Button disabled={savedSetupKey.loading} onClick={onCancel}>Cancel</Button>
|
||||
<Button type="primary" disabled={savedSetupKey.loading || !changesDetected()}
|
||||
onClick={handleFormSubmit}>{`${formSetupKey.id ? 'Save' : 'Create'}`}</Button>
|
||||
</Space>
|
||||
}
|
||||
<Modal
|
||||
style={{
|
||||
...{ maxWidth: window.screen.availWidth <= 425 ? "90%" : "414px" },
|
||||
}}
|
||||
open={setupNewKeyVisible}
|
||||
onCancel={onCancel}
|
||||
title={
|
||||
<Paragraph
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "18px",
|
||||
fontWeight: "600",
|
||||
lineHeight: "26px",
|
||||
color: "#252526",
|
||||
}}
|
||||
>
|
||||
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}
|
||||
initialValues={{
|
||||
expiresInFormatted: ExpiresInDefault,
|
||||
}}
|
||||
{isEditMode ? "Setup Key overview" : "Create Setup Key"}
|
||||
</Paragraph>
|
||||
}
|
||||
footer={[
|
||||
<Container
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "end",
|
||||
padding: 0,
|
||||
}}
|
||||
key={0}
|
||||
>
|
||||
<Button onClick={onCancel}>Cancel</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{
|
||||
height: "100%",
|
||||
fontSize: "14px",
|
||||
borderRadius: "2px",
|
||||
}}
|
||||
disabled={savedSetupKey.loading || !changesDetected()}
|
||||
onClick={handleFormSubmit}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
{`${formSetupKey.id ? "Save" : "Create"} Key`}
|
||||
</Button>
|
||||
</Container>,
|
||||
]}
|
||||
>
|
||||
<Form
|
||||
layout="vertical"
|
||||
hideRequiredMark
|
||||
form={form}
|
||||
onValuesChange={onChange}
|
||||
initialValues={{
|
||||
expiresIn: ExpiresInDefault,
|
||||
usage_limit: 1,
|
||||
}}
|
||||
>
|
||||
<Container
|
||||
style={{
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<Row style={{ marginTop: "28px" }}>
|
||||
{isEditMode ? (
|
||||
<></>
|
||||
) : (
|
||||
<Col span={24}>
|
||||
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
|
||||
<Row align="top">
|
||||
<Col flex="none" style={{display: "flex"}}>
|
||||
{!editName && setupKey.id &&
|
||||
<button type="button" aria-label="Close" className="ant-drawer-close"
|
||||
style={{paddingTop: 3}}
|
||||
onClick={onCancel}>
|
||||
<span role="img" aria-label="close"
|
||||
className="anticon anticon-close">
|
||||
<CloseOutlined size={16}/>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
{!editName && setupKey.id && formSetupKey.name ? (
|
||||
<div className={"access-control input-text ant-drawer-title"}
|
||||
onClick={() => toggleEditName(true)}>{formSetupKey.name ? formSetupKey.name : setupKey.name}
|
||||
<EditOutlined/></div>
|
||||
) : (
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Name"
|
||||
rules={[{
|
||||
required: true,
|
||||
message: 'Please add a new name for this peer',
|
||||
whitespace: true
|
||||
}]}
|
||||
>
|
||||
<Input
|
||||
placeholder={setupKey.name}
|
||||
ref={inputNameRef}
|
||||
onPressEnter={() => toggleEditName(false)}
|
||||
onBlur={() => toggleEditName(false)}
|
||||
autoComplete="off"/>
|
||||
</Form.Item>)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
</Col>
|
||||
{setupKey.id && formSetupKey.name &&
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="key"
|
||||
label={<>
|
||||
<span style={{
|
||||
marginRight: "5px",
|
||||
}}>Key</span>
|
||||
<Tag
|
||||
color={formSetupKey.state === "valid" ? "green" : "red"}>{formSetupKey.state}</Tag>
|
||||
</>}
|
||||
>
|
||||
<Input
|
||||
disabled={true}
|
||||
style={{color: "#5a5c5a"}}
|
||||
autoComplete="off"/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
}
|
||||
|
||||
{setupKey.id && formSetupKey.name &&
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="exp"
|
||||
label="Expires"
|
||||
tooltip="The expiration date of the key"
|
||||
>
|
||||
<DatePicker disabled={true}
|
||||
style={{width: "100%", color: "#5a5c5a"}}
|
||||
format={customExpiresFormat}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
}
|
||||
{setupKey.id && formSetupKey.name &&
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="last"
|
||||
label="Last Used"
|
||||
tooltip="The last time the key was used"
|
||||
>
|
||||
<DatePicker disabled={true}
|
||||
style={{width: "100%", color: "#5a5c5a"}}
|
||||
format={customLastUsedFormat}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
}
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="type"
|
||||
label="Type"
|
||||
rules={[{required: true, message: 'Please enter key type'}]}
|
||||
<Paragraph
|
||||
style={{ whiteSpace: "pre-line", fontSize: "16px", fontWeight: "600", margin: 0 }}
|
||||
>
|
||||
<Radio.Group style={{display: 'flex'}} disabled={setupKey.id}>
|
||||
<Space direction="vertical" style={{flex: 1}}>
|
||||
<List
|
||||
size="large"
|
||||
bordered
|
||||
>
|
||||
<List.Item>
|
||||
<Radio value={"one-off"}>
|
||||
<Space direction="vertical" size="small">
|
||||
<Text strong>One-off</Text>
|
||||
<Text>This key can be used only once</Text>
|
||||
</Space>
|
||||
</Radio>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<Radio value={"reusable"}>
|
||||
<Space direction="vertical" size="small">
|
||||
<Text strong>Reusable</Text>
|
||||
<Text>This type of a setup key allows to enroll multiple
|
||||
machines</Text>
|
||||
</Space>
|
||||
</Radio>
|
||||
</List.Item>
|
||||
</List>
|
||||
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
|
||||
</Form.Item>
|
||||
Key name
|
||||
</Paragraph>
|
||||
<Text style={{ color: "#818183" }}>Name the key to identify it easily</Text>
|
||||
</Col>
|
||||
{!setupKey.id &&
|
||||
<Col span={24}>
|
||||
<Form.Item name="expiresInFormatted" label="Expires In"
|
||||
rules={[{validator: checkExpiresIn}]}>
|
||||
<ExpiresInInput options={
|
||||
Array.of(
|
||||
{key: "day", title: "Days"},
|
||||
{key: "month", title: "Months"},
|
||||
{key: "year", title: "Years"})
|
||||
}/>
|
||||
</Form.Item>
|
||||
</Col>}
|
||||
<Col span={12}>
|
||||
<Form.Item name="usage_limit"
|
||||
label="Usage Limit"
|
||||
tooltip="Limit the number of times this key can be used. Use 0 for unlimited use."
|
||||
>
|
||||
<InputNumber min={0} defaultValue={0}
|
||||
disabled={setupKey.id || formSetupKey.type !== "reusable"}
|
||||
style={{width: "100%"}}
|
||||
)}
|
||||
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
style={{ marginBottom: "0px" }}
|
||||
name="name"
|
||||
rules={[{ required: true, message: "Please enter key name." }]}
|
||||
>
|
||||
{isEditMode ? (
|
||||
<Input
|
||||
key={"edit-name-input"}
|
||||
readOnly={!editName}
|
||||
placeholder={setupKey.name}
|
||||
autoComplete="off"
|
||||
ref={inputNameRef}
|
||||
suffix={<EditOutlined onClick={() => toggleEditName(true)} />}
|
||||
onPressEnter={() => toggleEditName(false)}
|
||||
onBlur={() => toggleEditName(false)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="used_times"
|
||||
label="Used Times"
|
||||
>
|
||||
<InputNumber min={0} defaultValue={0} disabled={true}
|
||||
style={{width: "100%"}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
) : (
|
||||
<Input placeholder={`e.g. "AWS servers"`} />
|
||||
)}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{isEditMode && (
|
||||
<Row style={{ marginTop: "28px" }}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="autoGroupNames"
|
||||
label="Auto-assigned groups"
|
||||
tooltip="Every peer enrolled with this key will be automatically added to these groups"
|
||||
rules={[{validator: selectValidator}]}
|
||||
<Paragraph
|
||||
style={{ whiteSpace: "pre-line", fontSize: "16px", fontWeight: "600", margin: 0 }}
|
||||
>
|
||||
<Select mode="tags"
|
||||
style={{width: '100%'}}
|
||||
placeholder="Associate groups with the key"
|
||||
tagRender={tagRender}
|
||||
onChange={handleChangeTags}
|
||||
dropdownRender={dropDownRender}
|
||||
// enabled only when we have a new key !setupkey.id or when the key is valid
|
||||
disabled={!(!setupKey.id || setupKey.valid)}
|
||||
Key
|
||||
<Tag
|
||||
color={`${formSetupKey.state === "valid" ? "green" : "red"}`}
|
||||
style={{ marginLeft: "10px", borderRadius: "2px", fontWeight: "500" }}
|
||||
>
|
||||
{
|
||||
tagGroups.map(m =>
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
)
|
||||
}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Divider></Divider>
|
||||
<Button icon={<QuestionCircleFilled/>} type="link" target="_blank"
|
||||
href="https://netbird.io/docs/overview/setup-keys">Learn more about setup keys</Button>
|
||||
{formSetupKey.state}
|
||||
</Tag>
|
||||
</Paragraph>
|
||||
<Input
|
||||
style={{ marginTop: "10px" }}
|
||||
disabled
|
||||
value={formSetupKey.key}
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
)}
|
||||
|
||||
</Drawer>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
<Row style={{ marginTop: "28px" }}>
|
||||
{isEditMode ? (
|
||||
<Row style={{ fontSize: "16px" }}>
|
||||
<Paragraph style={{ whiteSpace: "pre-line", margin: 0, fontWeight: "600" }}>
|
||||
{formSetupKey.type === "one-off" ? "One-off" : "Reusable"}
|
||||
</Paragraph>
|
||||
<Text style={{ marginLeft: "5px", color: "#818183" }}>type</Text>
|
||||
</Row>
|
||||
) : (
|
||||
<Row>
|
||||
<Col style={{ width: "80%" }}>
|
||||
<Paragraph
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "16px",
|
||||
margin: 0,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
>
|
||||
Reusable
|
||||
</Paragraph>
|
||||
<Text style={{ color: "#818183" }}>
|
||||
This type of a setup key allows to enroll multiple peers
|
||||
</Text>
|
||||
</Col>
|
||||
<Form.Item
|
||||
name="reusable"
|
||||
valuePropName="checked"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "end",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
onChange={(checked) => {
|
||||
setFormSetupKey({
|
||||
...formSetupKey,
|
||||
type: checked ? "reusable" : "one-off",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
export default SetupKeyNew
|
||||
<Row style={{ marginTop: "28px" }}>
|
||||
<Col span={24}>
|
||||
<Paragraph
|
||||
style={{ whiteSpace: "pre-line", fontSize: "16px", margin: 0, fontWeight: "600" }}
|
||||
>
|
||||
{isEditMode ? "Available uses" : "Usage limit"}
|
||||
</Paragraph>
|
||||
</Col>
|
||||
<Col>
|
||||
{isEditMode ? (
|
||||
<Input
|
||||
disabled
|
||||
value={formSetupKey.usage_limit}
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
style={{ width: "104px" }}
|
||||
/>
|
||||
) : (
|
||||
<Form.Item
|
||||
style={{ marginTop: "5px", width: "112px", marginBottom: "5px" }}
|
||||
name="usage_limit"
|
||||
>
|
||||
<InputNumber
|
||||
disabled={setupKey.id || formSetupKey.type !== "reusable"}
|
||||
controls={false}
|
||||
min={0}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Col>
|
||||
{isEditMode ? (
|
||||
<></>
|
||||
) : (
|
||||
<Col span={24}>
|
||||
<Text style={{ color: "#818183" }}>
|
||||
1 usage = 1 peer. E.g., set to 30 if you want to enroll 30 peers
|
||||
</Text>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
<Row style={{ marginTop: "20px" }}>
|
||||
{isEditMode ? (
|
||||
<Container style={{ width: "100%", padding: 0, margin: 0 }}>
|
||||
<Row>
|
||||
<Col span={11}>
|
||||
<Paragraph style={{ fontSize: "16px", margin: 0, fontWeight: "600" }}>
|
||||
Expires
|
||||
</Paragraph>
|
||||
<Row>
|
||||
<Input
|
||||
disabled
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
value={customExpiresFormat(new Date(formSetupKey.expires))!}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={11} offset={1}>
|
||||
<Paragraph style={{ fontSize: "16px", margin: 0, fontWeight: "600" }}>
|
||||
Last used
|
||||
</Paragraph>
|
||||
<Row>
|
||||
<Input
|
||||
disabled
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
value={customLastUsedFormat(new Date(formSetupKey.last_used))!}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
) : (
|
||||
<>
|
||||
<Col span={24}>
|
||||
<Paragraph
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "16px",
|
||||
margin: 0,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
>
|
||||
Expires in
|
||||
</Paragraph>
|
||||
</Col>
|
||||
<Col>
|
||||
<Form.Item
|
||||
style={{
|
||||
marginTop: "5px",
|
||||
width: "112px",
|
||||
marginBottom: "0px",
|
||||
}}
|
||||
name="expiresIn"
|
||||
rules={[{ required: true, message: "Please enter expiration date." }]}
|
||||
>
|
||||
<Col
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<InputNumber
|
||||
type="number"
|
||||
controls={false}
|
||||
placeholder={`0`}
|
||||
min={0}
|
||||
max={180}
|
||||
/>
|
||||
<Col
|
||||
style={{
|
||||
marginLeft: "7px",
|
||||
padding: "4px 12px",
|
||||
border: "1px solid #D9D9D9",
|
||||
borderRadius: "2px",
|
||||
}}
|
||||
>
|
||||
Days
|
||||
</Col>
|
||||
</Col>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col span={24} style={{ marginTop: "5px" }}>
|
||||
<Text style={{ color: "#818183" }}>Should be between 1 and 180 days</Text>
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
<Row style={{ marginTop: "20px" }}>
|
||||
<Col span={24}>
|
||||
<Paragraph
|
||||
style={{ whiteSpace: "pre-line", fontSize: "16px", margin: 0, fontWeight: "600" }}
|
||||
>
|
||||
Auto-assigned groups
|
||||
</Paragraph>
|
||||
{isEditMode ? (
|
||||
<></>
|
||||
) : (
|
||||
<Text style={{ color: "#818183" }}>
|
||||
These groups will be automatically assigned to peers enrolled with this key
|
||||
</Text>
|
||||
)}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
style={{ marginTop: "5px", marginBottom: 0 }}
|
||||
name="autoGroupNames"
|
||||
rules={[{ validator: selectValidator }]}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Associate groups with the key"
|
||||
tagRender={tagRender}
|
||||
dropdownRender={dropDownRender}
|
||||
// enabled only when we have a new key !setupkey.id or when the key is valid
|
||||
disabled={!(!setupKey.id || setupKey.valid)}
|
||||
>
|
||||
{tagGroups.map((m) => (
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{ marginTop: "40px", marginBottom: "28px" }}>
|
||||
<Text style={{ color: "#818183" }}>
|
||||
Learn more about
|
||||
<a target="_blank" rel="noreferrer" href="https://netbird.io/docs/overview/setup-keys">
|
||||
{" "}
|
||||
Setup Keys
|
||||
</a>
|
||||
</Text>
|
||||
</Row>
|
||||
</Container>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetupKeyNew;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {actions as setupKeyActions} from '../store/setup-key';
|
||||
import {Container} from "../components/Container";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "typesafe-actions";
|
||||
import { actions as setupKeyActions } from "../store/setup-key";
|
||||
import { Container } from "../components/Container";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Dropdown,
|
||||
Input,
|
||||
Menu,
|
||||
message,
|
||||
Modal,
|
||||
Popover,
|
||||
@@ -21,330 +19,334 @@ import {
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Typography
|
||||
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 { 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 ButtonCopyMessage from "../components/ButtonCopyMessage";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {actions as groupActions} from "../store/group";
|
||||
import {Group} from "../store/group/types";
|
||||
import {TooltipPlacement} from "antd/es/tooltip";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {usePageSizeHelpers} from "../utils/pageSize";
|
||||
import { actions as groupActions } from "../store/group";
|
||||
import { Group } from "../store/group/types";
|
||||
import { TooltipPlacement } from "antd/es/tooltip";
|
||||
import { useGetTokenSilently } from "../utils/token";
|
||||
import { usePageSizeHelpers } from "../utils/pageSize";
|
||||
|
||||
const {Title, Text, Paragraph} = Typography;
|
||||
const {Column} = Table;
|
||||
const {confirm} = Modal;
|
||||
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 groups = useSelector((state: RootState) => state.group.data);
|
||||
|
||||
const [textToSearch, setTextToSearch] = useState('');
|
||||
const [optionValidAll, setOptionValidAll] = useState('valid');
|
||||
const [textToSearch, setTextToSearch] = useState("");
|
||||
const [optionValidAll, setOptionValidAll] = useState("valid");
|
||||
const [dataTable, setDataTable] = useState([] as SetupKeyDataTable[]);
|
||||
const [setupKeyToAction, setSetupKeyToAction] = useState(null as SetupKeyDataTable | null);
|
||||
const setupNewKeyVisible = useSelector((state: RootState) => state.setupKey.setupNewKeyVisible)
|
||||
const [groupPopupVisible,setGroupPopupVisible] = useState("")
|
||||
const setupNewKeyVisible = useSelector((state: RootState) => state.setupKey.setupNewKeyVisible);
|
||||
const [groupPopupVisible, setGroupPopupVisible] = useState("");
|
||||
|
||||
const styleNotification = {marginTop: 85}
|
||||
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 itemsMenuAction = [
|
||||
{
|
||||
key: "revoke",
|
||||
label: (<Button type="text" onClick={() => showConfirmRevoke()}>Revoke</Button>)
|
||||
},
|
||||
{
|
||||
key: "edit",
|
||||
label: (<Button type="text" onClick={() => onClickEditSetupKey()}>View</Button>)
|
||||
},
|
||||
|
||||
]
|
||||
const actionsMenu = (<Menu items={itemsMenuAction}></Menu>)
|
||||
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))
|
||||
}
|
||||
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}));
|
||||
}, [])
|
||||
dispatch(setupKeyActions.getSetupKeys.request({ getAccessTokenSilently: getTokenSilently, payload: null }));
|
||||
dispatch(groupActions.getGroups.request({ getAccessTokenSilently: getTokenSilently, payload: null }));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setDataTable(transformDataTable(filterDataTable()))
|
||||
}, [setupKeys])
|
||||
setDataTable(transformDataTable(filterDataTable()));
|
||||
}, [setupKeys]);
|
||||
|
||||
useEffect(() => {
|
||||
setDataTable(transformDataTable(filterDataTable()))
|
||||
}, [textToSearch, optionValidAll])
|
||||
setDataTable(transformDataTable(filterDataTable()));
|
||||
}, [textToSearch, optionValidAll]);
|
||||
|
||||
const deleteKey = 'deleting';
|
||||
const deleteKey = "deleting";
|
||||
useEffect(() => {
|
||||
if (deletedSetupKey.loading) {
|
||||
message.loading({content: 'Deleting...', key: deleteKey, style: styleNotification});
|
||||
message.loading({ content: "Deleting...", key: deleteKey, style: styleNotification });
|
||||
} else if (deletedSetupKey.success) {
|
||||
message.success({
|
||||
content: 'Setup key has been successfully removed.',
|
||||
content: "Setup key has been successfully removed.",
|
||||
key: deleteKey,
|
||||
duration: 2,
|
||||
style: styleNotification
|
||||
style: styleNotification,
|
||||
});
|
||||
dispatch(setupKeyActions.setDeleteSetupKey({...deletedSetupKey, success: false}))
|
||||
dispatch(setupKeyActions.resetDeletedSetupKey(null))
|
||||
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.',
|
||||
content: "Failed to delete setup key. You might not have enough permissions.",
|
||||
key: deleteKey,
|
||||
duration: 2,
|
||||
style: styleNotification
|
||||
style: styleNotification,
|
||||
});
|
||||
dispatch(setupKeyActions.setDeleteSetupKey({...deletedSetupKey, error: null}))
|
||||
dispatch(setupKeyActions.resetDeletedSetupKey(null))
|
||||
dispatch(setupKeyActions.setDeleteSetupKey({ ...deletedSetupKey, error: null }));
|
||||
dispatch(setupKeyActions.resetDeletedSetupKey(null));
|
||||
}
|
||||
}, [deletedSetupKey])
|
||||
}, [deletedSetupKey]);
|
||||
|
||||
const createKey = 'saving';
|
||||
const createKey = "saving";
|
||||
useEffect(() => {
|
||||
if (savedSetupKey.loading) {
|
||||
message.loading({content: 'Saving...', key: createKey, duration: 0, style: styleNotification});
|
||||
message.loading({ content: "Saving...", key: createKey, duration: 0, style: styleNotification });
|
||||
} else if (savedSetupKey.success) {
|
||||
message.success({
|
||||
content: 'Setup key has been successfully saved.',
|
||||
content: "Setup key has been successfully saved.",
|
||||
key: createKey,
|
||||
duration: 2,
|
||||
style: styleNotification
|
||||
style: styleNotification,
|
||||
});
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(false));
|
||||
dispatch(setupKeyActions.setSavedSetupKey({...savedSetupKey, success: false}));
|
||||
dispatch(setupKeyActions.resetSavedSetupKey(null))
|
||||
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.',
|
||||
content: "Failed to update setup key. You might not have enough permissions.",
|
||||
key: createKey,
|
||||
duration: 2,
|
||||
style: styleNotification
|
||||
style: styleNotification,
|
||||
});
|
||||
dispatch(setupKeyActions.setSavedSetupKey({...savedSetupKey, error: null}));
|
||||
dispatch(setupKeyActions.resetSavedSetupKey(null))
|
||||
dispatch(setupKeyActions.setSavedSetupKey({ ...savedSetupKey, error: null }));
|
||||
dispatch(setupKeyActions.resetSavedSetupKey(null));
|
||||
}
|
||||
}, [savedSetupKey])
|
||||
}, [savedSetupKey]);
|
||||
|
||||
const filterDataTable = (): SetupKey[] => {
|
||||
const t = textToSearch.toLowerCase().trim()
|
||||
let f: SetupKey[] = [...setupKeys]
|
||||
const t = textToSearch.toLowerCase().trim();
|
||||
let f: SetupKey[] = [...setupKeys];
|
||||
if (optionValidAll === "valid") {
|
||||
f = filter(setupKeys, (_f: SetupKey) => _f.valid && !_f.revoked)
|
||||
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
|
||||
}
|
||||
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<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setTextToSearch(e.target.value)
|
||||
setTextToSearch(e.target.value);
|
||||
};
|
||||
|
||||
const searchDataTable = () => {
|
||||
const data = filterDataTable()
|
||||
setDataTable(transformDataTable(data))
|
||||
}
|
||||
const data = filterDataTable();
|
||||
setDataTable(transformDataTable(data));
|
||||
};
|
||||
|
||||
const onChangeValidAll = ({target: {value}}: RadioChangeEvent) => {
|
||||
setOptionValidAll(value)
|
||||
}
|
||||
const onChangeValidAll = ({ target: { value } }: RadioChangeEvent) => {
|
||||
setOptionValidAll(value);
|
||||
};
|
||||
|
||||
const showConfirmRevoke = () => {
|
||||
let name = setupKeyToAction ? setupKeyToAction.name : ''
|
||||
const showConfirmRevoke = (setupKeyToAction: SetupKeyDataTable) => {
|
||||
let name = setupKeyToAction ? setupKeyToAction.name : "";
|
||||
confirmModal.confirm({
|
||||
icon: <ExclamationCircleOutlined/>,
|
||||
title: "Revoke setupKey \"" + name + "\"",
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
title: 'Revoke setupKey "' + name + '"',
|
||||
width: 600,
|
||||
content: <Space direction="vertical" size="small">
|
||||
<Paragraph>Are you sure you want to revoke key?</Paragraph>
|
||||
</Space>,
|
||||
content: (
|
||||
<Space direction="vertical" size="small">
|
||||
<Paragraph>Are you sure you want to revoke key?</Paragraph>
|
||||
</Space>
|
||||
),
|
||||
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
|
||||
}));
|
||||
},
|
||||
onCancel() {
|
||||
setSetupKeyToAction(null);
|
||||
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[] = []
|
||||
const autoGroups: string[] = [];
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(true));
|
||||
dispatch(setupKeyActions.setSetupKey({
|
||||
name: "",
|
||||
type: "one-off",
|
||||
auto_groups: autoGroups
|
||||
} as SetupKey))
|
||||
}
|
||||
dispatch(
|
||||
setupKeyActions.setSetupKey({
|
||||
name: "",
|
||||
type: "one-off",
|
||||
auto_groups: autoGroups,
|
||||
} as SetupKey)
|
||||
);
|
||||
};
|
||||
|
||||
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 onClickEditSetupKey = () => {
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(true));
|
||||
dispatch(setupKeyActions.setSetupKey({
|
||||
id: setupKeyToAction?.id || null,
|
||||
key: setupKeyToAction?.key,
|
||||
name: setupKeyToAction?.name,
|
||||
revoked: setupKeyToAction?.revoked,
|
||||
expires: setupKeyToAction?.expires,
|
||||
state: setupKeyToAction?.state,
|
||||
type: setupKeyToAction?.type,
|
||||
used_times: setupKeyToAction?.used_times,
|
||||
valid: setupKeyToAction?.valid,
|
||||
auto_groups: setupKeyToAction?.auto_groups,
|
||||
last_used: setupKeyToAction?.last_used,
|
||||
usage_limit: setupKeyToAction?.usage_limit
|
||||
} as SetupKey))
|
||||
}
|
||||
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)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (setupNewKeyVisible) {
|
||||
setGroupPopupVisible("")
|
||||
setGroupPopupVisible("");
|
||||
}
|
||||
}, [setupNewKeyVisible])
|
||||
}, [setupNewKeyVisible]);
|
||||
|
||||
const onPopoverVisibleChange = (b:boolean, key: string) => {
|
||||
const onPopoverVisibleChange = (b: boolean, key: string) => {
|
||||
if (setupNewKeyVisible) {
|
||||
setGroupPopupVisible("")
|
||||
setGroupPopupVisible("");
|
||||
} else {
|
||||
if (b) {
|
||||
setGroupPopupVisible(key)
|
||||
setGroupPopupVisible(key);
|
||||
} else {
|
||||
setGroupPopupVisible("")
|
||||
setGroupPopupVisible("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const renderPopoverGroups = (label: string, rowGroups: string[] | string[] | null, setupKeyToAction: SetupKeyDataTable) => {
|
||||
};
|
||||
|
||||
const renderPopoverGroups = (
|
||||
label: string,
|
||||
rowGroups: string[] | string[] | null,
|
||||
setupKeyToAction: SetupKeyDataTable
|
||||
) => {
|
||||
let groupsMap = new Map<string, Group>();
|
||||
groups.forEach(g => {
|
||||
groupsMap.set(g.id!, g)
|
||||
})
|
||||
groups.forEach((g) => {
|
||||
groupsMap.set(g.id!, g);
|
||||
});
|
||||
|
||||
let displayGroups :Group[] = []
|
||||
let displayGroups: Group[] = [];
|
||||
if (rowGroups) {
|
||||
displayGroups = rowGroups.filter(g => groupsMap.get(g)).map(g => groupsMap.get(g)!)
|
||||
displayGroups = rowGroups.filter((g) => groupsMap.get(g)).map((g) => groupsMap.get(g)!);
|
||||
}
|
||||
|
||||
let btn = <Button type="link" onClick={() => setUpdateGroupsVisible(setupKeyToAction, true)}>{displayGroups.length}</Button>
|
||||
let btn = (
|
||||
<Button type="link" onClick={() => setUpdateGroupsVisible(setupKeyToAction, true)}>
|
||||
{displayGroups.length}
|
||||
</Button>
|
||||
);
|
||||
if (!displayGroups || displayGroups!.length < 1) {
|
||||
return btn
|
||||
return btn;
|
||||
}
|
||||
|
||||
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'} `
|
||||
const _g = g as Group;
|
||||
const peersCount = ` - ${_g.peers_count || 0} ${
|
||||
!_g.peers_count || parseInt(_g.peers_count) !== 1 ? "peers" : "peer"
|
||||
} `;
|
||||
return (
|
||||
<div key={i}>
|
||||
<Tag
|
||||
color="blue"
|
||||
style={{marginRight: 3}}
|
||||
>
|
||||
<Tag color="blue" style={{ marginRight: 3 }}>
|
||||
<strong>{_g.name}</strong>
|
||||
</Tag>
|
||||
<span style={{fontSize: ".85em"}}>{peersCount}</span>
|
||||
<span style={{ fontSize: ".85em" }}>{peersCount}</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
const mainContent = (<Space direction="vertical">{content}</Space>)
|
||||
let popoverPlacement = "top"
|
||||
);
|
||||
});
|
||||
const mainContent = <Space direction="vertical">{content}</Space>;
|
||||
let popoverPlacement = "top";
|
||||
if (content && content.length > 5) {
|
||||
popoverPlacement = "rightTop"
|
||||
popoverPlacement = "rightTop";
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover placement={popoverPlacement as TooltipPlacement}
|
||||
key={setupKeyToAction.key}
|
||||
onOpenChange={(b: boolean) => onPopoverVisibleChange(b, setupKeyToAction.key)}
|
||||
open={groupPopupVisible === setupKeyToAction.key}
|
||||
content={mainContent}
|
||||
title={null}>
|
||||
<Popover
|
||||
placement={popoverPlacement as TooltipPlacement}
|
||||
key={setupKeyToAction.key}
|
||||
onOpenChange={(b: boolean) => onPopoverVisibleChange(b, setupKeyToAction.key)}
|
||||
open={groupPopupVisible === setupKeyToAction.key}
|
||||
content={mainContent}
|
||||
title={null}
|
||||
>
|
||||
{btn}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const setUpdateGroupsVisible = (setupKeyToAction: SetupKey, status: boolean) => {
|
||||
if (status) {
|
||||
dispatch(setupKeyActions.setSetupKey({...setupKeyToAction}))
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(true))
|
||||
return
|
||||
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 autoGroups: string[] = [];
|
||||
dispatch(
|
||||
setupKeyActions.setSetupKey({
|
||||
name: "",
|
||||
type: "one-off",
|
||||
auto_groups: autoGroups,
|
||||
} as SetupKey)
|
||||
);
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container style={{paddingTop: "40px"}}>
|
||||
<Container style={{ paddingTop: "40px" }}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Title level={4}>Setup Keys</Title>
|
||||
<Paragraph>A list of all the setup keys in your account including their name, state, type and
|
||||
expiration.</Paragraph>
|
||||
<Space direction="vertical" size="large" style={{display: 'flex'}}>
|
||||
<Paragraph
|
||||
style={{
|
||||
color: dataTable.length ? "black" : "#818183",
|
||||
}}
|
||||
>
|
||||
A list of all the setup keys in your account including their name, state, type and
|
||||
expiration.
|
||||
</Paragraph>
|
||||
<Space direction="vertical" size="large" style={{ display: "flex" }}>
|
||||
<Row gutter={[16, 24]}>
|
||||
<Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} span={8}>
|
||||
{/*<Input.Search allowClear value={textToSearch} onPressEnter={searchDataTable} onSearch={searchDataTable} placeholder="Search..." onChange={onChangeTextToSearch} />*/}
|
||||
<Input allowClear value={textToSearch} onPressEnter={searchDataTable}
|
||||
placeholder="Search..." onChange={onChangeTextToSearch}/>
|
||||
<Input
|
||||
allowClear
|
||||
value={textToSearch}
|
||||
onPressEnter={searchDataTable}
|
||||
placeholder="Search..."
|
||||
onChange={onChangeTextToSearch}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
|
||||
<Space size="middle">
|
||||
@@ -354,118 +356,240 @@ export const SetupKeys = () => {
|
||||
value={optionValidAll}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
disabled={!dataTable?.length}
|
||||
/>
|
||||
<Select
|
||||
disabled={!dataTable?.length}
|
||||
value={pageSize.toString()}
|
||||
options={pageSizeOptions}
|
||||
onChange={onChangePageSize}
|
||||
className="select-rows-per-page-en"
|
||||
/>
|
||||
<Select value={pageSize.toString()} options={pageSizeOptions}
|
||||
onChange={onChangePageSize} className="select-rows-per-page-en"/>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col xs={24}
|
||||
sm={24}
|
||||
md={5}
|
||||
lg={5}
|
||||
xl={5}
|
||||
xxl={5} span={5}>
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button type="primary" onClick={onClickAddNewSetupKey}>Add Key</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
{failed &&
|
||||
<Alert message={failed.message} description={failed.data ? failed.data.message : " "} type="error" showIcon
|
||||
closable/>
|
||||
}
|
||||
<Card bodyStyle={{padding: 0}}>
|
||||
<Table
|
||||
pagination={{
|
||||
pageSize,
|
||||
showSizeChanger: false,
|
||||
showTotal: ((total, range) => `Showing ${range[0]} to ${range[1]} of ${total} setup keys`)
|
||||
}}
|
||||
className="card-table"
|
||||
showSorterTooltip={false}
|
||||
scroll={{x: true}}
|
||||
loading={tableSpin(loading)}
|
||||
dataSource={dataTable}>
|
||||
<Column title="Name" dataIndex="name"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)}
|
||||
sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))}
|
||||
render={(text, record, index) => {
|
||||
return <Button type="text"
|
||||
onClick={() => setKeyAndView(record as SetupKeyDataTable)}
|
||||
className="tooltip-label"> <Text strong>{text}</Text>
|
||||
{dataTable.length ? (
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button type="primary" onClick={onClickAddNewSetupKey}>
|
||||
Add Key
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Row>
|
||||
{failed && (
|
||||
<Alert
|
||||
message={failed.message}
|
||||
description={failed.data ? failed.data.message : " "}
|
||||
type="error"
|
||||
showIcon
|
||||
closable
|
||||
/>
|
||||
)}
|
||||
{showTutorial ? (
|
||||
<Space
|
||||
style={{
|
||||
width: "100%",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
border: "1px solid #f0f0f0",
|
||||
borderRadius: "4px",
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
<Container
|
||||
style={{
|
||||
textAlign: "center",
|
||||
width: "615px",
|
||||
}}
|
||||
>
|
||||
<Col style={{ marginTop: "41px" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: "600",
|
||||
fontSize: "22px",
|
||||
lineHeight: "26px",
|
||||
color: "#252526",
|
||||
}}
|
||||
>
|
||||
Create Setup Key
|
||||
</Text>
|
||||
</Col>
|
||||
<Col style={{ marginTop: "17px" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: "400",
|
||||
fontSize: "14px",
|
||||
lineHeight: "22px",
|
||||
}}
|
||||
>
|
||||
Manage Setup Keys to register new machines in your network. The key
|
||||
links the machine to an account during initial setup.
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://netbird.io/docs/overview/setup-keys"
|
||||
>
|
||||
{" "}
|
||||
Learn more
|
||||
</a>
|
||||
</Text>
|
||||
</Col>
|
||||
<Col
|
||||
style={{
|
||||
marginTop: "16px",
|
||||
marginBottom: "112px",
|
||||
}}
|
||||
defaultSortOrder='ascend'
|
||||
/>
|
||||
|
||||
<Column title="State" dataIndex="state"
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
padding: "6.4px, 15px",
|
||||
gap: "10px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
onClick={onClickAddNewSetupKey}
|
||||
>
|
||||
Add Key
|
||||
</Button>
|
||||
</Col>
|
||||
</Container>
|
||||
</Space>
|
||||
) : (
|
||||
<Card bodyStyle={{ padding: 0 }}>
|
||||
<Table
|
||||
pagination={{
|
||||
pageSize,
|
||||
showSizeChanger: false,
|
||||
showTotal: (total, range) =>
|
||||
`Showing ${range[0]} to ${range[1]} of ${total} setup keys`,
|
||||
}}
|
||||
className="card-table"
|
||||
showSorterTooltip={false}
|
||||
scroll={{ x: true }}
|
||||
loading={tableSpin(loading)}
|
||||
dataSource={dataTable}
|
||||
>
|
||||
<Column
|
||||
title="Name"
|
||||
dataIndex="name"
|
||||
onFilter={(value: string | number | boolean, record) =>
|
||||
(record as any).name.includes(value)
|
||||
}
|
||||
sorter={(a, b) => (a as any).name.localeCompare((b as any).name)}
|
||||
render={(text, record, index) => {
|
||||
return (text === 'valid') ? <Tag color="green">{text}</Tag> :
|
||||
<Tag color="red">{text}</Tag>
|
||||
return (
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => setKeyAndView(record as SetupKeyDataTable)}
|
||||
className="tooltip-label"
|
||||
>
|
||||
{" "}
|
||||
<Text strong>{text}</Text>
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
sorter={(a, b) => ((a as any).state.localeCompare((b as any).state))}
|
||||
/>
|
||||
defaultSortOrder="ascend"
|
||||
/>
|
||||
<Column
|
||||
title="Type"
|
||||
dataIndex="type"
|
||||
onFilter={(value: string | number | boolean, record) =>
|
||||
(record as any).type.includes(value)
|
||||
}
|
||||
sorter={(a, b) => (a as any).type.localeCompare((b as any).type)}
|
||||
/>
|
||||
<Column
|
||||
title="Key"
|
||||
dataIndex="key"
|
||||
onFilter={(value: string | number | boolean, record) =>
|
||||
(record as any).key.includes(value)
|
||||
}
|
||||
sorter={(a, b) => (a as any).key.localeCompare((b as any).key)}
|
||||
render={(text, record, index) => {
|
||||
const body = <Text>{text}</Text>;
|
||||
return (
|
||||
<ButtonCopyMessage
|
||||
keyMessage={(record as SetupKeyDataTable).key}
|
||||
toCopy={text}
|
||||
body={body}
|
||||
messageText={"Key copied"}
|
||||
styleNotification={{}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Column title="Type" dataIndex="type"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).type.includes(value)}
|
||||
sorter={(a, b) => ((a as any).type.localeCompare((b as any).type))}
|
||||
/>
|
||||
<Column title="Groups" dataIndex="groupsCount" align="center"
|
||||
<Column
|
||||
title="Last Used"
|
||||
dataIndex="last_used"
|
||||
sorter={(a, b) => (a as any).last_used.localeCompare((b as any).last_used)}
|
||||
render={(text, record, index) => {
|
||||
return !(record as SetupKey).used_times ? "never" : timeAgo(text);
|
||||
}}
|
||||
/>
|
||||
<Column
|
||||
title="Groups"
|
||||
dataIndex="groupsCount"
|
||||
align="center"
|
||||
render={(text, record: SetupKeyDataTable, index) => {
|
||||
return renderPopoverGroups(text, record.auto_groups, record)
|
||||
return renderPopoverGroups(text, record.auto_groups, record);
|
||||
}}
|
||||
/>
|
||||
<Column title="Key" dataIndex="key"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).key.includes(value)}
|
||||
sorter={(a, b) => ((a as any).key.localeCompare((b as any).key))}
|
||||
/>
|
||||
<Column
|
||||
title="Expires"
|
||||
dataIndex="expires"
|
||||
render={(text, record, index) => {
|
||||
const body = <Text>{text}</Text>
|
||||
return <ButtonCopyMessage keyMessage={(record as SetupKeyDataTable).key}
|
||||
toCopy={text}
|
||||
body={body} messageText={"Key copied"}
|
||||
styleNotification={{}}/>
|
||||
return formatDate(text);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Column title="Last Used" dataIndex="last_used"
|
||||
sorter={(a, b) => ((a as any).last_used.localeCompare((b as any).last_used))}
|
||||
/>
|
||||
<Column
|
||||
title="State"
|
||||
dataIndex="state"
|
||||
render={(text, record, index) => {
|
||||
return !(record as SetupKey).used_times ? 'never' : timeAgo(text)
|
||||
return text === "valid" ? (
|
||||
<Tag color="green">{text}</Tag>
|
||||
) : (
|
||||
<Tag color="red">{text}</Tag>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Column title="Used Times" dataIndex="used_times"
|
||||
sorter={(a, b) => ((a as any).used_times - ((b as any).used_times))}
|
||||
/>
|
||||
|
||||
<Column title="Expires" dataIndex="expires"
|
||||
sorter={(a, b) => (a as any).state.localeCompare((b as any).state)}
|
||||
/>
|
||||
<Column
|
||||
title=""
|
||||
align="center"
|
||||
render={(text, record, index) => {
|
||||
return formatDate(text)
|
||||
return (
|
||||
<Button
|
||||
style={{
|
||||
color: "rgba(210, 64, 64, 0.85)",
|
||||
}}
|
||||
type="text"
|
||||
onClick={() => showConfirmRevoke(record as SetupKeyDataTable)}
|
||||
>
|
||||
Revoke
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Column title="" align="center"
|
||||
render={(text, record, index) => {
|
||||
return !(record as SetupKeyDataTable).revoked ? (
|
||||
<Dropdown.Button type="text" overlay={actionsMenu}
|
||||
trigger={["click"]}
|
||||
onOpenChange={visible => {
|
||||
if (visible) setSetupKeyToAction(record as SetupKeyDataTable)
|
||||
}}></Dropdown.Button>) : <></>
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
</Card>
|
||||
/>
|
||||
</Table>
|
||||
</Card>
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
</Container>
|
||||
<SetupKeyNew/>
|
||||
{setupNewKeyVisible && <SetupKeyNew />}
|
||||
{confirmModalContextHolder}
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default SetupKeys;
|
||||
export default SetupKeys;
|
||||
|
||||
Reference in New Issue
Block a user