mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e57e5b726d | ||
|
|
2c4ada0ad8 | ||
|
|
8195587c85 | ||
|
|
adf6e1e71f |
No files matched your search
@@ -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`
|
||||
@@ -5,7 +5,7 @@ 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 {useOidc, useOidcIdToken, useOidcUser} from '@axa-fr/react-oidc';
|
||||
import {getConfig} from "../config";
|
||||
import {User} from "../store/user/types";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
@@ -23,6 +23,7 @@ const Navbar = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const {oidcUser} = useOidcUser();
|
||||
const {idTokenPayload} = useOidcIdToken()
|
||||
const user = oidcUser;
|
||||
const [currentUser, setCurrentUser] = useState({} as User)
|
||||
|
||||
@@ -85,9 +86,13 @@ const Navbar = () => {
|
||||
if (users.length === 0 && isRefreshingUserState) {
|
||||
return
|
||||
}
|
||||
let runUser = oidcUser
|
||||
if (!oidcUser) {
|
||||
runUser = idTokenPayload
|
||||
}
|
||||
setIsRefreshingUserState(false)
|
||||
if (oidcUser && oidcUser.sub) {
|
||||
const found = users.find(u => u.id == oidcUser.sub)
|
||||
if (runUser) {
|
||||
const found = users.find(u => u.is_current ? u.is_current : runUser.sub ? u.id == runUser.sub : false)
|
||||
if (found) {
|
||||
setCurrentUser(found)
|
||||
}
|
||||
|
||||
@@ -73,14 +73,14 @@ const RouteUpdate = () => {
|
||||
const [masqueradeMSG, setMasqueradeMSG] = useState(defaultMasqueradeMSG)
|
||||
const defaultStatusMSG = "Status"
|
||||
const [statusMSG, setStatusMSG] = useState(defaultStatusMSG)
|
||||
const [peerNameToIP, peerIPToName] = initPeerMaps(peers);
|
||||
const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers);
|
||||
const [newRoute, setNewRoute] = useState(false)
|
||||
|
||||
const optionsDisabledEnabled = [{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]
|
||||
|
||||
useEffect(() => {
|
||||
if (!newRoute ) {
|
||||
setRoutingPeerMSG("Add additional routing peer")
|
||||
setRoutingPeerMSG(defaultRoutingPeerMSG)
|
||||
setMasqueradeMSG("Update Masquerade")
|
||||
setStatusMSG("Update Status")
|
||||
} else {
|
||||
@@ -136,9 +136,9 @@ const RouteUpdate = () => {
|
||||
let peerIDList = inputRoute.peer.split(routePeerSeparator)
|
||||
let peerID: string
|
||||
if (peerIDList[1]) {
|
||||
peerID = peerIDList[1]
|
||||
peerID = peerIPToID[peerIDList[1]]
|
||||
} else {
|
||||
peerID = peerNameToIP[inputRoute.peer]
|
||||
peerID = peerIPToID[peerNameToIP[inputRoute.peer]]
|
||||
}
|
||||
|
||||
let [ existingGroups, groupsToCreate ] = getExistingAndToCreateGroupsLists(inputRoute.groups)
|
||||
@@ -167,7 +167,7 @@ const RouteUpdate = () => {
|
||||
payload: routeToSave
|
||||
}))
|
||||
} else {
|
||||
let groupedDataTable = transformGroupedDataTable(routes, peerIPToName)
|
||||
let groupedDataTable = transformGroupedDataTable(routes, peers)
|
||||
groupedDataTable.forEach((group) => {
|
||||
if (group.key == previousRouteKey) {
|
||||
group.groupedRoutes.forEach((route) => {
|
||||
|
||||
@@ -233,7 +233,8 @@ const UserUpdate = () => {
|
||||
role: "",
|
||||
status: "",
|
||||
auto_groups: [],
|
||||
name: user.name
|
||||
name: user.name,
|
||||
is_current: user.is_current,
|
||||
} as User));
|
||||
setFormUser({} as FormUser)
|
||||
toggleEditName(false)
|
||||
|
||||
@@ -59,7 +59,7 @@ export function* deletePeer(action: ReturnType<typeof actions.deletedPeer.reques
|
||||
} as DeleteResponse<string | null>));
|
||||
|
||||
const peers = (yield select(state => state.peer.data)) as Peer[]
|
||||
yield put(actions.getPeers.success(peers.filter((p:Peer) => p.ip !== action.payload.payload)))
|
||||
yield put(actions.getPeers.success(peers.filter((p:Peer) => p.id !== action.payload.payload)))
|
||||
} catch (err) {
|
||||
yield put(actions.deletedPeer.failure({
|
||||
loading: false,
|
||||
|
||||
@@ -40,6 +40,10 @@ export interface PeerIPToName {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface PeerIPToID {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface PeerDataTable extends Peer {
|
||||
key: string;
|
||||
groups: Group[];
|
||||
|
||||
@@ -3,15 +3,15 @@ export interface User {
|
||||
email?: string;
|
||||
name: string;
|
||||
role: string;
|
||||
status: string
|
||||
auto_groups: string[]
|
||||
status: string;
|
||||
auto_groups: string[];
|
||||
is_current?: boolean;
|
||||
}
|
||||
|
||||
export interface FormUser extends User {
|
||||
autoGroupsNames: string[]
|
||||
}
|
||||
|
||||
export interface UserToSave extends User
|
||||
{
|
||||
export interface UserToSave extends User {
|
||||
groupsToCreate: string[]
|
||||
}
|
||||
}
|
||||
+26
-18
@@ -1,4 +1,4 @@
|
||||
import {Peer, PeerNameToIP, PeerIPToName} from "../store/peer/types";
|
||||
import {Peer, PeerIPToID, PeerIPToName, PeerNameToIP} from "../store/peer/types";
|
||||
import {Route} from "../store/route/types";
|
||||
|
||||
export const routePeerSeparator = " - "
|
||||
@@ -7,22 +7,26 @@ export const masqueradeDisabledMSG = "Enabling this option hides other NetBird n
|
||||
|
||||
export const masqueradeEnabledMSG = "Disabling this option stops hiding all traffic coming from other NetBird peers behind the routing peer local address when accessing the target Network CIDR. You will need to configure routes for your NetBird network pointing to your routing peer on your local routers or other devices."
|
||||
|
||||
export const peerToPeerIP = (name:string,ip:string):string => {
|
||||
export const peerToPeerIP = (name: string, ip: string): string => {
|
||||
return name + routePeerSeparator + ip
|
||||
}
|
||||
|
||||
export const initPeerMaps = (peers:Peer[]): [PeerNameToIP, PeerIPToName] => {
|
||||
export const initPeerMaps = (peers: Peer[]): [PeerNameToIP, PeerIPToName, PeerIPToID] => {
|
||||
let peerNameToIP = {} as PeerNameToIP
|
||||
let peerIPToName = {} as PeerIPToName
|
||||
peers.forEach((p) =>{
|
||||
let peerIPToID = {} as PeerIPToID
|
||||
peers.forEach((p) => {
|
||||
peerNameToIP[p.name] = p.ip
|
||||
peerIPToName[p.ip] = p.name
|
||||
peerIPToID[p.ip] = p.id ? p.id : ""
|
||||
})
|
||||
return [ peerNameToIP, peerIPToName]
|
||||
return [peerNameToIP, peerIPToName, peerIPToID]
|
||||
}
|
||||
|
||||
export interface RouteDataTable extends Route {
|
||||
key: string;
|
||||
peer_ip: string;
|
||||
peer_name: string;
|
||||
}
|
||||
|
||||
export interface GroupedDataTable {
|
||||
@@ -37,30 +41,34 @@ export interface GroupedDataTable {
|
||||
routesGroups: string[]
|
||||
}
|
||||
|
||||
export const transformDataTable = (d:Route[],peerIPToName:PeerIPToName):RouteDataTable[] => {
|
||||
return d.map(p => {
|
||||
export const transformDataTable = (routes: Route[], peers: Peer[]): RouteDataTable[] => {
|
||||
|
||||
let peerMap = Object.fromEntries(peers.map(p => [p.id, p]));
|
||||
return routes.map(route => {
|
||||
return {
|
||||
key: p.id,
|
||||
...p,
|
||||
peer: peerIPToName[p.peer] ? peerIPToName[p.peer] : p.peer,
|
||||
key: route.id,
|
||||
...route,
|
||||
peer: route.peer,
|
||||
peer_ip: peerMap[route.peer] ? peerMap[route.peer].ip : route.peer,
|
||||
peer_name: peerMap[route.peer] ? peerMap[route.peer].name : route.peer,
|
||||
} as RouteDataTable
|
||||
})
|
||||
}
|
||||
|
||||
export const transformGroupedDataTable = (routes:Route[],peerIPToName:PeerIPToName):GroupedDataTable[] => {
|
||||
export const transformGroupedDataTable = (routes: Route[], peers: Peer[]): GroupedDataTable[] => {
|
||||
let keySet = new Set(routes.map(r => {
|
||||
return r.network_id + r.network
|
||||
}))
|
||||
|
||||
let groupedRoutes:GroupedDataTable[] = []
|
||||
let groupedRoutes: GroupedDataTable[] = []
|
||||
|
||||
keySet.forEach((p) => {
|
||||
let hasEnabled = false
|
||||
let lastRoute:Route
|
||||
let listedRoutes:Route[] = []
|
||||
let groupList:string[] = []
|
||||
let lastRoute: Route
|
||||
let listedRoutes: Route[] = []
|
||||
let groupList: string[] = []
|
||||
routes.forEach((r) => {
|
||||
if ( p === r.network_id + r.network ) {
|
||||
if (p === r.network_id + r.network) {
|
||||
lastRoute = r
|
||||
if (r.enabled) {
|
||||
hasEnabled = true
|
||||
@@ -69,8 +77,8 @@ export const transformGroupedDataTable = (routes:Route[],peerIPToName:PeerIPToNa
|
||||
groupList = groupList.concat(r.groups)
|
||||
}
|
||||
})
|
||||
groupList = groupList.filter((value,index,arrary) => arrary.indexOf(value) === index)
|
||||
let groupDataTableRoutes = transformDataTable(listedRoutes,peerIPToName)
|
||||
groupList = groupList.filter((value, index, arrary) => arrary.indexOf(value) === index)
|
||||
let groupDataTableRoutes = transformDataTable(listedRoutes, peers)
|
||||
groupedRoutes.push({
|
||||
key: p.toString(),
|
||||
network_id: lastRoute!.network_id,
|
||||
|
||||
+12
-2
@@ -160,10 +160,21 @@ export const Activity = () => {
|
||||
case "group.add":
|
||||
case "group.update":
|
||||
return renderMultiRowSpan(event.meta.name,"Group")
|
||||
case "nameserver.group.add":
|
||||
case "nameserver.group.update":
|
||||
case "nameserver.group.delete":
|
||||
return renderMultiRowSpan(event.meta.name,"Nameserver group")
|
||||
case "setupkey.peer.add":
|
||||
case "user.peer.add":
|
||||
case "user.peer.delete":
|
||||
case "peer.ssh.enable":
|
||||
case "peer.ssh.disable":
|
||||
case "peer.rename":
|
||||
return renderMultiRowSpan(event.meta.fqdn,event.meta.ip)
|
||||
case "route.add":
|
||||
case "route.delete":
|
||||
case "route.update":
|
||||
return renderMultiRowSpan(event.meta.name, "Route for range " + event.meta.network_range)
|
||||
case "user.group.add":
|
||||
case "user.group.delete":
|
||||
if (user) {
|
||||
@@ -177,9 +188,8 @@ export const Activity = () => {
|
||||
case "peer.group.delete":
|
||||
return renderMultiRowSpan(event.meta.peer_fqdn,event.meta.peer_ip)
|
||||
case "dns.setting.disabled.management.group.add":
|
||||
return "-"
|
||||
case "dns.setting.disabled.management.group.delete":
|
||||
return "-"
|
||||
return renderMultiRowSpan("","System setting")
|
||||
case "user.invite":
|
||||
if (user) {
|
||||
return renderMultiRowSpan(user.name ? user.name : user.id,user.email ? user.email : "User")
|
||||
|
||||
+2
-2
@@ -237,7 +237,7 @@ export const Peers = () => {
|
||||
const showConfirmDelete = () => {
|
||||
let peerRoutes: string[] = []
|
||||
routes.forEach((r) => {
|
||||
if (r.peer == peerToAction?.ip) {
|
||||
if (r.peer == peerToAction?.id) {
|
||||
peerRoutes.push(r.network_id)
|
||||
}
|
||||
})
|
||||
@@ -288,7 +288,7 @@ export const Peers = () => {
|
||||
onOk() {
|
||||
dispatch(peerActions.deletedPeer.request({
|
||||
getAccessTokenSilently: getAccessTokenSilently,
|
||||
payload: peerToAction ? peerToAction.ip : ''
|
||||
payload: (peerToAction && peerToAction.id) ? peerToAction.id! : ""
|
||||
}));
|
||||
},
|
||||
onCancel() {
|
||||
|
||||
@@ -127,7 +127,7 @@ export const Routes = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setGroupedDataTable(filterGroupedDataTable(transformGroupedDataTable(routes, peerIPToName)))
|
||||
setGroupedDataTable(filterGroupedDataTable(transformGroupedDataTable(routes, peers)))
|
||||
}, [dataTable])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -135,12 +135,12 @@ export const Routes = () => {
|
||||
setShowTutorial(false)
|
||||
} else {
|
||||
setShowTutorial(isShowTutorial(routes))
|
||||
setDataTable(sortBy(transformDataTable(routes, peerIPToName), "network_id"))
|
||||
setDataTable(sortBy(transformDataTable(routes, peers), "network_id"))
|
||||
}
|
||||
}, [routes])
|
||||
|
||||
useEffect(() => {
|
||||
setGroupedDataTable(filterGroupedDataTable(transformGroupedDataTable(routes, peerIPToName)))
|
||||
setGroupedDataTable(filterGroupedDataTable(transformGroupedDataTable(routes, peers)))
|
||||
}, [textToSearch, optionAllEnable])
|
||||
|
||||
const styleNotification = {marginTop: 85}
|
||||
@@ -186,7 +186,7 @@ export const Routes = () => {
|
||||
if (deletedRoute.loading) {
|
||||
message.loading({content: 'Deleting...', key: deleteKey, style})
|
||||
} else if (deletedRoute.success) {
|
||||
message.success({content: 'Route has been successfully disabled.', key: deleteKey, duration: 2, style})
|
||||
message.success({content: 'Route has been successfully deleted.', key: deleteKey, duration: 2, style})
|
||||
dispatch(routeActions.resetDeletedRoute(null))
|
||||
} else if (deletedRoute.error) {
|
||||
message.error({
|
||||
@@ -204,7 +204,7 @@ export const Routes = () => {
|
||||
};
|
||||
|
||||
const searchDataTable = () => {
|
||||
setGroupedDataTable(filterGroupedDataTable(transformGroupedDataTable(routes, peerIPToName)))
|
||||
setGroupedDataTable(filterGroupedDataTable(transformGroupedDataTable(routes, peers)))
|
||||
}
|
||||
|
||||
const onChangeAllEnabled = ({target: {value}}: RadioChangeEvent) => {
|
||||
@@ -278,7 +278,7 @@ export const Routes = () => {
|
||||
network: route.network,
|
||||
network_id: route.network_id,
|
||||
description: route.description,
|
||||
peer: route.peer ? peerToPeerIP(route.peer, peerNameToIP[route.peer]) : '',
|
||||
peer: route.peer ? peerToPeerIP(route.peer_name, route.peer_ip) : '',
|
||||
metric: route.metric ? route.metric : 9999,
|
||||
masquerade: route.masquerade,
|
||||
enabled: route.enabled,
|
||||
@@ -393,7 +393,7 @@ export const Routes = () => {
|
||||
size="small"
|
||||
bordered={true}
|
||||
>
|
||||
<Column title="Routing Peer" dataIndex="peer" align="center"
|
||||
<Column title="Routing Peer" dataIndex="peer_name" align="center"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).peer.includes(value)}
|
||||
sorter={(a, b) => ((a as any).peer.localeCompare((b as any).peer))}
|
||||
render={(text, record) => {
|
||||
|
||||
+8
-3
@@ -28,7 +28,7 @@ import UserUpdate from "../components/UserUpdate";
|
||||
import {actions as groupActions} from "../store/group";
|
||||
import {Group} from "../store/group/types";
|
||||
import {TooltipPlacement} from "antd/es/tooltip";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
import {useOidcIdToken, useOidcUser} from "@axa-fr/react-oidc";
|
||||
import {Link} from "react-router-dom";
|
||||
import {actions as setupKeyActions} from "../store/setup-key";
|
||||
import {SetupKey} from "../store/setup-key/types";
|
||||
@@ -46,6 +46,7 @@ const styleNotification = {marginTop: 85}
|
||||
export const Users = () => {
|
||||
const {getAccessTokenSilently} = useGetAccessTokenSilently()
|
||||
const {oidcUser} = useOidcUser();
|
||||
const {idTokenPayload} = useOidcIdToken()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const groups = useSelector((state: RootState) => state.group.data)
|
||||
@@ -96,8 +97,12 @@ export const Users = () => {
|
||||
}, [textToSearch])
|
||||
|
||||
useEffect(() => {
|
||||
if (oidcUser && oidcUser.sub) {
|
||||
const found = users.find(u => u.id == oidcUser.sub)
|
||||
let runUser = oidcUser
|
||||
if (!oidcUser) {
|
||||
runUser = idTokenPayload
|
||||
}
|
||||
if (runUser && runUser.sub) {
|
||||
const found = users.find(u => u.id == runUser.sub)
|
||||
if (found) {
|
||||
setCurrentUser(found)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user