Compare commits

..
6 Commits
Author SHA1 Message Date
Maycon Santos b733a186ae Remove console.log in peers page (#125) 2023-01-19 14:55:42 +01:00
Maycon Santos 5d901470c2 Feature/dns settings (#126)
Add DNS settings view to the DNS tab.

Split the page into sub-tabs for Nameservers and DNS settings

Added API calls to the new DNS settings API
2023-01-18 18:12:29 +01:00
Misha Bragin 29ab28847d Add Events view (#119) 2023-01-02 17:29:11 +01:00
Maycon Santos 0361825e04 Add peer's last seem time on popover (#122) 2022-12-16 19:37:10 +01:00
Moath Qasim 2fa33ec06a Fix MacOS custom netbird up command (#121) 2022-12-11 18:54:48 +01:00
Maycon Santos c677eeaae4 Add distribution groups to Network routes (#118)
Users can add distribution groups to network routes

Groups can be added to individual network routes or to all routes in the group

Adding a new group in the modal is restricted to individual network route operations
2022-12-08 17:24:34 +01:00
35 changed files with 1583 additions and 505 deletions

No files matched your search

+2
View File
@@ -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/>
+3 -2
View File
@@ -39,13 +39,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});
+1 -1
View File
@@ -353,7 +353,7 @@ const PeerUpdate = () => {
max={59}/>
</Form.Item>
<Form.Item
label="Possible domain name after saving"
label="New peer domain name preview"
tooltip="If the domain name already exists, we add an increment number suffix to it"
style={{margin: '1px'}}
>
+81 -16
View File
@@ -19,7 +19,7 @@ import {
Typography
} from "antd";
import {CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
import {Route} from "../store/route/types";
import {Route, RouteToSave} from "../store/route/types";
import {Header} from "antd/es/layout/layout";
import {RuleObject} from "antd/lib/form";
import cidrRegex from 'cidr-regex';
@@ -31,6 +31,7 @@ import {
transformGroupedDataTable
} from '../utils/routes'
import {useGetAccessTokenSilently} from "../utils/token";
import {useGetGroupTagHelpers} from "../utils/groups";
const {Paragraph} = Typography;
@@ -38,6 +39,17 @@ interface FormRoute extends Route {
}
const RouteUpdate = () => {
const {
tagRender,
handleChangeTags,
dropDownRender,
optionRender,
tagGroups,
getExistingAndToCreateGroupsLists,
getGroupNamesFromIDs,
selectValidator
} = useGetGroupTagHelpers()
const {Option} = Select;
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const setupNewRouteVisible = useSelector((state: RootState) => state.route.setupNewRouteVisible)
@@ -46,7 +58,6 @@ const RouteUpdate = () => {
const route = useSelector((state: RootState) => state.route.route)
const routes = useSelector((state: RootState) => state.route.data)
const savedRoute = useSelector((state: RootState) => state.route.savedRoute)
// const [groupedDataTable, setGroupedDataTable] = useState([] as GroupedDataTable[]);
const [previousRouteKey, setPreviousRouteKey] = useState("")
const [editName, setEditName] = useState(false)
const [editDescription, setEditDescription] = useState(false)
@@ -63,11 +74,12 @@ const RouteUpdate = () => {
const defaultStatusMSG = "Status"
const [statusMSG, setStatusMSG] = useState(defaultStatusMSG)
const [peerNameToIP, peerIPToName] = initPeerMaps(peers);
const [newRoute, setNewRoute] = useState(false)
const optionsDisabledEnabled = [{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]
useEffect(() => {
if (setupNewRouteHA) {
if (!newRoute ) {
setRoutingPeerMSG("Add additional routing peer")
setMasqueradeMSG("Update Masquerade")
setStatusMSG("Update Status")
@@ -77,7 +89,7 @@ const RouteUpdate = () => {
setStatusMSG(defaultStatusMSG)
setPreviousRouteKey("")
}
}, [setupNewRouteHA])
}, [newRoute])
useEffect(() => {
if (editName) inputNameRef.current!.focus({
@@ -96,9 +108,15 @@ const RouteUpdate = () => {
const fRoute = {
...route,
groups: getGroupNamesFromIDs(route.groups)
} as FormRoute
setFormRoute(fRoute)
setPreviousRouteKey(fRoute.network_id + fRoute.network)
if (!route.network_id) {
setNewRoute(true)
} else {
setNewRoute(false)
}
form.setFieldsValue(fRoute)
}, [route])
@@ -114,7 +132,7 @@ const RouteUpdate = () => {
}
})
const createRouteToSave = (inputRoute: FormRoute): Route => {
const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
let peerIDList = inputRoute.peer.split(routePeerSeparator)
let peerID: string
if (peerIDList[1]) {
@@ -123,6 +141,8 @@ const RouteUpdate = () => {
peerID = peerNameToIP[inputRoute.peer]
}
let [ existingGroups, groupsToCreate ] = getExistingAndToCreateGroupsLists(inputRoute.groups)
return {
id: inputRoute.id,
network: inputRoute.network,
@@ -131,8 +151,10 @@ const RouteUpdate = () => {
peer: peerID,
enabled: inputRoute.enabled,
masquerade: inputRoute.masquerade,
metric: inputRoute.metric
} as Route
metric: inputRoute.metric,
groups: existingGroups,
groupsToCreate: groupsToCreate,
} as RouteToSave
}
const handleFormSubmit = () => {
@@ -195,13 +217,14 @@ const RouteUpdate = () => {
setVisibleNewRoute(false)
setSetupNewRouteHA(false)
setPreviousRouteKey("")
setNewRoute(false)
}
const onChange = (data: any) => {
setFormRoute({...formRoute, ...data})
}
const dropDownRender = (menu: React.ReactElement) => (
const peerDropDownRender = (menu: React.ReactElement) => (
<>
{menu}
</>
@@ -227,13 +250,32 @@ const RouteUpdate = () => {
return Promise.resolve()
}
const peerValidator = (_: RuleObject, value: string) => {
if (value == "" && newRoute) {
return Promise.reject(new Error("Please select routing one peer"))
}
return Promise.resolve()
}
const selectPreValidator = (obj: RuleObject, value: string[]) => {
if (setupNewRouteHA && formRoute.peer == '') {
let [, newGroups ] = getExistingAndToCreateGroupsLists(value)
if (newGroups.length > 0) {
return Promise.reject(new Error("You can't add new Groups from the group update view, please remove:\"" + newGroups +"\""))
}
}
return selectValidator(obj, value)
}
return (
<>
{route &&
<Drawer
headerStyle={{display: "none"}}
forceRender={true}
visible={setupNewRouteVisible}
open={setupNewRouteVisible}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
autoFocus={true}
@@ -241,11 +283,11 @@ const RouteUpdate = () => {
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button onClick={onCancel} disabled={savedRoute.loading}>Cancel</Button>
<Button type="primary" disabled={savedRoute.loading}
onClick={handleFormSubmit}>{`${formRoute.network_id ? 'Save' : 'Create'}`}</Button>
onClick={handleFormSubmit}>{`${newRoute ? 'Create' : 'Save'}`}</Button>
</Space>
}
>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
<Form layout="vertical" form={form} requiredMark={false} onValuesChange={onChange}>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
@@ -278,7 +320,7 @@ const RouteUpdate = () => {
}]}
>
<Input placeholder="e.g. aws-eu-central-1-vpc" ref={inputNameRef}
disabled={!setupNewRouteHA}
disabled={!setupNewRouteHA && !newRoute}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)} autoComplete="off"
maxLength={40}/>
@@ -294,7 +336,7 @@ const RouteUpdate = () => {
style={{marginTop: 24}}
>
<Input placeholder="Add description..." ref={inputDescriptionRef}
disabled={!setupNewRouteHA}
disabled={!setupNewRouteHA && !newRoute}
onPressEnter={() => toggleEditDescription(false)}
onBlur={() => toggleEditDescription(false)}
autoComplete="off" maxLength={200}/>
@@ -321,7 +363,7 @@ const RouteUpdate = () => {
tooltip="Use CIDR notation. e.g. 192.168.10.0/24 or 172.16.0.0/16"
rules={[{validator: networkRangeValidator}]}
>
<Input placeholder="e.g. 172.16.0.0/16" disabled={!setupNewRouteHA}
<Input placeholder="e.g. 172.16.0.0/16" disabled={!setupNewRouteHA && !newRoute}
autoComplete="off" minLength={9} maxLength={43}/>
</Form.Item>
</Col>
@@ -343,12 +385,13 @@ const RouteUpdate = () => {
name="peer"
label={routingPeerMSG}
tooltip="Assign a peer as a routing peer for the Network CIDR"
rules={[{validator:peerValidator}]}
>
<Select
showSearch
style={{width: '100%'}}
placeholder="Select Peer"
dropdownRender={dropDownRender}
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
/>
@@ -360,7 +403,7 @@ const RouteUpdate = () => {
label={masqueradeMSG}
tooltip={masqueradeDisabledMSG}
>
<Switch size={"small"} disabled={!setupNewRouteHA} checked={formRoute.masquerade}/>
<Switch size={"small"} disabled={!setupNewRouteHA && !newRoute} checked={formRoute.masquerade}/>
</Form.Item>
</Col>
<Col span={24}>
@@ -372,6 +415,28 @@ const RouteUpdate = () => {
<InputNumber min={1} max={9999} autoComplete="off"/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="groups"
label="Distribution groups"
tooltip="NetBird will advertise this route to peers that belong to the following groups"
rules={[{validator: selectPreValidator}]}
>
<Select mode="tags"
style={{width: '100%'}}
placeholder="Associate groups with the network route"
tagRender={tagRender}
onChange={handleChangeTags}
dropdownRender={dropDownRender}
>
{
tagGroups.map(m =>
<Option key={m}>{optionRender(m)}</Option>
)
}
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Row wrap={false} gutter={12}>
<Col flex="none">
+2 -3
View File
@@ -3,6 +3,7 @@ import React, {useState} from 'react';
import { Button } from "antd";
import TabSteps from "./TabSteps";
import { StepCommand } from "./types"
import { formatNetBirdUP } from "./common"
export const LinuxTab = () => {
@@ -30,9 +31,7 @@ export const LinuxTab = () => {
{
key: 3,
title: 'Run NetBird and log in the browser:',
commands: [
`sudo netbird up`
].join('\n'),
commands: formatNetBirdUP(),
copied: false,
showCopyButton: true
} as StepCommand,
+2 -11
View File
@@ -5,21 +5,12 @@ import TabSteps from "./TabSteps";
import { StepCommand } from "./types"
import { getConfig } from "../../config";
import Paragraph from 'antd/lib/skeleton/Paragraph';
const { grpcApiOrigin } = getConfig();
import { formatNetBirdUP } from "./common"
export const UbuntuTab = () => {
const formatNetBirdUP = () => {
let cmd = "sudo netbird up"
if (grpcApiOrigin) {
cmd = "sudo netbird up --management-url " + grpcApiOrigin
}
return [
cmd
].join('\n')
}
const [steps, setSteps] = useState([
{
key: 1,
+13
View File
@@ -0,0 +1,13 @@
import {getConfig} from "../../config";
const { grpcApiOrigin } = getConfig();
export const formatNetBirdUP = () => {
let cmd = "sudo netbird up"
if (grpcApiOrigin) {
cmd = "sudo netbird up --management-url " + grpcApiOrigin
}
return [
cmd
].join('\n')
}
+26
View File
@@ -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;
+7
View File
@@ -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 };
+79
View File
@@ -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
});
+95
View File
@@ -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),
]);
}
+18
View File
@@ -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
);
},
};
+8
View File
@@ -0,0 +1,8 @@
export interface DNSSettings {
disabled_management_groups: string[]
}
export interface DNSSettingsToSave extends DNSSettings
{
groupsToCreate: string[]
}
+14
View File
@@ -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;
+7
View File
@@ -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 };
+38
View File
@@ -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,
});
+23
View File
@@ -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),
]);
}
+12
View File
@@ -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
);
}
};
+9
View File
@@ -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 }
}
+4
View File
@@ -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 };
Loaded 20 of 35 files, more files were not shown because too many files have changed in this diff. Show more