Compare commits

..
Author SHA1 Message Date
Maycon Santos baf37c2eaf List dependencies on build 2022-09-17 17:03:34 +05:00
Maycon Santos 01bcc620a5 Check token before do api requests for the page
This will prevent the case where we are using
an expired access token to call our API

Added a loadOnce to prevent not loading the page at least once
2022-09-17 17:02:35 +05:00
53 changed files with 5457 additions and 6424 deletions

No files matched your search

+10 -7
View File
@@ -11,10 +11,10 @@ jobs:
build_n_push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: setup-node
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: '16'
cache: 'npm'
@@ -22,31 +22,34 @@ jobs:
- name: Install dependecies
run: npm install
- name: List dependencies
run: npm list
- name: Build
# skiping fail on warning for now
run: CI=false npm run build
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v1
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v3
with:
images: wiretrustee/dashboard
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
-
name: Docker build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v2
with:
context: .
file: docker/Dockerfile
+1 -5
View File
@@ -44,14 +44,11 @@ fi
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
@@ -59,8 +56,7 @@ export NETBIRD_LATEST_VERSION=$(basename $(curl -fs -o/dev/null -w %{redirect_ur
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_LATEST_VERSION"
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
cp "$MAIN_JS" "$MAIN_JS".copy
+4756 -2370
View File
File diff suppressed because it is too large. Load diff
-3
View File
@@ -29,11 +29,9 @@
"lodash": "^4.17.21",
"postcss": "^8.4.12",
"prop-types": "^15.7.2",
"punycode": "^2.1.1",
"rc-overflow": "^1.2.8",
"react": "^18.2.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",
@@ -44,7 +42,6 @@
"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"
+20 -56
View File
@@ -1,39 +1,23 @@
import React, {useEffect, useRef, useState} from 'react';
import React, {useEffect, useState} from 'react';
import {Provider} from "react-redux";
import {apiClient, store} from "./store";
import {hotjar} from 'react-hotjar';
import {getConfig} from "./config";
import Banner from "./components/Banner";
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 {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 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 AddPeer from "./views/AddPeer";
import Users from './views/Users';
import AccessControl from './views/AccessControl';
import Routes from './views/Routes';
import Banner from "./components/Banner";
import {store} from "./store";
import { Col, Layout, Row} from 'antd';
import {Container} from "./components/Container";
import {withOidcSecure} from '@axa-fr/react-oidc';
const {Header, Content} = Layout;
function App() {
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);
@@ -41,6 +25,7 @@ function App() {
const hideMenu = () => {
if (window.innerWidth > 768 && isOpen) {
setIsOpen(false);
console.log('i resized');
}
};
@@ -49,28 +34,10 @@ function App() {
return () => {
window.removeEventListener('resize', hideMenu);
};
}, []);
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)
})
}
}, [getAccessTokenSilently])
});
return (
<>
<Provider store={store}>
{!show && <SecureLoading padding="3em" width={50} height={50}/>}
{show &&
<Provider store={store}>
<Layout>
<Banner/>
<Header className="header" style={{
@@ -87,7 +54,7 @@ function App() {
</Col>
</Row>
</Header>
<Content style={{minHeight: "100vh"}}>
<Content style={{ minHeight: "100vh"}}>
<Switch>
<Route
exact
@@ -104,15 +71,12 @@ function App() {
<Route path="/acls" component={withOidcSecure(AccessControl)}/>
<Route path="/routes" component={withOidcSecure(Routes)}/>
<Route path="/users" component={withOidcSecure(Users)}/>
<Route path="/dns" component={withOidcSecure(DNS)}/>
</Switch>
</Content>
<FooterComponent/>
</Layout>
}
</Provider>
</>
)
</Provider>
);
}
export default App;
+46 -77
View File
@@ -1,32 +1,26 @@
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 ruleActions } from '../store/rule';
import {
Button,
Col,
Divider,
Drawer,
Form,
Input,
Radio,
RadioChangeEvent,
Row,
Select,
Typography,
Input,
Space,
Tag,
Typography
Switch,
Button, Drawer, Form, Divider, Select, Tag, Radio, RadioChangeEvent
} from "antd";
import {CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
import type {CustomTagProps} from 'rc-select/lib/BaseSelect'
import {ArrowRightOutlined, CheckOutlined, CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
import type { CustomTagProps } from 'rc-select/lib/BaseSelect'
import {Rule, RuleToSave} from "../store/rule/types";
import {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";
import {useOidcAccessToken} from "@axa-fr/react-oidc";
const {Paragraph} = Typography;
const {Option} = Select;
const { Paragraph } = Typography;
const { Option } = Select;
interface FormRule extends Rule {
tagSourceGroups: string[]
@@ -34,11 +28,11 @@ interface FormRule extends Rule {
}
const AccessControlNew = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const {accessToken} = useOidcAccessToken()
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)
@@ -78,7 +72,7 @@ const AccessControlNew = () => {
setTagGroups(groups?.map(g => g.name) || [])
}, [groups])
const createRuleToSave = (): RuleToSave => {
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))
@@ -102,17 +96,14 @@ const AccessControlNew = () => {
form.validateFields()
.then((values) => {
const ruleToSave = createRuleToSave()
dispatch(ruleActions.saveRule.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: ruleToSave
}))
dispatch(ruleActions.saveRule.request({getAccessTokenSilently:accessToken, payload: ruleToSave}))
})
.catch((errorInfo) => {
console.log('errorInfo', errorInfo)
});
};
const setVisibleNewRule = (status: boolean) => {
const setVisibleNewRule = (status:boolean) => {
dispatch(ruleActions.setSetupNewRuleVisible(status));
}
@@ -130,7 +121,7 @@ const AccessControlNew = () => {
setVisibleNewRule(false)
}
const onChange = (data: any) => {
const onChange = (data:any) => {
setFormRule({...formRule, ...data})
}
@@ -148,7 +139,7 @@ const AccessControlNew = () => {
})
};
const handleChangeDisabled = ({target: {value}}: RadioChangeEvent) => {
const handleChangeDisabled = ({ target: { value } }: RadioChangeEvent) => {
setFormRule({
...formRule,
disabled: value
@@ -156,7 +147,7 @@ const AccessControlNew = () => {
};
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();
@@ -168,7 +159,7 @@ const AccessControlNew = () => {
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{marginRight: 3}}
style={{ marginRight: 3 }}
>
<strong>{value}</strong>
</Tag>
@@ -178,12 +169,12 @@ const AccessControlNew = () => {
const optionRender = (label: string) => {
let peersCount = ''
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'} `
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>
@@ -195,27 +186,25 @@ const AccessControlNew = () => {
const dropDownRender = (menu: React.ReactElement) => (
<>
{menu}
<Divider style={{margin: '8px 0'}}/>
<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"/>
<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) => {
const toggleEditName = (status:boolean) => {
setEditName(status);
}
const toggleEditDescription = (status: boolean) => {
const toggleEditDescription = (status:boolean) => {
setEditDescription(status);
}
@@ -228,10 +217,10 @@ const AccessControlNew = () => {
const selectValidator = (_: RuleObject, value: string[]) => {
let hasSpaceNamed = []
if (!value.length) {
return Promise.reject(new Error("Please enter at least one group"))
return Promise.reject(new Error("Please enter ate least one group"))
}
value.forEach(function (v: string) {
value.forEach(function(v: string) {
if (!v.trim().length) {
hasSpaceNamed.push(v)
}
@@ -259,8 +248,7 @@ const AccessControlNew = () => {
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>
}
>
@@ -270,51 +258,37 @@ const AccessControlNew = () => {
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
<Row align="top">
<Col flex="none" style={{display: "flex"}}>
{!editName && !editDescription && 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 input-text 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="Name"
rules={[{
required: true,
message: 'Please add a name for this access rule',
whitespace: true
}]}
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>
{ !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"/>
<Input placeholder="Add description..." ref={inputDescriptionRef} onPressEnter={() => toggleEditDescription(false)} onBlur={() => toggleEditDescription(false)} autoComplete="off"/>
</Form.Item>
)}
</Col>
@@ -348,10 +322,10 @@ const AccessControlNew = () => {
<Form.Item
name="tagSourceGroups"
label="Source groups"
rules={[{validator: selectValidator}]}
rules={[{ validator: selectValidator }]}
>
<Select mode="tags"
style={{width: '100%'}}
style={{ width: '100%' }}
placeholder="Tags Mode"
tagRender={tagRender}
onChange={handleChangeSource}
@@ -369,10 +343,10 @@ const AccessControlNew = () => {
<Form.Item
name="tagDestinationGroups"
label="Destination groups"
rules={[{validator: selectValidator}]}
rules={[{ validator: selectValidator }]}
>
<Select
mode="tags" style={{width: '100%'}}
mode="tags" style={{ width: '100%' }}
placeholder="Tags Mode"
tagRender={tagRender}
onChange={handleChangeDestination}
@@ -393,14 +367,10 @@ 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>
</Col>
</Row>
@@ -408,8 +378,7 @@ const AccessControlNew = () => {
<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
href="https://docs.netbird.io/docs/overview/acls" style={{color: 'rgb(07, 114, 128)'}}>Learn
more about access controls</Button>
</Col>
</Row>
+5 -25
View File
@@ -1,28 +1,20 @@
import {useEffect, useState} from "react";
import { 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(false);
const banner_md5_key = 'banner_md5'
const banner_closed_key = 'banner_closed'
const [show, setShow] = useState(true);
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/nameservers"
href="https://netbird.io/docs/how-to-guides/network-routes"
className="font-bold underline"
target="_blank"
rel="noreferrer"
@@ -30,24 +22,12 @@ 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"}}>
{announcement}
New Release! Access private networks with the Network Routes feature.
</Text>
</Col>
<Col xs={24} sm={0} lg={0}>
@@ -58,7 +38,7 @@ const Banner = () => {
<Col xs={0} sm={24}>
<Space align="center" style={{display: "flex", justifyContent: "center"}}>
<Text style={{color: "#ffffff"}}>
{announcement}
New Release! Access private networks with the Network Routes feature.
</Text>
<span>
{linkLearnMore()}
+9 -21
View File
@@ -1,36 +1,24 @@
import {copyToClipboard} from "../utils/common";
import {Button, message, Typography} from "antd";
import React, {ReactNode} from "react";
const {Text} = Typography;
import {Button, message} from "antd";
import {StepCommand} from "./addpeer/types";
import React from "react";
type Props = {
keyMessage: string;
toCopy: string;
body: ReactNode;
text: string;
messageText: string;
styleNotification?: any;
style?: any;
className?: any;
className?:any;
};
const ButtonCopyMessage: React.FC<Props> = ({
keyMessage,
toCopy,
body,
messageText,
styleNotification,
style,
className
}) => {
const ButtonCopyMessage:React.FC<Props> = ({ keyMessage, text, messageText, styleNotification, style, className}) => {
const copyTextMessage = () => {
copyToClipboard(toCopy)
message.success({content: `${messageText}`, key: keyMessage, duration: 1, style: (styleNotification || {})});
copyToClipboard(text)
message.success({ content: `${messageText}`, key: keyMessage, duration: 1, style: (styleNotification || {}) });
}
return (
<Button type="text" onClick={copyTextMessage} style={style || {}} className={className}>
{body}
</Button>
<Button type="text" onClick={copyTextMessage} style={style || {}} className={className}>{text}</Button>
)
}
+6 -21
View File
@@ -1,32 +1,17 @@
import React from "react";
import loading from "../assets/bars.svg";
import {Space} from "antd";
import {OidcSecure} from "@axa-fr/react-oidc";
type LoadingProps = {
type Props = {
padding?: string;
width?: number;
height?: number;
width?: string;
height?: string;
};
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'}`}}/>
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'}`}}/>
</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>
);
+15 -37
View File
@@ -1,57 +1,35 @@
import {useOidc, useOidcUser} from "@axa-fr/react-oidc";
import {Anchor, Button, Col, Result, Row, Space} from "antd";
import {OidcUserStatus, useOidc, useOidcUser} from "@axa-fr/react-oidc";
import {Button, Result} 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={status}
title={title}
status="warning"
title={urlParams.get("error_description")}
extra={<>
<Space style={{
display: "flex-inline",
flexDirection: "column",
justifyContent: "space-around",
alignContent: "center"
<a href={window.location.origin}>
<Button type="primary">
Try again
</Button>
</a>
<Button type="primary" onClick={function () {
logout("",{client_id:config.clientId})
}}>
<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.
Log out
</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
+29 -75
View File
@@ -1,60 +1,49 @@
import React, {useEffect, useState} from 'react';
import {Link, useLocation} from 'react-router-dom';
import {Link} from 'react-router-dom';
import logo from "../assets/logo.png";
import {Avatar, Button, Col, Dropdown, Grid, Menu, Row} from 'antd'
import {useLocation} from 'react-router-dom';
import {Menu, Row, Col, Grid, Dropdown, Avatar, Button, Typography, Space} 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,useOidcUser } from '@axa-fr/react-oidc';
import {getConfig} from "../config";
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 {useGetAccessTokenSilently} from "../utils/token";
const {useBreakpoint} = Grid;
const { Text } = Typography
const { useBreakpoint } = Grid;
const Navbar = () => {
let location = useLocation();
const config = getConfig();
const { logout } = useOidc();
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const {
isAuthenticated,
logout,
} = useOidc();
const {oidcUser} = useOidcUser();
const { oidcUser } = useOidcUser();
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="/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="/dns">DNS</Link>), key: '/dns' },
{label: (<Link to="/users">Users</Link>), key: '/users'}
] as ItemType[]
const userEmailKey = 'user-email'
const userLogoutKey = 'user-logout'
const userDividerKey = 'user-divider'
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns"]
const [menuItems, setMenuItems] = useState(items)
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 logoutWithRedirect = () =>
logout("/", {client_id: config.clientId});
logout("/",{client_id:config.clientId});
useEffect(() => {
const fs = items.filter(m => showTab(m?.key?.toString(), currentUser) && m?.key !== userEmailKey && m?.key !== userLogoutKey && m?.key !== userDividerKey)
const fs = menuItems.filter(m => 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>
@@ -62,47 +51,13 @@ 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, currentUser])
useEffect(() => {
if (users.length === 0 && !isRefreshingUserState &&
window.location.pathname !== '/peers' &&
window.location.pathname !== '/users') {
setIsRefreshingUserState(true)
dispatch(userActions.getUsers.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}))
return
}
if (users.length === 0 && isRefreshingUserState) {
return
}
setIsRefreshingUserState(false)
if (oidcUser && oidcUser.sub) {
const found = users.find(u => u.id == oidcUser.sub)
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)
}
}, [screens])
const menuUser = (
<Menu
@@ -112,16 +67,16 @@ const Navbar = () => {
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>
)
@@ -141,8 +96,7 @@ 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]} defaultSelectedKeys={[location.pathname]} items={menuItems}/>
</div>
</Col>
{hideMenuUser &&
+61 -242
View File
@@ -2,28 +2,23 @@ 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, Divider, Drawer, Form, Input, 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";
import {Peer, PeerGroupsToSave} from "../store/peer/types";
import {Group, GroupPeer} from "../store/group/types";
import {CloseOutlined, EditOutlined} from "@ant-design/icons";
import {CloseOutlined, EditOutlined, FlagFilled} from "@ant-design/icons";
import {RuleObject} from 'antd/lib/form';
import {useGetAccessTokenSilently} from "../utils/token";
import {timeAgo} from "../utils/common";
const {Paragraph} = Typography;
const {Option} = Select;
const {Panel} = Collapse;
const punycode = require('punycode/')
import { useOidcAccessToken } from '@axa-fr/react-oidc';
const { Paragraph } = Typography;
const { Option } = Select;
const PeerUpdate = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const { accessToken } = useOidcAccessToken()
const dispatch = useDispatch()
const groups = useSelector((state: RootState) => state.group.data)
const users = useSelector((state: RootState) => state.user.data)
const peer: Peer = useSelector((state: RootState) => state.peer.peer)
const [formPeer, setFormPeer] = useState({} as FormPeer)
const groups = useSelector((state: RootState) => state.group.data)
const peer = useSelector((state: RootState) => state.peer.peer)
const [formPeer, setFormPeer] = useState({} as Peer)
const updateGroupsVisible = useSelector((state: RootState) => state.peer.updateGroupsVisible)
const savedGroups = useSelector((state: RootState) => state.peer.savedGroups)
const updatedPeers = useSelector((state: RootState) => state.peer.updatedPeer)
@@ -33,7 +28,6 @@ const PeerUpdate = () => {
const [peerGroups, setPeerGroups] = useState([] as GroupPeer[])
const inputNameRef = useRef<any>(null)
const [editName, setEditName] = useState(false)
const [estimatedName, setEstimatedName] = useState("")
const [callingPeerAPI, setCallingPeerAPI] = useState(false)
const [callingGroupAPI, setCallingGroupAPI] = useState(false)
const [isSubmitRunning, setSubmitRunning] = useState(false)
@@ -52,21 +46,21 @@ const PeerUpdate = () => {
if (callingPeerAPI && updatedPeers.success) {
setCallingPeerAPI(false)
}
}, [updatedPeers])
},[updatedPeers])
// wait save groups to succeed
useEffect(() => {
if (callingGroupAPI && savedGroups.success) {
setCallingGroupAPI(false)
}
}, [savedGroups])
},[savedGroups])
// clean temp state and close
useEffect(() => {
if (isSubmitRunning && !callingGroupAPI && !callingPeerAPI) {
onCancel()
}
}, [callingGroupAPI, callingPeerAPI])
},[callingGroupAPI,callingPeerAPI])
useEffect(() => {
if (editName) inputNameRef.current!.focus({
@@ -80,26 +74,18 @@ const PeerUpdate = () => {
const gs_name = gs?.map(g => g.name) as string[]
setPeerGroups(gs)
setSelectedTagGroups(gs_name)
const fPeer = {
...peer,
name: formPeer.name ? formPeer.name : peer.name,
groupsNames: gs_name,
userEmail: users?.find(u => u.id === peer.user_id)?.email,
last_seen: peer.connected ? "just now" : String(timeAgo(peer.last_seen)),
ui_version: peer.ui_version ? peer.ui_version.replace("netbird-desktop-ui/", "") : ""
} as FormPeer
setFormPeer(fPeer)
form.setFieldsValue(fPeer)
setFormPeer(peer)
form.setFieldsValue({
name: formPeer.name ? formPeer.name: peer.name,
groups: gs_name
})
}, [peer])
useEffect(() => {
setTagGroups(groups?.map(g => g.name) || [])
}, [groups])
useEffect(() => {
}, [users])
const toggleEditName = (status: boolean) => {
const toggleEditName = (status:boolean) => {
setEditName(status)
}
@@ -117,7 +103,7 @@ const PeerUpdate = () => {
}, [selectedTagGroups])
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();
@@ -134,7 +120,7 @@ const PeerUpdate = () => {
onMouseDown={onPreventMouseDown}
closable={tagClosable}
onClose={onClose}
style={{marginRight: 3}}
style={{ marginRight: 3 }}
>
<strong>{value}</strong>
</Tag>
@@ -144,12 +130,12 @@ const PeerUpdate = () => {
const optionRender = (label: string) => {
let peersCount = ''
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'} `
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>
@@ -161,23 +147,21 @@ const PeerUpdate = () => {
const dropDownRender = (menu: React.ReactElement) => (
<>
{menu}
<Divider style={{margin: '8px 0'}}/>
<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"/>
<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 setUpdateGroupsVisible = (status: boolean) => {
const setUpdateGroupsVisible = (status:boolean) => {
dispatch(peerActions.setUpdateGroupsVisible(status));
}
@@ -186,27 +170,27 @@ const PeerUpdate = () => {
setUpdateGroupsVisible(false)
setEditName(false)
// setSaveBtnDisabled(true)
setFormPeer({} as FormPeer)
setFormPeer({} as Peer)
setCallingPeerAPI(false)
setCallingPeerAPI(false)
setSubmitRunning(false)
}
const noUpdateToGroups = (): Boolean => {
const noUpdateToGroups = ():Boolean => {
return !peerGroupsToSave.groupsToRemove.length && !peerGroupsToSave.groupsToAdd.length && !peerGroupsToSave.groupsNoId.length
}
const noUpdateToName = (): Boolean => {
const noUpdateToName = ():Boolean => {
return !formPeer.name || formPeer.name === peer.name
}
const onChange = (data: any) => {
const onChange = (data:any) => {
setFormPeer({...formPeer, ...data})
}
const handleChangeTags = (value: string[]) => {
let validatedValues: string[] = []
value.forEach(function (v) {
value.forEach(function(v) {
if (v.trim().length) {
validatedValues.push(v)
}
@@ -214,21 +198,7 @@ const PeerUpdate = () => {
setSelectedTagGroups(validatedValues)
};
const nameValidator = (_: RuleObject, value: string) => {
let punyName = punycode.toASCII(value.toLowerCase())
let domain = ""
if (formPeer.dns_label) {
let labelList = formPeer.dns_label.split(".")
if (labelList.length > 1) {
labelList.splice(0,1)
domain = "." + labelList.join(".")
}
}
setEstimatedName(punyName+domain)
return Promise.resolve()
}
const createPeerToSave = (): Peer => {
const createPeerToSave = ():Peer => {
return {
id: formPeer.id,
ssh_enabled: formPeer.ssh_enabled,
@@ -243,17 +213,11 @@ const PeerUpdate = () => {
if (!noUpdateToName()) {
const peerUpdate = createPeerToSave()
setCallingPeerAPI(true)
dispatch(peerActions.updatePeer.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: peerUpdate
}))
dispatch(peerActions.updatePeer.request({getAccessTokenSilently:accessToken, payload: peerUpdate}))
}
if (peerGroupsToSave.groupsToRemove.length || peerGroupsToSave.groupsToAdd.length || peerGroupsToSave.groupsNoId.length) {
setCallingGroupAPI(true)
dispatch(peerActions.saveGroups.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: peerGroupsToSave
}))
dispatch(peerActions.saveGroups.request({getAccessTokenSilently:accessToken, payload: peerGroupsToSave}))
}
})
.catch((errorInfo) => {
@@ -269,7 +233,7 @@ const PeerUpdate = () => {
return Promise.reject(new Error("Please enter ate least one group"))
}
value.forEach(function (v: string) {
value.forEach(function(v: string) {
if (!v.trim().length) {
hasSpaceNamed.push(v)
}
@@ -295,28 +259,25 @@ const PeerUpdate = () => {
<Drawer
forceRender={true}
headerStyle={{display: "none"}}
open={true}
visible={true}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
autoFocus={true}
footer={
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button onClick={onCancel} disabled={savedGroups.loading}>Cancel</Button>
<Button type="primary"
disabled={(savedGroups.loading || updatedPeers.loading) || (noUpdateToGroups() && noUpdateToName())}
onClick={handleFormSubmit}>Save</Button>
<Button type="primary" disabled={(savedGroups.loading || updatedPeers.loading) || (noUpdateToGroups() && noUpdateToName())} onClick={handleFormSubmit}>Save</Button>
</Space>
}
>
<Form layout="vertical" requiredMark={false} form={form} onValuesChange={onChange}>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
<Row align="top">
<Col flex="none" style={{display: "flex"}}>
{!editName && peer.id &&
<button type="button" aria-label="Close" autoFocus={true}
className="ant-drawer-close"
<button type="button" aria-label="Close" autoFocus={true} className="ant-drawer-close"
style={{paddingTop: 3}}
onClick={onCancel}>
<span role="img" aria-label="close"
@@ -332,119 +293,40 @@ const PeerUpdate = () => {
onClick={() => toggleEditName(true)}>{formPeer.name ? formPeer.name : peer.name}
<EditOutlined/></div>
) : (
<Row>
<Space direction={"vertical"} size="small">
<Form.Item
name="name"
label="Name"
style={{margin: '1px'}}
rules={[{
required: true,
message: 'Please add a new name for this peer',
whitespace: true
},{validator:nameValidator}]}
>
<Input
placeholder={peer.name}
ref={inputNameRef}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)}
autoComplete="off"
max={59}/>
</Form.Item>
<Form.Item
label="New peer domain name preview"
tooltip="If the domain name already exists, we add an increment number suffix to it"
style={{margin: '1px'}}
>
<Paragraph>
<Tag>
{estimatedName}
</Tag>
</Paragraph>
</Form.Item>
</Space>
</Row>
)}
<Form.Item
name="name"
label="Name"
rules={[{
required: true,
message: 'Please add a new name for this peer',
whitespace: true
}]}
>
<Input
placeholder={peer.name}
ref={inputNameRef}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)}
autoComplete="off"/>
</Form.Item>)}
</Col>
</Row>
</Header>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="ip"
label={<>
<span style={{
marginRight: "5px",
}}>NetBird IP</span>
<Tag
color={formPeer.connected ? "green" : "red"}>{formPeer.connected ? "online" : "offline"}</Tag>
</>}
>
<Input
disabled={true}
value={formPeer.ip}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="last_seen"
label="Last seen"
>
<Input
disabled={true}
value={formPeer.last_seen}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Row gutter={16}>
<Col span={24}>
<Form.Item
name="dns_label"
label="Domain name"
>
<Input
disabled={true}
value={formPeer.userEmail}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
{formPeer.user_id && (
<Col span={24}>
<Form.Item
name="userEmail"
label="User"
>
<Input
disabled={true}
value={formPeer.userEmail}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
)}
<Col span={24}>
<Form.Item
name="groupsNames"
name="groups"
label="Select peer groups"
rules={[{validator: selectValidator}]}
rules={[{ validator: selectValidator }]}
>
<Select
mode="tags"
style={{width: '100%'}}
style={{ width: '100%' }}
placeholder="Select groups..."
tagRender={tagRender}
@@ -458,7 +340,7 @@ const PeerUpdate = () => {
</Select>
</Form.Item>
</Col>
{/*<Col span={24}>
<Col span={24}>
<Row wrap={false} gutter={12}>
<Col flex="none">
<FlagFilled/>
@@ -469,69 +351,6 @@ const PeerUpdate = () => {
</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"}}>
<Panel key="0" header="System Info">
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="hostname"
label="Hostname"
>
<Input
disabled={true}
value={formPeer.hostname}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="os"
label="Operating system"
>
<Input
disabled={true}
value={formPeer.os}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="version"
label="Agent version"
>
<Input
disabled={true}
value={formPeer.os}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
{formPeer.ui_version && (
<Col span={12}>
<Form.Item
name="ui_version"
label="UI version"
>
<Input
disabled={true}
value={formPeer.ui_version}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>)}
</Row>
</Panel>
</Collapse>
</Col>
</Row>
</Form>
+55 -151
View File
@@ -1,63 +1,46 @@
import React, {useEffect, useRef, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
import {actions as routeActions} from '../store/route';
import { actions as routeActions } from '../store/route';
import {
Button,
Col,
Divider,
Drawer,
Form,
Row,
Input,
InputNumber,
Radio,
Row,
Select,
SelectProps,
Space,
Switch,
Typography
SelectProps,
Button, Drawer, Form, Divider, Select, Radio, Typography
} from "antd";
import {CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
import {Route, RouteToSave} from "../store/route/types";
import {Route} from "../store/route/types";
import {Header} from "antd/es/layout/layout";
import {RuleObject} from "antd/lib/form";
import {useOidcAccessToken} from "@axa-fr/react-oidc";
import cidrRegex from 'cidr-regex';
import {
initPeerMaps,
masqueradeDisabledMSG,
peerToPeerIP,
initPeerMaps,
routePeerSeparator,
transformGroupedDataTable
} from '../utils/routes'
import {useGetAccessTokenSilently} from "../utils/token";
import {useGetGroupTagHelpers} from "../utils/groups";
const {Paragraph} = Typography;
const { Paragraph } = Typography;
interface FormRoute extends Route {
}
const RouteUpdate = () => {
const {
tagRender,
handleChangeTags,
dropDownRender,
optionRender,
tagGroups,
getExistingAndToCreateGroupsLists,
getGroupNamesFromIDs,
selectValidator
} = useGetGroupTagHelpers()
const {Option} = Select;
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const {accessToken} = useOidcAccessToken()
const dispatch = useDispatch()
const setupNewRouteVisible = useSelector((state: RootState) => state.route.setupNewRouteVisible)
const setupNewRouteHA = useSelector((state: RootState) => state.route.setupNewRouteHA)
const peers = useSelector((state: RootState) => state.peer.data)
const route = useSelector((state: RootState) => state.route.route)
const routes = useSelector((state: RootState) => state.route.data)
const peers = useSelector((state: RootState) => state.peer.data)
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)
@@ -74,12 +57,11 @@ const RouteUpdate = () => {
const defaultStatusMSG = "Status"
const [statusMSG, setStatusMSG] = useState(defaultStatusMSG)
const [peerNameToIP, peerIPToName] = initPeerMaps(peers);
const [newRoute, setNewRoute] = useState(false)
const optionsDisabledEnabled = [{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]
useEffect(() => {
if (!newRoute ) {
if (setupNewRouteHA) {
setRoutingPeerMSG("Add additional routing peer")
setMasqueradeMSG("Update Masquerade")
setStatusMSG("Update Status")
@@ -89,7 +71,7 @@ const RouteUpdate = () => {
setStatusMSG(defaultStatusMSG)
setPreviousRouteKey("")
}
}, [newRoute])
}, [setupNewRouteHA])
useEffect(() => {
if (editName) inputNameRef.current!.focus({
@@ -108,41 +90,33 @@ 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)
}
setPreviousRouteKey(fRoute.network_id+fRoute.network)
form.setFieldsValue(fRoute)
}, [route])
peers.forEach((p) => {
let os: string
let os:string
os = p.os
if (!os.toLowerCase().startsWith("darwin") && !os.toLowerCase().startsWith("windows")) {
options?.push({
label: peerToPeerIP(p.name, p.ip),
value: peerToPeerIP(p.name, p.ip),
label: peerToPeerIP(p.name,p.ip),
value: peerToPeerIP(p.name,p.ip),
disabled: false
})
}
})
const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
const createRouteToSave = (inputRoute:FormRoute):Route => {
let peerIDList = inputRoute.peer.split(routePeerSeparator)
let peerID: string
let peerID:string
if (peerIDList[1]) {
peerID = peerIDList[1]
} else {
peerID = peerNameToIP[inputRoute.peer]
}
let [ existingGroups, groupsToCreate ] = getExistingAndToCreateGroupsLists(inputRoute.groups)
return {
id: inputRoute.id,
network: inputRoute.network,
@@ -151,10 +125,8 @@ const RouteUpdate = () => {
peer: peerID,
enabled: inputRoute.enabled,
masquerade: inputRoute.masquerade,
metric: inputRoute.metric,
groups: existingGroups,
groupsToCreate: groupsToCreate,
} as RouteToSave
metric: inputRoute.metric
} as Route
}
const handleFormSubmit = () => {
@@ -162,16 +134,13 @@ const RouteUpdate = () => {
.then(() => {
if (!setupNewRouteHA || formRoute.peer != '') {
const routeToSave = createRouteToSave(formRoute)
dispatch(routeActions.saveRoute.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: routeToSave
}))
dispatch(routeActions.saveRoute.request({getAccessTokenSilently:accessToken, payload: routeToSave}))
} else {
let groupedDataTable = transformGroupedDataTable(routes, peerIPToName)
let groupedDataTable = transformGroupedDataTable(routes,peerIPToName)
groupedDataTable.forEach((group) => {
if (group.key == previousRouteKey) {
group.groupedRoutes.forEach((route) => {
let updateRoute: FormRoute = {
let updateRoute:FormRoute = {
...formRoute,
id: route.id,
peer: route.peer,
@@ -179,10 +148,7 @@ const RouteUpdate = () => {
enabled: (formRoute.enabled != group.enabled) ? formRoute.enabled : route.enabled
}
const routeToSave = createRouteToSave(updateRoute)
dispatch(routeActions.saveRoute.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: routeToSave
}))
dispatch(routeActions.saveRoute.request({getAccessTokenSilently:accessToken, payload: routeToSave}))
})
}
})
@@ -194,11 +160,11 @@ const RouteUpdate = () => {
});
};
const setVisibleNewRoute = (status: boolean) => {
const setVisibleNewRoute = (status:boolean) => {
dispatch(routeActions.setSetupNewRouteVisible(status));
}
const setSetupNewRouteHA = (status: boolean) => {
const setSetupNewRouteHA = (status:boolean) => {
dispatch(routeActions.setSetupNewRouteHA(status));
}
@@ -217,24 +183,23 @@ const RouteUpdate = () => {
setVisibleNewRoute(false)
setSetupNewRouteHA(false)
setPreviousRouteKey("")
setNewRoute(false)
}
const onChange = (data: any) => {
const onChange = (data:any) => {
setFormRoute({...formRoute, ...data})
}
const peerDropDownRender = (menu: React.ReactElement) => (
const dropDownRender = (menu: React.ReactElement) => (
<>
{menu}
</>
)
const toggleEditName = (status: boolean) => {
const toggleEditName = (status:boolean) => {
setEditName(status);
}
const toggleEditDescription = (status: boolean) => {
const toggleEditDescription = (status:boolean) => {
setEditDescription(status);
}
@@ -250,96 +215,61 @@ 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}
open={setupNewRouteVisible}
visible={setupNewRouteVisible}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
autoFocus={true}
footer={
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button onClick={onCancel} disabled={savedRoute.loading}>Cancel</Button>
<Button type="primary" disabled={savedRoute.loading}
onClick={handleFormSubmit}>{`${newRoute ? 'Create' : 'Save'}`}</Button>
<Button type="primary" disabled={savedRoute.loading} onClick={handleFormSubmit}>{`${formRoute.network_id ? 'Save' : 'Create'}`}</Button>
</Space>
}
>
<Form layout="vertical" form={form} requiredMark={false} onValuesChange={onChange}>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
<Row align="top">
<Col flex="none" style={{display: "flex"}}>
{!editName && !editDescription && formRoute.id &&
{!editName && !editDescription && formRoute.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 && formRoute.id ? (
<div className={"access-control input-text ant-drawer-title"}
onClick={() => toggleEditName(true)}>{formRoute.id ? formRoute.network_id : 'New Route'}</div>
{ !editName && formRoute.id ? (
<div className={"access-control input-text ant-drawer-title"} onClick={() => toggleEditName(true)}>{formRoute.id ? formRoute.network_id : 'New Route'}</div>
) : (
<Form.Item
name="network_id"
label="Network Identifier"
tooltip="You can enable high-availability by assigning the same network identifier and network CIDR to multiple routes"
rules={[{
required: true,
message: 'Please add an identifier for this access route',
whitespace: true
}]}
rules={[{required: true, message: 'Please add an identifier for this access route', whitespace: true}]}
>
<Input placeholder="e.g. aws-eu-central-1-vpc" ref={inputNameRef}
disabled={!setupNewRouteHA && !newRoute}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)} autoComplete="off"
maxLength={40}/>
<Input placeholder="e.g. aws-eu-central-1-vpc" ref={inputNameRef} disabled={!setupNewRouteHA} onPressEnter={() => toggleEditName(false)} onBlur={() => toggleEditName(false)} autoComplete="off" maxLength={40}/>
</Form.Item>
)}
{!editDescription ? (
<div className={"access-control input-text ant-drawer-subtitle"}
onClick={() => toggleEditDescription(true)}>{formRoute.description && formRoute.description.trim() !== "" ? formRoute.description : 'Add description...'}</div>
{ !editDescription ? (
<div className={"access-control input-text ant-drawer-subtitle"} onClick={() => toggleEditDescription(true)}>{formRoute.description && formRoute.description.trim() !== "" ? formRoute.description : 'Add description...'}</div>
) : (
<Form.Item
name="description"
label="Description"
style={{marginTop: 24}}
>
<Input placeholder="Add description..." ref={inputDescriptionRef}
disabled={!setupNewRouteHA && !newRoute}
onPressEnter={() => toggleEditDescription(false)}
onBlur={() => toggleEditDescription(false)}
autoComplete="off" maxLength={200}/>
<Input placeholder="Add description..." ref={inputDescriptionRef} disabled={!setupNewRouteHA} onPressEnter={() => toggleEditDescription(false)} onBlur={() => toggleEditDescription(false)} autoComplete="off" maxLength={200}/>
</Form.Item>
)}
@@ -363,8 +293,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 && !newRoute}
autoComplete="off" minLength={9} maxLength={43}/>
<Input placeholder="e.g. 172.16.0.0/16" disabled={!setupNewRouteHA} autoComplete="off" minLength={9} maxLength={43}/>
</Form.Item>
</Col>
<Col span={24}>
@@ -385,15 +314,14 @@ 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={peerDropDownRender}
options={options}
allowClear={true}
showSearch
style={{ width: '100%' }}
placeholder="Select Peer"
dropdownRender={dropDownRender}
options={options}
allowClear={true}
/>
</Form.Item>
</Col>
@@ -403,7 +331,7 @@ const RouteUpdate = () => {
label={masqueradeMSG}
tooltip={masqueradeDisabledMSG}
>
<Switch size={"small"} disabled={!setupNewRouteHA && !newRoute} checked={formRoute.masquerade}/>
<Switch size={"small"} disabled={!setupNewRouteHA} checked={formRoute.masquerade}/>
</Form.Item>
</Col>
<Col span={24}>
@@ -415,28 +343,6 @@ 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">
@@ -444,8 +350,7 @@ const RouteUpdate = () => {
</Col>
<Col flex="auto">
<Paragraph>
You can enable high-availability by assigning the same network identifier
and network CIDR to multiple routes.
You can enable high-availability by assigning the same network identifier and network CIDR to multiple routes.
</Paragraph>
</Col>
</Row>
@@ -453,8 +358,7 @@ const RouteUpdate = () => {
<Col span={24}>
<Divider></Divider>
<Button icon={<QuestionCircleFilled/>} type="link" target="_blank"
href="https://netbird.io/docs/how-to-guides/network-routes"
style={{color: 'rgb(07, 114, 128)'}}>Learn
href="https://netbird.io/docs/how-to-guides/network-routes" style={{color: 'rgb(07, 114, 128)'}}>Learn
more about network routes</Button>
</Col>
</Row>
+27 -115
View File
@@ -10,7 +10,6 @@ import {
Drawer,
Form,
Input,
InputNumber,
List,
Radio,
Row,
@@ -21,21 +20,18 @@ import {
} from "antd";
import {RootState} from "typesafe-actions";
import {CloseOutlined, EditOutlined, QuestionCircleFilled} from "@ant-design/icons";
import {FormSetupKey, SetupKey, SetupKeyToSave} from "../store/setup-key/types";
import {SetupKey, SetupKeyToSave} from "../store/setup-key/types";
import {useOidcAccessToken} from "@axa-fr/react-oidc";
import {Header} from "antd/es/layout/layout";
import {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";
const {Option} = Select;
const {Text} = Typography;
const ExpiresInDefault: ExpiresInValue = {number: 30, interval: "Days"}
const customExpiresFormat: DatePickerProps['format'] = value => {
return formatDate(value)
}
@@ -51,8 +47,12 @@ const customLastUsedFormat: DatePickerProps['format'] = value => {
return ago
}
interface FormSetupKey extends SetupKey {
autoGroupNames: string[]
}
const SetupKeyNew = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const {accessToken} = useOidcAccessToken()
const dispatch = useDispatch()
const setupNewKeyVisible = useSelector((state: RootState) => state.setupKey.setupNewKeyVisible)
const setupKey = useSelector((state: RootState) => state.setupKey.setupKey)
@@ -84,7 +84,7 @@ const SetupKeyNew = () => {
allGroups.set(g.id!, g)
})
let formKeyGroups: string[] = []
let formKeyGroups :string[] = []
if (setupKey.auto_groups) {
formKeyGroups = setupKey.auto_groups.filter(g => allGroups.get(g)).map(g => allGroups.get(g)!.name)
@@ -93,7 +93,6 @@ const SetupKeyNew = () => {
const fSetupKey = {
...setupKey,
autoGroupNames: setupKey.auto_groups ? formKeyGroups : [],
expiresInFormatted: ExpiresInDefault
} as FormSetupKey
setFormSetupKey(fSetupKey)
form.setFieldsValue(fSetupKey)
@@ -102,53 +101,23 @@ const SetupKeyNew = () => {
const createSetupKeyToSave = (): SetupKeyToSave => {
const autoGroups = groups?.filter(g => formSetupKey.autoGroupNames.includes(g.name)).map(g => g.id || '') || []
// find groups that do not yet exist (newly added by the user)
const allGroupsNames: string[] = groups?.map(g => g.name);
const allGroupsNames : string[] = groups?.map(g => g.name);
const groupsToCreate = formSetupKey.autoGroupNames.filter(s => !allGroupsNames.includes(s))
let expiresIn = expiresInToSeconds(formSetupKey.expiresInFormatted)
return {
id: formSetupKey.id,
name: formSetupKey.name,
type: formSetupKey.type,
auto_groups: autoGroups,
revoked: formSetupKey.revoked,
groupsToCreate: groupsToCreate,
expires_in: expiresIn,
usage_limit: formSetupKey.usage_limit
groupsToCreate: groupsToCreate
} 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()
.then((values) => {
let setupKeyToSave = createSetupKeyToSave()
dispatch(setupKeyActions.saveSetupKey.request({
getAccessTokenSilently: getAccessTokenSilently,
getAccessTokenSilently: accessToken,
payload: setupKeyToSave
}))
})
@@ -165,15 +134,12 @@ const SetupKeyNew = () => {
if (savedSetupKey.loading) return
dispatch(setupKeyActions.setSetupKey({
name: "",
type: "one-off",
type: "reusable",
key: "",
last_used: "",
expires: "",
state: "valid",
auto_groups: new Array(),
usage_limit: 0,
used_times: 0,
expires_in: 0
auto_groups: new Array()
} as SetupKey))
setFormSetupKey({} as FormSetupKey)
setVisibleNewSetupKey(false)
@@ -276,49 +242,24 @@ const SetupKeyNew = () => {
</>
)
const changesDetected = (): boolean => {
return formSetupKey.name == null || formSetupKey.name !== setupKey.name || groupsChanged()
|| formSetupKey.usage_limit !== setupKey.usage_limit
}
const groupsChanged = (): boolean => {
if (formSetupKey.autoGroupNames.length != setupKey.auto_groups.length) {
return true
}
const formGroupIds = groups?.filter(g => formSetupKey.autoGroupNames.includes(g.name)).map(g => g.id || '') || []
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 &&
<Drawer
forceRender={true}
headerStyle={{display: "none"}}
open={setupNewKeyVisible}
visible={setupNewKeyVisible}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
footer={
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button disabled={savedSetupKey.loading} onClick={onCancel}>Cancel</Button>
<Button type="primary" disabled={savedSetupKey.loading || !changesDetected()}
<Button type="primary" disabled={savedSetupKey.loading}
onClick={handleFormSubmit}>{`${formSetupKey.id ? 'Save' : 'Create'}`}</Button>
</Space>
}
>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}
initialValues={{
expiresInFormatted: ExpiresInDefault,
}}
>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
@@ -375,7 +316,6 @@ const SetupKeyNew = () => {
>
<Input
disabled={true}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
@@ -388,8 +328,7 @@ const SetupKeyNew = () => {
label="Expires"
tooltip="The expiration date of the key"
>
<DatePicker disabled={true}
style={{width: "100%", color: "#5a5c5a"}}
<DatePicker disabled={true} style={{width: '100%'}}
format={customExpiresFormat}/>
</Form.Item>
</Col>
@@ -401,8 +340,7 @@ const SetupKeyNew = () => {
label="Last Used"
tooltip="The last time the key was used"
>
<DatePicker disabled={true}
style={{width: "100%", color: "#5a5c5a"}}
<DatePicker disabled={true} style={{width: '100%'}}
format={customLastUsedFormat}/>
</Form.Item>
</Col>
@@ -419,14 +357,6 @@ const SetupKeyNew = () => {
size="large"
bordered
>
<List.Item>
<Radio value={"one-off"}>
<Space direction="vertical" size="small">
<Text strong>One-off</Text>
<Text>This key can be used only once</Text>
</Space>
</Radio>
</List.Item>
<List.Item>
<Radio value={"reusable"}>
<Space direction="vertical" size="small">
@@ -436,6 +366,14 @@ const SetupKeyNew = () => {
</Space>
</Radio>
</List.Item>
<List.Item>
<Radio value={"one-off"}>
<Space direction="vertical" size="small">
<Text strong>One-off</Text>
<Text>This key can be used only once</Text>
</Space>
</Radio>
</List.Item>
</List>
</Space>
@@ -443,32 +381,6 @@ const SetupKeyNew = () => {
</Form.Item>
</Col>
{!setupKey.id &&
<Col span={24}>
<Form.Item name="expiresInFormatted" label="Expires In"
rules={[{validator: checkExpiresIn}]}>
<ExpiresInInput/>
</Form.Item>
</Col>}
<Col span={12}>
<Form.Item name="usage_limit"
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"}
style={{width: "100%"}}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="used_times"
label="Used Times"
>
<InputNumber min={0} defaultValue={0} disabled={true}
style={{width: "100%"}}
/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="autoGroupNames"
@@ -482,7 +394,7 @@ const SetupKeyNew = () => {
tagRender={tagRender}
onChange={handleChangeTags}
dropdownRender={dropDownRender}
// enabled only when we have a new key !setupkey.id or when the key is valid
// enabled only when we have a new key !setupkey.id or when the key is valid
disabled={!(!setupKey.id || setupKey.valid)}
>
{
-413
View File
@@ -1,413 +0,0 @@
import React, {useEffect, useRef, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {Alert, Button, Col, Divider, Drawer, Form, Input, Modal, Row, Select, Space, Tag, Typography} from "antd";
import {RootState} from "typesafe-actions";
import {CloseOutlined, EditOutlined, ExclamationCircleOutlined} from "@ant-design/icons";
import {Header} from "antd/es/layout/layout";
import {Group} from "../store/group/types";
import {FormUser, User, UserToSave} from "../store/user/types";
import {RuleObject} from "antd/lib/form";
import {CustomTagProps} from "rc-select/lib/BaseSelect";
import {actions as userActions} from "../store/user";
import {useGetAccessTokenSilently} from "../utils/token";
import {useOidcUser} from "@axa-fr/react-oidc";
const {Paragraph, Text} = Typography;
const {confirm} = Modal;
const {Option} = Select;
const UserUpdate = () => {
const {oidcUser} = useOidcUser();
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const user = useSelector((state: RootState) => state.user.user)
const savedUser = useSelector((state: RootState) => state.user.savedUser)
const groups = useSelector((state: RootState) => state.group.data)
const users = useSelector((state: RootState) => state.user.data)
const updateUserDrawerVisible = useSelector((state: RootState) => state.user.updateUserDrawerVisible)
const [selectedTagGroups, setSelectedTagGroups] = useState([] as string[])
const [tagGroups, setTagGroups] = useState([] as string[])
const [editName, setEditName] = useState(false)
const inputNameRef = useRef<any>(null)
const [formUser, setFormUser] = useState({} as FormUser)
const [currentUser, setCurrentUser] = useState({} as User)
const [form] = Form.useForm()
useEffect(() => {
if (editName) inputNameRef.current!.focus({
cursor: 'end',
});
}, [editName]);
const toggleEditName = (status: boolean) => {
setEditName(status);
}
useEffect(() => {
setTagGroups(groups?.filter(g => g.name != "All").map(g => g.name) || [])
}, [groups])
useEffect(() => {
if (oidcUser && oidcUser.sub) {
const found = users.find(u => u.id == oidcUser.sub)
if (found) {
setCurrentUser(found)
}
} else {
setCurrentUser({} as User)
}
}, [oidcUser, users])
useEffect(() => {
if (!user) return
let allGroups = new Map<string, Group>();
groups.forEach(g => {
allGroups.set(g.id!, g)
})
if (!user.auto_groups) {
user.auto_groups = []
}
let formKeyGroups = user.auto_groups.filter(g => allGroups.get(g)).map(g => allGroups.get(g)!.name)
const fUser = {
...user,
autoGroupsNames: user.auto_groups ? formKeyGroups : [],
} as FormUser
setFormUser(fUser)
form.setFieldsValue(fUser)
}, [user])
const selectValidator = (_: RuleObject, value: string[]) => {
let hasSpaceNamed = []
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()
}
const tagRender = (props: CustomTagProps) => {
const {label, value, closable, onClose} = props;
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault();
event.stopPropagation();
};
return (
<Tag
color="blue"
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{marginRight: 3}}
>
<strong>{value}</strong>
</Tag>
);
}
const optionRender = (label: string) => {
let peersCount = ''
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}}
>
<strong>{label}</strong>
</Tag>
<span style={{fontSize: ".85em"}}>{peersCount}</span>
</>
)
}
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 handleChangeTags = (value: string[]) => {
let validatedValues: string[] = []
value.forEach(function (v) {
if (v.trim().length) {
validatedValues.push(v)
}
})
setSelectedTagGroups(validatedValues)
};
const createUserToSave = (): UserToSave => {
const autoGroups = groups?.filter(g => formUser.autoGroupsNames.includes(g.name)).map(g => g.id || '') || []
// find groups that do not yet exist (newly added by the user)
const allGroupsNames: string[] = groups?.map(g => g.name);
const groupsToCreate = formUser.autoGroupsNames.filter(s => !allGroupsNames.includes(s))
return {
id: formUser.id,
email: formUser.email,
role: formUser.role,
name: formUser.name,
groupsToCreate: groupsToCreate,
auto_groups: autoGroups,
} as UserToSave
}
const showConfirmChangeRole = (userToSave: UserToSave) => {
let content = <Paragraph>With this action, you will remove the administrative privileges of your user.
Your user will be limited to read-only operations in this account. Are you sure?</Paragraph>
let contentModule = <div>{content}</div>
let name = formUser ? formUser.email : ''
confirm({
icon: <ExclamationCircleOutlined/>,
title: "Update user \"" + name + "\"",
width: 600,
content: contentModule,
okType: 'danger',
onOk() {
dispatch(userActions.saveUser.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: userToSave
}))
},
onCancel() {
},
});
}
// check if currentUser (who is doing the modification) removes the administrative privileges from themselves
const isShowConfirmWarning = (userToSave: UserToSave): boolean => {
return currentUser.id == userToSave.id && currentUser.role === "admin" && userToSave.role !== "admin"
}
const handleFormSubmit = () => {
form.validateFields()
.then((values) => {
let userToSave = createUserToSave()
if (isShowConfirmWarning(userToSave)) {
showConfirmChangeRole(userToSave)
} else {
dispatch(userActions.saveUser.request({
getAccessTokenSilently: getAccessTokenSilently,
payload: userToSave
}))
}
})
.catch((errorInfo) => {
console.log('errorInfo', errorInfo)
});
};
const onCancel = () => {
if (savedUser.loading) return
dispatch(userActions.setUser({
id: "",
email: "",
role: "",
status: "",
auto_groups: [],
name: user.name
} as User));
setFormUser({} as FormUser)
toggleEditName(false)
dispatch(userActions.setUpdateUserDrawerVisible(false));
}
const onChange = (data: any) => {
setFormUser({...formUser, ...data})
}
const changesDetected = (): boolean => {
return emailChanged() || nameChanged() || groupsChanged() || roleChanged()
}
const emailChanged = (): boolean => {
return formUser.email !== user.email
}
const roleChanged = (): boolean => {
return formUser.role !== user.role
}
const nameChanged = (): boolean => {
return formUser.name !== user.name
}
const groupsChanged = (): boolean => {
if (!formUser.autoGroupsNames) {
return false
}
if (formUser.autoGroupsNames.length != user.auto_groups.length) {
return true
}
const formGroupIds = groups?.filter(g => formUser.autoGroupsNames.includes(g.name)).map(g => g.id || '') || []
return user.auto_groups?.filter(g => !formGroupIds.includes(g)).length > 0
}
return (
<>
{user &&
<Drawer
forceRender={true}
headerStyle={{display: "none"}}
open={updateUserDrawerVisible}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
footer={
<Space style={{display: 'flex', justifyContent: 'end'}}>
<Button disabled={savedUser.loading} onClick={onCancel}>Cancel</Button>
<Button type="primary" disabled={savedUser.loading || !changesDetected()}
onClick={handleFormSubmit}>{`${formUser.id ? 'Save' : 'Invite'}`}</Button>
</Space>
}
>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}
initialValues={{
["role"]: formUser.role
}}
>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
<Row align="top">
{/*Close Icon*/}
<Col flex="none" style={{display: "flex"}}>
{!editName && user.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">
<CloseOutlined size={16}/>
</span>
</button>
}
</Col>
{/* Name Label*/}
<Col flex="auto">
{!editName && user.id && formUser.name !== "" ? (
<div className={"access-control input-text ant-drawer-title"}
onClick={() => toggleEditName(true)}>{formUser.name ? formUser.name : formUser.name}
<EditOutlined/></div>
) : (
<Form.Item
name="name"
label="Name"
rules={[{
required: false,
message: 'Please add a new name for this user',
whitespace: true
}]}
>
<Input
placeholder={formUser.name}
ref={inputNameRef}
onPressEnter={() => toggleEditName(false)}
onBlur={() => toggleEditName(false)}
autoComplete="off"/>
</Form.Item>)}
</Col>
</Row>
</Header>
</Col>
<Col span={24}>
<Form.Item
name="email"
label="Email"
>
<Input
disabled={user.id}
value={formUser.email}
style={{color: "#5a5c5a"}}
autoComplete="off"/>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="role"
label="Role"
>
<Select
style={{width: '100%'}}
disabled={currentUser.role != null && currentUser.role !== "admin"}>
<Option value="admin">admin</Option>
<Option value="user">user</Option>
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="autoGroupsNames"
label="Auto-assigned groups"
tooltip="Every peer enrolled with this user will be automatically added to these groups"
rules={[{validator: selectValidator}]}
>
<Select mode="tags"
style={{width: '100%'}}
placeholder="Associate groups with the user"
tagRender={tagRender}
onChange={handleChangeTags}
disabled={currentUser.role != null && currentUser.role !== "admin"}
dropdownRender={dropDownRender}
>
{
tagGroups.map(m =>
<Option key={m}>{optionRender(m)}</Option>
)
}
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Divider></Divider>
</Col>
{currentUser && currentUser.role !== "admin" && (
<div>
<Col span={24}>
<Alert
message={<div style={{color: "#5a5c5a"}}>
You are not an administrator, therefore you can't update users.</div>}
showIcon={false}
type="warning"/>
</Col>
<br></br>
</div>
)}
</Row>
</Form>
</Drawer>
}
</>
)
}
export default UserUpdate
+1 -1
View File
@@ -26,7 +26,7 @@ export const UbuntuTab = () => {
title: 'Add repository:',
commands: [
`sudo apt install ca-certificates curl gnupg -y`,
`curl -L https://pkgs.wiretrustee.com/debian/public.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/wiretrustee.gpg`,
`curl -L https://pkgs.wiretrustee.com/debian/public.key | sudo apt-key add -`,
`echo 'deb https://pkgs.wiretrustee.com/debian stable main' | sudo tee /etc/apt/sources.list.d/wiretrustee.list`
].join('\n'),
copied: false,
+1 -4
View File
@@ -7,8 +7,5 @@
"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"
"latestVersion": "$NETBIRD_LATEST_VERSION"
}
-15
View File
@@ -7,19 +7,7 @@ if (process.env.NODE_ENV !== 'production') {
configJson = require("./config.json");
}
const defaultRedirectURI = '/#callback';
const defaultSilentRedirectURI = '/#silent-callback'
export function getConfig() {
let redirectURI = defaultRedirectURI
if (configJson.redirectURI) {
redirectURI = configJson.redirectURI
}
let silentRedirectURI = defaultSilentRedirectURI
if (configJson.silentRedirectURI) {
silentRedirectURI = configJson.silentRedirectURI
}
return {
auth0Auth: configJson.auth0Auth == "true", //due to substitution we can't use boolean in the config
@@ -30,8 +18,5 @@ export function getConfig() {
grpcApiOrigin: configJson.grpcApiOrigin,
latestVersion: configJson.latestVersion,
audience: configJson.authAudience,
hotjarTrackID: configJson.hotjarTrackID,
redirectURI: redirectURI,
silentRedirectURI: silentRedirectURI,
};
}
+3 -3
View File
@@ -26,9 +26,9 @@ const auth0AuthorityConfig: AuthorityConfiguration = {
const providerConfig = {
authority: config.authority,
client_id: config.clientId,
redirect_uri: window.location.origin + config.redirectURI,
redirect_uri: window.location.origin + '/#callback',
refresh_time_before_tokens_expiration_in_second: 30,
silent_redirect_uri: window.location.origin + config.silentRedirectURI,
silent_redirect_uri: window.location.origin + '/#silent-callback',
scope: config.scopesSupported,
// disabling service worker
// service_worker_relative_url:'/OidcServiceWorker.js',
@@ -41,7 +41,7 @@ const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const loadingComponent = () => <Loading padding="3em" width={50} height={50}/>
const loadingComponent = () => <Loading padding="3em" width="50px" height="50px"/>
root.render(
<OidcProvider
Loaded 20 of 53 files, more files were not shown because too many files have changed in this diff. Show more