Compare commits

...
Author SHA1 Message Date
Maycon Santos baf37c2eaf List dependencies on build 2022-09-17 17:03:34 +05:00
Maycon Santos 01bcc620a5 Check token before do api requests for the page
This will prevent the case where we are using
an expired access token to call our API

Added a loadOnce to prevent not loading the page at least once
2022-09-17 17:02:35 +05:00
Maycon Santos ace2bb61ef Fix popover for groups (#78)
* Fix popover for groups

Fixed all popover overlay when opening
update modal

also fixed group input size problem
 with peers update

* remove style display flex from form tittle
2022-09-15 22:24:20 +05:00
Misha Bragin f389862931 Support Auto Groups when updating setup keys (#75) 2022-09-13 17:51:05 +02:00
braginini 521df658ad Shorten the banner text 2022-09-13 11:01:19 +02:00
Maycon Santos 4db17c119a enable new route update 2022-09-08 10:51:36 +02:00
Maycon Santos 230a4cb05e Group network routes by identifier and range (#73)
* fix missing peer when disabling route

and passing peer to modal view

* group rows by HA routes

* Adjust high availability and show fields disabled

* add groupedRoutes to GroupedDataTable

* remove unused fields

* filter by grouped routes

* group routes by network id and network

* use better check for save button
2022-09-08 09:26:38 +02:00
braginini 06316239de Fix routes docs link 2022-09-05 19:45:14 +02:00
braginini 59eff85339 Fix routes docs link 2022-09-05 19:44:49 +02:00
braginini ffabdf8a1a Add new release banner 2022-09-05 19:09:43 +02:00
Maycon Santos 0fe5aa13b1 Rename Network range and enable masquerade by default (#72) 2022-09-05 16:13:01 +02:00
Maycon Santos 7166eb6e2f Network Routes page (#71)
Add Network routes page to interact with the routes API endpoint of our management
2022-09-05 09:08:51 +02:00
Misha Bragin c9f1955d6a Fix ACL modal groups input fields width (#69) 2022-08-22 15:27:52 +02:00
Misha Bragin c3236d05a1 Fix Auth0 OIDC integration 2022-08-17 20:38:52 +02:00
Misha Bragin 25e8a52465 Refactor to generic OIDC config (#66)
Cleans up config.json providing generic OIDC properties.
This allows for using other IDP providers besides Auth0.
The change is backward compatible with the previous versions.
2022-08-15 19:32:01 +02:00
Maycon Santos 2b26198741 As user to refresh page on 401 (#65)
* As user to refresh page on 401

* replace status code string
2022-08-04 20:42:16 +02:00
Maycon Santos 2b359b2140 Update dependencies to resolve issue with mobile (#64)
* Update dependencies to resolve issue with mobile

* ensure min rc-overflow fixes the issue with mobile
2022-08-03 16:48:24 +02:00
Maycon Santos d71d8214e7 Disable service worker (#63)
Service has caused some bugs where
the trust domains were ignored
and api calls were done without proper credentials
2022-08-03 09:44:46 +02:00
braginini 321e4d8311 Remove banner 2022-08-01 13:32:28 +02:00
Maycon Santos 224a889de3 Parse API URL when using default 443 and 80 ports (#62)
Init scripts will remove ports for replacement when the user
provided default HTTP and HTTPS ports.

This was causing issues with trusted domain logic from the OIDC library.
2022-07-27 11:09:45 +02:00
Maycon Santos b59865ae05 Replace Auth0 SDK with axa-fr/react-oidc (#60)
Replacing Auth0's SDK with a more generic implementation of an OIDC client.
This will allow us to use other IDP providers that follow the OIDC standards.
2022-07-26 16:13:08 +02:00
50 changed files with 23478 additions and 2988 deletions

No files matched your search

+4
View File
@@ -5,6 +5,7 @@ on:
- main
tags:
- "**"
pull_request:
jobs:
build_n_push:
@@ -21,6 +22,9 @@ jobs:
- name: Install dependecies
run: npm install
- name: List dependencies
run: npm list
- name: Build
# skiping fail on warning for now
run: CI=false npm run build
+3
View File
@@ -2,6 +2,7 @@
# dependencies
/node_modules
/node_modules.bkp
/.pnp
.pnp.js
@@ -26,3 +27,5 @@ src/auth_config.json
.idea
.eslintcache
src/.local-config.json
/public/OidcServiceWorker.js
/public/OidcTrustedDomains.js
+41 -19
View File
@@ -1,19 +1,39 @@
#!/bin/bash
set -e
if [[ -z "${AUTH0_DOMAIN}" ]]; then
echo "AUTH0_DOMAIN environment variable must be set"
exit 1
if [[ -z "${AUTH_AUTHORITY}" ]]; then
if [[ -z "${AUTH0_DOMAIN}" ]]; then
echo "AUTH_AUTHORITY or AUTH0_DOMAIN environment variable must be set"
exit 1
fi
fi
if [[ -z "${AUTH0_CLIENT_ID}" ]]; then
echo "AUTH0_CLIENT_ID environment variable must be set"
exit 1
if [[ -z "${AUTH_CLIENT_ID}" ]]; then
if [[ -z "${AUTH0_CLIENT_ID}" ]]; then
echo "AUTH_CLIENT_ID or AUTH0_CLIENT_ID environment variable must be set"
exit 1
fi
fi
if [[ -z "${AUTH0_AUDIENCE}" ]]; then
echo "AUTH0_AUDIENCE environment variable must be set"
exit 1
if [[ -z "${AUTH_AUDIENCE}" ]]; then
if [[ -z "${AUTH0_AUDIENCE}" ]]; then
echo "AUTH_AUDIENCE or AUTH0_AUDIENCE environment variable must be set"
exit 1
fi
fi
if [[ -z "${AUTH_SUPPORTED_SCOPES}" ]]; then
if [[ -z "${AUTH0_DOMAIN}" ]]; then
echo "AUTH_SUPPORTED_SCOPES environment variable must be set"
exit 1
fi
fi
if [[ -z "${USE_AUTH0}" ]]; then
if [[ -z "${AUTH0_DOMAIN}" ]]; then
echo "USE_AUTH0 environment variable must be set"
exit 1
fi
fi
if [[ -z "${NETBIRD_MGMT_API_ENDPOINT}" ]]; then
@@ -21,11 +41,14 @@ if [[ -z "${NETBIRD_MGMT_API_ENDPOINT}" ]]; then
exit 1
fi
AUTH0_DOMAIN=${AUTH0_DOMAIN}
AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID}
AUTH0_AUDIENCE=${AUTH0_AUDIENCE}
NETBIRD_MGMT_API_ENDPOINT=${NETBIRD_MGMT_API_ENDPOINT}
NETBIRD_MGMT_GRPC_API_ENDPOINT=${NETBIRD_MGMT_GRPC_API_ENDPOINT}
export AUTH_AUTHORITY=${AUTH_AUTHORITY:-https://$AUTH0_DOMAIN}
export AUTH_CLIENT_ID=${AUTH_CLIENT_ID:-$AUTH0_CLIENT_ID}
export AUTH_AUDIENCE=${AUTH_AUDIENCE:-$AUTH0_AUDIENCE}
export USE_AUTH0=${USE_AUTH0:-true}
export AUTH_SUPPORTED_SCOPES=${AUTH_SUPPORTED_SCOPES:-openid profile email api offline_access email_verified}
export NETBIRD_MGMT_API_ENDPOINT=$(echo $NETBIRD_MGMT_API_ENDPOINT | sed -E 's/(:80|:443)$//')
export NETBIRD_MGMT_GRPC_API_ENDPOINT=${NETBIRD_MGMT_GRPC_API_ENDPOINT}
REPO="https://github.com/netbirdio/netbird/"
# this command will fetch the latest release e.g. v0.6.3
@@ -33,11 +56,10 @@ export NETBIRD_LATEST_VERSION=$(basename $(curl -fs -o/dev/null -w %{redirect_ur
echo "NetBird latest version: ${NETBIRD_LATEST_VERSION}"
# replace ENVs in the config
ENV_STR="\$\$AUTH0_DOMAIN \$\$AUTH0_CLIENT_ID \$\$AUTH0_AUDIENCE \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_LATEST_VERSION"
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_LATEST_VERSION"
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
cp "$MAIN_JS" "$MAIN_JS".copy
envsubst "$ENV_STR" < "$MAIN_JS".copy > "$MAIN_JS"
rm "$MAIN_JS".copy
envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS".tmpl > "$OIDC_TRUSTED_DOMAINS"
rm "$MAIN_JS".copy
+20952 -2342
View File
File diff suppressed because it is too large. Load diff
+10 -5
View File
@@ -4,7 +4,7 @@
"private": true,
"dependencies": {
"@ant-design/icons": "^4.7.0",
"@auth0/auth0-react": "^1.6.0",
"@axa-fr/react-oidc": "^5.14.0",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.4",
"@testing-library/jest-dom": "^5.11.4",
@@ -21,18 +21,21 @@
"antd": "^4.20.6",
"autoprefixer": "^10.4.4",
"axios": "^0.27.2",
"cidr-regex": "^3.1.1",
"copyfiles": "^2.4.1",
"heroicons": "^1.0.6",
"highlight.js": "^11.2.0",
"history": "^5.0.1",
"lodash": "^4.17.21",
"postcss": "^8.4.12",
"prop-types": "^15.7.2",
"react": "^18.1.0",
"rc-overflow": "^1.2.8",
"react": "^18.2.0",
"react-dom": "^18.1.0",
"react-highlight": "^0.14.0",
"react-redux": "^8.0.2",
"react-router-dom": "^5.2.0",
"react-router-dom": "^5.3.3",
"react-scripts": "^5.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-table": "^7.7.0",
"redux": "^4.2.0",
"redux-devtools-extension": "^2.13.9",
@@ -44,6 +47,8 @@
"web-vitals": "^2.1.4"
},
"scripts": {
"copy": "copyfiles -f ./node_modules/@axa-fr/react-oidc/dist/OidcServiceWorker.js ./public",
"copytrusted": "copyfiles -f ./public/local/OidcTrustedDomains.js ./public",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
@@ -68,6 +73,6 @@
]
},
"devDependencies": {
"@types/react-highlight": "^0.12.5"
"@types/react-syntax-highlighter": "^15.5.3"
}
}
+6
View File
@@ -0,0 +1,6 @@
// Add here trusted domains, access tokens will be send to
const trustedDomains = {
default:["$NETBIRD_MGMT_API_ENDPOINT"],
auth0:[]
};
+6
View File
@@ -0,0 +1,6 @@
// Add here trusted domains, access tokens will be send to
const trustedDomains = {
default:["http://localhost:3001","http://127.0.0.1:3001", "http://0.0.0.0:33071"],
auth0:[]
};
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
MGMT_PORT=$1
npm run build
docker build -f docker/Dockerfile -t netbird/dashboard-local:latest .
docker rm -f netbird-dashboard
docker run -d --name netbird-dashboard \
-p 3000:80 -p 443:443 \
-e AUTH_AUDIENCE=netbird-client \
-e AUTH_AUTHORITY=http://localhost:8080/realms/netbird \
-e AUTH_CLIENT_ID=netbird-client \
-e USE_AUTH0=false \
-e AUTH_SUPPORTED_SCOPES='openid profile email api offline_access' \
-e NETBIRD_MGMT_API_ENDPOINT=http://localhost:$MGMT_PORT \
-e NETBIRD_MGMT_GRPC_API_ENDPOINT=http://localhost:$MGMT_PORT \
netbird/dashboard-local:latest
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
MGMT_PORT=$1
npm run build
docker build -f docker/Dockerfile -t netbird/dashboard-local:latest .
docker rm -f netbird-dashboard
docker run -d --name netbird-dashboard \
-p 3000:80 -p 443:443 \
-e AUTH0_AUDIENCE=http://localhost:3000/ \
-e AUTH0_DOMAIN=netbird-localdev.eu.auth0.com \
-e AUTH0_CLIENT_ID=kBRMAOqIZ7hvpVCaypQLCJvTzkYYIXVt \
-e NETBIRD_MGMT_API_ENDPOINT=http://localhost:$MGMT_PORT \
-e NETBIRD_MGMT_GRPC_API_ENDPOINT=http://localhost:$MGMT_PORT \
netbird/dashboard-local:latest
Executable
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
MGMT_PORT=$1
npm run build
docker build -f docker/Dockerfile -t netbird/dashboard-local:latest .
docker rm -f netbird-dashboard
docker run -d --name netbird-dashboard \
-p 3000:80 -p 443:443 \
-e AUTH_AUDIENCE=http://localhost:3000/ \
-e AUTH_AUTHORITY=https://netbird-localdev.eu.auth0.com \
-e AUTH_CLIENT_ID=kBRMAOqIZ7hvpVCaypQLCJvTzkYYIXVt \
-e USE_AUTH0=true \
-e AUTH_SUPPORTED_SCOPES='openid profile email api offline_access email_verified' \
-e NETBIRD_MGMT_API_ENDPOINT=http://localhost:$MGMT_PORT \
-e NETBIRD_MGMT_GRPC_API_ENDPOINT=http://localhost:$MGMT_PORT \
netbird/dashboard-local:latest
+10 -56
View File
@@ -1,40 +1,26 @@
import React, {useEffect, useState} from 'react';
import {Provider} from "react-redux";
import {Link, Redirect, Route, Switch} from 'react-router-dom';
import {useAuth0} from "@auth0/auth0-react";
import {Redirect, Route, Switch} from 'react-router-dom';
import Navbar from './components/Navbar';
import Peers from './views/Peers';
import FooterComponent from './components/FooterComponent';
import Loading from "./components/Loading";
import SetupKeys from "./views/SetupKeys";
import AddPeer from "./views/AddPeer";
import Users from './views/Users';
import AccessControl from './views/AccessControl';
// import Activity from './views/Activity';
import Routes from './views/Routes';
import Banner from "./components/Banner";
import {store} from "./store";
import {Button, Col, Layout, Result, Row} from 'antd';
import { Col, Layout, Row} from 'antd';
import {Container} from "./components/Container";
import {withOidcSecure} from '@axa-fr/react-oidc';
const {Header, Content} = Layout;
function App() {
const {
isLoading,
isAuthenticated,
loginWithRedirect,
logout,
error
} = useAuth0();
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
const hideMenu = () => {
if (window.innerWidth > 768 && isOpen) {
@@ -50,39 +36,8 @@ function App() {
};
});
if (error) {
return <Result
status="warning"
title={error.message}
extra={<>
<a href={window.location.origin}>
<Button type="primary">
Try again
</Button>
</a>
<Button type="primary" onClick={function () {
logout({
returnTo: window.location.origin,
})
}}>
Log out
</Button>
</>
}
/>
}
if (isLoading) {
return <Loading padding="3em" width="50px" height="50px"/>;
}
if (!isAuthenticated) {
loginWithRedirect({})
}
return (
<Provider store={store}>
{isAuthenticated &&
<Layout>
<Banner/>
<Header className="header" style={{
@@ -110,17 +65,16 @@ function App() {
)
}}
/>
<Route path='/peers' exact component={Peers}/>
<Route path="/add-peer" component={AddPeer}/>
<Route path="/setup-keys" component={SetupKeys}/>
<Route path="/acls" component={AccessControl}/>
{/*<Route path="/activity" component={Activity}/>*/}
<Route path="/users" component={Users}/>
<Route path='/peers' exact component={withOidcSecure(Peers)}/>
<Route path="/add-peer" component={withOidcSecure(AddPeer)}/>
<Route path="/setup-keys" component={withOidcSecure(SetupKeys)}/>
<Route path="/acls" component={withOidcSecure(AccessControl)}/>
<Route path="/routes" component={withOidcSecure(Routes)}/>
<Route path="/users" component={withOidcSecure(Users)}/>
</Switch>
</Content>
<FooterComponent/>
</Layout>
}
</Provider>
);
}
@@ -1,8 +1,4 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import { actions as ruleActions } from '../store/rule';
import {string} from "prop-types";
import {Avatar, List, Modal} from "antd";
import {Group} from "../store/group/types";
+3 -12
View File
@@ -14,10 +14,10 @@ import {
import {ArrowRightOutlined, CheckOutlined, CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
import type { CustomTagProps } from 'rc-select/lib/BaseSelect'
import {Rule, RuleToSave} from "../store/rule/types";
import {useAuth0} from "@auth0/auth0-react";
import { uniq } from "lodash"
import {Header} from "antd/es/layout/layout";
import {RuleObject} from "antd/lib/form";
import {useOidcAccessToken} from "@axa-fr/react-oidc";
const { Paragraph } = Typography;
const { Option } = Select;
@@ -28,7 +28,7 @@ interface FormRule extends Rule {
}
const AccessControlNew = () => {
const { getAccessTokenSilently } = useAuth0()
const {accessToken} = useOidcAccessToken()
const dispatch = useDispatch()
const setupNewRuleVisible = useSelector((state: RootState) => state.rule.setupNewRuleVisible)
const groups = useSelector((state: RootState) => state.group.data)
@@ -96,7 +96,7 @@ const AccessControlNew = () => {
form.validateFields()
.then((values) => {
const ruleToSave = createRuleToSave()
dispatch(ruleActions.saveRule.request({getAccessTokenSilently, payload: ruleToSave}))
dispatch(ruleActions.saveRule.request({getAccessTokenSilently:accessToken, payload: ruleToSave}))
})
.catch((errorInfo) => {
console.log('errorInfo', errorInfo)
@@ -308,14 +308,7 @@ const AccessControlNew = () => {
<Form.Item
name="disabled"
label="Status"
//valuePropName="checked"
>
{/*<Switch
checkedChildren={<CheckOutlined />}
unCheckedChildren={<CloseOutlined />}
onChange={handleChangeDisabled}
/>*/}
<Radio.Group
options={optionsDisabledEnabled}
@@ -330,7 +323,6 @@ const AccessControlNew = () => {
name="tagSourceGroups"
label="Source groups"
rules={[{ validator: selectValidator }]}
style={{display: 'flex'}}
>
<Select mode="tags"
style={{ width: '100%' }}
@@ -352,7 +344,6 @@ const AccessControlNew = () => {
name="tagDestinationGroups"
label="Destination groups"
rules={[{ validator: selectValidator }]}
style={{display: 'flex'}}
>
<Select
mode="tags" style={{ width: '100%' }}
+3 -3
View File
@@ -14,7 +14,7 @@ const Banner = () => {
const linkLearnMore = () => {
return (
<a
href="https://netbird.io/blog/introducing-access-control"
href="https://netbird.io/docs/how-to-guides/network-routes"
className="font-bold underline"
target="_blank"
rel="noreferrer"
@@ -27,7 +27,7 @@ const Banner = () => {
<Row>
<Col xs={24} sm={0} lg={0}>
<Text className="ant-col-md-0" style={{color: "#ffffff"}}>
Big news! Introducing NetBird Access Control.
New Release! Access private networks with the Network Routes feature.
</Text>
</Col>
<Col xs={24} sm={0} lg={0}>
@@ -38,7 +38,7 @@ const Banner = () => {
<Col xs={0} sm={24}>
<Space align="center" style={{display: "flex", justifyContent: "center"}}>
<Text style={{color: "#ffffff"}}>
Big news! Introducing NetBird Access Control.
New Release! Access private networks with the Network Routes feature.
</Text>
<span>
{linkLearnMore()}
+35
View File
@@ -0,0 +1,35 @@
import {OidcUserStatus, useOidc, useOidcUser} from "@axa-fr/react-oidc";
import {Button, Result} from "antd";
import React from "react";
import {getConfig} from "../config";
function LoginError() {
const { logout } = useOidc();
const config = getConfig();
const { oidcUserLoadingState } = useOidcUser();
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
if (urlParams.get("error") === "access_denied") {
return <Result
status="warning"
title={urlParams.get("error_description")}
extra={<>
<a href={window.location.origin}>
<Button type="primary">
Try again
</Button>
</a>
<Button type="primary" onClick={function () {
logout("",{client_id:config.clientId})
}}>
Log out
</Button>
</>
}
/>
}
return <div>{"Login Error: User state: "+oidcUserLoadingState}</div>
}
export default LoginError;
+11 -15
View File
@@ -1,23 +1,26 @@
import React, {useEffect, useState} from 'react';
import {Link} from 'react-router-dom';
import logo from "../assets/logo.png";
import {useAuth0} from "@auth0/auth0-react";
import {useLocation} from 'react-router-dom';
import {Menu, Row, Col, Grid, Dropdown, Avatar, Button, Typography, Space} 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 {getConfig} from "../config";
const { Text } = Typography
const { useBreakpoint } = Grid;
const Navbar = () => {
let location = useLocation();
const config = getConfig();
const {
user,
isAuthenticated,
logout,
} = useAuth0();
} = useOidc();
const { oidcUser } = useOidcUser();
const user = oidcUser;
const screens = useBreakpoint();
@@ -31,10 +34,11 @@ const Navbar = () => {
{ label: (<Link to="/add-peer">Add Peer</Link>), key: '/add-peer' },
{ label: (<Link to="/setup-keys">Setup Keys</Link>), key: '/setup-keys' },
{ label: (<Link to="/acls">Access Control</Link>), key: '/acls' },
// { label: (<Link to="/activity">Activity</Link>), key: '/activity' },
{ label: (<Link to="/routes">Network Routes</Link>), key: '/routes' },
{ label: (<Link to="/users">Users</Link>), key: '/users' }
] as ItemType[])
const logoutWithRedirect = () =>
logout("/",{client_id:config.clientId});
useEffect(() => {
const fs = menuItems.filter(m => m?.key !== userEmailKey && m?.key !== userLogoutKey && m?.key !== userDividerKey)
if (screens.xs === true) {
@@ -63,21 +67,13 @@ const Navbar = () => {
key: '0',
},
{
label: <a onClick={e => {
logoutWithRedirect()
e.preventDefault()}
}>Logout</a>,
label: (<Link to="/logout" onClick={logoutWithRedirect}>Logout</Link>),
key: '1',
}
]}
/>
);
const logoutWithRedirect = () =>
logout({
returnTo: window.location.origin,
});
const createAvatar = (size:AvatarSize) => {
return user?.picture ? (
<Avatar size={size} src={user?.picture} icon={<UserOutlined />} />
+8 -13
View File
@@ -5,17 +5,16 @@ import {actions as peerActions} from '../store/peer';
import {Button, Col, Divider, Drawer, Form, Input, Row, Select, Space, Tag, Typography} from "antd";
import {Header} from "antd/es/layout/layout";
import type {CustomTagProps} from 'rc-select/lib/BaseSelect'
import {useAuth0} from "@auth0/auth0-react";
import {Peer, PeerGroupsToSave} from "../store/peer/types";
import {Group, GroupPeer} from "../store/group/types";
import {CloseOutlined, EditOutlined, FlagFilled} from "@ant-design/icons";
import {RuleObject} from 'antd/lib/form';
import { useOidcAccessToken } from '@axa-fr/react-oidc';
const { Paragraph } = Typography;
const { Option } = Select;
const PeerUpdate = () => {
const { getAccessTokenSilently } = useAuth0()
const { accessToken } = useOidcAccessToken()
const dispatch = useDispatch()
const groups = useSelector((state: RootState) => state.group.data)
const peer = useSelector((state: RootState) => state.peer.peer)
@@ -214,11 +213,11 @@ const PeerUpdate = () => {
if (!noUpdateToName()) {
const peerUpdate = createPeerToSave()
setCallingPeerAPI(true)
dispatch(peerActions.updatePeer.request({getAccessTokenSilently, payload: peerUpdate}))
dispatch(peerActions.updatePeer.request({getAccessTokenSilently:accessToken, payload: peerUpdate}))
}
if (peerGroupsToSave.groupsToRemove.length || peerGroupsToSave.groupsToAdd.length || peerGroupsToSave.groupsNoId.length) {
setCallingGroupAPI(true)
dispatch(peerActions.saveGroups.request({getAccessTokenSilently, payload: peerGroupsToSave}))
dispatch(peerActions.saveGroups.request({getAccessTokenSilently:accessToken, payload: peerGroupsToSave}))
}
})
.catch((errorInfo) => {
@@ -278,7 +277,7 @@ const PeerUpdate = () => {
<Row align="top">
<Col flex="none" style={{display: "flex"}}>
{!editName && peer.id &&
<button type="button" aria-label="Close" className="ant-drawer-close"
<button type="button" aria-label="Close" autoFocus={true} className="ant-drawer-close"
style={{paddingTop: 3}}
onClick={onCancel}>
<span role="img" aria-label="close"
@@ -296,20 +295,18 @@ const PeerUpdate = () => {
) : (
<Form.Item
name="name"
label="Update Name"
label="Name"
rules={[{
required: true,
message: 'Please add a new name for this peer',
whitespace: true
}]}
style={{display: 'flex'}}
>
<Input
placeholder={peer.name}
ref={inputNameRef}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)}
// onChange={(e) => handleChangeName(e.)}
autoComplete="off"/>
</Form.Item>)}
</Col>
@@ -323,17 +320,15 @@ const PeerUpdate = () => {
<Col span={24}>
<Form.Item
name="groups"
label="Select groups to associate with this peer"
label="Select peer groups"
rules={[{ validator: selectValidator }]}
style={{display: 'flex'}}
>
<Select
mode="tags"
style={{ width: '100%' }}
placeholder="Select groups..."
tagRender={tagRender}
// onDropdownVisibleChange={evaluateSubmit}
// onSelect={evaluateSubmit}
dropdownRender={dropDownRender}
onChange={handleChangeTags}>
+373
View File
@@ -0,0 +1,373 @@
import React, {useEffect, useRef, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import { actions as routeActions } from '../store/route';
import {
Col,
Row,
Input,
InputNumber,
Space,
Switch,
SelectProps,
Button, Drawer, Form, Divider, Select, Radio, Typography
} from "antd";
import {CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
import {Route} from "../store/route/types";
import {Header} from "antd/es/layout/layout";
import {RuleObject} from "antd/lib/form";
import {useOidcAccessToken} from "@axa-fr/react-oidc";
import cidrRegex from 'cidr-regex';
import {
masqueradeDisabledMSG,
peerToPeerIP,
initPeerMaps,
routePeerSeparator,
transformGroupedDataTable
} from '../utils/routes'
const { Paragraph } = Typography;
interface FormRoute extends Route {
}
const RouteUpdate = () => {
const {accessToken} = useOidcAccessToken()
const dispatch = useDispatch()
const setupNewRouteVisible = useSelector((state: RootState) => state.route.setupNewRouteVisible)
const setupNewRouteHA = useSelector((state: RootState) => state.route.setupNewRouteHA)
const peers = useSelector((state: RootState) => state.peer.data)
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)
const options: SelectProps['options'] = [];
const [formRoute, setFormRoute] = useState({} as FormRoute)
const [form] = Form.useForm()
const inputNameRef = useRef<any>(null)
const inputDescriptionRef = useRef<any>(null)
const defaultRoutingPeerMSG = "Routing Peer"
const [routingPeerMSG, setRoutingPeerMSG] = useState(defaultRoutingPeerMSG)
const defaultMasqueradeMSG = "Masquerade"
const [masqueradeMSG, setMasqueradeMSG] = useState(defaultMasqueradeMSG)
const defaultStatusMSG = "Status"
const [statusMSG, setStatusMSG] = useState(defaultStatusMSG)
const [peerNameToIP, peerIPToName] = initPeerMaps(peers);
const optionsDisabledEnabled = [{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]
useEffect(() => {
if (setupNewRouteHA) {
setRoutingPeerMSG("Add additional routing peer")
setMasqueradeMSG("Update Masquerade")
setStatusMSG("Update Status")
} else {
setRoutingPeerMSG(defaultRoutingPeerMSG)
setMasqueradeMSG(defaultMasqueradeMSG)
setStatusMSG(defaultStatusMSG)
setPreviousRouteKey("")
}
}, [setupNewRouteHA])
useEffect(() => {
if (editName) inputNameRef.current!.focus({
cursor: 'end',
});
}, [editName]);
useEffect(() => {
if (editDescription) inputDescriptionRef.current!.focus({
cursor: 'end',
});
}, [editDescription]);
useEffect(() => {
if (!route) return
const fRoute = {
...route,
} as FormRoute
setFormRoute(fRoute)
setPreviousRouteKey(fRoute.network_id+fRoute.network)
form.setFieldsValue(fRoute)
}, [route])
peers.forEach((p) => {
let os:string
os = p.os
if (!os.toLowerCase().startsWith("darwin") && !os.toLowerCase().startsWith("windows")) {
options?.push({
label: peerToPeerIP(p.name,p.ip),
value: peerToPeerIP(p.name,p.ip),
disabled: false
})
}
})
const createRouteToSave = (inputRoute:FormRoute):Route => {
let peerIDList = inputRoute.peer.split(routePeerSeparator)
let peerID:string
if (peerIDList[1]) {
peerID = peerIDList[1]
} else {
peerID = peerNameToIP[inputRoute.peer]
}
return {
id: inputRoute.id,
network: inputRoute.network,
network_id: inputRoute.network_id,
description: inputRoute.description,
peer: peerID,
enabled: inputRoute.enabled,
masquerade: inputRoute.masquerade,
metric: inputRoute.metric
} as Route
}
const handleFormSubmit = () => {
form.validateFields()
.then(() => {
if (!setupNewRouteHA || formRoute.peer != '') {
const routeToSave = createRouteToSave(formRoute)
dispatch(routeActions.saveRoute.request({getAccessTokenSilently:accessToken, payload: routeToSave}))
} else {
let groupedDataTable = transformGroupedDataTable(routes,peerIPToName)
groupedDataTable.forEach((group) => {
if (group.key == previousRouteKey) {
group.groupedRoutes.forEach((route) => {
let updateRoute:FormRoute = {
...formRoute,
id: route.id,
peer: route.peer,
metric: route.metric,
enabled: (formRoute.enabled != group.enabled) ? formRoute.enabled : route.enabled
}
const routeToSave = createRouteToSave(updateRoute)
dispatch(routeActions.saveRoute.request({getAccessTokenSilently:accessToken, payload: routeToSave}))
})
}
})
}
})
.catch((errorInfo) => {
console.log('errorInfo', errorInfo)
});
};
const setVisibleNewRoute = (status:boolean) => {
dispatch(routeActions.setSetupNewRouteVisible(status));
}
const setSetupNewRouteHA = (status:boolean) => {
dispatch(routeActions.setSetupNewRouteHA(status));
}
const onCancel = () => {
if (savedRoute.loading) return
setEditName(false)
dispatch(routeActions.setRoute({
network: '',
network_id: '',
description: '',
peer: "",
metric: 9999,
masquerade: false,
enabled: true
} as Route))
setVisibleNewRoute(false)
setSetupNewRouteHA(false)
setPreviousRouteKey("")
}
const onChange = (data:any) => {
setFormRoute({...formRoute, ...data})
}
const dropDownRender = (menu: React.ReactElement) => (
<>
{menu}
</>
)
const toggleEditName = (status:boolean) => {
setEditName(status);
}
const toggleEditDescription = (status:boolean) => {
setEditDescription(status);
}
const networkRangeValidator = (_: RuleObject, value: string) => {
if (!cidrRegex().test(value)) {
return Promise.reject(new Error("Please enter a valid CIDR, e.g. 192.168.1.0/24"))
}
if (Number(value.split("/")[1]) < 7) {
return Promise.reject(new Error("Please enter a network mask larger than /7"))
}
return Promise.resolve()
}
return (
<>
{route &&
<Drawer
headerStyle={{display: "none"}}
forceRender={true}
visible={setupNewRouteVisible}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
autoFocus={true}
footer={
<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>
</Space>
}
>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
<Row align="top">
<Col flex="none" style={{display: "flex"}}>
{!editName && !editDescription && formRoute.id &&
<button type="button" aria-label="Close" className="ant-drawer-close"
style={{paddingTop: 3}}
onClick={onCancel}>
<span role="img" aria-label="close" className="anticon anticon-close">
<CloseOutlined size={16}/>
</span>
</button>
}
</Col>
<Col flex="auto">
{ !editName && formRoute.id ? (
<div className={"access-control input-text ant-drawer-title"} onClick={() => toggleEditName(true)}>{formRoute.id ? formRoute.network_id : 'New Route'}</div>
) : (
<Form.Item
name="network_id"
label="Network Identifier"
tooltip="You can enable high-availability by assigning the same network identifier and network CIDR to multiple routes"
rules={[{required: true, message: 'Please add an identifier for this access route', whitespace: true}]}
>
<Input placeholder="e.g. aws-eu-central-1-vpc" ref={inputNameRef} disabled={!setupNewRouteHA} onPressEnter={() => toggleEditName(false)} onBlur={() => toggleEditName(false)} autoComplete="off" maxLength={40}/>
</Form.Item>
)}
{ !editDescription ? (
<div className={"access-control input-text ant-drawer-subtitle"} onClick={() => toggleEditDescription(true)}>{formRoute.description && formRoute.description.trim() !== "" ? formRoute.description : 'Add description...'}</div>
) : (
<Form.Item
name="description"
label="Description"
style={{marginTop: 24}}
>
<Input placeholder="Add description..." ref={inputDescriptionRef} disabled={!setupNewRouteHA} onPressEnter={() => toggleEditDescription(false)} onBlur={() => toggleEditDescription(false)} autoComplete="off" maxLength={200}/>
</Form.Item>
)}
</Col>
</Row>
<Row align="top">
<Col flex="auto">
</Col>
</Row>
</Header>
</Col>
<Col span={24}>
</Col>
<Col span={24}>
<Form.Item
name="network"
label="Network Range"
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} autoComplete="off" minLength={9} maxLength={43}/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="enabled"
label={statusMSG}
>
<Radio.Group
options={optionsDisabledEnabled}
optionType="button"
buttonStyle="solid"
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="peer"
label={routingPeerMSG}
tooltip="Assign a peer as a routing peer for the Network CIDR"
>
<Select
showSearch
style={{ width: '100%' }}
placeholder="Select Peer"
dropdownRender={dropDownRender}
options={options}
allowClear={true}
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="masquerade"
label={masqueradeMSG}
tooltip={masqueradeDisabledMSG}
>
<Switch size={"small"} disabled={!setupNewRouteHA} checked={formRoute.masquerade}/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="metric"
label="Metric"
tooltip="Choose from 1 to 9999. Lower number has higher priority"
>
<InputNumber min={1} max={9999} autoComplete="off"/>
</Form.Item>
</Col>
<Col span={24}>
<Row wrap={false} gutter={12}>
<Col flex="none">
<FlagFilled/>
</Col>
<Col flex="auto">
<Paragraph>
You can enable high-availability by assigning the same network identifier and network CIDR to multiple routes.
</Paragraph>
</Col>
</Row>
</Col>
<Col span={24}>
<Divider></Divider>
<Button icon={<QuestionCircleFilled/>} type="link" target="_blank"
href="https://netbird.io/docs/how-to-guides/network-routes" style={{color: 'rgb(07, 114, 128)'}}>Learn
more about network routes</Button>
</Col>
</Row>
</Form>
</Drawer>
}
</>
)
}
export default RouteUpdate
File diff suppressed because it is too large. Load diff
+5 -8
View File
@@ -1,9 +1,8 @@
import {useDispatch, useSelector} from "react-redux";
import Highlight from 'react-highlight';
import "highlight.js/styles/mono-blue.css";
import "highlight.js/lib/languages/bash";
import { StepCommand } from './types'
import SyntaxHighlighter from 'react-syntax-highlighter';
import { monoBlue } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import {
Typography,
Space,
@@ -12,8 +11,6 @@ import {
import {copyToClipboard} from "../../utils/common";
import {CheckOutlined, CopyOutlined} from "@ant-design/icons";
import React, {useEffect, useState} from "react";
const { Title, Text } = Typography;
const { Step } = Steps;
type Props = {
@@ -48,10 +45,10 @@ const TabSteps:React.FC<Props> = ({stepsItems}) => {
title={c.title}
description={
<Space className="nb-code" direction="vertical" size="small" style={{display: "flex"}}>
{ (c.commands && (typeof c.commands === 'string' || c.commands instanceof String)) ? (
<Highlight className='bash'>
{ (c.commands && (typeof c.commands === 'string')) ? (
<SyntaxHighlighter language="bash" style={monoBlue}>
{c.commands}
</Highlight>
</SyntaxHighlighter>
) : (
c.commands
)}
Loaded 20 of 50 files, more files were not shown because too many files have changed in this diff. Show more