mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
88
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8195587c85 | ||
|
|
adf6e1e71f | ||
|
|
b733a186ae | ||
|
|
5d901470c2 | ||
|
|
29ab28847d | ||
|
|
0361825e04 | ||
|
|
2fa33ec06a | ||
|
|
c677eeaae4 | ||
|
|
7fb4b0b145 | ||
|
|
57f60a2fbf | ||
|
|
ec949da416 | ||
|
|
43710b8ada | ||
|
|
247665a846 | ||
|
|
010657c594 | ||
|
|
2d911af97f | ||
|
|
afdfed0160 | ||
|
|
6b86da3716 | ||
|
|
425fac8e9c | ||
|
|
fa2413f937 | ||
|
|
feec057933 | ||
|
|
9127686df7 | ||
|
|
479911ded8 | ||
|
|
69dcd6fadd | ||
|
|
0b7b34b490 | ||
|
|
fff93a3820 | ||
|
|
da21784c73 | ||
|
|
2e03a39b3e | ||
|
|
8e626cdd96 | ||
|
|
472704ad59 | ||
|
|
94c7288016 | ||
|
|
957ff98cec | ||
|
|
80178f66c3 | ||
|
|
37324cbcfc | ||
|
|
9dd362a8a4 | ||
|
|
90605a2067 | ||
|
|
18cfddbbe7 | ||
|
|
17e671200e | ||
|
|
bb94342cc8 | ||
|
|
b86cf8b99f | ||
|
|
f472c06cbf | ||
|
|
c58834309b | ||
|
|
75fdd3e17f | ||
|
|
568c5eccda | ||
|
|
363f226a1c | ||
|
|
bf447b1ada | ||
|
|
90cb05bd2d | ||
|
|
a98d6d9ce1 | ||
|
|
f83e39d734 | ||
|
|
52c1909229 | ||
|
|
f0d893c689 | ||
|
|
c8339c4be1 | ||
|
|
954d697b5f | ||
|
|
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,15 +5,16 @@ on:
|
||||
- main
|
||||
tags:
|
||||
- "**"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build_n_push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: setup-node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
@@ -26,26 +27,26 @@ jobs:
|
||||
run: CI=false npm run build
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: wiretrustee/dashboard
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
-
|
||||
name: Docker build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
|
||||
@@ -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
|
||||
|
||||
+50
-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,29 @@ 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 AUTH_REDIRECT_URI=${AUTH_REDIRECT_URI}
|
||||
export AUTH_SILENT_REDIRECT_URI=${AUTH_SILENT_REDIRECT_URI}
|
||||
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}
|
||||
export NETBIRD_HOTJAR_TRACK_ID=${NETBIRD_HOTJAR_TRACK_ID}
|
||||
|
||||
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 \$\$NETBIRD_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI"
|
||||
|
||||
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
+19887
-3663
File diff suppressed because it is too large.
Load diff
+13
-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,29 +21,37 @@
|
||||
"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",
|
||||
"punycode": "^2.1.1",
|
||||
"rc-overflow": "^1.2.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-highlight": "^0.14.0",
|
||||
"react-hotjar": "^5.1.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",
|
||||
"redux-saga": "^1.1.3",
|
||||
"styled-components": "^5.3.5",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"ts-md5": "^1.3.1",
|
||||
"typesafe-actions": "^5.1.0",
|
||||
"typescript": "^4.6.4",
|
||||
"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 +76,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
|
||||
+68
-52
@@ -1,44 +1,47 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useEffect, useRef, 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 {apiClient, store} from "./store";
|
||||
import {hotjar} from 'react-hotjar';
|
||||
import {getConfig} from "./config";
|
||||
import Banner from "./components/Banner";
|
||||
import {store} from "./store";
|
||||
import {Col, Layout, Row} from "antd";
|
||||
import {Container} from "./components/Container";
|
||||
import Navbar from "./components/Navbar";
|
||||
import {Redirect, Route, Switch} from "react-router-dom";
|
||||
import {withOidcSecure} from "@axa-fr/react-oidc";
|
||||
import Peers from "./views/Peers";
|
||||
import Routes from "./views/Routes";
|
||||
import AddPeer from "./views/AddPeer";
|
||||
import SetupKeys from "./views/SetupKeys";
|
||||
import AccessControl from "./views/AccessControl";
|
||||
import Users from "./views/Users";
|
||||
import FooterComponent from "./components/FooterComponent";
|
||||
import {useGetAccessTokenSilently} from "./utils/token";
|
||||
import {User} from "./store/user/types";
|
||||
import {SecureLoading} from "./components/Loading";
|
||||
import DNS from "./views/DNS";
|
||||
import Activity from "./views/Activity";
|
||||
|
||||
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 run = useRef(false)
|
||||
const [show, setShow] = useState(false)
|
||||
const {getAccessTokenSilently} = useGetAccessTokenSilently();
|
||||
const {hotjarTrackID} = getConfig();
|
||||
// @ts-ignore
|
||||
if (hotjarTrackID && window._DATADOG_SYNTHETICS_BROWSER === undefined) {
|
||||
hotjar.initialize(hotjarTrackID, 6);
|
||||
}
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggle = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const hideMenu = () => {
|
||||
if (window.innerWidth > 768 && isOpen) {
|
||||
setIsOpen(false);
|
||||
console.log('i resized');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,26 +50,36 @@ function App() {
|
||||
return () => {
|
||||
window.removeEventListener('resize', hideMenu);
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <div>Oops... {error.message}</div>;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!run.current) {
|
||||
run.current = true
|
||||
apiClient.request<User[]>('GET', `/api/users`, {getAccessTokenSilently: getAccessTokenSilently})
|
||||
.then(() => {
|
||||
setShow(true)
|
||||
})
|
||||
.catch(e => {
|
||||
setShow(true)
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading padding="3em" width="50px" height="50px"/>;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
loginWithRedirect({})
|
||||
}
|
||||
}, [getAccessTokenSilently])
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
{ isAuthenticated &&
|
||||
<>
|
||||
<Provider store={store}>
|
||||
{!show && <SecureLoading padding="3em" width={50} height={50}/>}
|
||||
{show &&
|
||||
<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 +88,7 @@ function App() {
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
<Content>
|
||||
<Content style={{minHeight: "100vh"}}>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
@@ -86,19 +99,22 @@ 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)}/>
|
||||
<Route path="/dns" component={withOidcSecure(DNS)}/>
|
||||
<Route path="/activity" component={withOidcSecure(Activity)}/>
|
||||
</Switch>
|
||||
</Content>
|
||||
<FooterComponent/>
|
||||
</Layout>
|
||||
}
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
</Provider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
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 {actions as ruleActions} from '../store/rule';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Row,
|
||||
Typography,
|
||||
Divider,
|
||||
Drawer,
|
||||
Form,
|
||||
Input,
|
||||
Space,
|
||||
Radio,
|
||||
Button, Drawer, Form, List, Divider, Select, Tag
|
||||
RadioChangeEvent,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Tag,
|
||||
Typography
|
||||
} from "antd";
|
||||
import {ArrowRightOutlined, CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
|
||||
import type { CustomTagProps } from 'rc-select/lib/BaseSelect'
|
||||
import {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 {uniq} from "lodash"
|
||||
import {Header} from "antd/es/layout/layout";
|
||||
import {RuleObject} from "antd/lib/form";
|
||||
import {useGetAccessTokenSilently} from "../utils/token";
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
const { Option } = Select;
|
||||
const {Paragraph} = Typography;
|
||||
const {Option} = Select;
|
||||
|
||||
interface FormRule extends Rule {
|
||||
tagSourceGroups: string[]
|
||||
@@ -30,18 +34,22 @@ interface FormRule extends Rule {
|
||||
}
|
||||
|
||||
const AccessControlNew = () => {
|
||||
const { getAccessTokenSilently } = useAuth0()
|
||||
const {getAccessTokenSilently} = useGetAccessTokenSilently()
|
||||
const dispatch = useDispatch()
|
||||
const setupNewRuleVisible = useSelector((state: RootState) => state.rule.setupNewRuleVisible)
|
||||
const groups = useSelector((state: RootState) => state.group.data)
|
||||
const rule = useSelector((state: RootState) => state.rule.rule)
|
||||
const groups = useSelector((state: RootState) => state.group.data)
|
||||
const rule = useSelector((state: RootState) => state.rule.rule)
|
||||
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 +57,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 createRuleToSave = (): RuleToSave => {
|
||||
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,14 +102,17 @@ const AccessControlNew = () => {
|
||||
form.validateFields()
|
||||
.then((values) => {
|
||||
const ruleToSave = createRuleToSave()
|
||||
dispatch(ruleActions.saveRule.request({getAccessTokenSilently, payload: ruleToSave}))
|
||||
dispatch(ruleActions.saveRule.request({
|
||||
getAccessTokenSilently: getAccessTokenSilently,
|
||||
payload: ruleToSave
|
||||
}))
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
console.log('errorInfo', errorInfo)
|
||||
});
|
||||
};
|
||||
|
||||
const setVisibleNewRule = (status:boolean) => {
|
||||
const setVisibleNewRule = (status: boolean) => {
|
||||
dispatch(ruleActions.setSetupNewRuleVisible(status));
|
||||
}
|
||||
|
||||
@@ -101,15 +120,17 @@ 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)
|
||||
}
|
||||
|
||||
const onChange = (data:any) => {
|
||||
const onChange = (data: any) => {
|
||||
setFormRule({...formRule, ...data})
|
||||
}
|
||||
|
||||
@@ -127,8 +148,15 @@ const AccessControlNew = () => {
|
||||
})
|
||||
};
|
||||
|
||||
const handleChangeDisabled = ({target: {value}}: RadioChangeEvent) => {
|
||||
setFormRule({
|
||||
...formRule,
|
||||
disabled: value
|
||||
})
|
||||
};
|
||||
|
||||
const tagRender = (props: CustomTagProps) => {
|
||||
const { label, value, closable, onClose } = props;
|
||||
const {label, value, closable, onClose} = props;
|
||||
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -140,7 +168,7 @@ const AccessControlNew = () => {
|
||||
onMouseDown={onPreventMouseDown}
|
||||
closable={closable}
|
||||
onClose={onClose}
|
||||
style={{ marginRight: 3 }}
|
||||
style={{marginRight: 3}}
|
||||
>
|
||||
<strong>{value}</strong>
|
||||
</Tag>
|
||||
@@ -149,13 +177,13 @@ 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
|
||||
color="blue"
|
||||
style={{ marginRight: 3 }}
|
||||
style={{marginRight: 3}}
|
||||
>
|
||||
<strong>{label}</strong>
|
||||
</Tag>
|
||||
@@ -164,16 +192,58 @@ const AccessControlNew = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const toggleEditName = (status:boolean) => {
|
||||
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 at 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 +255,12 @@ 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,43 +270,93 @@ 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}>
|
||||
<span role="img" aria-label="close" className="anticon anticon-close">
|
||||
<span role="img" aria-label="close"
|
||||
className="anticon anticon-close">
|
||||
<CloseOutlined size={16}/>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</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"/>
|
||||
<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 +368,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>
|
||||
@@ -266,20 +393,24 @@ const AccessControlNew = () => {
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<Paragraph>
|
||||
At the moment access rules are bi-directional by default, this means both source and destination can talk to each-other in both directions. However destination peers will not be able to communicate with each other, nor will the source peers.
|
||||
At the moment access rules are bi-directional by default, this means both
|
||||
source and destination can talk to each-other in both directions. However
|
||||
destination peers will not be able to communicate with each other, nor will
|
||||
the source peers.
|
||||
</Paragraph>
|
||||
<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.
|
||||
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>
|
||||
<Col span={24}>
|
||||
<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>
|
||||
href="https://docs.netbird.io/docs/overview/acls"
|
||||
style={{color: 'rgb(07, 114, 128)'}}>Learn
|
||||
more about access controls</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
import { useState } from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {Button, Col, Row, Space, Typography} from "antd";
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import {Md5} from "ts-md5";
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const Banner = () => {
|
||||
const [show, setShow] = useState(true);
|
||||
const [show, setShow] = useState(false);
|
||||
const banner_md5_key = 'banner_md5'
|
||||
const banner_closed_key = 'banner_closed'
|
||||
|
||||
const dismiss = () => {
|
||||
setShow(false);
|
||||
localStorage.setItem(banner_closed_key,'true');
|
||||
};
|
||||
|
||||
const announcement = "New Release! Manage DNS with NetBird."
|
||||
|
||||
const announcement_md5 = Md5.hashStr(announcement)
|
||||
|
||||
const linkLearnMore = () => {
|
||||
return (
|
||||
<a
|
||||
href="https://blog.netbird.io/wiretrustee-becomes-netbird"
|
||||
href="https://netbird.io/docs/how-to-guides/nameservers"
|
||||
className="font-bold underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -22,12 +30,24 @@ const Banner = () => {
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
let store_banner_md5 = localStorage.getItem(banner_md5_key);
|
||||
let stored_banner_closed = localStorage.getItem(banner_closed_key);
|
||||
|
||||
if((!stored_banner_closed || stored_banner_closed !== 'true') ||
|
||||
(!store_banner_md5 || store_banner_md5 !== announcement_md5)) {
|
||||
setShow(true);
|
||||
localStorage.setItem(banner_md5_key,announcement_md5);
|
||||
localStorage.setItem(banner_closed_key,'false');
|
||||
}
|
||||
},[])
|
||||
|
||||
return show ? (
|
||||
<div className="relative bg-indigo-600 white" color="white" style={{position: "relative", padding: "0.3rem"}} >
|
||||
<Row>
|
||||
<Col xs={24} sm={0} lg={0}>
|
||||
<Text className="ant-col-md-0" style={{color: "#ffffff"}}>
|
||||
Big news! Wiretrustee becomes <strong>NetBird</strong>!
|
||||
{announcement}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col xs={24} sm={0} lg={0}>
|
||||
@@ -38,7 +58,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>!
|
||||
{announcement}
|
||||
</Text>
|
||||
<span>
|
||||
{linkLearnMore()}
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
import {copyToClipboard} from "../utils/common";
|
||||
import {Button, message} from "antd";
|
||||
import {StepCommand} from "./addpeer/types";
|
||||
import React from "react";
|
||||
import {Button, message, Typography} from "antd";
|
||||
import React, {ReactNode} from "react";
|
||||
|
||||
const {Text} = Typography;
|
||||
|
||||
type Props = {
|
||||
keyMessage: string;
|
||||
text: string;
|
||||
toCopy: string;
|
||||
body: ReactNode;
|
||||
messageText: string;
|
||||
styleNotification?: any;
|
||||
style?: any;
|
||||
className?:any;
|
||||
className?: any;
|
||||
};
|
||||
|
||||
const ButtonCopyMessage:React.FC<Props> = ({ keyMessage, text, messageText, styleNotification, style, className}) => {
|
||||
const ButtonCopyMessage: React.FC<Props> = ({
|
||||
keyMessage,
|
||||
toCopy,
|
||||
body,
|
||||
messageText,
|
||||
styleNotification,
|
||||
style,
|
||||
className
|
||||
}) => {
|
||||
const copyTextMessage = () => {
|
||||
copyToClipboard(text)
|
||||
message.success({ content: `${messageText}`, key: keyMessage, duration: 1, style: (styleNotification || {}) });
|
||||
copyToClipboard(toCopy)
|
||||
message.success({content: `${messageText}`, key: keyMessage, duration: 1, style: (styleNotification || {})});
|
||||
}
|
||||
return (
|
||||
<Button type="text" onClick={copyTextMessage} style={style || {}} className={className}>{text}</Button>
|
||||
<Button type="text" onClick={copyTextMessage} style={style || {}} className={className}>
|
||||
{body}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Loaded 20 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user