diff --git a/src/components/PeerUpdate.tsx b/src/components/PeerUpdate.tsx index 8522a9b..372d90b 100644 --- a/src/components/PeerUpdate.tsx +++ b/src/components/PeerUpdate.tsx @@ -295,7 +295,7 @@ const PeerUpdate = () => { ) : ( { + 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(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(); + 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) => { + event.preventDefault(); + event.stopPropagation(); + }; + + return ( + + {value} + + ); + } + + const optionRender = (label: string) => { + let peersCount = '' + const g = groups.find(_g => _g.name === label) + if (g) peersCount = ` - ${g.peers_count || 0} ${(!g.peers_count || parseInt(g.peers_count) !== 1) ? 'peers' : 'peer'} ` + return ( + <> + + {label} + + {peersCount} + + ) + } + + const dropDownRender = (menu: React.ReactElement) => ( + <> + {menu} + + + + Add new group by pressing "Enter" + + + + + + + + + ) + + const handleChangeTags = (value: string[]) => { + let validatedValues: string[] = [] + value.forEach(function (v) { + if (v.trim().length) { + validatedValues.push(v) + } + }) + setSelectedTagGroups(validatedValues) + }; + + const inputLabel = (text: any) => ( + <> + {text} + {formSetupKey.state} + + ) return ( <> - {setupKey && - - - - - } - > -
- - - - - - - - - - - + + + + } + > + + + +
+ + + {!editName && setupKey.id && + + } + + + {!editName && setupKey.id && formSetupKey.name ? ( +
toggleEditName(true)}>{formSetupKey.name ? formSetupKey.name : setupKey.name} +
+ ) : ( + + toggleEditName(false)} + onBlur={() => toggleEditName(false)} + autoComplete="off"/> + )} + +
+
+ + {setupKey.id && formSetupKey.name && + + + Key + {formSetupKey.state} + } > - - - - Reusable - This type of a setup key allows to setup multiple - machine - - - - - - - One-off - This key can be used only once - - - - + + + + } - -
-
- - - - - -
-
+ {setupKey.id && formSetupKey.name && + + + + + + } + {setupKey.id && formSetupKey.name && + + + + + + } + + + + + + + + + Reusable + This type of a setup key allows to enroll multiple + machines + + + + + + + One-off + This key can be used only once + + + + -
- } + + + +
+ + + + + + + + + + + + + + + } ) } diff --git a/src/store/rule/sagas.ts b/src/store/rule/sagas.ts index 08a33a3..d632b56 100644 --- a/src/store/rule/sagas.ts +++ b/src/store/rule/sagas.ts @@ -53,7 +53,6 @@ export function* saveRule(action: ReturnType): }) )) - const resGroups = (responsesGroup as ApiResponse[]).filter(r => r.statusCode === 200).map(r => (r.body as Group)) const currentGroups = [...(yield select(state => state.group.data)) as Rule[]] diff --git a/src/store/setup-key/actions.ts b/src/store/setup-key/actions.ts index 1caedf0..214159e 100644 --- a/src/store/setup-key/actions.ts +++ b/src/store/setup-key/actions.ts @@ -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', ), SetupKey[], ApiError>(), - createSetupKey: createAsyncAction( - 'CREATE_SETUP_KEY_REQUEST', - 'CREATE_SETUP_KEY_SUCCESS', - 'CREATE_SETUP_KEY_FAILURE', - ), CreateResponse, CreateResponse>(), - setCreateSetupKey: createAction('SET_CREATE_SETUP_KEY')>(), + saveSetupKey: createAsyncAction( + 'SAVE_SETUP_KEY_REQUEST', + 'SAVE_SETUP_KEY_SUCCESS', + 'SAVE_SETUP_KEY_FAILURE', + ), CreateResponse, CreateResponse>(), + setSavedSetupKey: createAction('SET_SAVE_SETUP_KEY')>(), + resetSavedSetupKey: createAction('RESET_SAVE_SETUP_KEY')(), deleteSetupKey: createAsyncAction( 'DELETE_SETUP_KEY_REQUEST', @@ -30,15 +30,6 @@ const actions = { setDeleteSetupKey: createAction('SET_DELETE_SETUP_KEY')>(), resetDeletedSetupKey: createAction('RESET_DELETE_SETUP_KEY')(), - revokeSetupKey: createAsyncAction( - 'REVOKE_SETUP_KEY_REQUEST', - 'REVOKE_SETUP_KEY_SUCCESS', - 'REVOKE_SETUP_KEY_FAILURE' - ), ChangeResponse, ChangeResponse>(), - setRevokeSetupKey: createAction('SET_REVOKED_SETUP_KEY')>(), - resetRevokedSetupKey: createAction('RESET_REVOKED_SETUP_KEY')(), - - removeSetupKey: createAction('REMOVE_SETUP_KEY')(), setSetupKey: createAction('SET_SETUP_KEY')(), setSetupNewKeyVisible: createAction('SET_SETUP_NEW_KEY_VISIBLE')() diff --git a/src/store/setup-key/reducer.ts b/src/store/setup-key/reducer.ts index a45f03f..1dcf35f 100644 --- a/src/store/setup-key/reducer.ts +++ b/src/store/setup-key/reducer.ts @@ -12,7 +12,7 @@ type StateType = Readonly<{ saving: boolean; deletedSetupKey: DeleteResponse; revokedSetupKey: ChangeResponse; - createdSetupKey: CreateResponse; + savedSetupKey: CreateResponse; setupNewKeyVisible: boolean }>; @@ -36,7 +36,7 @@ const initialState: StateType = { error: null, data : null }, - createdSetupKey: >{ + savedSetupKey: >{ loading: false, success: false, failure: false, @@ -75,18 +75,12 @@ const deletedSetupKey = createReducer, ActionTypes .handleAction(actions.setDeleteSetupKey, (store, action) => action.payload) .handleAction(actions.resetDeletedSetupKey, (store, action) => initialState.deletedSetupKey); -const revokedSetupKey = createReducer, 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, 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, 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(initialState.setupNewKeyVisible) .handleAction(actions.setSetupNewKeyVisible, (store, action) => action.payload) @@ -98,7 +92,6 @@ export default combineReducers({ failed, saving, deletedSetupKey, - revokedSetupKey, - createdSetupKey, + savedSetupKey: savedSetupKey, setupNewKeyVisible }); diff --git a/src/store/setup-key/sagas.ts b/src/store/setup-key/sagas.ts index 0086b1e..99b339f 100644 --- a/src/store/setup-key/sagas.ts +++ b/src/store/setup-key/sagas.ts @@ -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): Generator { try { const effect = yield call(service.getSetupKeys, action.payload); const response = effect as ApiResponse; - 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): Generator { - yield put(actions.setCreateSetupKey(action.payload)) +export function* setCreateSetupKey(action: ReturnType): Generator { + yield put(actions.setSavedSetupKey(action.payload)) } -export function* createSetupKey(action: ReturnType): Generator { +export function* saveSetupKey(action: ReturnType): 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)) - 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[]).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; - 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)); - 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): Generator { - try { - yield put(actions.setRevokeSetupKey({ - loading: true, - success: false, - failure: false, - error: null, - data: null - } as ChangeResponse)) - - const effect = yield call(service.revokeSetupKey, action.payload); - const response = effect as ApiResponse; - - yield put(actions.revokeSetupKey.success({ - loading: false, - success: true, - failure: false, - error: null, - data: response.body - } as ChangeResponse)); - - 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)); - } -} - 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) ]); } diff --git a/src/store/setup-key/service.ts b/src/store/setup-key/service.ts index 6b40715..a6fb0e7 100644 --- a/src/store/setup-key/service.ts +++ b/src/store/setup-key/service.ts @@ -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): Promise> { @@ -15,22 +15,19 @@ export default { payload ); }, - async revokeSetupKey(payload:RequestPayload): Promise> { - return apiClient.put( - `/api/setup-keys/` + payload.payload.id, - payload - ); - }, - async renameSetupKey(payload:RequestPayload): Promise> { - return apiClient.put( - `/api/setup-keys/` + payload.payload.id, - payload - ); - }, - async createSetupKey(payload:RequestPayload): Promise> { + async createSetupKey(payload:RequestPayload): Promise> { return apiClient.post( `/api/setup-keys`, payload ); }, + async editSetupKey(payload:RequestPayload): Promise> { + const id = payload.payload.id + // @ts-ignore + delete payload.payload.id + return apiClient.put( + `/api/setup-keys/${id}`, + payload + ); + }, }; diff --git a/src/store/setup-key/types.ts b/src/store/setup-key/types.ts index 29eb320..476b7b9 100644 --- a/src/store/setup-key/types.ts +++ b/src/store/setup-key/types.ts @@ -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[] } diff --git a/src/utils/routes.ts b/src/utils/routes.ts index cf79ae9..6355b15 100644 --- a/src/utils/routes.ts +++ b/src/utils/routes.ts @@ -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 } \ No newline at end of file diff --git a/src/views/AccessControl.tsx b/src/views/AccessControl.tsx index 2315083..8f8e57c 100644 --- a/src/views/AccessControl.tsx +++ b/src/views/AccessControl.tsx @@ -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)) diff --git a/src/views/Peers.tsx b/src/views/Peers.tsx index ae73a58..6773f4b 100644 --- a/src/views/Peers.tsx +++ b/src/views/Peers.tsx @@ -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: () + label: () }, { key: "delete", @@ -300,7 +300,7 @@ export const Peers = () => { } - const onClickViewRule = () => { + const onClickViewPeer = () => { dispatch(peerActions.setUpdateGroupsVisible(true)) dispatch(peerActions.setPeer(peerToAction as Peer)) } diff --git a/src/views/SetupKeys.tsx b/src/views/SetupKeys.tsx index 61f2d60..5550175 100644 --- a/src/views/SetupKeys.tsx +++ b/src/views/SetupKeys.tsx @@ -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: () }, - /*{ - key: "delete", - label: () - }*/ - ] - const actionsMenu = () + { + key: "edit", + label: () + }, - const transformDataTable = (d:SetupKey[]):SetupKeyDataTable[] => { - return d.map(p => ({ ...p } as SetupKeyDataTable)) + ] + const actionsMenu = () + + 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: , + icon: , width: 600, content: {setupKeyToAction && @@ -175,7 +194,10 @@ export const SetupKeys = () => { , 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: , + icon: , width: 600, content: {setupKeyToAction && @@ -197,7 +219,15 @@ export const SetupKeys = () => { , 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(); + 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 = + 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 ( +
+ + {_g.name} + + {peersCount} +
+ ) + }) + const mainContent = ({content}) + let popoverPlacement = "top" + if (content && content.length > 5) { + popoverPlacement = "rightTop" + } + + return ( + + {btn} + + ) + } + + const setUpdateGroupsVisible = (setupKeyToAction: SetupKey, status: boolean) => { + if (status) { + dispatch(setupKeyActions.setSetupKey({...setupKeyToAction})) + dispatch(setupKeyActions.setSetupNewKeyVisible(true)) + return + } + const autoGroups : string[] = [] + dispatch(setupKeyActions.setSetupKey({ + name: "", + type: "reusable", + auto_groups: autoGroups + } as SetupKey)) + dispatch(setupKeyActions.setSetupNewKeyVisible(false)) + } + return ( <> Setup Keys - A list of all the setup keys in your account including their name, state, type and expiration. - + A list of all the setup keys in your account including their name, state, type and + expiration. + {/**/} - + @@ -235,7 +364,8 @@ export const SetupKeys = () => { optionType="button" buttonStyle="solid" /> - { {failed && - + } `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 = () => { (record as any).name.includes(value)} sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))} + render={(text, record, index) => { + return + }} defaultSortOrder='ascend' /> { - return (text === 'valid') ? {text} : {text} + return (text === 'valid') ? {text} : + {text} }} 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))} /> - + { + return renderPopoverGroups(text, record.auto_groups, record) + }} + /> (record as any).key.includes(value)} sorter={(a, b) => ((a as any).key.localeCompare((b as any).key))} render={(text, record, index) => { - return + return }} /> ((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) }} /> { { return !(record as SetupKeyDataTable).revoked ? ( - { - if (visible) setSetupKeyToAction(record as SetupKeyDataTable) - }}>) : <> + { + if (visible) setSetupKeyToAction(record as SetupKeyDataTable) + }}>) : <> }} />