mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baf37c2eaf | ||
|
|
01bcc620a5 | ||
|
|
ace2bb61ef | ||
|
|
f389862931 | ||
|
|
521df658ad | ||
|
|
4db17c119a | ||
|
|
230a4cb05e | ||
|
|
06316239de | ||
|
|
59eff85339 | ||
|
|
ffabdf8a1a | ||
|
|
0fe5aa13b1 | ||
|
|
7166eb6e2f | ||
|
|
c9f1955d6a | ||
|
|
c3236d05a1 | ||
|
|
25e8a52465 | ||
|
|
2b26198741 | ||
|
|
2b359b2140 | ||
|
|
d71d8214e7 | ||
|
|
321e4d8311 | ||
|
|
224a889de3 | ||
|
|
b59865ae05 | ||
|
|
54cd75301c | ||
|
|
1623dd243a | ||
|
|
cf0abd3029 | ||
|
|
5bc913ff74 | ||
|
|
0260dd5ce3 | ||
|
|
aad85a1f67 | ||
|
|
6724e5bfaa | ||
|
|
4e78fabb35 | ||
|
|
c4d679a371 | ||
|
|
48d25a0008 | ||
|
|
a65b892441 | ||
|
|
32e06013e9 | ||
|
|
0334899719 | ||
|
|
48e6041fd6 | ||
|
|
72e24b8330 | ||
|
|
665e6191ea | ||
|
|
e62e8a00f8 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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 |
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 61 KiB |
Generated
+20952
-2342
File diff suppressed because it is too large.
Load diff
+10
-5
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
// Add here trusted domains, access tokens will be send to
|
||||
const trustedDomains = {
|
||||
default:["$NETBIRD_MGMT_API_ENDPOINT"],
|
||||
auth0:[]
|
||||
};
|
||||
@@ -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:[]
|
||||
};
|
||||
Executable
+18
@@ -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
|
||||
Executable
+16
@@ -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
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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 <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 /> 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>
|
||||
|
||||
@@ -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,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>
|
||||
);
|
||||
|
||||
@@ -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
Reference in new issue
Block a user