mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8195587c85 | ||
|
|
adf6e1e71f | ||
|
|
b733a186ae | ||
|
|
5d901470c2 | ||
|
|
29ab28847d | ||
|
|
0361825e04 |
No files matched your search
@@ -20,6 +20,7 @@ import {useGetAccessTokenSilently} from "./utils/token";
|
||||
import {User} from "./store/user/types";
|
||||
import {SecureLoading} from "./components/Loading";
|
||||
import DNS from "./views/DNS";
|
||||
import Activity from "./views/Activity";
|
||||
|
||||
|
||||
|
||||
@@ -105,6 +106,7 @@ function App() {
|
||||
<Route path="/routes" component={withOidcSecure(Routes)}/>
|
||||
<Route path="/users" component={withOidcSecure(Users)}/>
|
||||
<Route path="/dns" component={withOidcSecure(DNS)}/>
|
||||
<Route path="/activity" component={withOidcSecure(Activity)}/>
|
||||
</Switch>
|
||||
</Content>
|
||||
<FooterComponent/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Avatar, Button, Col, Dropdown, Grid, Menu, Row} from 'antd'
|
||||
import {ItemType} from "antd/lib/menu/hooks/useItems";
|
||||
import {AvatarSize} from "antd/es/avatar/SizeContext";
|
||||
import {UserOutlined} from '@ant-design/icons';
|
||||
import {useOidc, useOidcUser} from '@axa-fr/react-oidc';
|
||||
import {useOidc, useOidcIdToken, useOidcUser} from '@axa-fr/react-oidc';
|
||||
import {getConfig} from "../config";
|
||||
import {User} from "../store/user/types";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
@@ -23,6 +23,7 @@ const Navbar = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const {oidcUser} = useOidcUser();
|
||||
const {idTokenPayload} = useOidcIdToken()
|
||||
const user = oidcUser;
|
||||
const [currentUser, setCurrentUser] = useState({} as User)
|
||||
|
||||
@@ -39,13 +40,14 @@ const Navbar = () => {
|
||||
{label: (<Link to="/acls">Access Control</Link>), key: '/acls'},
|
||||
{label: (<Link to="/routes">Network Routes</Link>), key: '/routes'},
|
||||
{ label: (<Link to="/dns">DNS</Link>), key: '/dns' },
|
||||
{label: (<Link to="/users">Users</Link>), key: '/users'}
|
||||
{label: (<Link to="/users">Users</Link>), key: '/users'},
|
||||
{label: (<Link to="/activity">Activity</Link>), key: '/activity'}
|
||||
] as ItemType[]
|
||||
|
||||
const userEmailKey = 'user-email'
|
||||
const userLogoutKey = 'user-logout'
|
||||
const userDividerKey = 'user-divider'
|
||||
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns"]
|
||||
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns", "/activity"]
|
||||
const [menuItems, setMenuItems] = useState(items)
|
||||
const logoutWithRedirect = () =>
|
||||
logout("/", {client_id: config.clientId});
|
||||
@@ -84,9 +86,13 @@ const Navbar = () => {
|
||||
if (users.length === 0 && isRefreshingUserState) {
|
||||
return
|
||||
}
|
||||
let runUser = oidcUser
|
||||
if (!oidcUser) {
|
||||
runUser = idTokenPayload
|
||||
}
|
||||
setIsRefreshingUserState(false)
|
||||
if (oidcUser && oidcUser.sub) {
|
||||
const found = users.find(u => u.id == oidcUser.sub)
|
||||
if (runUser && runUser.sub) {
|
||||
const found = users.find(u => u.id == runUser.sub)
|
||||
if (found) {
|
||||
setCurrentUser(found)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ActionType, createAction, createAsyncAction } from 'typesafe-actions';
|
||||
import {DNSSettings, DNSSettingsToSave} from './types';
|
||||
import {ApiError, CreateResponse, RequestPayload} from '../../services/api-client/types';
|
||||
|
||||
const actions = {
|
||||
getDNSSettings: createAsyncAction(
|
||||
'GET_DNSSettings_REQUEST',
|
||||
'GET_DNSSettings_SUCCESS',
|
||||
'GET_DNSSettings_FAILURE',
|
||||
)<RequestPayload<null>, DNSSettings, ApiError>(),
|
||||
|
||||
saveDNSSettings: createAsyncAction(
|
||||
'SAVE_DNSSettings_REQUEST',
|
||||
'SAVE_DNSSettings_SUCCESS',
|
||||
'SAVE_DNSSettings_FAILURE',
|
||||
)<RequestPayload<DNSSettingsToSave>, CreateResponse<DNSSettings | null>, CreateResponse<DNSSettings | null>>(),
|
||||
setSavedDNSSettings: createAction('SET_CREATE_DNSSettings')<CreateResponse<DNSSettings | null>>(),
|
||||
resetSavedDNSSettings: createAction('RESET_CREATE_DNSSettings')<null>(),
|
||||
|
||||
setDNSSettings: createAction('SET_DNSSettings')<DNSSettings>(),
|
||||
setSetupNewDNSSettingsVisible: createAction('SET_SETUP_NEW_DNSSettings_VISIBLE')<boolean>(),
|
||||
setSetupNewDNSSettingsHA: createAction('SET_SETUP_NEW_DNSSettings_HA')<boolean>()
|
||||
};
|
||||
|
||||
export type ActionTypes = ActionType<typeof actions>;
|
||||
export default actions;
|
||||
@@ -0,0 +1,7 @@
|
||||
import actions, { ActionTypes as _actionTypes } from './actions';
|
||||
import reducer from './reducer';
|
||||
import sagas from './sagas';
|
||||
|
||||
export type ActionTypes = _actionTypes;
|
||||
|
||||
export { actions, reducer, sagas };
|
||||
@@ -0,0 +1,79 @@
|
||||
import { createReducer } from 'typesafe-actions';
|
||||
import { combineReducers } from 'redux';
|
||||
import { DNSSettings } from './types';
|
||||
import actions, { ActionTypes } from './actions';
|
||||
import {ApiError, CreateResponse} from "../../services/api-client/types";
|
||||
|
||||
type StateType = Readonly<{
|
||||
data: DNSSettings | null;
|
||||
dnsSettings: DNSSettings | null;
|
||||
loading: boolean;
|
||||
failed: ApiError | null;
|
||||
saving: boolean;
|
||||
savedDNSSettings: CreateResponse<DNSSettings | null>;
|
||||
setupNewDNSSettingsVisible: boolean;
|
||||
setupNewDNSSettingsHA: boolean
|
||||
}>;
|
||||
|
||||
const initialState: StateType = {
|
||||
data: null,
|
||||
dnsSettings: null,
|
||||
loading: false,
|
||||
failed: null,
|
||||
saving: false,
|
||||
savedDNSSettings: <CreateResponse<DNSSettings | null>>{
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data : null
|
||||
},
|
||||
setupNewDNSSettingsVisible: false,
|
||||
setupNewDNSSettingsHA: false
|
||||
};
|
||||
|
||||
const data = createReducer<DNSSettings, ActionTypes>(initialState.data as DNSSettings)
|
||||
.handleAction(actions.getDNSSettings.success,(settings, action) => action.payload)
|
||||
.handleAction(actions.getDNSSettings.failure,(settings, _) => settings);
|
||||
|
||||
const dnsSettings = createReducer<DNSSettings, ActionTypes>(initialState.dnsSettings as DNSSettings)
|
||||
.handleAction(actions.setDNSSettings, (store, action) => action.payload);
|
||||
|
||||
const loading = createReducer<boolean, ActionTypes>(initialState.loading)
|
||||
.handleAction(actions.getDNSSettings.request, () => true)
|
||||
.handleAction(actions.getDNSSettings.success, () => false)
|
||||
.handleAction(actions.getDNSSettings.failure, () => false);
|
||||
|
||||
const failed = createReducer<ApiError | null, ActionTypes>(initialState.failed)
|
||||
.handleAction(actions.getDNSSettings.request, () => null)
|
||||
.handleAction(actions.getDNSSettings.success, () => null)
|
||||
.handleAction(actions.getDNSSettings.failure, (store, action) => action.payload);
|
||||
|
||||
const saving = createReducer<boolean, ActionTypes>(initialState.saving)
|
||||
.handleAction(actions.getDNSSettings.request, () => true)
|
||||
.handleAction(actions.getDNSSettings.success, () => false)
|
||||
.handleAction(actions.getDNSSettings.failure, () => false);
|
||||
|
||||
const savedDNSSettings = createReducer<CreateResponse<DNSSettings | null>, ActionTypes>(initialState.savedDNSSettings)
|
||||
.handleAction(actions.saveDNSSettings.request, () => initialState.savedDNSSettings)
|
||||
.handleAction(actions.saveDNSSettings.success, (store, action) => action.payload)
|
||||
.handleAction(actions.saveDNSSettings.failure, (store, action) => action.payload)
|
||||
.handleAction(actions.setSavedDNSSettings, (store, action) => action.payload)
|
||||
.handleAction(actions.resetSavedDNSSettings, () => initialState.savedDNSSettings)
|
||||
|
||||
const setupNewDNSSettingsVisible = createReducer<boolean, ActionTypes>(initialState.setupNewDNSSettingsVisible)
|
||||
.handleAction(actions.setSetupNewDNSSettingsVisible, (store, action) => action.payload)
|
||||
|
||||
const setupNewDNSSettingsHA = createReducer<boolean, ActionTypes>(initialState.setupNewDNSSettingsHA)
|
||||
.handleAction(actions.setSetupNewDNSSettingsHA, (store, action) => action.payload)
|
||||
|
||||
export default combineReducers({
|
||||
data,
|
||||
dnsSettings,
|
||||
loading,
|
||||
failed,
|
||||
saving,
|
||||
savedDNSSettings,
|
||||
setupNewDNSSettingsVisible,
|
||||
setupNewDNSSettingsHA
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import {all, call, put, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse, CreateResponse} from '../../services/api-client/types';
|
||||
import {DNSSettings} 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* getDNSSettings(action: ReturnType<typeof actions.getDNSSettings.request>): Generator {
|
||||
try {
|
||||
|
||||
const effect = yield call(service.getDNSSettings, action.payload);
|
||||
const response = effect as ApiResponse<DNSSettings>;
|
||||
|
||||
yield put(actions.getDNSSettings.success(response.body));
|
||||
} catch (err) {
|
||||
yield put(actions.getDNSSettings.failure(err as ApiError));
|
||||
}
|
||||
}
|
||||
|
||||
export function* saveDNSSettings(action: ReturnType<typeof actions.saveDNSSettings.request>): Generator {
|
||||
try {
|
||||
yield put(actions.setSavedDNSSettings({
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null
|
||||
} as CreateResponse<DNSSettings | null>))
|
||||
|
||||
const settingsToSave = action.payload.payload
|
||||
|
||||
let groupsToCreate = settingsToSave.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 = [...settingsToSave.disabled_management_groups, ...resGroups]
|
||||
|
||||
const payloadToSave = {
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: {
|
||||
disabled_management_groups: newGroups,
|
||||
} as DNSSettings
|
||||
}
|
||||
|
||||
let effect = yield call(service.editDNSSettings, payloadToSave);
|
||||
const response = effect as ApiResponse<DNSSettings>;
|
||||
|
||||
yield put(actions.saveDNSSettings.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body
|
||||
} as CreateResponse<DNSSettings | null>));
|
||||
|
||||
yield put(groupActions.getGroups.request({
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: null
|
||||
}));
|
||||
|
||||
yield put(actions.getDNSSettings.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
|
||||
|
||||
} catch (err) {
|
||||
yield put(groupActions.getGroups.request({
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: null
|
||||
}));
|
||||
yield put(actions.saveDNSSettings.failure({
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: true,
|
||||
error: err as ApiError,
|
||||
data: null
|
||||
} as CreateResponse<DNSSettings | null>));
|
||||
}
|
||||
}
|
||||
|
||||
export default function* sagas(): Generator {
|
||||
yield all([
|
||||
takeLatest(actions.getDNSSettings.request, getDNSSettings),
|
||||
takeLatest(actions.saveDNSSettings.request, saveDNSSettings),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {ApiResponse, RequestPayload} from '../../services/api-client/types';
|
||||
import { apiClient } from '../../services/api-client';
|
||||
import { DNSSettings } from './types';
|
||||
|
||||
export default {
|
||||
async getDNSSettings(payload:RequestPayload<null>): Promise<ApiResponse<DNSSettings>> {
|
||||
return apiClient.get<DNSSettings>(
|
||||
`/api/dns/settings`,
|
||||
payload
|
||||
);
|
||||
},
|
||||
async editDNSSettings(payload:RequestPayload<DNSSettings>): Promise<ApiResponse<DNSSettings>> {
|
||||
return apiClient.put<DNSSettings>(
|
||||
`/api/dns/settings`,
|
||||
payload
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface DNSSettings {
|
||||
disabled_management_groups: string[]
|
||||
}
|
||||
|
||||
export interface DNSSettingsToSave extends DNSSettings
|
||||
{
|
||||
groupsToCreate: string[]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import {ActionType, createAsyncAction} from 'typesafe-actions';
|
||||
import {Event} from './types';
|
||||
import {ApiError, RequestPayload} from '../../services/api-client/types';
|
||||
|
||||
const actions = {
|
||||
getEvents: createAsyncAction(
|
||||
'GET_EVENTS_REQUEST',
|
||||
'GET_EVENTS_SUCCESS',
|
||||
'GET_EVENTS_FAILURE',
|
||||
)<RequestPayload<null>, Event[], ApiError>(),
|
||||
};
|
||||
|
||||
export type ActionTypes = ActionType<typeof actions>;
|
||||
export default actions;
|
||||
@@ -0,0 +1,7 @@
|
||||
import actions, { ActionTypes as _actionTypes } from './actions';
|
||||
import reducer from './reducer';
|
||||
import sagas from './sagas';
|
||||
|
||||
export type ActionTypes = _actionTypes;
|
||||
|
||||
export { actions, reducer, sagas };
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createReducer } from 'typesafe-actions';
|
||||
import { combineReducers } from 'redux';
|
||||
import { Event } from './types';
|
||||
import actions, { ActionTypes } from './actions';
|
||||
import {ApiError, CreateResponse} from "../../services/api-client/types";
|
||||
|
||||
type StateType = Readonly<{
|
||||
data: Event[] | null;
|
||||
loading: boolean;
|
||||
failed: ApiError | null;
|
||||
}>;
|
||||
|
||||
const initialState: StateType = {
|
||||
data: [],
|
||||
loading: false,
|
||||
failed: null,
|
||||
};
|
||||
|
||||
const data = createReducer<Event[], ActionTypes>(initialState.data as Event[])
|
||||
.handleAction(actions.getEvents.success,(_, action) => action.payload)
|
||||
.handleAction(actions.getEvents.failure, () => []);
|
||||
|
||||
const loading = createReducer<boolean, ActionTypes>(initialState.loading)
|
||||
.handleAction(actions.getEvents.request, () => true)
|
||||
.handleAction(actions.getEvents.success, () => false)
|
||||
.handleAction(actions.getEvents.failure, () => false);
|
||||
|
||||
const failed = createReducer<ApiError | null, ActionTypes>(initialState.failed)
|
||||
.handleAction(actions.getEvents.request, () => null)
|
||||
.handleAction(actions.getEvents.success, () => null)
|
||||
.handleAction(actions.getEvents.failure, (store, action) => action.payload);
|
||||
|
||||
|
||||
export default combineReducers({
|
||||
data,
|
||||
loading,
|
||||
failed,
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import {all, call, put, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse} from '../../services/api-client/types';
|
||||
import {Event} from './types'
|
||||
import service from './service';
|
||||
import actions from './actions';
|
||||
|
||||
export function* getEvents(action: ReturnType<typeof actions.getEvents.request>): Generator {
|
||||
try {
|
||||
const effect = yield call(service.getEvents, action.payload);
|
||||
const response = effect as ApiResponse<Event[]>;
|
||||
|
||||
yield put(actions.getEvents.success(response.body));
|
||||
} catch (err) {
|
||||
yield put(actions.getEvents.failure(err as ApiError));
|
||||
}
|
||||
}
|
||||
|
||||
export default function* sagas(): Generator {
|
||||
yield all([
|
||||
takeLatest(actions.getEvents.request, getEvents),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import {ApiResponse, RequestPayload} from '../../services/api-client/types';
|
||||
import { apiClient } from '../../services/api-client';
|
||||
import {Event} from './types';
|
||||
|
||||
export default {
|
||||
async getEvents(payload:RequestPayload<null>): Promise<ApiResponse<Event[]>> {
|
||||
return apiClient.get<Event[]>(
|
||||
`/api/events`,
|
||||
payload
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface Event {
|
||||
id: string;
|
||||
timestamp: string
|
||||
activity: string
|
||||
activity_code: string
|
||||
initiator_id: string
|
||||
target_id: string
|
||||
meta: { [key: string]: string }
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import { sagas as ruleSagas } from './rule';
|
||||
import { sagas as groupSagas } from './group';
|
||||
import { sagas as routeSagas } from './route';
|
||||
import { sagas as nameserverGroupSagas } from './nameservers';
|
||||
import { sagas as eventSagas } from './event';
|
||||
import { sagas as dnsSettingsSagas } from './dns-settings';
|
||||
|
||||
import rootReducer from './root-reducer';
|
||||
import { apiClient } from '../services/api-client';
|
||||
@@ -27,5 +29,7 @@ sagaMiddleware.run(ruleSagas);
|
||||
sagaMiddleware.run(groupSagas);
|
||||
sagaMiddleware.run(routeSagas);
|
||||
sagaMiddleware.run(nameserverGroupSagas);
|
||||
sagaMiddleware.run(eventSagas);
|
||||
sagaMiddleware.run(dnsSettingsSagas);
|
||||
|
||||
export { apiClient, rootReducer, store };
|
||||
@@ -1,10 +1,12 @@
|
||||
import { actions as PeerActions } from './peer';
|
||||
import { actions as SetupKeyActions } from './setup-key';
|
||||
import { actions as UserActions } from './user';
|
||||
import { actions as GroupActions } from './group';
|
||||
import { actions as RuleActions } from './rule';
|
||||
import { actions as RouteActions } from './route';
|
||||
import { actions as NameServerGroupActions } from './nameservers';
|
||||
import {actions as PeerActions} from './peer';
|
||||
import {actions as SetupKeyActions} from './setup-key';
|
||||
import {actions as UserActions} from './user';
|
||||
import {actions as GroupActions} from './group';
|
||||
import {actions as RuleActions} from './rule';
|
||||
import {actions as RouteActions} from './route';
|
||||
import {actions as NameServerGroupActions} from './nameservers';
|
||||
import {actions as EventActions} from './event';
|
||||
import {actions as DNSSettingsActions} from './dns-settings';
|
||||
|
||||
export default {
|
||||
peer: PeerActions,
|
||||
@@ -13,5 +15,7 @@ export default {
|
||||
group: GroupActions,
|
||||
rule: RuleActions,
|
||||
route: RouteActions,
|
||||
nameserverGroup: NameServerGroupActions
|
||||
nameserverGroup: NameServerGroupActions,
|
||||
event: EventActions,
|
||||
dnsSettings: DNSSettingsActions
|
||||
};
|
||||
@@ -7,6 +7,8 @@ import { reducer as group } from './group';
|
||||
import { reducer as rule } from './rule';
|
||||
import { reducer as route } from './route';
|
||||
import { reducer as nameserverGroup } from './nameservers';
|
||||
import { reducer as event } from './event';
|
||||
import { reducer as dnsSettings } from './dns-settings';
|
||||
|
||||
export default combineReducers({
|
||||
peer,
|
||||
@@ -15,5 +17,7 @@ export default combineReducers({
|
||||
group,
|
||||
rule,
|
||||
route,
|
||||
nameserverGroup
|
||||
nameserverGroup,
|
||||
event,
|
||||
dnsSettings
|
||||
});
|
||||
@@ -17,6 +17,20 @@ export const formatDate = date => {
|
||||
return new Date(date).toLocaleDateString("en-GB", { weekday: 'short', year: '2-digit', month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
export const capitalize = text => {
|
||||
if (!text) {
|
||||
return text
|
||||
}
|
||||
return text.charAt(0).toUpperCase() + text.slice(1)
|
||||
}
|
||||
|
||||
export const formatDateTime = date => {
|
||||
if (new Date(date).getTime() > new Date("2099-12-31").getTime()) {
|
||||
return new Date(date).toLocaleDateString("en-GB", { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' });
|
||||
}
|
||||
return new Date(date).toLocaleDateString("en-GB", { weekday: 'short', year: '2-digit', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' });
|
||||
}
|
||||
|
||||
export const classNames = (...classes) => {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
@@ -94,12 +94,16 @@ export const useGetGroupTagHelpers = () => {
|
||||
return groups?.filter(g => groupIDList.includes(g.id!)).map(g => g.name || '') || []
|
||||
}
|
||||
|
||||
const selectValidator = (_: RuleObject, value: string[]) => {
|
||||
let hasSpaceNamed = []
|
||||
const selectValidator = (obj: RuleObject, value: string[]) => {
|
||||
if (!value.length) {
|
||||
return Promise.reject(new Error("Please enter at least one group"))
|
||||
}
|
||||
|
||||
return selectValidatorEmptyStrings(obj,value)
|
||||
}
|
||||
|
||||
const selectValidatorEmptyStrings = (_: RuleObject, value: string[]) => {
|
||||
let hasSpaceNamed = []
|
||||
value.forEach(function (v: string) {
|
||||
if (!v.trim().length) {
|
||||
hasSpaceNamed.push(v)
|
||||
@@ -131,6 +135,7 @@ export const useGetGroupTagHelpers = () => {
|
||||
setGroupTagFilterAll,
|
||||
getExistingAndToCreateGroupsLists,
|
||||
getGroupNamesFromIDs,
|
||||
selectValidator
|
||||
selectValidator,
|
||||
selectValidatorEmptyStrings
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {actions as eventActions} from '../store/event';
|
||||
import {Container} from "../components/Container";
|
||||
import {Alert, Card, Col, Input, Row, Select, Space, Table, Typography,} from "antd";
|
||||
import {Event} from "../store/event/types";
|
||||
import {filter} from "lodash";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {useGetAccessTokenSilently} from "../utils/token";
|
||||
import UserUpdate from "../components/UserUpdate";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
import {capitalize, formatDateTime} from "../utils/common";
|
||||
import {User} from "../store/user/types";
|
||||
|
||||
const {Title, Paragraph, Text} = Typography;
|
||||
const {Column} = Table;
|
||||
|
||||
interface EventDataTable extends Event {
|
||||
}
|
||||
|
||||
export const Activity = () => {
|
||||
const {getAccessTokenSilently} = useGetAccessTokenSilently()
|
||||
const {oidcUser} = useOidcUser();
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const events = useSelector((state: RootState) => state.event.data);
|
||||
const failed = useSelector((state: RootState) => state.event.failed);
|
||||
const loading = useSelector((state: RootState) => state.event.loading);
|
||||
const users = useSelector((state: RootState) => state.user.data);
|
||||
const setupKeys = useSelector((state: RootState) => state.setupKey.data);
|
||||
|
||||
const [textToSearch, setTextToSearch] = useState('');
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [dataTable, setDataTable] = useState([] as EventDataTable[]);
|
||||
const pageSizeOptions = [
|
||||
{label: "5", value: "5"},
|
||||
{label: "10", value: "10"},
|
||||
{label: "15", value: "15"},
|
||||
{label: "20", value: "20"}
|
||||
]
|
||||
|
||||
const transformDataTable = (d: Event[]): EventDataTable[] => {
|
||||
return d.map(p => ({key: p.id, ...p} as EventDataTable))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(eventActions.getEvents.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
setDataTable(transformDataTable(events))
|
||||
}, [events])
|
||||
|
||||
useEffect(() => {
|
||||
setDataTable(transformDataTable(filterDataTable()))
|
||||
}, [textToSearch])
|
||||
|
||||
const filterDataTable = (): Event[] => {
|
||||
const t = textToSearch.toLowerCase().trim()
|
||||
let usrsMatch: User[] = filter(users, (u: User) => (u.name)?.toLowerCase().includes(t) || (u.email)?.toLowerCase().includes(t)) as User[]
|
||||
let f: Event[] = filter(events, (f: Event) =>
|
||||
((f.activity || f.id).toLowerCase().includes(t) || t === "" || usrsMatch.find(u => u.id === f.initiator_id))
|
||||
) as Event[]
|
||||
return f
|
||||
}
|
||||
|
||||
const onChangeTextToSearch = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setTextToSearch(e.target.value)
|
||||
};
|
||||
|
||||
const searchDataTable = () => {
|
||||
const data = filterDataTable()
|
||||
setDataTable(transformDataTable(data))
|
||||
}
|
||||
|
||||
const onChangePageSize = (value: string) => {
|
||||
setPageSize(parseInt(value.toString()))
|
||||
}
|
||||
|
||||
const getActivityRow = (group:string,text:string) => {
|
||||
return <Row> <Text>Group <Text type="secondary">{group}</Text> {text}</Text> </Row>
|
||||
}
|
||||
|
||||
const renderActivity = (event: EventDataTable) => {
|
||||
let body = <Text>{event.activity}</Text>
|
||||
switch (event.activity_code) {
|
||||
case "peer.group.add":
|
||||
return getActivityRow(event.meta.group,"added to peer")
|
||||
case "peer.group.delete":
|
||||
return getActivityRow(event.meta.group,"removed from peer")
|
||||
case "user.group.add":
|
||||
return getActivityRow(event.meta.group,"added to user")
|
||||
case "user.group.delete":
|
||||
return getActivityRow(event.meta.group,"removed from user")
|
||||
case "setupkey.group.add":
|
||||
return getActivityRow(event.meta.group,"added to setup key")
|
||||
case "setupkey.group.delete":
|
||||
return getActivityRow(event.meta.group,"removed setup key")
|
||||
case "dns.setting.disabled.management.group.add":
|
||||
return getActivityRow(event.meta.group,"added to disabled management DNS setting")
|
||||
case "dns.setting.disabled.management.group.delete":
|
||||
return getActivityRow(event.meta.group,"removed from disabled management DNS setting")
|
||||
}
|
||||
return body
|
||||
}
|
||||
const renderInitiator = (event: EventDataTable) => {
|
||||
let body = <></>
|
||||
const user = users?.find(u => u.id === event.initiator_id)
|
||||
switch (event.activity_code) {
|
||||
case "setupkey.peer.add":
|
||||
const key = setupKeys?.find(k => k.id === event.initiator_id)
|
||||
if (key) {
|
||||
body = <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
|
||||
<Row> <Text>{key.name}</Text> </Row>
|
||||
<Row> <Text type="secondary">Setup Key</Text> </Row>
|
||||
</span>
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (user) {
|
||||
body = <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
|
||||
<Row> <Text>{user.name ? user.name : user.id}</Text> </Row>
|
||||
<Row> <Text type="secondary">{user.email ? user.email : "User"}</Text> </Row>
|
||||
</span>
|
||||
return body
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
const renderMultiRowSpan = (primaryRowText:string,secondaryRowText:string) => {
|
||||
return <span style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}>
|
||||
<Row> <Text>{primaryRowText}</Text> </Row>
|
||||
<Row> <Text type="secondary">{secondaryRowText}</Text> </Row>
|
||||
</span>
|
||||
}
|
||||
|
||||
const renderTarget = (event: EventDataTable) => {
|
||||
if (event.activity_code === "account.create" || event.activity_code === "user.join") {
|
||||
return "-"
|
||||
}
|
||||
const user = users?.find(u => u.id === event.target_id)
|
||||
switch (event.activity_code) {
|
||||
case "account.create":
|
||||
case "user.join":
|
||||
return "-"
|
||||
case "rule.add":
|
||||
case "rule.delete":
|
||||
case "rule.update":
|
||||
return renderMultiRowSpan(event.meta.name,"Rule")
|
||||
case "setupkey.add":
|
||||
case "setupkey.revoke":
|
||||
case "setupkey.update":
|
||||
case "setupkey.overuse":
|
||||
let cType:string
|
||||
cType = capitalize(event.meta.type)
|
||||
return renderMultiRowSpan(event.meta.name,cType+" setup key "+event.meta.key)
|
||||
case "group.add":
|
||||
case "group.update":
|
||||
return renderMultiRowSpan(event.meta.name,"Group")
|
||||
case "nameserver.group.add":
|
||||
case "nameserver.group.update":
|
||||
case "nameserver.group.delete":
|
||||
return renderMultiRowSpan(event.meta.name,"Nameserver group")
|
||||
case "setupkey.peer.add":
|
||||
case "user.peer.add":
|
||||
case "user.peer.delete":
|
||||
case "peer.ssh.enable":
|
||||
case "peer.ssh.disable":
|
||||
case "peer.rename":
|
||||
return renderMultiRowSpan(event.meta.fqdn,event.meta.ip)
|
||||
case "route.add":
|
||||
case "route.delete":
|
||||
case "route.update":
|
||||
return renderMultiRowSpan(event.meta.name, "Route for range " + event.meta.network_range)
|
||||
case "user.group.add":
|
||||
case "user.group.delete":
|
||||
if (user) {
|
||||
return renderMultiRowSpan(user.name ? user.name : user.id,user.email ? user.email : "User")
|
||||
}
|
||||
return "n/a"
|
||||
case "setupkey.group.add":
|
||||
case "setupkey.group.delete":
|
||||
return renderMultiRowSpan(event.meta.setupkey,"Setup Key")
|
||||
case "peer.group.add":
|
||||
case "peer.group.delete":
|
||||
return renderMultiRowSpan(event.meta.peer_fqdn,event.meta.peer_ip)
|
||||
case "dns.setting.disabled.management.group.add":
|
||||
case "dns.setting.disabled.management.group.delete":
|
||||
return renderMultiRowSpan("","System setting")
|
||||
case "user.invite":
|
||||
if (user) {
|
||||
return renderMultiRowSpan(user.name ? user.name : user.id,user.email ? user.email : "User")
|
||||
}
|
||||
}
|
||||
|
||||
return event.target_id
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container style={{paddingTop: "40px"}}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Title level={4}>Activity</Title>
|
||||
<Paragraph>Here you can see all the account and network activity events</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 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">
|
||||
<Select value={pageSize.toString()} options={pageSizeOptions}
|
||||
onChange={onChangePageSize} className="select-rows-per-page-en"/>
|
||||
</Space>
|
||||
</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} users`)
|
||||
}}
|
||||
className="card-table"
|
||||
showSorterTooltip={false}
|
||||
scroll={{x: true}}
|
||||
loading={tableSpin(loading)}
|
||||
dataSource={dataTable}
|
||||
size="small"
|
||||
>
|
||||
<Column title="Timestamp" dataIndex="timestamp"
|
||||
render={(text, record, index) => {
|
||||
return formatDateTime(text)
|
||||
}}
|
||||
/>
|
||||
<Column title="Activity" dataIndex="activity"
|
||||
render={(text, record, index) => {
|
||||
return renderActivity(record as EventDataTable)
|
||||
}}
|
||||
/>
|
||||
<Column title="Initiated By" dataIndex="initiator_id"
|
||||
render={(text, record, index) => {
|
||||
return renderInitiator(record as EventDataTable)
|
||||
}}
|
||||
/>
|
||||
<Column title="Target" dataIndex="target_id"
|
||||
render={(text, record, index) => {
|
||||
return renderTarget(record as EventDataTable)
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
</Card>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<UserUpdate/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Activity;
|
||||
Loaded 20 of 25 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user