mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a402680816 | ||
|
|
e57e5b726d | ||
|
|
2c4ada0ad8 | ||
|
|
8195587c85 | ||
|
|
adf6e1e71f | ||
|
|
b733a186ae | ||
|
|
5d901470c2 | ||
|
|
29ab28847d | ||
|
|
0361825e04 | ||
|
|
2fa33ec06a | ||
|
|
c677eeaae4 |
No files matched your search
@@ -36,24 +36,43 @@ Disclaimer. We believe that proper user management system is not a trivial task
|
||||
use Auth0 service that covers all our needs (user management, social login, JTW for the management API).
|
||||
Auth0 so far is the only 3rd party dependency that can't be really self-hosted.
|
||||
|
||||
1. install [Docker](https://docs.docker.com/get-docker/)
|
||||
2. register [Auth0](https://auth0.com/) account
|
||||
3. running Wiretrustee UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
|
||||
1. Install [Docker](https://docs.docker.com/get-docker/)
|
||||
2. Register [Auth0](https://auth0.com/) account
|
||||
3. Running Wiretrustee UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
|
||||
|
||||
```AUTH0_DOMAIN``` ```AUTH0_CLIENT_ID``` ```AUTH0_AUDIENCE```
|
||||
`AUTH0_DOMAIN` `AUTH0_CLIENT_ID` `AUTH0_AUDIENCE`
|
||||
|
||||
To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Configure Allowed Web Origins"
|
||||
|
||||
4. Wiretrustee UI Dashboard uses Wiretrustee Management Service HTTP API, so setting ```WIRETRUSTEE_MGMT_API_ENDPOINT``` is required. Most likely it will be ```http://localhost:33071``` if you are hosting Management API on the same server.
|
||||
4. Wiretrustee UI Dashboard uses Wiretrustee Management Service HTTP API, so setting `NETBIRD_MGMT_API_ENDPOINT` is required. Most likely it will be `http://localhost:33071` if you are hosting Management API on the same server.
|
||||
5. Run docker container without SSL (Let's Encrypt):
|
||||
|
||||
```docker run -d --name wiretrustee-dashboard --rm -p 80:80 -p 443:443 -e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> -e AUTH0_CLIENT_ID=<SET YOUR CLIENT ID> -e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> -e WIRETRUSTEE_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> wiretrustee/dashboard:main```
|
||||
```shell
|
||||
docker run -d --name wiretrustee-dashboard \
|
||||
--rm -p 80:80 -p 443:443 \
|
||||
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
|
||||
-e AUTH0_CLIENT_ID=<SET YOUR CLIENT ID> \
|
||||
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
|
||||
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
|
||||
wiretrustee/dashboard:main
|
||||
```
|
||||
6. Run docker container with SSL (Let's Encrypt):
|
||||
|
||||
```docker run -d --name wiretrustee-dashboard --rm -p 80:80 -p 443:443 -e NGINX_SSL_PORT=443 -e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> -e LETSENCRYPT_EMAIL=<YOUR EMAIL> -e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> -e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> -e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> -e WIRETRUSTEE_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> wiretrustee/dashboard:main```
|
||||
```shell
|
||||
docker run -d --name wiretrustee-dashboard \
|
||||
--rm -p 80:80 -p 443:443 \
|
||||
-e NGINX_SSL_PORT=443 \
|
||||
-e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> \
|
||||
-e LETSENCRYPT_EMAIL=<YOUR EMAIL> \
|
||||
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
|
||||
-e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> \
|
||||
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
|
||||
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
|
||||
wiretrustee/dashboard:main
|
||||
```
|
||||
|
||||
## How to run local development
|
||||
1. Install node 16
|
||||
2. create and update the src/.local-config.json file. This file should contain values to be replaced from src/config.json
|
||||
2. create and update the `src/.local-config.json` file. This file should contain values to be replaced from `src/config.json`
|
||||
3. run `npm install`
|
||||
4. run `npm run start dev`
|
||||
4. run `npm run start dev`
|
||||
@@ -5,6 +5,7 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="robots" content="noindex">
|
||||
<meta
|
||||
name="description"
|
||||
content="NetBird Management Dashboard"
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /
|
||||
@@ -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) {
|
||||
const found = users.find(u => u.is_current ? u.is_current : runUser.sub ? u.id == runUser.sub : false)
|
||||
if (found) {
|
||||
setCurrentUser(found)
|
||||
}
|
||||
|
||||
@@ -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'}}
|
||||
>
|
||||
|
||||
@@ -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)
|
||||
@@ -62,13 +73,14 @@ const RouteUpdate = () => {
|
||||
const [masqueradeMSG, setMasqueradeMSG] = useState(defaultMasqueradeMSG)
|
||||
const defaultStatusMSG = "Status"
|
||||
const [statusMSG, setStatusMSG] = useState(defaultStatusMSG)
|
||||
const [peerNameToIP, peerIPToName] = initPeerMaps(peers);
|
||||
const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers);
|
||||
const [newRoute, setNewRoute] = useState(false)
|
||||
|
||||
const optionsDisabledEnabled = [{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]
|
||||
|
||||
useEffect(() => {
|
||||
if (setupNewRouteHA) {
|
||||
setRoutingPeerMSG("Add additional routing peer")
|
||||
if (!newRoute ) {
|
||||
setRoutingPeerMSG(defaultRoutingPeerMSG)
|
||||
setMasqueradeMSG("Update Masquerade")
|
||||
setStatusMSG("Update Status")
|
||||
} else {
|
||||
@@ -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,15 +132,17 @@ const RouteUpdate = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const createRouteToSave = (inputRoute: FormRoute): Route => {
|
||||
const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
|
||||
let peerIDList = inputRoute.peer.split(routePeerSeparator)
|
||||
let peerID: string
|
||||
if (peerIDList[1]) {
|
||||
peerID = peerIDList[1]
|
||||
peerID = peerIPToID[peerIDList[1]]
|
||||
} else {
|
||||
peerID = peerNameToIP[inputRoute.peer]
|
||||
peerID = peerIPToID[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 = () => {
|
||||
@@ -145,7 +167,7 @@ const RouteUpdate = () => {
|
||||
payload: routeToSave
|
||||
}))
|
||||
} else {
|
||||
let groupedDataTable = transformGroupedDataTable(routes, peerIPToName)
|
||||
let groupedDataTable = transformGroupedDataTable(routes, peers)
|
||||
groupedDataTable.forEach((group) => {
|
||||
if (group.key == previousRouteKey) {
|
||||
group.groupedRoutes.forEach((route) => {
|
||||
@@ -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">
|
||||
|
||||
@@ -233,7 +233,8 @@ const UserUpdate = () => {
|
||||
role: "",
|
||||
status: "",
|
||||
auto_groups: [],
|
||||
name: user.name
|
||||
name: user.name,
|
||||
is_current: user.is_current,
|
||||
} as User));
|
||||
setFormUser({} as FormUser)
|
||||
toggleEditName(false)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
Loaded 20 of 42 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user