diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index fb60a7e8d..7e7fd805e 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -825,6 +825,7 @@ importers: '@rjsf/validator-ajv8': ~5.1.0 '@rollup/browser': ~3.26.2 '@supabase/supabase-js': ~2.31.0 + '@tanstack/react-table': ~8.12.0 '@types/downloadjs': ~1.4.3 '@types/json-schema': ~7.0.11 '@types/path-browserify': ^1.0.0 @@ -879,6 +880,7 @@ importers: '@rjsf/validator-ajv8': 5.1.0_@rjsf+utils@5.1.0 '@rollup/browser': 3.26.3 '@supabase/supabase-js': 2.31.0 + '@tanstack/react-table': 8.12.0_react-dom@18.2.0+react@18.2.0 '@yuants/agent': link:../../libraries/agent '@yuants/data-model': link:../../libraries/data-model '@yuants/extension': link:../../libraries/extension @@ -5931,6 +5933,23 @@ packages: defer-to-connect: 2.0.1 dev: false + /@tanstack/react-table/8.12.0_react-dom@18.2.0+react@18.2.0: + resolution: {integrity: sha512-LlEQ1Gpz4bfpiET+qmle4BhKDgKN3Y/sssc+O/wLqX8HRtjV+nhusYbllZlutZfMR8oeef83whKTj/VhaV8EeA==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16' + react-dom: '>=16' + dependencies: + '@tanstack/table-core': 8.12.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@tanstack/table-core/8.12.0: + resolution: {integrity: sha512-cq/ylWVrOwixmwNXQjgZaQw1Izf7+nPxjczum7paAnMtwPg1S2qRAJU+Jb8rEBUWm69voC/zcChmePlk2hc6ug==} + engines: {node: '>=12'} + dev: false + /@tootallnate/once/1.1.2: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} diff --git a/ui/web/package.json b/ui/web/package.json index b5628a543..1a6f83296 100644 --- a/ui/web/package.json +++ b/ui/web/package.json @@ -55,7 +55,8 @@ "unzipit": "~1.4.3", "yaml": "~2.2.1", "react-markdown": "~9.0.1", - "rehype-raw": "~7.0.0" + "rehype-raw": "~7.0.0", + "@tanstack/react-table": "~8.12.0" }, "devDependencies": { "@originjs/vite-plugin-commonjs": "~1.0.3", diff --git a/ui/web/src/modules/Terminals/TerminalList.tsx b/ui/web/src/modules/Terminals/TerminalList.tsx index d0a3cbf86..739bdeb7f 100644 --- a/ui/web/src/modules/Terminals/TerminalList.tsx +++ b/ui/web/src/modules/Terminals/TerminalList.tsx @@ -1,8 +1,13 @@ import { List, Space, Typography } from '@douyinfe/semi-ui'; +import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; +import { formatTime } from '@yuants/data-model'; +import { ITerminalInfo } from '@yuants/protocol'; +import { formatDuration, intervalToDuration } from 'date-fns'; import { useObservableState } from 'observable-hooks'; import { of, shareReplay, switchMap } from 'rxjs'; +import { Button } from '../Interactive'; import { registerPage } from '../Pages'; -import { TerminalListItem } from './TerminalListItem'; +import { TerminalListItem, terminate } from './TerminalListItem'; import { terminal$ } from './create-connection'; export const terminalList$ = terminal$.pipe( @@ -10,9 +15,129 @@ export const terminalList$ = terminal$.pipe( shareReplay(1), ); +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor('terminal_id', { + header: () => '终端 ID', + cell: (info) => {info.getValue()}, + }), + columnHelper.accessor('name', { + header: () => '终端名', + }), + columnHelper.accessor('updated_at', { + header: () => '最近更新时间', + cell: (info) => formatTime(info.getValue() || NaN), + }), + columnHelper.accessor('start_timestamp_in_ms', { + header: () => '启动时间', + cell: (info) => formatTime(info.getValue() || NaN), + }), + columnHelper.accessor( + (x) => formatDuration(intervalToDuration({ start: x.start_timestamp_in_ms!, end: Date.now() })), + { + id: 'start_time', + header: () => '启动时长', + }, + ), + columnHelper.accessor((x) => Object.values(x.serviceInfo || {}).length, { + id: 'serviceLength', + header: () => '提供服务数', + }), + columnHelper.accessor((x) => x.channelIdSchemas?.length, { + id: 'channelIdSchemaLength', + header: () => '提供频道数', + }), + columnHelper.accessor((x) => Object.keys(x.subscriptions || {}).length, { + id: 'subscribeTerminalLength', + header: () => `订阅终端数`, + }), + columnHelper.accessor( + (x) => Object.values(x.subscriptions || {}).reduce((acc, cur) => acc + cur.length, 0), + { + id: 'subscribeChannelLength', + header: () => '订阅频道数', + }, + ), + columnHelper.accessor((x) => 0, { + id: 'actions', + header: () => '操作', + cell: (x) => { + const term = x.row.original; + + return ( + + ); + }, + }), +]; + +const TerminalListDesktop = (props: { terminals: ITerminalInfo[] }) => { + const table = useReactTable({ columns, data: props.terminals, getCoreRowModel: getCoreRowModel() }); + + return ( + +
{table.getRowModel().rows.length} Items
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + + + {table.getFooterGroups().map((footerGroup) => ( + + {footerGroup.headers.map((header) => ( + + ))} + + ))} + +
+ {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+ {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.footer, header.getContext())} +
+
+ ); +}; + registerPage('TerminalList', () => { const terminals = useObservableState(terminalList$, []); + if (window.outerWidth >= 1080) { + return ; + } + return (