mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Display additional peer info (#84)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
}}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user