mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Support Auto Groups when updating setup keys (#75)
This commit is contained in:
@@ -295,7 +295,7 @@ const PeerUpdate = () => {
|
||||
) : (
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Update Name"
|
||||
label="Name"
|
||||
rules={[{
|
||||
required: true,
|
||||
message: 'Please add a new name for this peer',
|
||||
|
||||
@@ -1,139 +1,424 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import { actions as setupKeyActions } from '../store/setup-key';
|
||||
import {actions as setupKeyActions} from '../store/setup-key';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Row,
|
||||
Typography,
|
||||
DatePicker,
|
||||
DatePickerProps,
|
||||
Divider,
|
||||
Drawer,
|
||||
Form,
|
||||
Input,
|
||||
Space,
|
||||
List,
|
||||
Radio,
|
||||
Button, Drawer, Form, List, Divider
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Tag,
|
||||
Typography
|
||||
} from "antd";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {QuestionCircleFilled} from "@ant-design/icons";
|
||||
import {SetupKey} from "../store/setup-key/types";
|
||||
import {CloseOutlined, EditOutlined, QuestionCircleFilled} from "@ant-design/icons";
|
||||
import {SetupKey, SetupKeyToSave} from "../store/setup-key/types";
|
||||
import {useOidcAccessToken} from "@axa-fr/react-oidc";
|
||||
const { Text } = Typography;
|
||||
import {Header} from "antd/es/layout/layout";
|
||||
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";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
const {Text} = Typography;
|
||||
|
||||
const customExpiresFormat: DatePickerProps['format'] = value => {
|
||||
return formatDate(value)
|
||||
}
|
||||
|
||||
const customLastUsedFormat: DatePickerProps['format'] = value => {
|
||||
if (value.toString().startsWith("0001")) {
|
||||
return "never"
|
||||
}
|
||||
let ago = timeAgo(value.toString())
|
||||
if (!ago) {
|
||||
return "unused"
|
||||
}
|
||||
return ago
|
||||
}
|
||||
|
||||
interface FormSetupKey extends SetupKey {
|
||||
autoGroupNames: string[]
|
||||
}
|
||||
|
||||
const SetupKeyNew = () => {
|
||||
const {accessToken} = useOidcAccessToken()
|
||||
const dispatch = useDispatch()
|
||||
const setupNewKeyVisible = useSelector((state: RootState) => state.setupKey.setupNewKeyVisible)
|
||||
const setupKey = useSelector((state: RootState) => state.setupKey.setupKey)
|
||||
const createdSetupKey = useSelector((state: RootState) => state.setupKey.createdSetupKey)
|
||||
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 [formSetupKey, setFormSetupKey] = useState({} as SetupKey)
|
||||
const [formSetupKey, setFormSetupKey] = useState({} as FormSetupKey)
|
||||
const [form] = Form.useForm()
|
||||
|
||||
useEffect(() => {
|
||||
setFormSetupKey({ ...setupKey } as SetupKey)
|
||||
form.setFieldsValue(setupKey)
|
||||
if (editName) inputNameRef.current!.focus({
|
||||
cursor: 'end',
|
||||
});
|
||||
}, [editName]);
|
||||
|
||||
useEffect(() => {
|
||||
setTagGroups(groups?.filter(g => g.name != "All").map(g => g.name) || [])
|
||||
}, [groups])
|
||||
|
||||
useEffect(() => {
|
||||
if (!setupKey) return
|
||||
|
||||
let allGroups = new Map<string, Group>();
|
||||
groups.forEach(g => {
|
||||
allGroups.set(g.id!, g)
|
||||
})
|
||||
|
||||
let formKeyGroups :string[] = []
|
||||
|
||||
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 : [],
|
||||
} as FormSetupKey
|
||||
setFormSetupKey(fSetupKey)
|
||||
form.setFieldsValue(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))
|
||||
return {
|
||||
id: formSetupKey.id,
|
||||
name: formSetupKey.name,
|
||||
type: formSetupKey.type,
|
||||
auto_groups: autoGroups,
|
||||
revoked: formSetupKey.revoked,
|
||||
groupsToCreate: groupsToCreate
|
||||
} as SetupKeyToSave
|
||||
}
|
||||
const handleFormSubmit = () => {
|
||||
form.validateFields()
|
||||
.then((values) => {
|
||||
dispatch(setupKeyActions.createSetupKey.request({getAccessTokenSilently:accessToken, payload: formSetupKey}))
|
||||
let setupKeyToSave = createSetupKeyToSave()
|
||||
dispatch(setupKeyActions.saveSetupKey.request({
|
||||
getAccessTokenSilently: accessToken,
|
||||
payload: setupKeyToSave
|
||||
}))
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
console.log('errorInfo', errorInfo)
|
||||
});
|
||||
};
|
||||
|
||||
const setVisibleNewSetupKey = (status:boolean) => {
|
||||
|
||||
const setVisibleNewSetupKey = (status: boolean) => {
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(status));
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
if (createdSetupKey.loading) return
|
||||
if (savedSetupKey.loading) return
|
||||
dispatch(setupKeyActions.setSetupKey({
|
||||
name: '',
|
||||
type: 'reusable'
|
||||
name: "",
|
||||
type: "reusable",
|
||||
key: "",
|
||||
last_used: "",
|
||||
expires: "",
|
||||
state: "valid",
|
||||
auto_groups: new Array()
|
||||
} as SetupKey))
|
||||
setFormSetupKey({} as FormSetupKey)
|
||||
setVisibleNewSetupKey(false)
|
||||
}
|
||||
|
||||
const onChange = (data:any) => {
|
||||
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 {label, value, closable, onClose} = props;
|
||||
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tag
|
||||
color="blue"
|
||||
onMouseDown={onPreventMouseDown}
|
||||
closable={closable}
|
||||
onClose={onClose}
|
||||
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'} `
|
||||
return (
|
||||
<>
|
||||
<Tag
|
||||
color="blue"
|
||||
style={{marginRight: 3}}
|
||||
>
|
||||
<strong>{label}</strong>
|
||||
</Tag>
|
||||
<span style={{fontSize: ".85em"}}>{peersCount}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const dropDownRender = (menu: React.ReactElement) => (
|
||||
<>
|
||||
{menu}
|
||||
<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>
|
||||
</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"/>
|
||||
</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>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{setupKey &&
|
||||
<Drawer
|
||||
title="New setup key"
|
||||
forceRender={true}
|
||||
// width={512}
|
||||
visible={setupNewKeyVisible}
|
||||
bodyStyle={{paddingBottom: 80}}
|
||||
onClose={onCancel}
|
||||
footer={
|
||||
<Space style={{display: 'flex', justifyContent: 'end'}}>
|
||||
<Button disabled={createdSetupKey.loading} onClick={onCancel}>Cancel</Button>
|
||||
<Button disabled={createdSetupKey.loading} type="primary" onClick={handleFormSubmit}>Create</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="Name"
|
||||
label="Name"
|
||||
rules={[{required: true, message: 'Please enter key name'}]}
|
||||
>
|
||||
<Input placeholder="Please enter key name" autoComplete="off"/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="Type"
|
||||
label="Type"
|
||||
rules={[{required: true, message: 'Please enter key type'}]}
|
||||
style={{display: 'flex'}}
|
||||
>
|
||||
<Radio.Group style={{display: 'flex'}}>
|
||||
<Space direction="vertical" style={{flex: 1}}>
|
||||
<List
|
||||
size="large"
|
||||
bordered
|
||||
{setupKey &&
|
||||
<Drawer
|
||||
forceRender={true}
|
||||
headerStyle={{display: "none"}}
|
||||
visible={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}
|
||||
onClick={handleFormSubmit}>{`${formSetupKey.id ? 'Save' : 'Create'}`}</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
|
||||
<Row gutter={16}>
|
||||
<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
|
||||
}]}
|
||||
style={{display: 'flex'}}
|
||||
>
|
||||
<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>
|
||||
</>}
|
||||
>
|
||||
<List.Item>
|
||||
<Radio value={"reusable"}>
|
||||
<Space direction="vertical" size="small">
|
||||
<Text strong>Reusable</Text>
|
||||
<Text>This type of a setup key allows to setup multiple
|
||||
machine</Text>
|
||||
</Space>
|
||||
</Radio>
|
||||
</List.Item>
|
||||
<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>
|
||||
<Input
|
||||
disabled={true}
|
||||
autoComplete="off"/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
}
|
||||
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Divider></Divider>
|
||||
<Button icon={<QuestionCircleFilled/>} type="link" target="_blank"
|
||||
href="https://docs.netbird.io/docs/overview/setup-keys" style={{color: 'rgb(07, 114, 128)'}}>Learn
|
||||
more about setup keys</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
{setupKey.id && formSetupKey.name &&
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="expires"
|
||||
label="Expires"
|
||||
tooltip="The expiration date of the key"
|
||||
>
|
||||
<DatePicker disabled={true} style={{width: '100%'}}
|
||||
format={customExpiresFormat}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
}
|
||||
{setupKey.id && formSetupKey.name &&
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="last_used"
|
||||
label="Last Used"
|
||||
tooltip="The last time the key was used"
|
||||
>
|
||||
<DatePicker disabled={true} style={{width: '100%'}}
|
||||
format={customLastUsedFormat}/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
}
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="type"
|
||||
label="Type"
|
||||
rules={[{required: true, message: 'Please enter key type'}]}
|
||||
style={{display: 'flex'}}
|
||||
>
|
||||
<Radio.Group style={{display: 'flex'}} disabled={setupKey.id}>
|
||||
<Space direction="vertical" style={{flex: 1}}>
|
||||
<List
|
||||
size="large"
|
||||
bordered
|
||||
>
|
||||
<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.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>
|
||||
|
||||
</Drawer>
|
||||
}
|
||||
</Space>
|
||||
</Radio.Group>
|
||||
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<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}]}
|
||||
>
|
||||
<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)}
|
||||
>
|
||||
{
|
||||
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"
|
||||
style={{color: 'rgb(07, 114, 128)'}}>Learn
|
||||
more about setup keys</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
|
||||
</Drawer>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ export function* saveRule(action: ReturnType<typeof actions.saveRule.request>):
|
||||
})
|
||||
))
|
||||
|
||||
|
||||
const resGroups = (responsesGroup as ApiResponse<Rule>[]).filter(r => r.statusCode === 200).map(r => (r.body as Group))
|
||||
|
||||
const currentGroups = [...(yield select(state => state.group.data)) as Rule[]]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ActionType, createAction, createAsyncAction } from 'typesafe-actions';
|
||||
import {SetupKey, SetupKeyRevoke} from './types';
|
||||
import {SetupKey, SetupKeyToSave} from './types';
|
||||
import {
|
||||
ApiError,
|
||||
ChangeResponse,
|
||||
CreateResponse,
|
||||
DeleteResponse,
|
||||
RequestPayload
|
||||
@@ -15,12 +14,13 @@ const actions = {
|
||||
'GET_SETUP_KEYS_FAILURE',
|
||||
)<RequestPayload<null>, SetupKey[], ApiError>(),
|
||||
|
||||
createSetupKey: createAsyncAction(
|
||||
'CREATE_SETUP_KEY_REQUEST',
|
||||
'CREATE_SETUP_KEY_SUCCESS',
|
||||
'CREATE_SETUP_KEY_FAILURE',
|
||||
)<RequestPayload<SetupKey>, CreateResponse<SetupKey | null>, CreateResponse<SetupKey | null>>(),
|
||||
setCreateSetupKey: createAction('SET_CREATE_SETUP_KEY')<CreateResponse<SetupKey | null>>(),
|
||||
saveSetupKey: createAsyncAction(
|
||||
'SAVE_SETUP_KEY_REQUEST',
|
||||
'SAVE_SETUP_KEY_SUCCESS',
|
||||
'SAVE_SETUP_KEY_FAILURE',
|
||||
)<RequestPayload<SetupKeyToSave>, CreateResponse<SetupKey | null>, CreateResponse<SetupKey | null>>(),
|
||||
setSavedSetupKey: createAction('SET_SAVE_SETUP_KEY')<CreateResponse<SetupKey | null>>(),
|
||||
resetSavedSetupKey: createAction('RESET_SAVE_SETUP_KEY')<null>(),
|
||||
|
||||
deleteSetupKey: createAsyncAction(
|
||||
'DELETE_SETUP_KEY_REQUEST',
|
||||
@@ -30,15 +30,6 @@ const actions = {
|
||||
setDeleteSetupKey: createAction('SET_DELETE_SETUP_KEY')<DeleteResponse<string | null>>(),
|
||||
resetDeletedSetupKey: createAction('RESET_DELETE_SETUP_KEY')<null>(),
|
||||
|
||||
revokeSetupKey: createAsyncAction(
|
||||
'REVOKE_SETUP_KEY_REQUEST',
|
||||
'REVOKE_SETUP_KEY_SUCCESS',
|
||||
'REVOKE_SETUP_KEY_FAILURE'
|
||||
)<RequestPayload<SetupKeyRevoke>, ChangeResponse<SetupKey | null>, ChangeResponse<SetupKey | null>>(),
|
||||
setRevokeSetupKey: createAction('SET_REVOKED_SETUP_KEY')<ChangeResponse<SetupKey | null>>(),
|
||||
resetRevokedSetupKey: createAction('RESET_REVOKED_SETUP_KEY')<null>(),
|
||||
|
||||
|
||||
removeSetupKey: createAction('REMOVE_SETUP_KEY')<string>(),
|
||||
setSetupKey: createAction('SET_SETUP_KEY')<SetupKey>(),
|
||||
setSetupNewKeyVisible: createAction('SET_SETUP_NEW_KEY_VISIBLE')<boolean>()
|
||||
|
||||
@@ -12,7 +12,7 @@ type StateType = Readonly<{
|
||||
saving: boolean;
|
||||
deletedSetupKey: DeleteResponse<string | null>;
|
||||
revokedSetupKey: ChangeResponse<SetupKey | null>;
|
||||
createdSetupKey: CreateResponse<SetupKey | null>;
|
||||
savedSetupKey: CreateResponse<SetupKey | null>;
|
||||
setupNewKeyVisible: boolean
|
||||
}>;
|
||||
|
||||
@@ -36,7 +36,7 @@ const initialState: StateType = {
|
||||
error: null,
|
||||
data : null
|
||||
},
|
||||
createdSetupKey: <CreateResponse<SetupKey | null>>{
|
||||
savedSetupKey: <CreateResponse<SetupKey | null>>{
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
@@ -75,18 +75,12 @@ const deletedSetupKey = createReducer<DeleteResponse<string | null>, ActionTypes
|
||||
.handleAction(actions.setDeleteSetupKey, (store, action) => action.payload)
|
||||
.handleAction(actions.resetDeletedSetupKey, (store, action) => initialState.deletedSetupKey);
|
||||
|
||||
const revokedSetupKey = createReducer<ChangeResponse<SetupKey | null>, ActionTypes>(initialState.revokedSetupKey)
|
||||
.handleAction(actions.revokeSetupKey.request, () => initialState.revokedSetupKey)
|
||||
.handleAction(actions.revokeSetupKey.success, (store, action) => action.payload)
|
||||
.handleAction(actions.revokeSetupKey.failure, (store, action) => action.payload)
|
||||
.handleAction(actions.setRevokeSetupKey, (store, action) => action.payload)
|
||||
.handleAction(actions.resetRevokedSetupKey, () => initialState.revokedSetupKey)
|
||||
|
||||
const createdSetupKey = createReducer<CreateResponse<SetupKey | null>, ActionTypes>(initialState.createdSetupKey)
|
||||
.handleAction(actions.createSetupKey.request, () => initialState.createdSetupKey)
|
||||
.handleAction(actions.createSetupKey.success, (store, action) => action.payload)
|
||||
.handleAction(actions.createSetupKey.failure, (store, action) => action.payload)
|
||||
.handleAction(actions.setCreateSetupKey, (store, action) => action.payload)
|
||||
const savedSetupKey = createReducer<CreateResponse<SetupKey | null>, ActionTypes>(initialState.savedSetupKey)
|
||||
.handleAction(actions.saveSetupKey.request, () => initialState.savedSetupKey)
|
||||
.handleAction(actions.saveSetupKey.success, (store, action) => action.payload)
|
||||
.handleAction(actions.saveSetupKey.failure, (store, action) => action.payload)
|
||||
.handleAction(actions.setSavedSetupKey, (store, action) => action.payload)
|
||||
.handleAction(actions.resetSavedSetupKey, () => initialState.savedSetupKey)
|
||||
|
||||
const setupNewKeyVisible = createReducer<boolean, ActionTypes>(initialState.setupNewKeyVisible)
|
||||
.handleAction(actions.setSetupNewKeyVisible, (store, action) => action.payload)
|
||||
@@ -98,7 +92,6 @@ export default combineReducers({
|
||||
failed,
|
||||
saving,
|
||||
deletedSetupKey,
|
||||
revokedSetupKey,
|
||||
createdSetupKey,
|
||||
savedSetupKey: savedSetupKey,
|
||||
setupNewKeyVisible
|
||||
});
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
import {all, call, put, select, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse, ChangeResponse, CreateResponse, DeleteResponse} from '../../services/api-client/types';
|
||||
import {SetupKey, SetupKeyRevoke} from './types'
|
||||
import {ApiError, ApiResponse, CreateResponse, DeleteResponse} from '../../services/api-client/types';
|
||||
import {SetupKey, SetupKeyToSave} from './types'
|
||||
import service from './service';
|
||||
import actions from './actions';
|
||||
import serviceGroup from "../group/service";
|
||||
import {Group} from "../group/types";
|
||||
import {actions as groupActions} from "../group";
|
||||
|
||||
export function* getSetupKeys(action: ReturnType<typeof actions.getSetupKeys.request>): Generator {
|
||||
try {
|
||||
const effect = yield call(service.getSetupKeys, action.payload);
|
||||
const response = effect as ApiResponse<SetupKey[]>;
|
||||
|
||||
yield put(actions.getSetupKeys.success(response.body));
|
||||
yield put(actions.getSetupKeys.success(response.body.map(k => {
|
||||
// always set auto_groups even if absent (avoid null)
|
||||
if (k.auto_groups) {
|
||||
return k
|
||||
}
|
||||
return {...k, auto_groups: []}
|
||||
})));
|
||||
} catch (err) {
|
||||
yield put(actions.getSetupKeys.failure(err as ApiError));
|
||||
}
|
||||
}
|
||||
|
||||
export function* setCreateSetupKey(action: ReturnType<typeof actions.setCreateSetupKey>): Generator {
|
||||
yield put(actions.setCreateSetupKey(action.payload))
|
||||
export function* setCreateSetupKey(action: ReturnType<typeof actions.setSavedSetupKey>): Generator {
|
||||
yield put(actions.setSavedSetupKey(action.payload))
|
||||
}
|
||||
|
||||
export function* createSetupKey(action: ReturnType<typeof actions.createSetupKey.request>): Generator {
|
||||
export function* saveSetupKey(action: ReturnType<typeof actions.saveSetupKey.request>): Generator {
|
||||
try {
|
||||
yield put(actions.setCreateSetupKey({
|
||||
yield put(actions.setSavedSetupKey({
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
@@ -29,10 +38,46 @@ export function* createSetupKey(action: ReturnType<typeof actions.createSetupKey
|
||||
data: null
|
||||
} as CreateResponse<SetupKey | null>))
|
||||
|
||||
const effect = yield call(service.createSetupKey, action.payload);
|
||||
const keyToSave = action.payload.payload
|
||||
|
||||
let groupsToCreate = keyToSave.groupsToCreate
|
||||
if (!groupsToCreate) {
|
||||
groupsToCreate = []
|
||||
}
|
||||
|
||||
// first, create groups that were newly added by user
|
||||
const responsesGroup = yield all(groupsToCreate.map(g => call(serviceGroup.createGroup, {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: { name: g }
|
||||
})
|
||||
))
|
||||
|
||||
const resGroups = (responsesGroup as ApiResponse<Group>[]).filter(r => r.statusCode === 200).map(g => (g.body as Group)).map(g => g.id)
|
||||
const newGroups = [...keyToSave.auto_groups, ...resGroups]
|
||||
let effect
|
||||
if (!keyToSave.id) {
|
||||
effect = yield call(service.createSetupKey, {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: {
|
||||
name: keyToSave.name,
|
||||
auto_groups: newGroups,
|
||||
type: keyToSave.type
|
||||
} as SetupKeyToSave
|
||||
});
|
||||
} else {
|
||||
effect = yield call(service.editSetupKey, {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: {
|
||||
id: keyToSave.id,
|
||||
name: keyToSave.name,
|
||||
revoked: keyToSave.revoked,
|
||||
auto_groups: newGroups,
|
||||
} as SetupKeyToSave
|
||||
});
|
||||
}
|
||||
const response = effect as ApiResponse<SetupKey>;
|
||||
|
||||
yield put(actions.createSetupKey.success({
|
||||
yield put(actions.saveSetupKey.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
@@ -40,11 +85,10 @@ export function* createSetupKey(action: ReturnType<typeof actions.createSetupKey
|
||||
data: response.body
|
||||
} as CreateResponse<SetupKey | null>));
|
||||
|
||||
const setupKeys = [...(yield select(state => state.setupKey.data)) as SetupKey[]]
|
||||
setupKeys.unshift(response.body)
|
||||
yield put(actions.getSetupKeys.success(setupKeys));
|
||||
yield put(groupActions.getGroups.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
|
||||
yield put(actions.getSetupKeys.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
|
||||
} catch (err) {
|
||||
yield put(actions.createSetupKey.failure({
|
||||
yield put(actions.saveSetupKey.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
@@ -92,53 +136,11 @@ export function* deleteSetupKey(action: ReturnType<typeof actions.deleteSetupKey
|
||||
}
|
||||
}
|
||||
|
||||
export function* revokeSetupKey(action: ReturnType<typeof actions.revokeSetupKey.request>): Generator {
|
||||
try {
|
||||
yield put(actions.setRevokeSetupKey({
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null
|
||||
} as ChangeResponse<SetupKey | null>))
|
||||
|
||||
const effect = yield call(service.revokeSetupKey, action.payload);
|
||||
const response = effect as ApiResponse<SetupKey>;
|
||||
|
||||
yield put(actions.revokeSetupKey.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body
|
||||
} as ChangeResponse<SetupKey | null>));
|
||||
|
||||
const setupKeys = [...(yield select(state => state.setupKey.data)) as SetupKey[]]
|
||||
let setupKey = setupKeys.find(s => s.id === response.body.id) as SetupKey
|
||||
if (setupKey) {
|
||||
setupKey.revoked = response.body.revoked
|
||||
setupKey.valid = response.body.valid
|
||||
setupKey.state = response.body.state
|
||||
setupKey.expires = response.body.expires
|
||||
}
|
||||
yield put(actions.getSetupKeys.success(setupKeys));
|
||||
} catch (err) {
|
||||
yield put(actions.createSetupKey.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: err as ApiError,
|
||||
data: null
|
||||
} as CreateResponse<SetupKey | null>));
|
||||
}
|
||||
}
|
||||
|
||||
export default function* sagas(): Generator {
|
||||
yield all([
|
||||
takeLatest(actions.getSetupKeys.request, getSetupKeys),
|
||||
takeLatest(actions.createSetupKey.request, createSetupKey),
|
||||
takeLatest(actions.deleteSetupKey.request, deleteSetupKey),
|
||||
takeLatest(actions.revokeSetupKey.request, revokeSetupKey)
|
||||
takeLatest(actions.saveSetupKey.request, saveSetupKey),
|
||||
takeLatest(actions.deleteSetupKey.request, deleteSetupKey)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ApiResponse, RequestPayload} from '../../services/api-client/types';
|
||||
import { apiClient } from '../../services/api-client';
|
||||
import {SetupKey, SetupKeyNew, SetupKeyRevoke} from './types';
|
||||
import {SetupKey, SetupKeyToSave} from './types';
|
||||
|
||||
export default {
|
||||
async getSetupKeys(payload:RequestPayload<null>): Promise<ApiResponse<SetupKey[]>> {
|
||||
@@ -15,22 +15,19 @@ export default {
|
||||
payload
|
||||
);
|
||||
},
|
||||
async revokeSetupKey(payload:RequestPayload<SetupKeyRevoke>): Promise<ApiResponse<SetupKey>> {
|
||||
return apiClient.put<SetupKey>(
|
||||
`/api/setup-keys/` + payload.payload.id,
|
||||
payload
|
||||
);
|
||||
},
|
||||
async renameSetupKey(payload:RequestPayload<any>): Promise<ApiResponse<SetupKey>> {
|
||||
return apiClient.put<SetupKey>(
|
||||
`/api/setup-keys/` + payload.payload.id,
|
||||
payload
|
||||
);
|
||||
},
|
||||
async createSetupKey(payload:RequestPayload<SetupKey>): Promise<ApiResponse<SetupKey>> {
|
||||
async createSetupKey(payload:RequestPayload<SetupKeyToSave>): Promise<ApiResponse<SetupKey>> {
|
||||
return apiClient.post<SetupKey>(
|
||||
`/api/setup-keys`,
|
||||
payload
|
||||
);
|
||||
},
|
||||
async editSetupKey(payload:RequestPayload<SetupKeyToSave>): Promise<ApiResponse<SetupKey>> {
|
||||
const id = payload.payload.id
|
||||
// @ts-ignore
|
||||
delete payload.payload.id
|
||||
return apiClient.put<SetupKey>(
|
||||
`/api/setup-keys/${id}`,
|
||||
payload
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import {Group} from "../group/types";
|
||||
|
||||
export interface SetupKey {
|
||||
expires: string;
|
||||
id: string;
|
||||
@@ -9,15 +11,10 @@ export interface SetupKey {
|
||||
type: string;
|
||||
used_times: number;
|
||||
valid: boolean;
|
||||
auto_groups: string[]
|
||||
}
|
||||
|
||||
export interface SetupKeyNew {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface SetupKeyRevoke {
|
||||
id: string;
|
||||
revoked: boolean;
|
||||
export interface SetupKeyToSave extends SetupKey
|
||||
{
|
||||
groupsToCreate: string[]
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ export const transformGroupedDataTable = (routes:Route[],peerIPToName:PeerIPToNa
|
||||
}
|
||||
})
|
||||
let groupDataTableRoutes = transformDataTable(listedRoutes,peerIPToName)
|
||||
console.log(groupDataTableRoutes.length)
|
||||
groupedRoutes.push({
|
||||
key: p.toString(),
|
||||
network_id: lastRoute!.network_id,
|
||||
@@ -78,6 +77,5 @@ export const transformGroupedDataTable = (routes:Route[],peerIPToName:PeerIPToNa
|
||||
groupedRoutes: groupDataTableRoutes,
|
||||
})
|
||||
})
|
||||
console.log(groupedRoutes)
|
||||
return groupedRoutes
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export const AccessControl = () => {
|
||||
if (savedRule.loading) {
|
||||
message.loading({ content: 'Saving...', key: saveKey, duration: 0, style: styleNotification })
|
||||
} else if (savedRule.success) {
|
||||
message.success({ content: 'Rule has been successfully updated.', key: saveKey, duration: 2, style: styleNotification });
|
||||
message.success({ content: 'Rule has been successfully saved.', key: saveKey, duration: 2, style: styleNotification });
|
||||
dispatch(ruleActions.setSetupNewRuleVisible(false))
|
||||
dispatch(ruleActions.setSavedRule({ ...savedRule, success: false }))
|
||||
dispatch(ruleActions.resetSavedRule(null))
|
||||
|
||||
@@ -67,7 +67,7 @@ export const Peers = () => {
|
||||
|
||||
const [textToSearch, setTextToSearch] = useState('');
|
||||
const [optionOnOff, setOptionOnOff] = useState('all');
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [dataTable, setDataTable] = useState([] as PeerDataTable[]);
|
||||
const [peerToAction, setPeerToAction] = useState(null as PeerDataTable | null);
|
||||
|
||||
@@ -82,7 +82,7 @@ export const Peers = () => {
|
||||
const itemsMenuAction = [
|
||||
{
|
||||
key: "view",
|
||||
label: (<Button type="text" block onClick={() => onClickViewRule()}>View</Button>)
|
||||
label: (<Button type="text" block onClick={() => onClickViewPeer()}>View</Button>)
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
@@ -300,7 +300,7 @@ export const Peers = () => {
|
||||
|
||||
}
|
||||
|
||||
const onClickViewRule = () => {
|
||||
const onClickViewPeer = () => {
|
||||
dispatch(peerActions.setUpdateGroupsVisible(true))
|
||||
dispatch(peerActions.setPeer(peerToAction as Peer))
|
||||
}
|
||||
|
||||
@@ -1,38 +1,46 @@
|
||||
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 {RootState} from "typesafe-actions";
|
||||
import {actions as setupKeyActions} from '../store/setup-key';
|
||||
import {Container} from "../components/Container";
|
||||
import {useOidcAccessToken} from '@axa-fr/react-oidc';
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Typography,
|
||||
Table,
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Tag,
|
||||
Col,
|
||||
Dropdown,
|
||||
Input,
|
||||
Space,
|
||||
Menu,
|
||||
message,
|
||||
Modal, Popover,
|
||||
Radio,
|
||||
RadioChangeEvent,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Alert, Select, Modal, Button, message, Drawer, Form, List
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Typography
|
||||
} from "antd";
|
||||
import {SetupKey, SetupKeyRevoke} from "../store/setup-key/types";
|
||||
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";
|
||||
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { Column } = Table;
|
||||
const { confirm } = Modal;
|
||||
const {Title, Text, Paragraph} = Typography;
|
||||
const {Column} = Table;
|
||||
const {confirm} = Modal;
|
||||
|
||||
interface SetupKeyDataTable extends SetupKey {
|
||||
key: string
|
||||
groupsCount: number
|
||||
}
|
||||
|
||||
export const SetupKeys = () => {
|
||||
@@ -43,16 +51,16 @@ export const SetupKeys = () => {
|
||||
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 revokedSetupKey = useSelector((state: RootState) => state.setupKey.revokedSetupKey);
|
||||
const createdSetupKey = useSelector((state: RootState) => state.setupKey.createdSetupKey);
|
||||
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 [pageSize, setPageSize] = useState(5);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [dataTable, setDataTable] = useState([] as SetupKeyDataTable[]);
|
||||
const [setupKeyToAction, setSetupKeyToAction] = useState(null as SetupKeyDataTable | null);
|
||||
|
||||
const styleNotification = { marginTop: 85 }
|
||||
const styleNotification = {marginTop: 85}
|
||||
|
||||
const pageSizeOptions = [
|
||||
{label: "5", value: "5"},
|
||||
@@ -67,19 +75,21 @@ export const SetupKeys = () => {
|
||||
key: "revoke",
|
||||
label: (<Button type="text" onClick={() => showConfirmRevoke()}>Revoke</Button>)
|
||||
},
|
||||
/*{
|
||||
key: "delete",
|
||||
label: (<Button type="text" onClick={() => showConfirmDelete()}>Delete</Button>)
|
||||
}*/
|
||||
]
|
||||
const actionsMenu = (<Menu items={itemsMenuAction} ></Menu>)
|
||||
{
|
||||
key: "edit",
|
||||
label: (<Button type="text" onClick={() => onClickEditSetupKey()}>View</Button>)
|
||||
},
|
||||
|
||||
const transformDataTable = (d:SetupKey[]):SetupKeyDataTable[] => {
|
||||
return d.map(p => ({ ...p } as SetupKeyDataTable))
|
||||
]
|
||||
const actionsMenu = (<Menu items={itemsMenuAction}></Menu>)
|
||||
|
||||
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:accessToken, payload: null}));
|
||||
dispatch(setupKeyActions.getSetupKeys.request({getAccessTokenSilently: accessToken, payload: null}));
|
||||
dispatch(groupActions.getGroups.request({getAccessTokenSilently: accessToken, payload: null}));
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -93,52 +103,61 @@ export const SetupKeys = () => {
|
||||
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.', key: deleteKey, duration: 2, style: styleNotification });
|
||||
dispatch(setupKeyActions.setDeleteSetupKey({ ...deletedSetupKey, success: false }))
|
||||
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 }))
|
||||
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 revokeKey = 'revoking';
|
||||
const createKey = 'saving';
|
||||
useEffect(() => {
|
||||
if (revokedSetupKey.loading) {
|
||||
message.loading({ content: 'Revoking...', key: revokeKey, duration: 0, style: styleNotification })
|
||||
} else if (revokedSetupKey.success) {
|
||||
message.success({ content: 'Setup key has been successfully revoked.', key: revokeKey, duration: 2, style: styleNotification });
|
||||
dispatch(setupKeyActions.resetRevokedSetupKey(null))
|
||||
} else if (revokedSetupKey.error) {
|
||||
message.error({ content: 'Failed to revoke setup key. You might not have enough permissions.', key: revokeKey, duration: 2, style: styleNotification });
|
||||
dispatch(setupKeyActions.resetRevokedSetupKey(null))
|
||||
}
|
||||
}, [revokedSetupKey])
|
||||
|
||||
const createKey = 'creating';
|
||||
useEffect(() => {
|
||||
if (createdSetupKey.loading) {
|
||||
message.loading({ content: 'Creating...', key: createKey, duration: 0, style: styleNotification });
|
||||
} else if (createdSetupKey.success) {
|
||||
message.success({ content: 'Setup key has been successfully created.', key: createKey, duration: 2, style: styleNotification });
|
||||
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.setCreateSetupKey({ ...createdSetupKey, success: false }));
|
||||
} else if (createdSetupKey.error) {
|
||||
message.error({ content: 'Failed to create setup key. You might not have enough permissions.', key: createKey, duration: 2, style: styleNotification });
|
||||
dispatch(setupKeyActions.setCreateSetupKey({ ...createdSetupKey, error: 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.',
|
||||
key: createKey,
|
||||
duration: 2,
|
||||
style: styleNotification
|
||||
});
|
||||
dispatch(setupKeyActions.setSavedSetupKey({...savedSetupKey, error: null}));
|
||||
dispatch(setupKeyActions.resetSavedSetupKey(null))
|
||||
}
|
||||
}, [createdSetupKey])
|
||||
}, [savedSetupKey])
|
||||
|
||||
const filterDataTable = ():SetupKey[] => {
|
||||
const filterDataTable = (): SetupKey[] => {
|
||||
const t = textToSearch.toLowerCase().trim()
|
||||
let f:SetupKey[] = [...setupKeys]
|
||||
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 = 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
|
||||
@@ -153,7 +172,7 @@ export const SetupKeys = () => {
|
||||
setDataTable(transformDataTable(data))
|
||||
}
|
||||
|
||||
const onChangeValidAll = ({ target: { value } }: RadioChangeEvent) => {
|
||||
const onChangeValidAll = ({target: {value}}: RadioChangeEvent) => {
|
||||
setOptionValidAll(value)
|
||||
}
|
||||
|
||||
@@ -163,7 +182,7 @@ export const SetupKeys = () => {
|
||||
|
||||
const showConfirmDelete = () => {
|
||||
confirm({
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
icon: <ExclamationCircleOutlined/>,
|
||||
width: 600,
|
||||
content: <Space direction="vertical" size="small">
|
||||
{setupKeyToAction &&
|
||||
@@ -175,7 +194,10 @@ export const SetupKeys = () => {
|
||||
</Space>,
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
dispatch(setupKeyActions.deleteSetupKey.request({getAccessTokenSilently:accessToken, payload: setupKeyToAction ? setupKeyToAction.id : ''}));
|
||||
dispatch(setupKeyActions.deleteSetupKey.request({
|
||||
getAccessTokenSilently: accessToken,
|
||||
payload: setupKeyToAction ? setupKeyToAction.id : ''
|
||||
}));
|
||||
},
|
||||
onCancel() {
|
||||
setSetupKeyToAction(null);
|
||||
@@ -185,7 +207,7 @@ export const SetupKeys = () => {
|
||||
|
||||
const showConfirmRevoke = () => {
|
||||
confirm({
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
icon: <ExclamationCircleOutlined/>,
|
||||
width: 600,
|
||||
content: <Space direction="vertical" size="small">
|
||||
{setupKeyToAction &&
|
||||
@@ -197,7 +219,15 @@ export const SetupKeys = () => {
|
||||
</Space>,
|
||||
okType: 'danger',
|
||||
onOk() {
|
||||
dispatch(setupKeyActions.revokeSetupKey.request({getAccessTokenSilently:accessToken, payload: { id: setupKeyToAction ? setupKeyToAction.id : null,revoked: true } as SetupKeyRevoke}));
|
||||
dispatch(setupKeyActions.saveSetupKey.request({
|
||||
getAccessTokenSilently: accessToken,
|
||||
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);
|
||||
@@ -206,25 +236,124 @@ export const SetupKeys = () => {
|
||||
}
|
||||
|
||||
const onClickAddNewSetupKey = () => {
|
||||
const autoGroups : string[] = []
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(true));
|
||||
dispatch(setupKeyActions.setSetupKey({
|
||||
name: '',
|
||||
type: 'reusable'
|
||||
name: "",
|
||||
type: "reusable",
|
||||
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,
|
||||
} 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,
|
||||
} as SetupKey))
|
||||
}
|
||||
|
||||
const renderPopoverGroups = (label: string, rowGroups: string[] | string[] | null, setupKeyToAction: SetupKeyDataTable) => {
|
||||
|
||||
let groupsMap = new Map<string, Group>();
|
||||
groups.forEach(g => {
|
||||
groupsMap.set(g.id!, g)
|
||||
})
|
||||
|
||||
let displayGroups :Group[] = []
|
||||
if (rowGroups) {
|
||||
displayGroups = rowGroups.filter(g => groupsMap.get(g)).map(g => groupsMap.get(g)!)
|
||||
}
|
||||
|
||||
let btn = <Button type="link" onClick={() => setUpdateGroupsVisible(setupKeyToAction, true)}>{displayGroups.length}</Button>
|
||||
if (!displayGroups || displayGroups!.length < 1) {
|
||||
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'} `
|
||||
return (
|
||||
<div key={i}>
|
||||
<Tag
|
||||
color="blue"
|
||||
style={{marginRight: 3}}
|
||||
>
|
||||
<strong>{_g.name}</strong>
|
||||
</Tag>
|
||||
<span style={{fontSize: ".85em"}}>{peersCount}</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
const mainContent = (<Space direction="vertical">{content}</Space>)
|
||||
let popoverPlacement = "top"
|
||||
if (content && content.length > 5) {
|
||||
popoverPlacement = "rightTop"
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover placement={popoverPlacement as TooltipPlacement} key={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
|
||||
}
|
||||
const autoGroups : string[] = []
|
||||
dispatch(setupKeyActions.setSetupKey({
|
||||
name: "",
|
||||
type: "reusable",
|
||||
auto_groups: autoGroups
|
||||
} as SetupKey))
|
||||
dispatch(setupKeyActions.setSetupNewKeyVisible(false))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<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>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">
|
||||
@@ -235,7 +364,8 @@ export const SetupKeys = () => {
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
/>
|
||||
<Select 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}
|
||||
@@ -252,11 +382,16 @@ export const SetupKeys = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
{failed &&
|
||||
<Alert message={failed.code} description={failed.message} type="error" showIcon closable/>
|
||||
<Alert message={failed.code} description={failed.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`)}}
|
||||
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}}
|
||||
@@ -265,12 +400,18 @@ export const SetupKeys = () => {
|
||||
<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}</Button>
|
||||
}}
|
||||
defaultSortOrder='ascend'
|
||||
/>
|
||||
|
||||
<Column title="State" dataIndex="state"
|
||||
render={(text, record, index) => {
|
||||
return (text === 'valid') ? <Tag color="green">{text}</Tag> : <Tag color="red">{text}</Tag>
|
||||
return (text === 'valid') ? <Tag color="green">{text}</Tag> :
|
||||
<Tag color="red">{text}</Tag>
|
||||
}}
|
||||
sorter={(a, b) => ((a as any).state.localeCompare((b as any).state))}
|
||||
/>
|
||||
@@ -279,19 +420,25 @@ export const SetupKeys = () => {
|
||||
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"
|
||||
render={(text, record: SetupKeyDataTable, index) => {
|
||||
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))}
|
||||
render={(text, record, index) => {
|
||||
return <ButtonCopyMessage keyMessage={(record as SetupKeyDataTable).key} text={text} messageText={`Key copied!`} styleNotification={{}}/>
|
||||
return <ButtonCopyMessage keyMessage={(record as SetupKeyDataTable).key}
|
||||
text={text} messageText={`Key copied!`}
|
||||
styleNotification={{}}/>
|
||||
}}
|
||||
/>
|
||||
|
||||
<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 ? 'unused' : timeAgo(text)
|
||||
return !(record as SetupKey).used_times ? 'never' : timeAgo(text)
|
||||
}}
|
||||
/>
|
||||
<Column title="Used Times" dataIndex="used_times"
|
||||
@@ -307,10 +454,11 @@ export const SetupKeys = () => {
|
||||
<Column title="" align="center"
|
||||
render={(text, record, index) => {
|
||||
return !(record as SetupKeyDataTable).revoked ? (
|
||||
<Dropdown.Button type="text" overlay={actionsMenu} trigger={["click"]}
|
||||
onVisibleChange={visible => {
|
||||
if (visible) setSetupKeyToAction(record as SetupKeyDataTable)
|
||||
}}></Dropdown.Button>) : <></>
|
||||
<Dropdown.Button type="text" overlay={actionsMenu}
|
||||
trigger={["click"]}
|
||||
onVisibleChange={visible => {
|
||||
if (visible) setSetupKeyToAction(record as SetupKeyDataTable)
|
||||
}}></Dropdown.Button>) : <></>
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
Reference in New Issue
Block a user