Compare commits

...
Author SHA1 Message Date
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
Maycon Santos 54cd75301c Add rename peer (#59)
Added View option in the menu

Click on name to open the peer View option
2022-07-14 10:37:33 +02:00
braginini 1623dd243a Add access control banner 2022-06-24 22:43:23 +02:00
Misha Bragin cf0abd3029 Enable/Disable SSH server in the peers table (#54) 2022-06-23 17:01:40 +02:00
Misha Bragin 5bc913ff74 Make footer sticky (#56)
* Make footer sticky

* Fix footer positioning
2022-06-22 15:15:13 +02:00
Sang Woo Bae 0260dd5ce3 Link to documentation in installpage (#57)
* Link to documentation in installpage

* Add Documentation link for other distros

* Fix OS autodetection

* Reorder AddPeer tabs and add other tab
2022-06-22 14:36:40 +02:00
Maycon Santos aad85a1f67 Validate tag input and handle popover for 5+ groups (#55)
Validate tag naming input

check if only spaces has been added
check if group All is being removed
group All is not closeable in peers

Fixed issue when group list is too large, 
preventing click on the group counter

removed arrows from rule inputs
2022-06-20 17:07:59 +02:00
6724e5bfaa Feature/access control (#49)
Add Access control feature page

Users can create and update access control rules from the UI

Users can assign groups to peers

Minor enhancements to the UI

Co-authored-by: Raphael Oliveira <raphael.oliveira@dataontabs.com>
Co-authored-by: DataOnTabs <68952619+dataontabs@users.noreply.github.com>
Co-authored-by: mlsmaycon <mlsmaycon@gmail.com>
2022-06-15 11:42:15 +02:00
braginini 4e78fabb35 Update images size in README 2022-06-12 09:07:53 +02:00
braginini c4d679a371 Update readme and media 2022-06-12 09:06:47 +02:00
braginini 48d25a0008 Correct wording for popups 2022-06-08 12:15:04 +02:00
braginini a65b892441 Fix windows download 2022-06-08 00:19:03 +02:00
braginini 32e06013e9 Fix Try Again btn on auth error 2022-06-07 18:01:58 +02:00
braginini 0334899719 Add logout button on error 2022-06-07 16:26:16 +02:00
braginini 48e6041fd6 Don't log out when email not confirmed 2022-06-07 14:41:58 +02:00
braginini 72e24b8330 Add custom page for auth errors 2022-06-07 12:05:29 +02:00
Misha Bragin 665e6191ea Dynamically fetch the latest release of NetBird on startup (#52) 2022-06-06 13:44:18 +02:00
braginini e62e8a00f8 Detect OS to select installation Tab 2022-06-05 16:35:03 +02:00
72 changed files with 24629 additions and 3303 deletions

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ on:
- main
tags:
- "**"
pull_request:
jobs:
build_n_push:
+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
+18 -20
View File
@@ -1,37 +1,35 @@
# NetBird dashboard
### Big News! Wiretrustee becomes `NetBird`. [See details](https://blog.netbird.io/wiretrustee-becomes-netbird).
\
This project is the UI for NetBird's management service.
This project is the UI for NetBird's Management service.
**Hosted demo version:** https://app.netbird.io/
**Hosted version:** https://app.netbird.io/
See [NetBird repo](https://github.com/netbirdio/netbird)
## Why UI dashboard is needed?
## Why?
The purpose of this project is simple - make it easy to manage VPN built with [NetBird](https://github.com/netbirdio/netbird).
The dashboard makes it possible to:
- track the status of your peers
- remove Peers
- remove peers
- manage Setup Keys (to authenticate new peers)
- monitor system activity (coming soon)
- manage peer access (coming soon)
- list users
- define access controls
## Some Screenshots
<img src="./media/auth.png" alt="auth" width="350"/>
<img src="./media/peers.png" alt="peers" width="350"/>
<img src="./media/add-peer.png" alt="add-peer" width="350"/>
<img src="./media/auth.png" alt="auth"/>
<img src="./media/peers.png" alt="peers"/>
<img src="./media/add-peer.png" alt="add-peer"/>
## Technologies Used
- ReactJS
- Tailwind CSS. We are using paid License of [TailwindUI](https://tailwindui.com/)
- AntD UI framework
- Auth0
- Nginx
- Nginx
- Docker
- Letsencrypt
- Let's Encrypt
## How to run
Disclaimer. We believe that proper user management system is not a trivial task and requires quite some effort to make it right. Therefore we decided to
@@ -40,18 +38,18 @@ 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):
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```
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"
```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.
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```
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```
## How to run local development
+46 -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,18 +41,25 @@ 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
export NETBIRD_LATEST_VERSION=$(basename $(curl -fs -o/dev/null -w %{redirect_url} ${REPO}releases/latest))
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"
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 61 KiB

+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
+19 -41
View File
@@ -1,39 +1,26 @@
import React, {useEffect, useState} from 'react';
import {Provider} from "react-redux";
import {Redirect, Route, Switch} from 'react-router-dom';
import {useAuth0} from "@auth0/auth0-react";
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 AccessControl from "./views/AccessControl";
import Activity from "./views/Activity";
import Users from './views/Users';
import AccessControl from './views/AccessControl';
import Routes from './views/Routes';
import Banner from "./components/Banner";
import {store} from "./store";
import { Col, Layout, Row} from 'antd';
import {Container} from "./components/Container";
import {withOidcSecure} from '@axa-fr/react-oidc';
import {Col, Layout, Row} from 'antd';
import { Container } from "./components/Container";
const { Header, Content } = Layout;
const {Header, Content} = Layout;
function App() {
const {
isLoading,
isAuthenticated,
loginWithRedirect,
error
} = useAuth0();
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
const hideMenu = () => {
if (window.innerWidth > 768 && isOpen) {
@@ -49,24 +36,16 @@ function App() {
};
});
if (error) {
return <div>Oops... {error.message}</div>;
}
if (isLoading) {
return <Loading padding="3em" width="50px" height="50px"/>;
}
if (!isAuthenticated) {
loginWithRedirect({})
}
return (
<Provider store={store}>
{ isAuthenticated &&
<Layout>
<Banner/>
<Header className="header" style={{display: "flex", flexDirection: "column", justifyContent: "space-around", alignContent: "center"}}>
<Header className="header" style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
alignContent: "center"
}}>
<Row justify="space-around" align="middle">
<Col span={24}>
<Container>
@@ -75,7 +54,7 @@ function App() {
</Col>
</Row>
</Header>
<Content>
<Content style={{ minHeight: "100vh"}}>
<Switch>
<Route
exact
@@ -86,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>
);
}
+3 -7
View File
@@ -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";
@@ -27,9 +23,9 @@ const AccessControlModalGroups:React.FC<Props> = ({data, title, visible, onCance
renderItem={(item:Group) => (
<List.Item>
<List.Item.Meta
avatar={<Avatar>{item.Name.slice(0,1).toUpperCase()}</Avatar>}
title={item.Name}
description={`${item.PeersCount} peers`}
avatar={<Avatar>{item.name.slice(0,1).toUpperCase()}</Avatar>}
title={item.name}
description={`${item.peers_count} peers`}
/>
</List.Item>
)}
+142 -42
View File
@@ -2,24 +2,22 @@ import React, {useEffect, useRef, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import { actions as ruleActions } from '../store/rule';
import { actions as groupsActions } from '../store/group';
import inbound from '../assets/direct_in.svg';
import outbound from '../assets/direct_out.svg';
import {
Col,
Row,
Typography,
Input,
Space,
Radio,
Button, Drawer, Form, List, Divider, Select, Tag
Switch,
Button, Drawer, Form, Divider, Select, Tag, Radio, RadioChangeEvent
} from "antd";
import {ArrowRightOutlined, CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
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;
@@ -30,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)
@@ -38,10 +36,14 @@ const AccessControlNew = () => {
const savedRule = useSelector((state: RootState) => state.rule.savedRule)
const [editName, setEditName] = useState(false)
const [editDescription, setEditDescription] = useState(false)
const [tagGroups, setTagGroups] = useState([] as string[])
const [formRule, setFormRule] = useState({} as FormRule)
const [form] = Form.useForm()
const inputNameRef = useRef<any>(null)
const inputDescriptionRef = useRef<any>(null)
const optionsDisabledEnabled = [{label: 'Enabled', value: false}, {label: 'Disabled', value: true}]
useEffect(() => {
if (editName) inputNameRef.current!.focus({
@@ -49,36 +51,44 @@ const AccessControlNew = () => {
});
}, [editName]);
useEffect(() => {
if (editDescription) inputDescriptionRef.current!.focus({
cursor: 'end',
});
}, [editDescription]);
useEffect(() => {
if (!rule) return
const fRule = {
...rule,
tagSourceGroups: rule.Source ? rule.Source?.map(t => t.Name) : [],
tagDestinationGroups: rule.Destination ? rule.Destination?.map(t => t.Name) : []
tagSourceGroups: rule.sources ? rule.sources?.map(t => t.name) : [],
tagDestinationGroups: rule.destinations ? rule.destinations?.map(t => t.name) : []
} as FormRule
setFormRule(fRule)
form.setFieldsValue(fRule)
}, [rule])
useEffect(() => {
setTagGroups(groups?.map(g => g.Name) || [])
setTagGroups(groups?.map(g => g.name) || [])
}, [groups])
const createRuleToSave = ():RuleToSave => {
const Source = groups?.filter(g => formRule.tagSourceGroups.includes(g.Name)).map(g => g.ID || '') || []
const Destination = groups?.filter(g => formRule.tagDestinationGroups.includes(g.Name)).map(g => g.ID || '') || []
const sources = groups?.filter(g => formRule.tagSourceGroups.includes(g.name)).map(g => g.id || '') || []
const destinations = groups?.filter(g => formRule.tagDestinationGroups.includes(g.name)).map(g => g.id || '') || []
const sourcesNoId = formRule.tagSourceGroups.filter(s => !tagGroups.includes(s))
const destinationsNoId = formRule.tagDestinationGroups.filter(s => !tagGroups.includes(s))
const groupsToSave = uniq([...sourcesNoId, ...destinationsNoId])
return {
ID: formRule.ID,
Name: formRule.Name,
Source,
Destination,
id: formRule.id,
name: formRule.name,
description: formRule.description,
sources,
destinations,
sourcesNoId,
destinationsNoId,
groupsToSave,
Flow: formRule.Flow
flow: formRule.flow,
disabled: formRule.disabled
} as RuleToSave
}
@@ -86,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)
@@ -101,10 +111,12 @@ const AccessControlNew = () => {
if (savedRule.loading) return
setEditName(false)
dispatch(ruleActions.setRule({
Name: '',
Source: [],
Destination: [],
Flow: 'bidirect'
name: '',
description: '',
sources: [],
destinations: [],
flow: 'bidirect',
disabled: false
} as Rule))
setVisibleNewRule(false)
}
@@ -127,6 +139,13 @@ const AccessControlNew = () => {
})
};
const handleChangeDisabled = ({ target: { value } }: RadioChangeEvent) => {
setFormRule({
...formRule,
disabled: value
})
};
const tagRender = (props: CustomTagProps) => {
const { label, value, closable, onClose } = props;
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
@@ -149,8 +168,8 @@ const AccessControlNew = () => {
const optionRender = (label: string) => {
let peersCount = ''
const g = groups.find(_g => _g.Name === label)
if (g) peersCount = ` - ${g.PeersCount || 0} ${(g.PeersCount && parseInt(g.PeersCount) > 1) ? 'peers' : 'peer'} `
const g = groups.find(_g => _g.name === label)
if (g) peersCount = ` - ${g.peers_count || 0} ${(!g.peers_count || parseInt(g.peers_count) !== 1) ? 'peers' : 'peer'} `
return (
<>
<Tag
@@ -164,16 +183,56 @@ const AccessControlNew = () => {
)
}
const dropDownRender = (menu: React.ReactElement) => (
<>
{menu}
<Divider style={{ margin: '8px 0' }} />
<Row style={{padding: '0 8px 4px'}}>
<Col flex="auto">
<span style={{color: "#9CA3AF"}}>Add new group by pressing "Enter"</span>
</Col>
<Col flex="none">
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.70455 7.19176V5.89915H10.3949C10.7727 5.89915 11.1174 5.80634 11.429 5.62074C11.7405 5.43513 11.9875 5.18655 12.1697 4.875C12.3554 4.56345 12.4482 4.21875 12.4482 3.84091C12.4482 3.46307 12.3554 3.12003 12.1697 2.81179C11.9841 2.50024 11.7356 2.25166 11.424 2.06605C11.1158 1.88044 10.7727 1.78764 10.3949 1.78764H9.83807V0.5H10.3949C11.0114 0.5 11.5715 0.650805 12.0753 0.952414C12.5791 1.25402 12.9818 1.65672 13.2834 2.16051C13.585 2.6643 13.7358 3.22443 13.7358 3.84091C13.7358 4.30161 13.648 4.73414 13.4723 5.13849C13.3 5.54285 13.0613 5.89915 12.7564 6.20739C12.4515 6.51562 12.0968 6.75758 11.6925 6.93324C11.2881 7.10559 10.8556 7.19176 10.3949 7.19176H1.70455ZM4.90128 11.0646L0.382102 6.54545L4.90128 2.02628L5.79119 2.91619L2.15696 6.54545L5.79119 10.1747L4.90128 11.0646Z" fill="#9CA3AF"/>
</svg>
</Col>
</Row>
</>
)
const toggleEditName = (status:boolean) => {
setEditName(status);
}
const toggleEditDescription = (status:boolean) => {
setEditDescription(status);
}
// const testDeleteGroup = () => {
// groups.forEach(g => {
// dispatch(groupsActions.deleteGroup.request({getAccessTokenSilently, payload: g.ID || ''}))
// })
// }
const selectValidator = (_: RuleObject, value: string[]) => {
let hasSpaceNamed = []
if (!value.length) {
return Promise.reject(new Error("Please enter ate least one group"))
}
value.forEach(function(v: string) {
if (!v.trim().length) {
hasSpaceNamed.push(v)
}
})
if (hasSpaceNamed.length) {
return Promise.reject(new Error("Group names with just spaces are not allowed"))
}
return Promise.resolve()
}
return (
<>
{rule &&
@@ -185,10 +244,11 @@ const AccessControlNew = () => {
visible={setupNewRuleVisible}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
autoFocus={true}
footer={
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button onClick={onCancel} disabled={savedRule.loading}>Cancel</Button>
<Button type="primary" disabled={savedRule.loading} onClick={handleFormSubmit}>{`${formRule.ID ? 'Save' : 'Create'}`}</Button>
<Button type="primary" disabled={savedRule.loading} onClick={handleFormSubmit}>{`${formRule.id ? 'Save' : 'Create'}`}</Button>
</Space>
}
>
@@ -198,7 +258,7 @@ const AccessControlNew = () => {
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
<Row align="top">
<Col flex="none" style={{display: "flex"}}>
{!editName && formRule.ID &&
{!editName && !editDescription && formRule.id &&
<button type="button" aria-label="Close" className="ant-drawer-close"
style={{paddingTop: 3}}
onClick={onCancel}>
@@ -209,32 +269,68 @@ const AccessControlNew = () => {
}
</Col>
<Col flex="auto">
{ !editName && formRule.ID ? (
<div className={"access-control ant-drawer-title"} onClick={() => toggleEditName(true)}>{formRule.ID ? formRule.Name : 'New Rule'}</div>
{ !editName && formRule.id ? (
<div className={"access-control input-text ant-drawer-title"} onClick={() => toggleEditName(true)}>{formRule.id ? formRule.name : 'New Rule'}</div>
) : (
<Form.Item
name="Name"
label={null}
rules={[{required: true, message: 'Please add a name for this access rule'}]}
name="name"
label="Name"
rules={[{required: true, message: 'Please add a name for this access rule', whitespace: true}]}
>
<Input placeholder="Add rule name..." ref={inputNameRef} onPressEnter={() => toggleEditName(false)} onBlur={() => toggleEditName(false)} autoComplete="off"/>
</Form.Item>
)}
{ !editDescription ? (
<div className={"access-control input-text ant-drawer-subtitle"} onClick={() => toggleEditDescription(true)}>{formRule.description && formRule.description.trim() !== "" ? formRule.description : 'Add description...'}</div>
) : (
<Form.Item
name="description"
label="Description"
style={{marginTop: 24}}
>
<Input placeholder="Add description..." ref={inputDescriptionRef} onPressEnter={() => toggleEditDescription(false)} onBlur={() => toggleEditDescription(false)} autoComplete="off"/>
</Form.Item>
)}
</Col>
</Row>
<Row align="top">
<Col flex="auto">
</Col>
</Row>
</Header>
</Col>
<Col span={24}>
</Col>
<Col span={24}>
<Form.Item
name="disabled"
label="Status"
>
<Radio.Group
options={optionsDisabledEnabled}
onChange={handleChangeDisabled}
optionType="button"
buttonStyle="solid"
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="tagSourceGroups"
label={<>Source groups&nbsp;<ArrowRightOutlined /></>}
rules={[{required: true, message: 'Please enter ate least one group'}]}
style={{display: 'flex'}}
label="Source groups"
rules={[{ validator: selectValidator }]}
>
<Select mode="tags" style={{ width: '100%' }} placeholder="Tags Mode" tagRender={tagRender} onChange={handleChangeSource}>
<Select mode="tags"
style={{ width: '100%' }}
placeholder="Tags Mode"
tagRender={tagRender}
onChange={handleChangeSource}
dropdownRender={dropDownRender}
>
{
tagGroups.map(m =>
<Option key={m}>{optionRender(m)}</Option>
@@ -246,11 +342,16 @@ const AccessControlNew = () => {
<Col span={24}>
<Form.Item
name="tagDestinationGroups"
label={<><ArrowRightOutlined />&nbsp;Destination groups</>}
rules={[{required: true, message: 'Please enter ate least one group'}]}
style={{display: 'flex'}}
label="Destination groups"
rules={[{ validator: selectValidator }]}
>
<Select mode="tags" style={{ width: '100%' }} placeholder="Tags Mode" tagRender={tagRender} onChange={handleChangeDestination}>
<Select
mode="tags" style={{ width: '100%' }}
placeholder="Tags Mode"
tagRender={tagRender}
onChange={handleChangeDestination}
dropdownRender={dropDownRender}
>
{
tagGroups.map(m =>
<Option key={m}>{optionRender(m)}</Option>
@@ -271,7 +372,6 @@ const AccessControlNew = () => {
<Paragraph>
If you want to enable all peers of the same group to talk to each other - you can add that group both as a receiver and as a destination.
</Paragraph>
<a style={{color: 'rgb(07, 114, 128)'}} href="https://docs.netbird.io/overview/access-control" target="_blank">Learn more about access control...</a>
</Col>
</Row>
</Col>
@@ -279,7 +379,7 @@ const AccessControlNew = () => {
<Divider></Divider>
<Button icon={<QuestionCircleFilled/>} type="link" target="_blank"
href="https://docs.netbird.io/docs/overview/acls" style={{color: 'rgb(07, 114, 128)'}}>Learn
more about setup keys</Button>
more about access controls</Button>
</Col>
</Row>
</Form>
+3 -3
View File
@@ -14,7 +14,7 @@ const Banner = () => {
const linkLearnMore = () => {
return (
<a
href="https://blog.netbird.io/wiretrustee-becomes-netbird"
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! Wiretrustee becomes <strong>NetBird</strong>!
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! Wiretrustee becomes <strong>NetBird</strong>!
New Release! Access private networks with the Network Routes feature.
</Text>
<span>
{linkLearnMore()}
+1 -2
View File
@@ -1,12 +1,11 @@
import React from 'react';
import { Layout } from 'antd';
import {Link} from 'react-router-dom';
const { Footer } = Layout
export default () => {
return (
<Footer style={{ textAlign: 'center' }}>
<Footer style={{ textAlign: 'center', bottom: "0"}}>
Copyright © 2022 <a href="https://netbird.io">NetBird Authors</a>
</Footer>
);
+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;
Loaded 20 of 72 files, more files were not shown because too many files have changed in this diff. Show more