diff options
| author | Haishan <[email protected]> | 2019-11-08 00:40:48 +0800 |
|---|---|---|
| committer | Haishan <[email protected]> | 2019-11-09 13:21:25 +0800 |
| commit | 6754620d7abfb7273cff5f173349eb507fc0c8b7 (patch) | |
| tree | d91136e4863b4c41ff521bf182dd8cc08bb355bc /src/components | |
| parent | 130793446476e47b2e5f0457482dfbfdf1944b48 (diff) | |
feat: connections inspection
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/ConnectionTable.js | 99 | ||||
| -rw-r--r-- | src/components/ConnectionTable.module.css | 38 | ||||
| -rw-r--r-- | src/components/Connections.js | 55 | ||||
| -rw-r--r-- | src/components/Root.css | 5 | ||||
| -rw-r--r-- | src/components/Root.js | 2 | ||||
| -rw-r--r-- | src/components/SideBar.js | 25 | ||||
| -rw-r--r-- | src/components/SvgActivity.js | 24 | ||||
| -rw-r--r-- | src/components/SvgCommand.js | 25 | ||||
| -rw-r--r-- | src/components/SvgFile.js | 25 | ||||
| -rw-r--r-- | src/components/SvgGlobe.js | 25 | ||||
| -rw-r--r-- | src/components/SvgSettings.js | 25 |
11 files changed, 213 insertions, 135 deletions
diff --git a/src/components/ConnectionTable.js b/src/components/ConnectionTable.js new file mode 100644 index 0000000..547925c --- /dev/null +++ b/src/components/ConnectionTable.js @@ -0,0 +1,99 @@ +import React from 'react'; +import { ArrowUp, ArrowDown } from 'react-feather'; +import prettyBytes from '../misc/pretty-bytes'; +import { formatDistance } from 'date-fns'; +import cx from 'classnames'; +import { useTable, useSortBy } from 'react-table'; + +import s from './ConnectionTable.module.css'; + +const columns = [ + { Header: 'Host', accessor: 'host' }, + { Header: 'Download', accessor: 'download' }, + { Header: 'Upload', accessor: 'upload' }, + { Header: 'Network', accessor: 'network' }, + { Header: 'Type', accessor: 'type' }, + { Header: 'Chains', accessor: 'chains' }, + { Header: 'Rule', accessor: 'rule' }, + { Header: 'Time', accessor: 'start' }, + { Header: 'Source IP', accessor: 'sourceIP' }, + { Header: 'Source Port', accessor: 'sourcePort' }, + { Header: 'Designation IP', accessor: 'destinationIP' }, + { Header: 'Designation Port', accessor: 'destinationPort' } +]; + +function renderCell(cell, now) { + switch (cell.column.id) { + case 'start': + return formatDistance(-cell.value, now); + case 'download': + case 'upload': + return prettyBytes(cell.value); + default: + return cell.value; + } +} + +function Table({ data }) { + const now = new Date(); + const { + getTableProps, + // getTableBodyProps, + headerGroups, + rows, + prepareRow + } = useTable( + { + columns, + data + }, + useSortBy + ); + return ( + <div {...getTableProps()}> + <div className={s.thead}> + {headerGroups.map(headerGroup => ( + <div {...headerGroup.getHeaderGroupProps()} className={s.tr}> + {headerGroup.headers.map(column => ( + <div + {...column.getHeaderProps(column.getSortByToggleProps())} + className={s.th} + > + <span>{column.render('Header')}</span> + <span> + {column.isSorted ? ( + column.isSortedDesc ? ( + <ArrowDown size={16} /> + ) : ( + <ArrowUp size={16} /> + ) + ) : null} + </span> + </div> + ))} + + {rows.map((row, i) => { + prepareRow(row); + return row.cells.map((cell, j) => { + return ( + <div + {...cell.getCellProps()} + className={cx( + s.td, + i % 2 === 0 ? s.odd : false, + j === 1 || j === 2 ? s.du : false + )} + > + {renderCell(cell, now)} + </div> + ); + }); + })} + </div> + ))} + </div> + </div> + ); +} + +export default Table; diff --git a/src/components/ConnectionTable.module.css b/src/components/ConnectionTable.module.css new file mode 100644 index 0000000..f145542 --- /dev/null +++ b/src/components/ConnectionTable.module.css @@ -0,0 +1,38 @@ +.thead .tr { + display: grid; + grid-template-columns: repeat(12, max-content); +} + +.th { + padding: 8px 0 14px 10px; + height: 50px; + background: var(--color-background); + position: sticky; + top: 0; + + display: flex; + align-items: center; + justify-content: space-between; +} + +.th span:last-child { + margin-left: 10px; + width: 16px; + height: 16px; +} + +.td { + padding: 8px 10px; + font-size: 0.9em; + + font-family: var(--font-normal); +} + +.td.odd { + background: var(--color-row-odd); +} + +/* download upload td cells */ +.du { + text-align: right; +} diff --git a/src/components/Connections.js b/src/components/Connections.js new file mode 100644 index 0000000..66ce2ef --- /dev/null +++ b/src/components/Connections.js @@ -0,0 +1,55 @@ +import React from 'react'; +import ContentHeader from 'c/ContentHeader'; +import ConnectionTable from 'c/ConnectionTable'; +import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight'; +import { useStoreState } from 'm/store'; +import { getClashAPIConfig } from 'd/app'; +import * as connAPI from '../api/connections'; + +const { useEffect, useState } = React; + +const paddingBottom = 30; + +function formatConnectionDataItem(i) { + const { id, metadata, upload, download, start, chains, rule } = i; + // const started = formatDistance(new Date(start), now); + return { + id, + upload, + download, + start: 0 - new Date(start), + chains: chains.reverse().join(' / '), + rule, + ...metadata + }; +} + +function Conn() { + const [refContainer, containerHeight] = useRemainingViewPortHeight(); + const config = useStoreState(getClashAPIConfig); + const [conns, setConns] = useState([]); + useEffect(() => { + function read({ connections }) { + const x = connections.map(c => formatConnectionDataItem(c)); + setConns(x); + } + return connAPI.fetchData(config, read); + }, [config]); + return ( + <div> + <ContentHeader title="Connections" /> + <div + ref={refContainer} + style={{ padding: 30, paddingBottom, paddingTop: 0 }} + > + <div + style={{ height: containerHeight - paddingBottom, overflow: 'auto' }} + > + <ConnectionTable data={conns} /> + </div> + </div> + </div> + ); +} + +export default Conn; diff --git a/src/components/Root.css b/src/components/Root.css index c08c538..88b3d6c 100644 --- a/src/components/Root.css +++ b/src/components/Root.css @@ -61,6 +61,9 @@ :root { --font-mono: 'Roboto Mono', Menlo, monospace; + --font-normal: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, + Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, + 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; --color-focus-blue: #1a73e8; } @@ -88,6 +91,7 @@ body.dark { --color-btn-bg: #232323; --color-btn-fg: #bebebe; --color-bg-proxy-selected: #303030; + --color-row-odd: #282828; } body.light { @@ -104,4 +108,5 @@ body.light { --color-btn-bg: #f4f4f4; --color-btn-fg: #101010; --color-bg-proxy-selected: #cfcfcf; + --color-row-odd: #f5f5f5; } diff --git a/src/components/Root.js b/src/components/Root.js index e7f0e60..56c0be0 100644 --- a/src/components/Root.js +++ b/src/components/Root.js @@ -8,6 +8,7 @@ import SideBar from 'c/SideBar'; import Home from 'c/Home'; import Logs from 'c/Logs'; import Config from 'c/Config'; +import Connections from 'c/Connections'; import APIDiscovery from 'c/APIDiscovery'; import { store } from '../store/configureStore'; import './Root.css'; @@ -45,6 +46,7 @@ const Root = () => ( <div className={s0.content}> <Suspense fallback={<Loading2 />}> <Route exact path="/" render={() => <Home />} /> + <Route exact path="/connections" component={Connections} /> <Route exact path="/overview" render={() => <Home />} /> <Route exact path="/configs" component={Config} /> <Route exact path="/logs" component={Logs} /> diff --git a/src/components/SideBar.js b/src/components/SideBar.js index c78d6a9..09b517e 100644 --- a/src/components/SideBar.js +++ b/src/components/SideBar.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { Link } from 'react-router-dom'; +import { Command, Activity, Globe, Link2, Settings, File } from 'react-feather'; import { useActions } from 'm/store'; import { switchTheme } from 'd/app'; @@ -11,20 +12,16 @@ import Icon from 'c/Icon'; import moon from 's/moon.svg'; import SvgYacd from './SvgYacd'; -import SvgActivity from './SvgActivity'; -import SvgGlobe from './SvgGlobe'; -import SvgCommand from './SvgCommand'; -import SvgSettings from './SvgSettings'; -import SvgFile from './SvgFile'; import s from 'c/SideBar.module.css'; const icons = { - activity: SvgActivity, - globe: SvgGlobe, - command: SvgCommand, - file: SvgFile, - settings: SvgSettings + activity: Activity, + globe: Globe, + command: Command, + file: File, + settings: Settings, + link: Link2 }; const SideBarRow = React.memo(function SideBarRow({ @@ -38,7 +35,7 @@ const SideBarRow = React.memo(function SideBarRow({ const className = cx(s.row, isActive ? s.rowActive : null); return ( <Link to={to} className={className}> - <Comp isActive={isActive} /> + <Comp /> <div className={s.label}>{labelText}</div> </Link> ); @@ -90,6 +87,12 @@ function SideBar({ location }) { labelText="Rules" /> <SideBarRow + to="/connections" + location={location} + iconId="link" + labelText="Conns" + /> + <SideBarRow to="/configs" location={location} iconId="settings" diff --git a/src/components/SvgActivity.js b/src/components/SvgActivity.js deleted file mode 100644 index 38946b1..0000000 --- a/src/components/SvgActivity.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -function SvgActivity() { - return ( - <svg - width="28" - height="28" - viewBox="0 0 28 28" - xmlns="http://www.w3.org/2000/svg" - > - <g - fill="none" - fillRule="evenodd" - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - > - <path d="M24 14h-4l-3 9-6-18-3 9H4" stroke="currentColor" /> - </g> - </svg> - ); -} - -export default SvgActivity; diff --git a/src/components/SvgCommand.js b/src/components/SvgCommand.js deleted file mode 100644 index 58fb607..0000000 --- a/src/components/SvgCommand.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -export default function SvgCommand() { - return ( - <svg - width="28" - height="28" - viewBox="0 0 28 28" - xmlns="http://www.w3.org/2000/svg" - > - <g - fill="none" - fillRule="evenodd" - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - > - <path - d="M20 5a3 3 0 0 0-3 3v12a3 3 0 1 0 3-3H8a3 3 0 1 0 3 3V8a3 3 0 1 0-3 3h12a3 3 0 0 0 0-6z" - stroke="currentColor" - /> - </g> - </svg> - ); -} diff --git a/src/components/SvgFile.js b/src/components/SvgFile.js deleted file mode 100644 index 503f7b3..0000000 --- a/src/components/SvgFile.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -export default function SvgFile() { - return ( - <svg - width="28" - height="28" - viewBox="0 0 28 28" - xmlns="http://www.w3.org/2000/svg" - > - <g - fill="none" - fillRule="evenodd" - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - > - <g stroke="currentColor"> - <path d="M16 4H8a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V10l-6-6z" /> - <path d="M16 4v6h6M18 15h-8M18 19h-8M12 11h-2" /> - </g> - </g> - </svg> - ); -} diff --git a/src/components/SvgGlobe.js b/src/components/SvgGlobe.js deleted file mode 100644 index d7c66da..0000000 --- a/src/components/SvgGlobe.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -export default function SvgGlobe() { - return ( - <svg - width="28" - height="28" - viewBox="0 0 28 28" - xmlns="http://www.w3.org/2000/svg" - > - <g - fill="none" - fillRule="evenodd" - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - > - <g transform="translate(4 4)" stroke="currentColor"> - <circle cx="10" cy="10" r="10" /> - <path d="M0 10h20M10 0a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /> - </g> - </g> - </svg> - ); -} diff --git a/src/components/SvgSettings.js b/src/components/SvgSettings.js deleted file mode 100644 index 1d7af11..0000000 --- a/src/components/SvgSettings.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -export default function SvgSettings() { - return ( - <svg - width="28" - height="28" - viewBox="0 0 28 28" - xmlns="http://www.w3.org/2000/svg" - > - <g - fill="none" - fillRule="evenodd" - strokeLinecap="round" - strokeLinejoin="round" - strokeWidth="2" - > - <g transform="translate(3 3)" stroke="currentColor"> - <circle cx="11" cy="11" r="3" /> - <path d="M18.4 14a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V20a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 8 18.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H2a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 3.6 8a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H8a1.65 1.65 0 0 0 1-1.51V2a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V8c.26.604.852.997 1.51 1H20a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" /> - </g> - </g> - </svg> - ); -} |
