Display additional peer info (#84)

This commit is contained in:
Misha Bragin
2022-09-26 18:40:06 +02:00
committed by GitHub
parent f83e39d734
commit a98d6d9ce1
7 changed files with 193 additions and 36 deletions

View File

@@ -2,24 +2,27 @@ 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, Divider, Drawer, Form, Input, Row, Select, Space, Tag, Typography} from "antd";
import {Button, Col, Collapse, 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 {Peer, PeerGroupsToSave} from "../store/peer/types";
import {FormPeer, Peer, PeerGroupsToSave} from "../store/peer/types";
import {Group, GroupPeer} from "../store/group/types";
import {CloseOutlined, EditOutlined, FlagFilled} from "@ant-design/icons";
import {CloseOutlined, EditOutlined} 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 PeerUpdate = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const groups = useSelector((state: RootState) => state.group.data)
const peer = useSelector((state: RootState) => state.peer.peer)
const [formPeer, setFormPeer] = useState({} as Peer)
const users = useSelector((state: RootState) => state.user.data)
const peer: Peer = useSelector((state: RootState) => state.peer.peer)
const [formPeer, setFormPeer] = useState({} as FormPeer)
const updateGroupsVisible = useSelector((state: RootState) => state.peer.updateGroupsVisible)
const savedGroups = useSelector((state: RootState) => state.peer.savedGroups)
const updatedPeers = useSelector((state: RootState) => state.peer.updatedPeer)
@@ -75,17 +78,25 @@ const PeerUpdate = () => {
const gs_name = gs?.map(g => g.name) as string[]
setPeerGroups(gs)
setSelectedTagGroups(gs_name)
setFormPeer(peer)
form.setFieldsValue({
const fPeer = {
...peer,
name: formPeer.name ? formPeer.name : peer.name,
groups: gs_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)
}, [peer])
useEffect(() => {
setTagGroups(groups?.map(g => g.name) || [])
}, [groups])
useEffect(() => {
}, [users])
const toggleEditName = (status: boolean) => {
setEditName(status)
}
@@ -173,7 +184,7 @@ const PeerUpdate = () => {
setUpdateGroupsVisible(false)
setEditName(false)
// setSaveBtnDisabled(true)
setFormPeer({} as Peer)
setFormPeer({} as FormPeer)
setCallingPeerAPI(false)
setCallingPeerAPI(false)
setSubmitRunning(false)
@@ -268,7 +279,7 @@ const PeerUpdate = () => {
<Drawer
forceRender={true}
headerStyle={{display: "none"}}
visible={true}
open={true}
bodyStyle={{paddingBottom: 80}}
onClose={onCancel}
autoFocus={true}
@@ -281,7 +292,7 @@ const PeerUpdate = () => {
</Space>
}
>
<Form layout="vertical" hideRequiredMark form={form} onValuesChange={onChange}>
<Form layout="vertical" requiredMark={false} form={form} onValuesChange={onChange}>
<Row gutter={16}>
<Col span={24}>
<Header style={{margin: "-32px -24px 20px -24px", padding: "24px 24px 0 24px"}}>
@@ -328,10 +339,55 @@ const PeerUpdate = () => {
</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}>
{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="groups"
name="groupsNames"
label="Select peer groups"
rules={[{validator: selectValidator}]}
@@ -352,7 +408,7 @@ const PeerUpdate = () => {
</Select>
</Form.Item>
</Col>
<Col span={24}>
{/*<Col span={24}>
<Row wrap={false} gutter={12}>
<Col flex="none">
<FlagFilled/>
@@ -363,6 +419,69 @@ 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>

View File

@@ -10,6 +10,14 @@ export interface Peer {
version: string,
groups?: Group[]
ssh_enabled: boolean,
hostname: string,
user_id?: string,
ui_version?: string,
}
export interface FormPeer extends Peer {
groupsNames: string[],
userEmail?: string
}
export interface PeerToSave extends Peer {
@@ -30,3 +38,9 @@ export interface PeerNameToIP {
export interface PeerIPToName {
[key: string]: string;
}
export interface PeerDataTable extends Peer {
key: string;
groups: Group[];
groupsCount: number;
}

View File

@@ -415,7 +415,7 @@ export const AccessControl = () => {
return <Tooltip title={desc !== "" ? desc : "no description"}
arrowPointAtCenter>
<span onClick={() => setRuleAndView(record as RuleDataTable)}
className="tooltip-label">{text}</span>
className="tooltip-label"><strong style={{color: "#5a5c5a"}}>{text}</strong></span>
</Tooltip>
}}
/>

View File

@@ -1,4 +1,4 @@
import React, {useCallback, useEffect, useRef, useState} from 'react';
import React, {useEffect, useState} from 'react';
import {Link} from 'react-router-dom';
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
@@ -29,7 +29,7 @@ import {
Tooltip,
Typography
} from "antd";
import {Peer} from "../store/peer/types";
import {Peer, PeerDataTable} from "../store/peer/types";
import {filter} from "lodash"
import {formatOS, timeAgo} from "../utils/common";
import {ExclamationCircleOutlined} from "@ant-design/icons";
@@ -39,18 +39,12 @@ import PeerUpdate from "../components/PeerUpdate";
import tableSpin from "../components/Spin";
import {TooltipPlacement} from "antd/es/tooltip";
import {useGetAccessTokenSilently} from "../utils/token";
import {actions as userActions} from "../store/user";
const {Title, Paragraph, Text} = Typography;
const {Column} = Table;
const {confirm} = Modal;
interface PeerDataTable extends Peer {
key: string;
groups: Group[];
groupsCount: number;
}
export const Peers = () => {
//const {accessToken} = useOidcAccessToken()
@@ -67,6 +61,7 @@ export const Peers = () => {
const savedGroups = useSelector((state: RootState) => state.peer.savedGroups);
const updatedPeer = useSelector((state: RootState) => state.peer.updatedPeer);
const updateGroupsVisible = useSelector((state: RootState) => state.peer.updateGroupsVisible)
const users = useSelector((state: RootState) => state.user.data);
const [textToSearch, setTextToSearch] = useState('');
const [optionOnOff, setOptionOnOff] = useState('all');
@@ -111,6 +106,7 @@ export const Peers = () => {
}
useEffect(() => {
dispatch(userActions.getUsers.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
dispatch(peerActions.getPeers.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
dispatch(groupActions.getGroups.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
dispatch(routeActions.getRoutes.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
@@ -191,8 +187,13 @@ export const Peers = () => {
const filterDataTable = (): Peer[] => {
const t = textToSearch.toLowerCase().trim()
let f: Peer[] = filter(peers, (f: Peer) =>
(f.name.toLowerCase().includes(t) || f.ip.includes(t) || f.os.includes(t) || t === "")
let f: Peer[] = filter(peers, (f: Peer) => {
let userEmail: string | null
const u = users?.find(u => u.id === f.user_id)?.email
userEmail = u ? u : ""
return (f.name.toLowerCase().includes(t) || f.ip.includes(t) || f.os.includes(t) || t === "" ||
(userEmail && userEmail.toLowerCase().includes(t)))
}
) as Peer[]
if (optionOnOff === "on") {
f = filter(peers, (f: Peer) => f.connected)
@@ -359,13 +360,30 @@ export const Peers = () => {
return (
<Popover placement={popoverPlacement as TooltipPlacement} key={peerToAction.key} content={mainContent}
onVisibleChange={onPopoverVisibleChange} visible={groupPopupVisible}
onOpenChange={onPopoverVisibleChange} open={groupPopupVisible}
title={null}>
<Button type="link" onClick={() => setUpdateGroupsVisible(peerToAction, true)}>{label}</Button>
</Popover>
)
}
const renderName = (peer: PeerDataTable) => {
const userEmail = users?.find(u => u.id === peer.user_id)?.email
if (!userEmail) {
return <Button type="text" onClick={() => setUpdateGroupsVisible(peer, true)}>
<strong style={{color: "#5a5c5a"}}>{peer.name}</strong>
</Button>
}
return <div>
<Button type="text" style={{height: "auto", whiteSpace: "normal", textAlign: "left"}}
onClick={() => setUpdateGroupsVisible(peer, true)}>
<strong style={{color: "#5a5c5a"}}>{peer.name}</strong>
<br/>
<div style={{color: "#5a5c5a"}}>{userEmail}</div>
</Button>
</div>
}
return (
<>
<Container style={{paddingTop: "40px"}}>
@@ -377,7 +395,6 @@ export const Peers = () => {
<Space direction="vertical" size="large" style={{display: 'flex'}}>
<Row gutter={[16, 24]}>
<Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} span={8}>
{/*<Input.Search allowClear value={textToSearch} onPressEnter={searchDataTable} onSearch={searchDataTable} placeholder="Search..." onChange={onChangeTextToSearch} />*/}
<Input allowClear value={textToSearch} onPressEnter={searchDataTable}
placeholder="Search..." onChange={onChangeTextToSearch}/>
</Col>
@@ -428,10 +445,10 @@ export const Peers = () => {
<Column title="Name" dataIndex="name"
onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)}
defaultSortOrder='ascend'
align="left"
sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))}
render={(text: string, record: PeerDataTable,) => {
return <Button type="text"
onClick={() => setUpdateGroupsVisible(record, true)}>{text}</Button>
return renderName(record)
}}
/>
<Column title="IP" dataIndex="ip"
@@ -501,7 +518,7 @@ export const Peers = () => {
render={(text, record, index) => {
return <Dropdown.Button type="text" overlay={actionsMenu}
trigger={["click"]}
onVisibleChange={visible => {
onOpenChange={visible => {
if (visible) setPeerToAction(record as PeerDataTable)
}}></Dropdown.Button>
}}

View File

@@ -424,7 +424,9 @@ export const Routes = () => {
render={(text, record) => {
const desc = (record as RouteDataTable).description.trim()
return <Tooltip title={desc !== "" ? desc : "no description"}
arrowPointAtCenter>{text}</Tooltip>
arrowPointAtCenter>
<strong style={{color: "#5a5c5a"}}>{text}</strong>
</Tooltip>
}}
/>
<Column title="Network Range" dataIndex="network" align="center"

View File

@@ -12,7 +12,8 @@ import {
Input,
Menu,
message,
Modal, Popover,
Modal,
Popover,
Radio,
RadioChangeEvent,
Row,
@@ -399,7 +400,8 @@ export const SetupKeys = () => {
render={(text, record, index) => {
return <Button type="text"
onClick={() => setKeyAndView(record as SetupKeyDataTable)}
className="tooltip-label">{text}</Button>
className="tooltip-label"> <strong style={{color: "#5a5c5a"}}>{text}</strong>
</Button>
}}
defaultSortOrder='ascend'
/>

View File

@@ -260,7 +260,10 @@ export const Users = () => {
render={(text, record, index) => {
return <Button type="text"
onClick={() => setUserAndView(record as UserDataTable)}
className="tooltip-label">{(text && text.trim() !== "") ? text : (record as User).id}</Button>
className="tooltip-label">
<strong style={{color: "#5a5c5a"}}>{(text && text.trim() !== "") ? text : (record as User).id}</strong>
</Button>
}}
/>
<Column title="Name" dataIndex="name"