Compare commits

..
5 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
28 changed files with 1355 additions and 468 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});
+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 };
+12 -8
View File
@@ -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
};
+5 -1
View File
@@ -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
});
Loaded 20 of 28 files, more files were not shown because too many files have changed in this diff. Show more