Compare commits

..
27 Commits
Author SHA1 Message Date
Maycon Santos bce75c1ca9 Disable banner (#146) 2023-03-02 15:55:31 +01:00
Maycon Santos 0c09992b38 Use user.is_current (#145)
prevent update for own user

use the is_current for label

removed unused imports
2023-03-01 22:50:26 +01:00
Maycon Santos 86b12f30d2 Use new windows release URL (#144)
removed latestVersion logic and environment support
2023-03-01 15:00:36 +01:00
Maycon Santos d3e34d8448 Use single page size helper with new larger values (#143)
Reduce duplicated code with single helper for pagination

On larger deployments we should allow for larger pages

fix popover key issue and vertical spacing

update deprecated keys
2023-02-28 16:32:12 +01:00
Misha Bragin 76083168f6 Log error when activity event is not handled (#142) 2023-02-28 11:22:40 +01:00
Misha Bragin a54b3687ae Display user in the user role update event (#141)
Handling of the "User role updated" event (user.role.update) 
was missing in the Activity tab.
2023-02-27 15:27:00 +01:00
Zoltan Papp 25f154dc83 Capitalize first letter of OS (#140) 2023-02-27 14:54:39 +01:00
pascal-fischer f3c7d877f8 Add additional step in the Mac installation instructions to start the daemon (#138) 2023-02-22 20:55:36 +01:00
Misha Bragin 7cea7e7f54 Format individual peer login expiration event (#139)
Events peer.login.expiration.disable and peer.login.expiration.enable
are handled in the Activity tab now and properly displayed.
2023-02-22 20:54:56 +01:00
Misha Bragin aaa351635f Add peer expiration setting confirmation modal (#137)
Add a confirmation dialog to notify a user of possible
consequences of the peer login expiration enabling/disabling.
2023-02-21 08:47:14 +01:00
Misha Bragin 379ff5486e Account settings view (#136)
New Settings tab added to the dashboard.
It is possible to enable or disable peer login expiration globally for an account.
As well as defining the expiration time period.
2023-02-20 08:57:24 +01:00
Misha Bragin 8bcd9918e2 Fix expires in input filed of the setup key (#135) 2023-02-17 14:30:22 +01:00
Misha Bragin 044ccd0ce6 Display login expiration activities (#134) 2023-02-16 15:36:45 +01:00
braginini ab09ca3697 Display API error in peers view 2023-02-16 13:03:39 +01:00
Misha Bragin 1644ed5dce Add peer login expiration tooltip (#133) 2023-02-16 12:47:35 +01:00
Misha Bragin cea459792f Add peer login expiration (#132)
Display if peer login has expired in the Peers table
Enable/Disable peer login expiration
2023-02-16 12:18:15 +01:00
Maycon Santos a402680816 Disallow all crawlers and dashboard indexing (#131) 2023-02-10 18:45:11 +01:00
Givi Khojanashvili e57e5b726d Feat add custom id claim (#129)
* Fix management API endpoint ENV var. Format README.

* Add and use id_current user flag

* Use mix of the new and old methods to detect current user.
2023-02-03 21:48:03 +01:00
Misha Bragin 2c4ada0ad8 Use peerID in the Routes view (#130)
Relates to the netbirdio/netbird PR
Use Peer.ID instead of Peer.Key as peer identifier (#664)
2023-02-03 10:34:43 +01:00
Misha Bragin 8195587c85 Handle additional activity events (#128) 2023-01-31 13:56:46 +01:00
Maycon Santos adf6e1e71f Use ID token payload when oidcUser is nil (#127)
With some IDPs like MS Azure the
oidcUser is not being set, so we can fallback to id token
to validate UI and current user
2023-01-24 09:10:14 +01:00
Maycon Santos b733a186ae Remove console.log in peers page (#125) 2023-01-19 14:55:42 +01:00
Maycon Santos 5d901470c2 Feature/dns settings (#126)
Add DNS settings view to the DNS tab.

Split the page into sub-tabs for Nameservers and DNS settings

Added API calls to the new DNS settings API
2023-01-18 18:12:29 +01:00
Misha Bragin 29ab28847d Add Events view (#119) 2023-01-02 17:29:11 +01:00
Maycon Santos 0361825e04 Add peer's last seem time on popover (#122) 2022-12-16 19:37:10 +01:00
Moath Qasim 2fa33ec06a Fix MacOS custom netbird up command (#121) 2022-12-11 18:54:48 +01:00
Maycon Santos c677eeaae4 Add distribution groups to Network routes (#118)
Users can add distribution groups to network routes

Groups can be added to individual network routes or to all routes in the group

Adding a new group in the modal is restricted to individual network route operations
2022-12-08 17:24:34 +01:00
61 changed files with 2293 additions and 740 deletions

No files matched your search

+28 -9
View File
@@ -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`
+1 -4
View File
@@ -53,13 +53,10 @@ export NETBIRD_MGMT_API_ENDPOINT=$(echo $NETBIRD_MGMT_API_ENDPOINT | sed -E 's/(
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="\$\$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"
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$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"
+1
View File
@@ -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
View File
@@ -1,3 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Disallow: /
+4
View File
@@ -20,6 +20,8 @@ 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 Settings from "./views/Settings";
@@ -105,6 +107,8 @@ function App() {
<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/>
+1 -1
View File
@@ -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}
+1 -1
View File
@@ -36,7 +36,7 @@ const Banner = () => {
if((!stored_banner_closed || stored_banner_closed !== 'true') ||
(!store_banner_md5 || store_banner_md5 !== announcement_md5)) {
setShow(true);
//setShow(true);
localStorage.setItem(banner_md5_key,announcement_md5);
localStorage.setItem(banner_closed_key,'false');
}
+12 -5
View File
@@ -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)
@@ -39,13 +40,15 @@ const Navbar = () => {
{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="/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 adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns"]
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns", "/activity", "/settings"]
const [menuItems, setMenuItems] = useState(items)
const logoutWithRedirect = () =>
logout("/", {client_id: config.clientId});
@@ -84,9 +87,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)
}
+26 -23
View File
@@ -2,7 +2,7 @@ import React, {useEffect, useRef, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import {actions as peerActions} from '../store/peer';
import {Button, Col, Collapse, Divider, Drawer, Form, Input, Row, Select, Space, Tag, Typography} from "antd";
import {Button, Col, Collapse, Divider, Drawer, Form, Input, Radio, Row, Select, Space, Tag, Typography} from "antd";
import {Header} from "antd/es/layout/layout";
import type {CustomTagProps} from 'rc-select/lib/BaseSelect'
import {FormPeer, Peer, PeerGroupsToSave} from "../store/peer/types";
@@ -18,6 +18,7 @@ const {Panel} = Collapse;
const punycode = require('punycode/')
const PeerUpdate = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const groups = useSelector((state: RootState) => state.group.data)
@@ -200,6 +201,10 @@ const PeerUpdate = () => {
return !formPeer.name || formPeer.name === peer.name
}
const noUpdateToLoginExpiration = (): Boolean => {
return formPeer.login_expiration_enabled === peer.login_expiration_enabled
}
const onChange = (data: any) => {
setFormPeer({...formPeer, ...data})
}
@@ -232,7 +237,8 @@ const PeerUpdate = () => {
return {
id: formPeer.id,
ssh_enabled: formPeer.ssh_enabled,
name: formPeer.name
name: formPeer.name,
login_expiration_enabled: formPeer.login_expiration_enabled
} as Peer
}
@@ -240,7 +246,7 @@ const PeerUpdate = () => {
form.validateFields()
.then((values) => {
setSubmitRunning(true)
if (!noUpdateToName()) {
if (!noUpdateToName() || !noUpdateToLoginExpiration()) {
const peerUpdate = createPeerToSave()
setCallingPeerAPI(true)
dispatch(peerActions.updatePeer.request({
@@ -248,7 +254,7 @@ const PeerUpdate = () => {
payload: peerUpdate
}))
}
if (peerGroupsToSave.groupsToRemove.length || peerGroupsToSave.groupsToAdd.length || peerGroupsToSave.groupsNoId.length) {
if (!noUpdateToGroups()) {
setCallingGroupAPI(true)
dispatch(peerActions.saveGroups.request({
getAccessTokenSilently: getAccessTokenSilently,
@@ -303,7 +309,7 @@ const PeerUpdate = () => {
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button onClick={onCancel} disabled={savedGroups.loading}>Cancel</Button>
<Button type="primary"
disabled={(savedGroups.loading || updatedPeers.loading) || (noUpdateToGroups() && noUpdateToName())}
disabled={(savedGroups.loading || updatedPeers.loading) || (noUpdateToGroups() && noUpdateToName() && noUpdateToLoginExpiration())}
onClick={handleFormSubmit}>Save</Button>
</Space>
}
@@ -353,7 +359,7 @@ const PeerUpdate = () => {
max={59}/>
</Form.Item>
<Form.Item
label="Possible domain name after saving"
label="New peer domain name preview"
tooltip="If the domain name already exists, we add an increment number suffix to it"
style={{margin: '1px'}}
>
@@ -435,6 +441,20 @@ const PeerUpdate = () => {
</Form.Item>
</Col>
)}
<Col span={24}>
<Form.Item
name="login_expiration_enabled"
label="Login expiration"
tooltip="When login expires, the user has to re-authenticate this peer. This only applies to peers added with the SSO login."
>
<Radio.Group
options={[{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]}
optionType="button"
buttonStyle="solid"
disabled={!formPeer.user_id}
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="groupsNames"
@@ -458,23 +478,6 @@ const PeerUpdate = () => {
</Select>
</Form.Item>
</Col>
{/*<Col span={24}>
<Row wrap={false} gutter={12}>
<Col flex="none">
<FlagFilled/>
</Col>
<Col flex="auto">
<Paragraph>
Every peer is part of the group All, thus you can't remove it.
</Paragraph>
</Col>
</Row>
</Col>*/}
{/*<Col span={24}>
<Divider orientation="left" plain style={{color: "#5a5c5a"}}>
System Info
</Divider>
</Col>*/}
<Col span={24}>
<Collapse onChange={onChange} bordered={false} ghost={true}
style={{color: "#5a5c5a"}}>
+86 -21
View File
@@ -19,7 +19,7 @@ import {
Typography
} from "antd";
import {CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
import {Route} from "../store/route/types";
import {Route, RouteToSave} from "../store/route/types";
import {Header} from "antd/es/layout/layout";
import {RuleObject} from "antd/lib/form";
import cidrRegex from 'cidr-regex';
@@ -31,6 +31,7 @@ import {
transformGroupedDataTable
} from '../utils/routes'
import {useGetAccessTokenSilently} from "../utils/token";
import {useGetGroupTagHelpers} from "../utils/groups";
const {Paragraph} = Typography;
@@ -38,6 +39,17 @@ interface FormRoute extends Route {
}
const RouteUpdate = () => {
const {
tagRender,
handleChangeTags,
dropDownRender,
optionRender,
tagGroups,
getExistingAndToCreateGroupsLists,
getGroupNamesFromIDs,
selectValidator
} = useGetGroupTagHelpers()
const {Option} = Select;
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const setupNewRouteVisible = useSelector((state: RootState) => state.route.setupNewRouteVisible)
@@ -46,7 +58,6 @@ const RouteUpdate = () => {
const route = useSelector((state: RootState) => state.route.route)
const routes = useSelector((state: RootState) => state.route.data)
const savedRoute = useSelector((state: RootState) => state.route.savedRoute)
// const [groupedDataTable, setGroupedDataTable] = useState([] as GroupedDataTable[]);
const [previousRouteKey, setPreviousRouteKey] = useState("")
const [editName, setEditName] = useState(false)
const [editDescription, setEditDescription] = useState(false)
@@ -62,13 +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 (setupNewRouteHA) {
setRoutingPeerMSG("Add additional routing peer")
if (!newRoute ) {
setRoutingPeerMSG(defaultRoutingPeerMSG)
setMasqueradeMSG("Update Masquerade")
setStatusMSG("Update Status")
} else {
@@ -77,7 +89,7 @@ const RouteUpdate = () => {
setStatusMSG(defaultStatusMSG)
setPreviousRouteKey("")
}
}, [setupNewRouteHA])
}, [newRoute])
useEffect(() => {
if (editName) inputNameRef.current!.focus({
@@ -96,9 +108,15 @@ const RouteUpdate = () => {
const fRoute = {
...route,
groups: getGroupNamesFromIDs(route.groups)
} as FormRoute
setFormRoute(fRoute)
setPreviousRouteKey(fRoute.network_id + fRoute.network)
if (!route.network_id) {
setNewRoute(true)
} else {
setNewRoute(false)
}
form.setFieldsValue(fRoute)
}, [route])
@@ -114,15 +132,17 @@ const RouteUpdate = () => {
}
})
const createRouteToSave = (inputRoute: FormRoute): Route => {
const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
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)
return {
id: inputRoute.id,
network: inputRoute.network,
@@ -131,8 +151,10 @@ const RouteUpdate = () => {
peer: peerID,
enabled: inputRoute.enabled,
masquerade: inputRoute.masquerade,
metric: inputRoute.metric
} as Route
metric: inputRoute.metric,
groups: existingGroups,
groupsToCreate: groupsToCreate,
} as RouteToSave
}
const handleFormSubmit = () => {
@@ -145,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) => {
@@ -195,13 +217,14 @@ const RouteUpdate = () => {
setVisibleNewRoute(false)
setSetupNewRouteHA(false)
setPreviousRouteKey("")
setNewRoute(false)
}
const onChange = (data: any) => {
setFormRoute({...formRoute, ...data})
}
const dropDownRender = (menu: React.ReactElement) => (
const peerDropDownRender = (menu: React.ReactElement) => (
<>
{menu}
</>
@@ -227,13 +250,32 @@ const RouteUpdate = () => {
return Promise.resolve()
}
const peerValidator = (_: RuleObject, value: string) => {
if (value == "" && newRoute) {
return Promise.reject(new Error("Please select routing one peer"))
}
return Promise.resolve()
}
const selectPreValidator = (obj: RuleObject, value: string[]) => {
if (setupNewRouteHA && formRoute.peer == '') {
let [, newGroups ] = getExistingAndToCreateGroupsLists(value)
if (newGroups.length > 0) {
return Promise.reject(new Error("You can't add new Groups from the group update view, please remove:\"" + newGroups +"\""))
}
}
return selectValidator(obj, value)
}
return (
<>
{route &&
<Drawer
headerStyle={{display: "none"}}
forceRender={true}
visible={setupNewRouteVisible}
open={setupNewRouteVisible}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
autoFocus={true}
@@ -241,11 +283,11 @@ const RouteUpdate = () => {
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button onClick={onCancel} disabled={savedRoute.loading}>Cancel</Button>
<Button type="primary" disabled={savedRoute.loading}
onClick={handleFormSubmit}>{`${formRoute.network_id ? 'Save' : 'Create'}`}</Button>
onClick={handleFormSubmit}>{`${newRoute ? 'Create' : 'Save'}`}</Button>
</Space>
}
>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
<Form layout="vertical" form={form} requiredMark={false} onValuesChange={onChange}>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
@@ -278,7 +320,7 @@ const RouteUpdate = () => {
}]}
>
<Input placeholder="e.g. aws-eu-central-1-vpc" ref={inputNameRef}
disabled={!setupNewRouteHA}
disabled={!setupNewRouteHA && !newRoute}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)} autoComplete="off"
maxLength={40}/>
@@ -294,7 +336,7 @@ const RouteUpdate = () => {
style={{marginTop: 24}}
>
<Input placeholder="Add description..." ref={inputDescriptionRef}
disabled={!setupNewRouteHA}
disabled={!setupNewRouteHA && !newRoute}
onPressEnter={() => toggleEditDescription(false)}
onBlur={() => toggleEditDescription(false)}
autoComplete="off" maxLength={200}/>
@@ -321,7 +363,7 @@ const RouteUpdate = () => {
tooltip="Use CIDR notation. e.g. 192.168.10.0/24 or 172.16.0.0/16"
rules={[{validator: networkRangeValidator}]}
>
<Input placeholder="e.g. 172.16.0.0/16" disabled={!setupNewRouteHA}
<Input placeholder="e.g. 172.16.0.0/16" disabled={!setupNewRouteHA && !newRoute}
autoComplete="off" minLength={9} maxLength={43}/>
</Form.Item>
</Col>
@@ -343,12 +385,13 @@ const RouteUpdate = () => {
name="peer"
label={routingPeerMSG}
tooltip="Assign a peer as a routing peer for the Network CIDR"
rules={[{validator:peerValidator}]}
>
<Select
showSearch
style={{width: '100%'}}
placeholder="Select Peer"
dropdownRender={dropDownRender}
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
/>
@@ -360,7 +403,7 @@ const RouteUpdate = () => {
label={masqueradeMSG}
tooltip={masqueradeDisabledMSG}
>
<Switch size={"small"} disabled={!setupNewRouteHA} checked={formRoute.masquerade}/>
<Switch size={"small"} disabled={!setupNewRouteHA && !newRoute} checked={formRoute.masquerade}/>
</Form.Item>
</Col>
<Col span={24}>
@@ -372,6 +415,28 @@ const RouteUpdate = () => {
<InputNumber min={1} max={9999} autoComplete="off"/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="groups"
label="Distribution groups"
tooltip="NetBird will advertise this route to peers that belong to the following groups"
rules={[{validator: selectPreValidator}]}
>
<Select mode="tags"
style={{width: '100%'}}
placeholder="Associate groups with the network route"
tagRender={tagRender}
onChange={handleChangeTags}
dropdownRender={dropDownRender}
>
{
tagGroups.map(m =>
<Option key={m}>{optionRender(m)}</Option>
)
}
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Row wrap={false} gutter={12}>
<Col flex="none">
+11 -37
View File
@@ -23,18 +23,18 @@ import {RootState} from "typesafe-actions";
import {CloseOutlined, EditOutlined, QuestionCircleFilled} from "@ant-design/icons";
import {FormSetupKey, SetupKey, SetupKeyToSave} from "../store/setup-key/types";
import {Header} from "antd/es/layout/layout";
import {formatDate, timeAgo} from "../utils/common";
import {checkExpiresIn, formatDate, timeAgo} from "../utils/common";
import {RuleObject} from "antd/lib/form";
import {CustomTagProps} from "rc-select/lib/BaseSelect";
import {Group} from "../store/group/types";
import {useGetAccessTokenSilently} from "../utils/token";
import ExpiresInInput, {ExpiresInValue} from "../views/ExpiresInInput";
import ExpiresInInput, {expiresInToSeconds, ExpiresInValue} from "../views/ExpiresInInput";
const {Option} = Select;
const {Text} = Typography;
const ExpiresInDefault: ExpiresInValue = {number: 30, interval: "Days"}
const ExpiresInDefault: ExpiresInValue = {number: 30, interval: "day"}
const customExpiresFormat: DatePickerProps['format'] = value => {
return formatDate(value)
@@ -117,31 +117,6 @@ const SetupKeyNew = () => {
usage_limit: formSetupKey.usage_limit
} as SetupKeyToSave
}
const expiresInToSeconds = (expiresIn: ExpiresInValue): number => {
if (!expiresIn.number || !expiresIn.interval) {
return 0
}
let multiplier = 0
switch (expiresIn.interval.toLowerCase()) {
case "day":
multiplier = 24 * 3600
break
case "week":
multiplier = 7 * 24 * 3600
break
case "month":
multiplier = 30 * 24 * 3600
break
case "year":
multiplier = 365 * 24 * 3600
break
default:
multiplier = 0
}
return expiresIn.number * multiplier
}
const handleFormSubmit = () => {
form.validateFields()
@@ -290,13 +265,6 @@ const SetupKeyNew = () => {
return setupKey.auto_groups?.filter(g => !formGroupIds.includes(g)).length > 0
}
const checkExpiresIn = (_: any, value: { number: number }) => {
if (value.number > 0) {
return Promise.resolve();
}
return Promise.reject(new Error("Expiration must be greater than zero"));
};
return (
<>
{setupKey &&
@@ -447,7 +415,12 @@ const SetupKeyNew = () => {
<Col span={24}>
<Form.Item name="expiresInFormatted" label="Expires In"
rules={[{validator: checkExpiresIn}]}>
<ExpiresInInput/>
<ExpiresInInput options={
Array.of(
{key: "day", title: "Days"},
{key: "month", title: "Months"},
{key: "year", title: "Years"})
}/>
</Form.Item>
</Col>}
<Col span={12}>
@@ -455,7 +428,8 @@ const SetupKeyNew = () => {
label="Usage Limit"
tooltip="Limit the number of times this key can be used. Use 0 for unlimited use."
>
<InputNumber min={0} defaultValue={0} disabled={setupKey.id || formSetupKey.type !== "reusable"}
<InputNumber min={0} defaultValue={0}
disabled={setupKey.id || formSetupKey.type !== "reusable"}
style={{width: "100%"}}
/>
</Form.Item>
+4 -3
View File
@@ -12,7 +12,7 @@ import {actions as userActions} from "../store/user";
import {useGetAccessTokenSilently} from "../utils/token";
import {useOidcUser} from "@axa-fr/react-oidc";
const {Paragraph, Text} = Typography;
const {Paragraph} = Typography;
const {confirm} = Modal;
@@ -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)
@@ -357,7 +358,7 @@ const UserUpdate = () => {
>
<Select
style={{width: '100%'}}
disabled={currentUser.role != null && currentUser.role !== "admin"}>
disabled={(currentUser.role != null && currentUser.role !== "admin") || formUser.is_current}>
<Option value="admin">admin</Option>
<Option value="user">user</Option>
</Select>
+11 -2
View File
@@ -3,6 +3,7 @@ import React, {useState} from 'react';
import { Button } from "antd";
import TabSteps from "./TabSteps";
import { StepCommand } from "./types"
import { formatNetBirdUP } from "./common"
export const LinuxTab = () => {
@@ -29,15 +30,23 @@ export const LinuxTab = () => {
} as StepCommand,
{
key: 3,
title: 'Run NetBird and log in the browser:',
title: 'Start NetBird daemon:',
commands: [
`sudo netbird up`
`sudo netbird service install`,
`sudo netbird service start`
].join('\n'),
copied: false,
showCopyButton: true
} as StepCommand,
{
key: 4,
title: 'Run NetBird and log in the browser:',
commands: formatNetBirdUP(),
copied: false,
showCopyButton: true
} as StepCommand,
{
key: 5,
title: 'Get your IP address:',
commands: [
`sudo ifconfig utun100`
+2 -11
View File
@@ -5,21 +5,12 @@ import TabSteps from "./TabSteps";
import { StepCommand } from "./types"
import { getConfig } from "../../config";
import Paragraph from 'antd/lib/skeleton/Paragraph';
const { grpcApiOrigin } = getConfig();
import { formatNetBirdUP } from "./common"
export const UbuntuTab = () => {
const formatNetBirdUP = () => {
let cmd = "sudo netbird up"
if (grpcApiOrigin) {
cmd = "sudo netbird up --management-url " + grpcApiOrigin
}
return [
cmd
].join('\n')
}
const [steps, setSteps] = useState([
{
key: 1,
+1 -4
View File
@@ -3,18 +3,15 @@ import React, {useState} from 'react';
import { Button } from "antd";
import TabSteps from "./TabSteps";
import { StepCommand } from "./types"
import {getConfig} from "../../config";
const {latestVersion} = getConfig();
export const WindowsTab = () => {
const releaseVersion = latestVersion ? latestVersion.replace("v", "") : "0.6.3"
const [steps, setSteps] = useState([
{
key: 1,
title: 'Download and run Windows installer:',
commands: (
<Button type="primary" href={`https://github.com/netbirdio/netbird/releases/download/v${releaseVersion}/netbird_installer_${releaseVersion}_windows_amd64.exe`} target="_blank">Download NetBird</Button>
<Button type="primary" href="https://pkgs.netbird.io/windows/x64" target="_blank">Download NetBird</Button>
),
copied: false
} as StepCommand,
+13
View File
@@ -0,0 +1,13 @@
import {getConfig} from "../../config";
const { grpcApiOrigin } = getConfig();
export const formatNetBirdUP = () => {
let cmd = "sudo netbird up"
if (grpcApiOrigin) {
cmd = "sudo netbird up --management-url " + grpcApiOrigin
}
return [
cmd
].join('\n')
}
-1
View File
@@ -7,7 +7,6 @@
"apiOrigin": "$NETBIRD_MGMT_API_ENDPOINT",
"grpcApiOrigin": "$NETBIRD_MGMT_GRPC_API_ENDPOINT",
"latestVersion": "$NETBIRD_LATEST_VERSION",
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
"redirectURI": "$AUTH_REDIRECT_URI",
"silentRedirectURI": "$AUTH_SILENT_REDIRECT_URI"
-1
View File
@@ -28,7 +28,6 @@ export function getConfig() {
scopesSupported: configJson.authScopesSupported,
apiOrigin: configJson.apiOrigin,
grpcApiOrigin: configJson.grpcApiOrigin,
latestVersion: configJson.latestVersion,
audience: configJson.authAudience,
hotjarTrackID: configJson.hotjarTrackID,
redirectURI: redirectURI,
-6
View File
@@ -3,12 +3,6 @@ import axios from 'axios';
import {ApiError, ApiRequestParams, ApiResponse} from './types';
import {headersFactory, RequestHeader} from './header-factory';
/*axios.interceptors.response.use(undefined, err => {
let res = err.response;
if (res.status === 401) {
}
})*/
async function apiRequest<T>(params: ApiRequestParams): Promise<ApiResponse<T>> {
const data = params.data ? (params.data as any).payload : undefined;
const url = `${params.urlBase}${params.url}`;
+23
View File
@@ -0,0 +1,23 @@
import {ActionType, createAction, createAsyncAction} from 'typesafe-actions';
import {Account} from './types';
import {ApiError, ChangeResponse, RequestPayload} from '../../services/api-client/types';
const actions = {
getAccounts: createAsyncAction(
'GET_ACCOUNTS_REQUEST',
'GET_ACCOUNTS_SUCCESS',
'GET_ACCOUNTS_FAILURE',
)<RequestPayload<null>, Account[], ApiError>(),
updateAccount: createAsyncAction(
'UPDATE_ACCOUNT',
'UPDATE_ACCOUNT_SUCCESS',
'UPDATE_ACCOUNT_FAILURE',
)<RequestPayload<Account>, ChangeResponse<Account | null>, ChangeResponse<Account | null>>(),
setUpdateAccount: createAction('SET_UPDATED_ACCOUNT')<ChangeResponse<Account | null>>(),
resetUpdateAccount: createAction('RESET_UPDATED_ACCOUNT')<null>(),
};
export type ActionTypes = ActionType<typeof actions>;
export default actions;
Loaded 20 of 61 files, more files were not shown because too many files have changed in this diff. Show more