diff --git a/package.json b/package.json index e582edf..7f1426b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@solid-primitives/keyed": "^1.2.0", "@solid-primitives/media": "^2.2.5", "@solid-primitives/storage": "^2.1.1", + "@solid-primitives/timer": "^1.3.7", "@solid-primitives/websocket": "^1.1.0", "@solidjs/router": "^0.8.3", "@tabler/icons-solidjs": "^2.32.0", @@ -28,6 +29,7 @@ "@tanstack/solid-virtual": "3.0.0-beta.6", "@thisbeyond/solid-dnd": "^0.7.4", "@types/byte-size": "^8.1.0", + "@types/lodash": "^4.14.197", "@types/node": "^20.5.8", "@types/uuid": "^9.0.3", "@typescript-eslint/eslint-plugin": "^6.5.0", @@ -45,6 +47,7 @@ "is-ip": "^5.0.1", "ky": "^1.0.0", "lint-staged": "^14.0.1", + "lodash": "^4.17.21", "prettier": "^3.0.3", "prettier-plugin-organize-imports": "^3.2.3", "prettier-plugin-tailwindcss": "^0.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e40290..9deecc8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: '@solid-primitives/storage': specifier: ^2.1.1 version: 2.1.1(solid-js@1.7.11) + '@solid-primitives/timer': + specifier: ^1.3.7 + version: 1.3.7(solid-js@1.7.11) '@solid-primitives/websocket': specifier: ^1.1.0 version: 1.1.0(solid-js@1.7.11) @@ -56,6 +59,9 @@ dependencies: '@types/byte-size': specifier: ^8.1.0 version: 8.1.0 + '@types/lodash': + specifier: ^4.14.197 + version: 4.14.197 '@types/node': specifier: ^20.5.8 version: 20.5.8 @@ -107,6 +113,9 @@ dependencies: lint-staged: specifier: ^14.0.1 version: 14.0.1 + lodash: + specifier: ^4.17.21 + version: 4.17.21 prettier: specifier: ^3.0.3 version: 3.0.3 @@ -2655,6 +2664,17 @@ packages: solid-js: 1.7.11 dev: false + /@solid-primitives/timer@1.3.7(solid-js@1.7.11): + resolution: + { + integrity: sha512-zS3qA7WVZYsW7+iTdk2M4W1wpMvRhdcMnO23Tcd+nX3YD7eMvjOnO15Oz2mymyfl/OC2ZgM1L5ec66GayEvPwQ==, + } + peerDependencies: + solid-js: ^1.6.12 + dependencies: + solid-js: 1.7.11 + dev: false + /@solid-primitives/utils@6.2.1(solid-js@1.7.11): resolution: { @@ -2851,6 +2871,13 @@ packages: } dev: false + /@types/lodash@4.14.197: + resolution: + { + integrity: sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==, + } + dev: false + /@types/minimist@1.2.2: resolution: { diff --git a/src/constants/index.ts b/src/constants/index.ts index a2545ee..c05cf15 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,3 +1,6 @@ +import { ApexOptions } from 'apexcharts' +import byteSize from 'byte-size' + export const themes = [ 'light', 'dark', @@ -40,6 +43,38 @@ export enum ROUTES { Config = '/config', } +export const CHART_MAX_XAXIS = 10 + +export const DEFAULT_CHART_OPTIONS: ApexOptions = { + title: { align: 'center', style: { color: 'gray' } }, + chart: { + toolbar: { show: false }, + zoom: { enabled: false }, + animations: { easing: 'linear' }, + }, + noData: { text: 'Loading...' }, + legend: { + fontSize: '14px', + labels: { colors: 'gray' }, + itemMargin: { horizontal: 64 }, + }, + dataLabels: { enabled: false }, + grid: { yaxis: { lines: { show: false } } }, + stroke: { curve: 'smooth' }, + tooltip: { enabled: false }, + xaxis: { + range: CHART_MAX_XAXIS, + labels: { show: false }, + axisTicks: { show: false }, + }, + yaxis: { + labels: { + style: { colors: 'gray' }, + formatter: (val) => byteSize(val).toString(), + }, + }, +} + export enum LATENCY_QUALITY_MAP_HTTP { NOT_CONNECTED = -1, MEDIUM = 200, diff --git a/src/pages/Connections.tsx b/src/pages/Connections.tsx index e6d4215..56c2cce 100644 --- a/src/pages/Connections.tsx +++ b/src/pages/Connections.tsx @@ -1,3 +1,4 @@ +import { writeClipboard } from '@solid-primitives/clipboard' import { createEventSignal } from '@solid-primitives/event-listener' import { useI18n } from '@solid-primitives/i18n' import { makePersisted } from '@solid-primitives/storage' @@ -304,7 +305,12 @@ export default () => { {(cell) => ( - + { + e.preventDefault() + writeClipboard(cell.renderValue() as string) + }} + > {flexRender( cell.column.columnDef.cell, cell.getContext(), diff --git a/src/pages/Overview.tsx b/src/pages/Overview.tsx index 5a4eb5f..bc81f85 100644 --- a/src/pages/Overview.tsx +++ b/src/pages/Overview.tsx @@ -1,8 +1,10 @@ import { createEventSignal } from '@solid-primitives/event-listener' import { useI18n } from '@solid-primitives/i18n' +import { makeTimer } from '@solid-primitives/timer' import { createReconnectingWS } from '@solid-primitives/websocket' import type { ApexOptions } from 'apexcharts' import byteSize from 'byte-size' +import { merge } from 'lodash' import { SolidApexCharts } from 'solid-apexcharts' import { JSX, @@ -11,13 +13,11 @@ import { createEffect, createMemo, createSignal, - onCleanup, } from 'solid-js' +import { CHART_MAX_XAXIS, DEFAULT_CHART_OPTIONS } from '~/constants' import { secret, wsEndpointURL } from '~/signals' import type { Connection } from '~/types' -const CHART_MAX_XAXIS = 10 - const TrafficWidget: ParentComponent<{ label: JSX.Element }> = (props) => (
{props.label}
@@ -34,26 +34,24 @@ export default () => { [], ) const [memories, setMemories] = createSignal([]) + // https://github.com/apexcharts/apexcharts.js/blob/main/samples/source/line/realtime.xml // TODO: need a better way - const preventLeakTimer = setInterval( + makeTimer( () => { setTraffics((traffics) => traffics.slice(-CHART_MAX_XAXIS)) setMemories((memo) => memo.slice(-CHART_MAX_XAXIS)) }, - // we shrink the chart data array size down every 10 minutes + // we shrink the chart data array size down every 10 minutes to prevent memory leaks 10 * 60 * 1000, + setInterval, ) - onCleanup(() => clearInterval(preventLeakTimer)) - const trafficWS = createReconnectingWS( `${wsEndpointURL()}/traffic?token=${secret()}`, ) - const trafficWSMessageEvent = createEventSignal<{ - message: WebSocketEventMap['message'] - }>(trafficWS, 'message') + const trafficWSMessageEvent = createEventSignal(trafficWS, 'message') const traffic = () => { const data = trafficWSMessageEvent()?.data @@ -64,50 +62,12 @@ export default () => { createEffect(() => { const t = traffic() - if (t) { - setTraffics((traffics) => [...traffics, t]) - } + if (t) setTraffics((traffics) => [...traffics, t]) }) - const defaultChartOptions: ApexOptions = { - chart: { - toolbar: { show: false }, - zoom: { enabled: false }, - animations: { easing: 'linear' }, - }, - noData: { text: 'Loading...' }, - legend: { - fontSize: '14px', - labels: { colors: 'gray' }, - itemMargin: { horizontal: 64 }, - }, - dataLabels: { enabled: false }, - grid: { yaxis: { lines: { show: false } } }, - stroke: { curve: 'smooth' }, - tooltip: { enabled: false }, - xaxis: { - range: CHART_MAX_XAXIS, - labels: { show: false }, - axisTicks: { show: false }, - }, - yaxis: { - labels: { - style: { colors: 'gray' }, - formatter(val) { - return byteSize(val).toString() - }, - }, - }, - } - - const trafficChartOptions = createMemo(() => ({ - title: { - text: t('traffic'), - align: 'center', - style: { color: 'gray' }, - }, - ...defaultChartOptions, - })) + const trafficChartOptions = createMemo(() => + merge({ title: { text: t('traffic') } }, DEFAULT_CHART_OPTIONS), + ) const trafficChartSeries = createMemo(() => [ { @@ -124,9 +84,7 @@ export default () => { `${wsEndpointURL()}/memory?token=${secret()}`, ) - const memoryWSMessageEvent = createEventSignal<{ - message: WebSocketEventMap['message'] - }>(memoryWS, 'message') + const memoryWSMessageEvent = createEventSignal(memoryWS, 'message') const memory = () => { const data = memoryWSMessageEvent()?.data @@ -137,19 +95,12 @@ export default () => { createEffect(() => { const m = memory() - if (m) { - setMemories((memories) => [...memories, m]) - } + if (m) setMemories((memories) => [...memories, m]) }) - const memoryChartOptions = createMemo(() => ({ - title: { - text: t('memory'), - align: 'center', - style: { color: 'gray' }, - }, - ...defaultChartOptions, - })) + const memoryChartOptions = createMemo(() => + merge({ title: { text: t('memory') } }, DEFAULT_CHART_OPTIONS), + ) const memoryChartSeries = createMemo(() => [{ data: memories() }])