mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
121
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4123800bc9 | ||
|
|
dc2760d5ff | ||
|
|
5ae4fd31f1 | ||
|
|
54d9d7c768 | ||
|
|
3a73781fca | ||
|
|
f3c5d4df6a | ||
|
|
b4c9135c58 | ||
|
|
a280d9c67c | ||
|
|
5caf06b086 | ||
|
|
95af1c4e32 | ||
|
|
d247e7f3ed | ||
|
|
391e534253 | ||
|
|
a7f64d4a15 | ||
|
|
53ed514803 | ||
|
|
6cadce1598 | ||
|
|
c03d4b8a4b | ||
|
|
bc1053fbb4 | ||
|
|
d154bfb799 | ||
|
|
67fd2fcb2e | ||
|
|
f4933e45ee | ||
|
|
331dd2b429 | ||
|
|
3a8106c1e7 | ||
|
|
b595e0d6a8 | ||
|
|
33621cae5d | ||
|
|
360d807008 | ||
|
|
6f8897ffa5 | ||
|
|
75d5f804c5 | ||
|
|
f7cac02a2d | ||
|
|
ec40730cb2 | ||
|
|
7f3648861b | ||
|
|
b50464db43 | ||
|
|
1eb5ccc131 | ||
|
|
ac42a17b11 | ||
|
|
77ca3c6fde | ||
|
|
20e24b4ede | ||
|
|
7a29dac01c | ||
|
|
444e9ec44a | ||
|
|
dff0313f82 | ||
|
|
11fbfb336a | ||
|
|
4a0ae8f27d | ||
|
|
9a72d8b0c4 | ||
|
|
8e038cf242 | ||
|
|
5bd94eff56 | ||
|
|
77f065b093 | ||
|
|
a4d55cfb90 | ||
|
|
cfd4c9075b | ||
|
|
962180030a | ||
|
|
485e1e8d79 | ||
|
|
b11007b29f | ||
|
|
bce75c1ca9 | ||
|
|
0c09992b38 | ||
|
|
86b12f30d2 | ||
|
|
d3e34d8448 | ||
|
|
76083168f6 | ||
|
|
a54b3687ae | ||
|
|
25f154dc83 | ||
|
|
f3c7d877f8 | ||
|
|
7cea7e7f54 | ||
|
|
aaa351635f | ||
|
|
379ff5486e | ||
|
|
8bcd9918e2 | ||
|
|
044ccd0ce6 | ||
|
|
ab09ca3697 | ||
|
|
1644ed5dce | ||
|
|
cea459792f | ||
|
|
a402680816 | ||
|
|
e57e5b726d | ||
|
|
2c4ada0ad8 | ||
|
|
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 |
No files matched your search
@@ -11,10 +11,10 @@ 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'
|
||||
@@ -27,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
|
||||
|
||||
@@ -36,24 +36,43 @@ Disclaimer. We believe that proper user management system is not a trivial task
|
||||
use Auth0 service that covers all our needs (user management, social login, JTW for the management API).
|
||||
Auth0 so far is the only 3rd party dependency that can't be really self-hosted.
|
||||
|
||||
1. install [Docker](https://docs.docker.com/get-docker/)
|
||||
2. register [Auth0](https://auth0.com/) account
|
||||
3. running Wiretrustee UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
|
||||
1. Install [Docker](https://docs.docker.com/get-docker/)
|
||||
2. Register [Auth0](https://auth0.com/) account
|
||||
3. Running Wiretrustee UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
|
||||
|
||||
```AUTH0_DOMAIN``` ```AUTH0_CLIENT_ID``` ```AUTH0_AUDIENCE```
|
||||
`AUTH0_DOMAIN` `AUTH0_CLIENT_ID` `AUTH0_AUDIENCE`
|
||||
|
||||
To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Configure Allowed Web Origins"
|
||||
|
||||
4. Wiretrustee UI Dashboard uses Wiretrustee Management Service HTTP API, so setting ```WIRETRUSTEE_MGMT_API_ENDPOINT``` is required. Most likely it will be ```http://localhost:33071``` if you are hosting Management API on the same server.
|
||||
4. Wiretrustee UI Dashboard uses Wiretrustee Management Service HTTP API, so setting `NETBIRD_MGMT_API_ENDPOINT` is required. Most likely it will be `http://localhost:33071` if you are hosting Management API on the same server.
|
||||
5. Run docker container without SSL (Let's Encrypt):
|
||||
|
||||
```docker run -d --name wiretrustee-dashboard --rm -p 80:80 -p 443:443 -e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> -e AUTH0_CLIENT_ID=<SET YOUR CLIENT ID> -e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> -e WIRETRUSTEE_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> wiretrustee/dashboard:main```
|
||||
```shell
|
||||
docker run -d --name wiretrustee-dashboard \
|
||||
--rm -p 80:80 -p 443:443 \
|
||||
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
|
||||
-e AUTH0_CLIENT_ID=<SET YOUR CLIENT ID> \
|
||||
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
|
||||
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
|
||||
wiretrustee/dashboard:main
|
||||
```
|
||||
6. Run docker container with SSL (Let's Encrypt):
|
||||
|
||||
```docker run -d --name wiretrustee-dashboard --rm -p 80:80 -p 443:443 -e NGINX_SSL_PORT=443 -e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> -e LETSENCRYPT_EMAIL=<YOUR EMAIL> -e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> -e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> -e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> -e WIRETRUSTEE_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> wiretrustee/dashboard:main```
|
||||
```shell
|
||||
docker run -d --name wiretrustee-dashboard \
|
||||
--rm -p 80:80 -p 443:443 \
|
||||
-e NGINX_SSL_PORT=443 \
|
||||
-e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> \
|
||||
-e LETSENCRYPT_EMAIL=<YOUR EMAIL> \
|
||||
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
|
||||
-e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> \
|
||||
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
|
||||
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
|
||||
wiretrustee/dashboard:main
|
||||
```
|
||||
|
||||
## How to run local development
|
||||
1. Install node 16
|
||||
2. create and update the src/.local-config.json file. This file should contain values to be replaced from src/config.json
|
||||
2. create and update the `src/.local-config.json` file. This file should contain values to be replaced from `src/config.json`
|
||||
3. run `npm install`
|
||||
4. run `npm run start dev`
|
||||
4. run `npm run start dev`
|
||||
@@ -43,20 +43,23 @@ fi
|
||||
|
||||
export AUTH_AUTHORITY=${AUTH_AUTHORITY:-https://$AUTH0_DOMAIN}
|
||||
export AUTH_CLIENT_ID=${AUTH_CLIENT_ID:-$AUTH0_CLIENT_ID}
|
||||
export AUTH_CLIENT_SECRET=${AUTH_CLIENT_SECRET}
|
||||
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}
|
||||
export NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
|
||||
|
||||
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="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_LATEST_VERSION"
|
||||
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE"
|
||||
|
||||
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
|
||||
|
||||
Generated
+2993
-5076
File diff suppressed because it is too large.
Load diff
+7
-2
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@axa-fr/react-oidc": "^5.14.0",
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@heroicons/react": "^1.0.4",
|
||||
@@ -18,7 +18,7 @@
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"antd": "^4.20.6",
|
||||
"antd": "^5.3.1",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"axios": "^0.27.2",
|
||||
"cidr-regex": "^3.1.1",
|
||||
@@ -27,11 +27,15 @@
|
||||
"highlight.js": "^11.2.0",
|
||||
"history": "^5.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"postcss": "^8.4.12",
|
||||
"prop-types": "^15.7.2",
|
||||
"punycode": "^2.1.1",
|
||||
"rc-overflow": "^1.2.8",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-hotjar": "^5.1.0",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^5.3.3",
|
||||
"react-scripts": "^5.0.1",
|
||||
@@ -42,6 +46,7 @@
|
||||
"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"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="robots" content="noindex">
|
||||
<meta
|
||||
name="description"
|
||||
content="NetBird Management Dashboard"
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /
|
||||
+106
-55
@@ -1,23 +1,41 @@
|
||||
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 Navbar from './components/Navbar';
|
||||
import Peers from './views/Peers';
|
||||
import FooterComponent from './components/FooterComponent';
|
||||
import SetupKeys from "./views/SetupKeys";
|
||||
import AddPeer from "./views/AddPeer";
|
||||
import Users from './views/Users';
|
||||
import AccessControl from './views/AccessControl';
|
||||
import Routes from './views/Routes';
|
||||
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 {Col, ConfigProvider, Layout, Row} from "antd";
|
||||
import {Container} from "./components/Container";
|
||||
import {withOidcSecure} from '@axa-fr/react-oidc';
|
||||
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 {useGetTokenSilently, useTokenSource} 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 Settings from "./views/Settings";
|
||||
|
||||
|
||||
const {Header, Content} = Layout;
|
||||
|
||||
function App() {
|
||||
const run = useRef(false)
|
||||
const [show, setShow] = useState(false)
|
||||
const {hotjarTrackID,tokenSource} = getConfig();
|
||||
useTokenSource(tokenSource)
|
||||
const {getTokenSilently} = useGetTokenSilently();
|
||||
// @ts-ignore
|
||||
if (hotjarTrackID && window._DATADOG_SYNTHETICS_BROWSER === undefined) {
|
||||
hotjar.initialize(hotjarTrackID, 6);
|
||||
}
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -25,7 +43,6 @@ function App() {
|
||||
const hideMenu = () => {
|
||||
if (window.innerWidth > 768 && isOpen) {
|
||||
setIsOpen(false);
|
||||
console.log('i resized');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,49 +51,83 @@ function App() {
|
||||
return () => {
|
||||
window.removeEventListener('resize', hideMenu);
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!run.current) {
|
||||
run.current = true
|
||||
apiClient.request<User[]>('GET', `/api/users`, {getAccessTokenSilently: getTokenSilently})
|
||||
.then(() => {
|
||||
setShow(true)
|
||||
})
|
||||
.catch(e => {
|
||||
setShow(true)
|
||||
console.log(e)
|
||||
})
|
||||
}
|
||||
|
||||
}, [getTokenSilently])
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Layout>
|
||||
<Banner/>
|
||||
<Header className="header" style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
alignContent: "center"
|
||||
}}>
|
||||
<Row justify="space-around" align="middle">
|
||||
<Col span={24}>
|
||||
<Container>
|
||||
<Navbar/>
|
||||
</Container>
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
<Content style={{ minHeight: "100vh"}}>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
render={() => {
|
||||
return (
|
||||
<Redirect to="/peers"/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
<>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
borderRadius: 4,
|
||||
colorPrimary: "#1890ff",
|
||||
fontFamily: "Arial"
|
||||
},
|
||||
components: {Badge: {fontSizeSM: 20}},
|
||||
}}
|
||||
>
|
||||
<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"
|
||||
}}>
|
||||
<Row justify="space-around" align="middle">
|
||||
<Col span={24}>
|
||||
<Container>
|
||||
<Navbar/>
|
||||
</Container>
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
<Content style={{minHeight: "100vh"}}>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
render={() => {
|
||||
return (
|
||||
<Redirect to="/peers"/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Route path='/peers' exact component={withOidcSecure(Peers)}/>
|
||||
<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)}/>
|
||||
<Route path="/settings" component={withOidcSecure(Settings)}/>
|
||||
</Switch>
|
||||
</Content>
|
||||
<FooterComponent/>
|
||||
</Layout>
|
||||
}
|
||||
</Provider>
|
||||
|
||||
</ConfigProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="29.435" height="7.636" viewBox="0 0 29.435 7.636">
|
||||
<path id="direct_bi" d="M3.64,8.158-.178,4.34,3.64.522,4.3,1.17l-2.7,2.7h25.89l-2.7-2.7.656-.648L29.257,4.34,27.213,6.384,25.439,8.158,24.783,7.5l2.7-2.693H1.595L4.3,7.5Z" transform="translate(0.178 -0.522)" fill="#1e429f"/>
|
||||
<svg width="39" height="6" viewBox="0 0 39 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.451731 2.66267C0.315048 2.79935 0.315048 3.02096 0.451731 3.15764L2.67912 5.38503C2.8158 5.52171 3.03741 5.52171 3.17409 5.38503C3.31078 5.24835 3.31078 5.02674 3.17409 4.89006L1.19419 2.91016L3.17409 0.930257C3.31078 0.793574 3.31078 0.571966 3.17409 0.435282C3.03741 0.298599 2.8158 0.298599 2.67912 0.435282L0.451731 2.66267ZM38.3807 3.15764C38.5174 3.02096 38.5174 2.79935 38.3807 2.66267L36.1533 0.435282C36.0166 0.298599 35.795 0.298599 35.6583 0.435282C35.5216 0.571966 35.5216 0.793574 35.6583 0.930257L37.6382 2.91016L35.6583 4.89006C35.5216 5.02674 35.5216 5.24835 35.6583 5.38503C35.795 5.52171 36.0166 5.52171 36.1533 5.38503L38.3807 3.15764ZM0.699219 3.26016H38.1332V2.56016H0.699219V3.26016Z" fill="#03543F"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 837 B |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="29.497" height="7.636" viewBox="0 0 29.497 7.636">
|
||||
<path id="direct_out" d="M26.589,8l-.656-.656,2.7-2.693H.91V3.713H28.635l-2.7-2.7.656-.648,3.818,3.818Z" transform="translate(-0.91 -0.364)" fill="#03543f"/>
|
||||
<svg width="39" height="6" viewBox="0 0 39 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.3807 3.15764C38.5174 3.02096 38.5174 2.79935 38.3807 2.66267L36.1533 0.435282C36.0166 0.298599 35.795 0.298599 35.6583 0.435282C35.5216 0.571966 35.5216 0.793574 35.6583 0.930257L37.6382 2.91016L35.6583 4.89006C35.5216 5.02674 35.5216 5.24835 35.6583 5.38503C35.795 5.52171 36.0166 5.52171 36.1533 5.38503L38.3807 3.15764ZM0.699219 3.26016H38.1332V2.56016H0.699219V3.26016Z" fill="#1E429F"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 506 B |
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
@@ -16,7 +16,7 @@ const AccessControlModalGroups:React.FC<Props> = ({data, title, visible, onCance
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal title={title} visible={isModalVisible} onCancel={() => onCancel()} footer={null}>
|
||||
<Modal title={title} open={isModalVisible} onCancel={() => onCancel()} footer={null}>
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={data as Group[] | undefined}
|
||||
|
||||
+670
-368
File diff suppressed because it is too large.
Load diff
@@ -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://netbird.io/docs/how-to-guides/network-routes"
|
||||
href="https://docs.netbird.io/how-to/manage-dns-in-your-network"
|
||||
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"}}>
|
||||
New Release! Access private networks with the Network Routes feature.
|
||||
{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"}}>
|
||||
New Release! Access private networks with the Network Routes feature.
|
||||
{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,17 +1,32 @@
|
||||
import React from "react";
|
||||
import loading from "../assets/bars.svg";
|
||||
import {Space} from "antd";
|
||||
import {OidcSecure} from "@axa-fr/react-oidc";
|
||||
|
||||
type Props = {
|
||||
type LoadingProps = {
|
||||
padding?: string;
|
||||
width?: string;
|
||||
height?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
const Loading:React.FC<Props> = ({padding, width, height}) => (
|
||||
<Space direction="vertical" align="center" style={{display: 'flex', padding: `${padding || `.25em`}`}}>
|
||||
<img src={loading} alt="Loading" style={{width: `${width || '25px'}`, height: `${height || '25px'}`}}/>
|
||||
const Loading: React.FC<LoadingProps> = ({padding, width, height}) => (
|
||||
<Space direction="vertical" align="center" style={{
|
||||
marginTop: `-${height ? (height / 2) + "px" : '-25px'}`,
|
||||
marginLeft: `-${width ? (width / 2) + "px" : '-25px'}`,
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
display: 'flex'
|
||||
}}>
|
||||
<img src={loading} alt="Loading"
|
||||
style={{width: `${width ? width + "px" : '25px'}`, height: `${height ? height + "px" : '25px'}`}}/>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default Loading;
|
||||
// Wrapper of Loading to handle cases when it is shown within the authenticated layout and has to trigger authentication when token expires.
|
||||
export const SecureLoading = (props: LoadingProps) => (
|
||||
<OidcSecure>
|
||||
<Loading {...props} />
|
||||
</OidcSecure>
|
||||
);
|
||||
@@ -1,35 +1,57 @@
|
||||
import {OidcUserStatus, useOidc, useOidcUser} from "@axa-fr/react-oidc";
|
||||
import {Button, Result} from "antd";
|
||||
import {useOidc, useOidcUser} from "@axa-fr/react-oidc";
|
||||
import {Anchor, Button, Col, Result, Row, Space} from "antd";
|
||||
import React from "react";
|
||||
import {getConfig} from "../config";
|
||||
import {ResultStatusType} from "antd/lib/result";
|
||||
|
||||
const {Link} = Anchor;
|
||||
|
||||
function LoginError() {
|
||||
const { logout } = useOidc();
|
||||
const {logout} = useOidc();
|
||||
const config = getConfig();
|
||||
const { oidcUserLoadingState } = useOidcUser();
|
||||
const {oidcUserLoadingState} = useOidcUser();
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
|
||||
if (urlParams.get("error") === "access_denied") {
|
||||
|
||||
let title = urlParams.get("error_description")
|
||||
let status: ResultStatusType = "warning"
|
||||
// this comes from the auth0 rule that links accounts
|
||||
if (title === "account linked successfully") {
|
||||
status = "success"
|
||||
title = "Your account has been linked successfully. Please log in again to complete the setup."
|
||||
}
|
||||
|
||||
return <Result
|
||||
status="warning"
|
||||
title={urlParams.get("error_description")}
|
||||
status={status}
|
||||
title={title}
|
||||
extra={<>
|
||||
<a href={window.location.origin}>
|
||||
<Button type="primary">
|
||||
Try again
|
||||
</Button>
|
||||
</a>
|
||||
<Button type="primary" onClick={function () {
|
||||
logout("",{client_id:config.clientId})
|
||||
<Space style={{
|
||||
display: "flex-inline",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-around",
|
||||
alignContent: "center"
|
||||
}}>
|
||||
Log out
|
||||
<h4>Already verified your email address?</h4>
|
||||
<a href={window.location.origin}>
|
||||
<Button type="primary">
|
||||
Continue
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
<Button type="link" onClick={function () {
|
||||
logout("", {client_id: config.clientId})
|
||||
}}>
|
||||
Trouble logging in? Try again.
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
return <div>{"Login Error: User state: "+oidcUserLoadingState}</div>
|
||||
return <div>{"Login Error: User state: " + oidcUserLoadingState}</div>
|
||||
}
|
||||
|
||||
export default LoginError;
|
||||
File diff suppressed because it is too large.
Load diff
+101
-30
@@ -1,49 +1,77 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import {Link, useLocation} from 'react-router-dom';
|
||||
import logo from "../assets/logo.png";
|
||||
import {useLocation} from 'react-router-dom';
|
||||
import {Menu, Row, Col, Grid, Dropdown, Avatar, Button, Typography, Space} from 'antd'
|
||||
import {Avatar, Button, Col, Dropdown, Grid, Menu, Row} from 'antd'
|
||||
import {ItemType} from "antd/lib/menu/hooks/useItems";
|
||||
import {AvatarSize} from "antd/es/avatar/SizeContext";
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { useOidc,useOidcUser } from '@axa-fr/react-oidc';
|
||||
import {UserOutlined} from '@ant-design/icons';
|
||||
import {useOidc, useOidcIdToken, useOidcUser} from '@axa-fr/react-oidc';
|
||||
import {getConfig} from "../config";
|
||||
const { Text } = Typography
|
||||
const { useBreakpoint } = Grid;
|
||||
import {User} from "../store/user/types";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {actions as userActions} from "../store/user";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {actions as personalAccessTokenActions} from "../store/personal-access-token";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const Navbar = () => {
|
||||
let location = useLocation();
|
||||
const config = getConfig();
|
||||
const {
|
||||
isAuthenticated,
|
||||
logout,
|
||||
} = useOidc();
|
||||
const { logout } = useOidc();
|
||||
const {getTokenSilently} = useGetTokenSilently()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { oidcUser } = useOidcUser();
|
||||
const {oidcUser} = useOidcUser();
|
||||
const {idTokenPayload} = useOidcIdToken()
|
||||
const user = oidcUser;
|
||||
const [currentUser, setCurrentUser] = useState({} as User)
|
||||
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const [hideMenuUser, setHideMenuUser] = useState(false)
|
||||
const users = useSelector((state: RootState) => state.user.data)
|
||||
const [isRefreshingUserState, setIsRefreshingUserState] = useState(false)
|
||||
|
||||
const items = [
|
||||
{label: (<Link to="/peers">Peers</Link>), key: '/peers'},
|
||||
{label: (<Link to="/setup-keys">Setup Keys</Link>), key: '/setup-keys'},
|
||||
{label: (<Link to="/acls">Access Control</Link>), key: '/acls'},
|
||||
{label: (<Link to="/routes">Network Routes</Link>), key: '/routes'},
|
||||
{ label: (<Link to="/dns">DNS</Link>), key: '/dns' },
|
||||
{label: (<Link to="/users">Users</Link>), key: '/users'},
|
||||
{label: (<Link to="/activity">Activity</Link>), key: '/activity'},
|
||||
{label: (<Link to="/settings">Settings</Link>), key: '/settings'}
|
||||
] as ItemType[]
|
||||
|
||||
const userEmailKey = 'user-email'
|
||||
const userLogoutKey = 'user-logout'
|
||||
const userDividerKey = 'user-divider'
|
||||
const [menuItems, setMenuItems] = useState([
|
||||
{ label: (<Link to="/peers">Peers</Link>), key: '/peers' },
|
||||
{ label: (<Link to="/add-peer">Add Peer</Link>), key: '/add-peer' },
|
||||
{ label: (<Link to="/setup-keys">Setup Keys</Link>), key: '/setup-keys' },
|
||||
{ label: (<Link to="/acls">Access Control</Link>), key: '/acls' },
|
||||
{ label: (<Link to="/routes">Network Routes</Link>), key: '/routes' },
|
||||
{ label: (<Link to="/users">Users</Link>), key: '/users' }
|
||||
] as ItemType[])
|
||||
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns", "/activity", "/settings"]
|
||||
const [menuItems, setMenuItems] = useState(items)
|
||||
const logoutWithRedirect = () =>
|
||||
logout("/",{client_id:config.clientId});
|
||||
logout("/", {client_id: config.clientId});
|
||||
|
||||
const openPersonalUserPage = () => {
|
||||
dispatch(userActions.setUser({
|
||||
id: currentUser.id,
|
||||
email: currentUser.email,
|
||||
role: currentUser.role,
|
||||
auto_groups: currentUser.auto_groups ? currentUser.auto_groups : [],
|
||||
name: currentUser.name,
|
||||
is_current: currentUser.is_current,
|
||||
is_service_user: currentUser.is_service_user,
|
||||
} as User));
|
||||
dispatch(userActions.setUserTabOpen("Users"));
|
||||
dispatch(userActions.setEditUserPopupVisible(true));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fs = menuItems.filter(m => m?.key !== userEmailKey && m?.key !== userLogoutKey && m?.key !== userDividerKey)
|
||||
const fs = items.filter(m => showTab(m?.key?.toString(), currentUser) && m?.key !== userEmailKey && m?.key !== userLogoutKey && m?.key !== userDividerKey)
|
||||
if (screens.xs === true) {
|
||||
setHideMenuUser(false)
|
||||
fs.push({ type: 'divider', key: userDividerKey })
|
||||
fs.push({type: 'divider', key: userDividerKey})
|
||||
fs.push({
|
||||
label: (
|
||||
<Link to="#">{user?.name}</Link>
|
||||
@@ -51,32 +79,70 @@ const Navbar = () => {
|
||||
icon: createAvatar("small"),
|
||||
key: userEmailKey
|
||||
})
|
||||
fs.push({ label: (<Button type="link" block onClick={logoutWithRedirect}>Logout</Button>), key: userLogoutKey })
|
||||
fs.push({
|
||||
label: (<Button type="link" block onClick={logoutWithRedirect}>Logout</Button>),
|
||||
key: userLogoutKey
|
||||
})
|
||||
setMenuItems([...fs])
|
||||
return
|
||||
}
|
||||
setMenuItems([...fs])
|
||||
setHideMenuUser(true)
|
||||
}, [screens])
|
||||
}, [screens, currentUser])
|
||||
|
||||
useEffect(() => {
|
||||
if (users.length === 0 && !isRefreshingUserState &&
|
||||
window.location.pathname !== '/peers' &&
|
||||
window.location.pathname !== '/users') {
|
||||
setIsRefreshingUserState(true)
|
||||
dispatch(userActions.getUsers.request({getAccessTokenSilently: getTokenSilently, payload: null}))
|
||||
return
|
||||
}
|
||||
if (users.length === 0 && isRefreshingUserState) {
|
||||
return
|
||||
}
|
||||
let runUser = oidcUser
|
||||
if (!oidcUser) {
|
||||
runUser = idTokenPayload
|
||||
}
|
||||
setIsRefreshingUserState(false)
|
||||
if (runUser) {
|
||||
const found = users.find(u => u.is_current ? u.is_current : runUser.sub ? u.id == runUser.sub : false)
|
||||
if (found) {
|
||||
setCurrentUser(found)
|
||||
}
|
||||
}
|
||||
}, [users, oidcUser])
|
||||
|
||||
const showTab = (key: string | undefined, user: User | undefined) => {
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (user.role?.toLowerCase() === "admin") {
|
||||
return true
|
||||
}
|
||||
return !adminOnlyTabs.find(t => t === key)
|
||||
}
|
||||
|
||||
const menuUser = (
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
label: <>{user?.email}</>,
|
||||
label: (<Link to="/users" onClick={openPersonalUserPage}>{user?.email}</Link>),
|
||||
key: '0',
|
||||
},
|
||||
{
|
||||
label: (<Link to="/logout" onClick={logoutWithRedirect}>Logout</Link>),
|
||||
label: (<Link to="/logout" onClick={logoutWithRedirect}>Logout</Link>),
|
||||
key: '1',
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const createAvatar = (size:AvatarSize) => {
|
||||
const createAvatar = (size: AvatarSize) => {
|
||||
return user?.picture ? (
|
||||
<Avatar size={size} src={user?.picture} icon={<UserOutlined />} />
|
||||
<Avatar size={size} src={user?.picture} icon={<UserOutlined/>}/>
|
||||
) : (
|
||||
<Avatar size={size}>{(user?.name || '').slice(0, 1).toUpperCase()}</Avatar>
|
||||
)
|
||||
@@ -96,7 +162,12 @@ const Navbar = () => {
|
||||
</Col>
|
||||
<Col flex="1 1 auto">
|
||||
<div>
|
||||
<Menu mode="horizontal" selectable={true} selectedKeys={[location.pathname]} defaultSelectedKeys={[location.pathname]} items={menuItems}/>
|
||||
<Menu mode="horizontal" selectable={true} selectedKeys={[location.pathname]}
|
||||
onSelect={(e) => {
|
||||
dispatch(userActions.setUser(null as unknown as User));
|
||||
dispatch(personalAccessTokenActions.resetPersonalAccessTokens(null))
|
||||
}}
|
||||
defaultSelectedKeys={[location.pathname]} items={menuItems}/>
|
||||
</div>
|
||||
</Col>
|
||||
{hideMenuUser &&
|
||||
|
||||
+894
-345
File diff suppressed because it is too large.
Load diff
Loaded 20 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user