From dbf7e39599425c19c2bf8a486fe8d0d343020775 Mon Sep 17 00:00:00 2001 From: divocat Date: Tue, 14 Oct 2025 20:17:19 +0300 Subject: [PATCH] feat: implement some diagnostics widget --- .../tabs/dashboard/initDashboardController.ts | 8 +- .../tabs/diagnostic/checks/contstants.ts | 32 ++ .../tabs/diagnostic/checks/runDnsCheck.ts | 12 +- .../tabs/diagnostic/checks/runFakeIPCheck.ts | 9 +- .../tabs/diagnostic/checks/runNftCheck.ts | 12 +- .../tabs/diagnostic/checks/runSingBoxCheck.ts | 12 +- .../tabs/diagnostic/diagnostic.store.ts | 86 ++++ .../diagnostic/initDiagnosticController.ts | 112 ++++- .../tabs/diagnostic/renderAvailableActions.ts | 11 + .../tabs/diagnostic/renderDiagnostic.ts | 52 +-- .../diagnostic/renderDiagnosticRunAction.ts | 17 + .../tabs/diagnostic/renderSystemInfo.ts | 19 + fe-app-podkop/src/store.ts | 19 +- fe-app-podkop/src/styles.ts | 56 +++ .../luci-static/resources/view/podkop/main.js | 395 +++++++++++++++--- 15 files changed, 713 insertions(+), 139 deletions(-) create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts create mode 100644 fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts diff --git a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts index fc7b5c9..2054df4 100644 --- a/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts +++ b/fe-app-podkop/src/podkop/tabs/dashboard/initDashboardController.ts @@ -431,7 +431,13 @@ export async function initDashboardController(): Promise { // Remove old listener store.unsubscribe(onStoreUpdate); // Clear store - store.reset(); + store.reset([ + 'bandwidthWidget', + 'trafficTotalWidget', + 'systemInfoWidget', + 'servicesInfoWidget', + 'sectionsWidget', + ]); // Add new listener store.subscribe(onStoreUpdate); diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts new file mode 100644 index 0000000..5c3b2d9 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/contstants.ts @@ -0,0 +1,32 @@ +export enum DIAGNOSTICS_CHECKS { + DNS = 'DNS', + SINGBOX = 'SINGBOX', + NFT = 'NFT', + FAKEIP = 'FAKEIP', +} + +export const DIAGNOSTICS_CHECKS_MAP: Record< + DIAGNOSTICS_CHECKS, + { order: number; title: string; code: DIAGNOSTICS_CHECKS } +> = { + [DIAGNOSTICS_CHECKS.DNS]: { + order: 1, + title: _('DNS checks'), + code: DIAGNOSTICS_CHECKS.DNS, + }, + [DIAGNOSTICS_CHECKS.SINGBOX]: { + order: 2, + title: _('Sing-box checks'), + code: DIAGNOSTICS_CHECKS.SINGBOX, + }, + [DIAGNOSTICS_CHECKS.NFT]: { + order: 3, + title: _('Nftables checks'), + code: DIAGNOSTICS_CHECKS.NFT, + }, + [DIAGNOSTICS_CHECKS.FAKEIP]: { + order: 4, + title: _('FakeIP checks'), + code: DIAGNOSTICS_CHECKS.FAKEIP, + }, +}; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts index 6ac19a2..28c62af 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runDnsCheck.ts @@ -2,13 +2,15 @@ import { getDNSCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { IDiagnosticsChecksItem } from '../../../../store'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runDnsCheck() { - const code = 'dns_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; updateDiagnosticsCheck({ + order, code, - title: _('DNS checks'), + title, description: _('Checking dns, please wait'), state: 'loading', items: [], @@ -18,8 +20,9 @@ export async function runDnsCheck() { if (!dnsChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _('DNS checks'), + title, description: _('Cannot receive DNS checks result'), state: 'error', items: [], @@ -55,8 +58,9 @@ export async function runDnsCheck() { } updateDiagnosticsCheck({ + order, code, - title: _('DNS checks'), + title, description: _('DNS checks passed'), state: getStatus(), items: [ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts index 55e0188..4b3548e 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts @@ -3,13 +3,15 @@ import * as fakeIPMethods from '../../../../fakeip/methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { insertIf } from '../../../../helpers'; import { IDiagnosticsChecksItem } from '../../../../store'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runFakeIPCheck() { - const code = 'fake_ip_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; updateDiagnosticsCheck({ + order, code, - title: _('FakeIP checks'), + title, description: _('Checking FakeIP, please wait'), state: 'loading', items: [], @@ -68,8 +70,9 @@ export async function runFakeIPCheck() { const { state, description } = getMeta(); updateDiagnosticsCheck({ + order, code, - title: _('FakeIP checks'), + title, description, state, items: [ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts index eb3c93f..b7fa0c2 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runNftCheck.ts @@ -1,13 +1,15 @@ import { getNftRulesCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; import { getFakeIpCheck, getIpCheck } from '../../../../fakeip'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runNftCheck() { - const code = 'nft_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; updateDiagnosticsCheck({ + order, code, - title: _('Nftables checks'), + title, description: _('Checking nftables, please wait'), state: 'loading', items: [], @@ -20,8 +22,9 @@ export async function runNftCheck() { if (!nftablesChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _('Nftables checks'), + title, description: _('Cannot receive nftables checks result'), state: 'error', items: [], @@ -67,8 +70,9 @@ export async function runNftCheck() { } updateDiagnosticsCheck({ + order, code, - title: _('Nftables checks'), + title, description: allGood ? _('Nftables checks passed') : _('Nftables checks partially passed'), diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts index c9e3f12..80a8bd1 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts @@ -1,12 +1,14 @@ import { getSingBoxCheck } from '../../../methods'; import { updateDiagnosticsCheck } from '../updateDiagnosticsCheck'; +import { DIAGNOSTICS_CHECKS_MAP } from './contstants'; export async function runSingBoxCheck() { - const code = 'sing_box_check'; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; updateDiagnosticsCheck({ + order, code, - title: _('Sing-box checks'), + title, description: _('Checking sing-box, please wait'), state: 'loading', items: [], @@ -16,8 +18,9 @@ export async function runSingBoxCheck() { if (!singBoxChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _('Sing-box checks'), + title, description: _('Cannot receive Sing-box checks result'), state: 'error', items: [], @@ -59,8 +62,9 @@ export async function runSingBoxCheck() { } updateDiagnosticsCheck({ + order, code, - title: _('Sing-box checks'), + title, description: _('Sing-box checks passed'), state: getStatus(), items: [ diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts new file mode 100644 index 0000000..a39c141 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/diagnostic.store.ts @@ -0,0 +1,86 @@ +import { + DIAGNOSTICS_CHECKS, + DIAGNOSTICS_CHECKS_MAP, +} from './checks/contstants'; +import { StoreType } from '../../../store'; + +export const initialDiagnosticStore: Pick< + StoreType, + 'diagnosticsChecks' | 'diagnosticsRunAction' +> = { + diagnosticsRunAction: { loading: false }, + diagnosticsChecks: [ + { + code: DIAGNOSTICS_CHECKS.DNS, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.SINGBOX, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.NFT, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.FAKEIP, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _('Not running'), + items: [], + state: 'skipped', + }, + ], +}; + +export const loadingDiagnosticsChecksStore: Pick< + StoreType, + 'diagnosticsChecks' +> = { + diagnosticsChecks: [ + { + code: DIAGNOSTICS_CHECKS.DNS, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.SINGBOX, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.NFT, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + { + code: DIAGNOSTICS_CHECKS.FAKEIP, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _('Queued'), + items: [], + state: 'skipped', + }, + ], +}; diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts index 8f1531b..a8f43df 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/initDiagnosticController.ts @@ -5,10 +5,16 @@ import { runDnsCheck } from './checks/runDnsCheck'; import { runSingBoxCheck } from './checks/runSingBoxCheck'; import { runNftCheck } from './checks/runNftCheck'; import { runFakeIPCheck } from './checks/runFakeIPCheck'; +import { renderDiagnosticRunAction } from './renderDiagnosticRunAction'; +import { renderAvailableActions } from './renderAvailableActions'; +import { renderSystemInfo } from './renderSystemInfo'; +import { loadingDiagnosticsChecksStore } from './diagnostic.store'; -async function renderDiagnosticsChecks() { +function renderDiagnosticsChecks() { console.log('renderDiagnosticsChecks'); - const diagnosticsChecks = store.get().diagnosticsChecks; + const diagnosticsChecks = store + .get() + .diagnosticsChecks.sort((a, b) => a.order - b.order); const container = document.getElementById('pdk_diagnostic-page-checks'); const renderedDiagnosticsChecks = diagnosticsChecks.map((check) => @@ -20,6 +26,69 @@ async function renderDiagnosticsChecks() { }); } +function renderDiagnosticRunActionWidget() { + console.log('renderDiagnosticRunActionWidget'); + + const { loading } = store.get().diagnosticsRunAction; + const container = document.getElementById('pdk_diagnostic-page-run-check'); + + const renderedAction = renderDiagnosticRunAction({ + loading, + click: () => runChecks(), + }); + + return preserveScrollForPage(() => { + container!.replaceChildren(renderedAction); + }); +} + +function renderDiagnosticAvailableActionsWidget() { + console.log('renderDiagnosticActionsWidget'); + + const container = document.getElementById('pdk_diagnostic-page-actions'); + + const renderedActions = renderAvailableActions(); + + return preserveScrollForPage(() => { + container!.replaceChildren(renderedActions); + }); +} + +function renderDiagnosticSystemInfoWidget() { + console.log('renderDiagnosticSystemInfoWidget'); + + const container = document.getElementById('pdk_diagnostic-page-system-info'); + + const renderedSystemInfo = renderSystemInfo({ + items: [ + { + key: 'Podkop', + value: '1', + }, + { + key: 'Luci App', + value: '1', + }, + { + key: 'Sing-box', + value: '1', + }, + { + key: 'OS', + value: '1', + }, + { + key: 'Device', + value: '1', + }, + ], + }); + + return preserveScrollForPage(() => { + container!.replaceChildren(renderedSystemInfo); + }); +} + async function onStoreUpdate( next: StoreType, prev: StoreType, @@ -28,16 +97,31 @@ async function onStoreUpdate( if (diff.diagnosticsChecks) { renderDiagnosticsChecks(); } + + if (diff.diagnosticsRunAction) { + renderDiagnosticRunActionWidget(); + } } async function runChecks() { - await runDnsCheck(); + try { + store.set({ + diagnosticsRunAction: { loading: true }, + diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks, + }); - await runSingBoxCheck(); + await runDnsCheck(); - await runNftCheck(); + await runSingBoxCheck(); - await runFakeIPCheck(); + await runNftCheck(); + + await runFakeIPCheck(); + } catch (e) { + console.log('runChecks - e', e); + } finally { + store.set({ diagnosticsRunAction: { loading: false } }); + } } export async function initDiagnosticController(): Promise { @@ -46,13 +130,19 @@ export async function initDiagnosticController(): Promise { // Remove old listener store.unsubscribe(onStoreUpdate); - // Clear store - store.reset(); - // Add new listener store.subscribe(onStoreUpdate); - // TMP run checks on mount - runChecks(); + // Initial checks render + renderDiagnosticsChecks(); + + // Initial run checks action render + renderDiagnosticRunActionWidget(); + + // Initial available actions render + renderDiagnosticAvailableActionsWidget(); + + // Initial system info render + renderDiagnosticSystemInfoWidget(); }); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts new file mode 100644 index 0000000..a8e2ed0 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderAvailableActions.ts @@ -0,0 +1,11 @@ +export function renderAvailableActions() { + return E('div', { class: 'pdk_diagnostic-page__right-bar__actions' }, [ + E('b', {}, 'Available actions'), + E('button', { class: 'btn' }, 'Restart podkop'), + E('button', { class: 'btn' }, 'Stop podkop'), + E('button', { class: 'btn' }, 'Disable podkop'), + E('button', { class: 'btn' }, 'Get global check'), + E('button', { class: 'btn' }, 'View logs'), + E('button', { class: 'btn' }, 'Show sing-box config'), + ]); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts index ef5fdac..b4a5766 100644 --- a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnostic.ts @@ -1,45 +1,15 @@ export function renderDiagnostic() { - return E( - 'div', - { id: 'diagnostic-status', class: 'pdk_diagnostic-page' }, - E( - 'div', - { + return E('div', { id: 'diagnostic-status', class: 'pdk_diagnostic-page' }, [ + E('div', { class: 'pdk_diagnostic-page__left-bar' }, [ + E('div', { id: 'pdk_diagnostic-page-run-check' }), + E('div', { class: 'pdk_diagnostic-page__checks', id: 'pdk_diagnostic-page-checks', - }, - // [ - // renderCheckSection({ - // state: 'loading', - // title: _('DNS Checks'), - // description: _('Checking, please wait'), - // items: [], - // }), - // renderCheckSection({ - // state: 'warning', - // title: _('DNS Checks'), - // description: _('Some checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'error', - // title: _('DNS Checks'), - // description: _('Checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'success', - // title: _('DNS Checks'), - // description: _('Checks was passed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'skipped', - // title: _('DNS Checks'), - // description: _('Checks was skipped'), - // items: [], - // }), - // ], - ), - ); + }), + ]), + E('div', { class: 'pdk_diagnostic-page__right-bar' }, [ + E('div', { id: 'pdk_diagnostic-page-actions' }), + E('div', { id: 'pdk_diagnostic-page-system-info' }), + ]), + ]); } diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts new file mode 100644 index 0000000..f26af81 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts @@ -0,0 +1,17 @@ +interface IRenderDiagnosticRunActionProps { + loading: boolean; + click: () => void; +} + +export function renderDiagnosticRunAction({ + loading, + click, +}: IRenderDiagnosticRunActionProps) { + return E('div', { class: 'pdk_diagnostic-page__run_check_wrapper' }, [ + E( + 'button', + { class: 'btn', disabled: loading ? true : undefined, click }, + loading ? _('Running... please wait') : _('Run Diagnostic'), + ), + ]); +} diff --git a/fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts b/fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts new file mode 100644 index 0000000..b1b3bc5 --- /dev/null +++ b/fe-app-podkop/src/podkop/tabs/diagnostic/renderSystemInfo.ts @@ -0,0 +1,19 @@ +interface IRenderSystemInfoProps { + items: Array<{ key: string; value: string }>; +} + +export function renderSystemInfo({ items }: IRenderSystemInfoProps) { + return E('div', { class: 'pdk_diagnostic-page__right-bar__system-info' }, [ + E( + 'b', + { class: 'pdk_diagnostic-page__right-bar__system-info__title' }, + 'System information', + ), + ...items.map((item) => + E('div', { class: 'pdk_diagnostic-page__right-bar__system-info__row' }, [ + E('b', {}, item.key), + E('div', {}, item.value), + ]), + ), + ]); +} diff --git a/fe-app-podkop/src/store.ts b/fe-app-podkop/src/store.ts index 4b734cd..3bd35a9 100644 --- a/fe-app-podkop/src/store.ts +++ b/fe-app-podkop/src/store.ts @@ -1,4 +1,5 @@ import { Podkop } from './podkop/types'; +import { initialDiagnosticStore } from './podkop/tabs/diagnostic/diagnostic.store'; function jsonStableStringify(obj: T): string { return JSON.stringify(obj, (_, value) => { @@ -61,9 +62,17 @@ class Store> { this.listeners.forEach((cb) => cb(this.value, prev, diff)); } - reset(): void { + reset(keys?: K[]): void { const prev = this.value; - const next = structuredClone(this.initial); + const next = structuredClone(this.value); + + if (keys && keys.length > 0) { + keys.forEach((key) => { + next[key] = structuredClone(this.initial[key]); + }); + } else { + Object.assign(next, structuredClone(this.initial)); + } if (jsonEqual(prev, next)) return; @@ -119,6 +128,7 @@ export interface IDiagnosticsChecksItem { } export interface IDiagnosticsChecksStoreItem { + order: number; code: string; title: string; description: string; @@ -157,6 +167,9 @@ export interface StoreType { data: Podkop.OutboundGroup[]; latencyFetching: boolean; }; + diagnosticsRunAction: { + loading: boolean; + }; diagnosticsChecks: Array; } @@ -191,7 +204,7 @@ const initialStore: StoreType = { latencyFetching: false, data: [], }, - diagnosticsChecks: [], + ...initialDiagnosticStore, }; export const store = new Store(initialStore); diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 08d7892..4b240bf 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -213,6 +213,62 @@ export const GlobalStyles = ` width: 100%; } +.pdk_diagnostic-page { + display: grid; + grid-template-columns: 2fr 1fr; + grid-column-gap: 10px; + align-items: start; +} + +.pdk_diagnostic-page__right-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__actions { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; + +} + +.pdk_diagnostic-page__right-bar__system-info { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__system-info__title { + +} + +.pdk_diagnostic-page__right-bar__system-info__row { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 5px; +} + +.pdk_diagnostic-page__left-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__run_check_wrapper {} + +.pdk_diagnostic-page__run_check_wrapper button { + width: 100%; +} + .pdk_diagnostic-page__checks { display: grid; grid-template-columns: 1fr; diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index fe29c15..d1cd702 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -436,6 +436,62 @@ var GlobalStyles = ` width: 100%; } +.pdk_diagnostic-page { + display: grid; + grid-template-columns: 2fr 1fr; + grid-column-gap: 10px; + align-items: start; +} + +.pdk_diagnostic-page__right-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__actions { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; + +} + +.pdk_diagnostic-page__right-bar__system-info { + border: 2px var(--background-color-low, lightgray) solid; + border-radius: 4px; + padding: 10px; + + display: grid; + grid-template-columns: auto; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__right-bar__system-info__title { + +} + +.pdk_diagnostic-page__right-bar__system-info__row { + display: grid; + grid-template-columns: auto 1fr; + grid-column-gap: 5px; +} + +.pdk_diagnostic-page__left-bar { + display: grid; + grid-template-columns: 1fr; + grid-row-gap: 10px; +} + +.pdk_diagnostic-page__run_check_wrapper {} + +.pdk_diagnostic-page__run_check_wrapper button { + width: 100%; +} + .pdk_diagnostic-page__checks { display: grid; grid-template-columns: 1fr; @@ -1354,6 +1410,105 @@ var TabService = class _TabService { }; var TabServiceInstance = TabService.getInstance(); +// src/podkop/tabs/diagnostic/checks/contstants.ts +var DIAGNOSTICS_CHECKS_MAP = { + ["DNS" /* DNS */]: { + order: 1, + title: _("DNS checks"), + code: "DNS" /* DNS */ + }, + ["SINGBOX" /* SINGBOX */]: { + order: 2, + title: _("Sing-box checks"), + code: "SINGBOX" /* SINGBOX */ + }, + ["NFT" /* NFT */]: { + order: 3, + title: _("Nftables checks"), + code: "NFT" /* NFT */ + }, + ["FAKEIP" /* FAKEIP */]: { + order: 4, + title: _("FakeIP checks"), + code: "FAKEIP" /* FAKEIP */ + } +}; + +// src/podkop/tabs/diagnostic/diagnostic.store.ts +var initialDiagnosticStore = { + diagnosticsRunAction: { loading: false }, + diagnosticsChecks: [ + { + code: "DNS" /* DNS */, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _("Not running"), + items: [], + state: "skipped" + }, + { + code: "SINGBOX" /* SINGBOX */, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _("Not running"), + items: [], + state: "skipped" + }, + { + code: "NFT" /* NFT */, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _("Not running"), + items: [], + state: "skipped" + }, + { + code: "FAKEIP" /* FAKEIP */, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _("Not running"), + items: [], + state: "skipped" + } + ] +}; +var loadingDiagnosticsChecksStore = { + diagnosticsChecks: [ + { + code: "DNS" /* DNS */, + title: DIAGNOSTICS_CHECKS_MAP.DNS.title, + order: DIAGNOSTICS_CHECKS_MAP.DNS.order, + description: _("Queued"), + items: [], + state: "skipped" + }, + { + code: "SINGBOX" /* SINGBOX */, + title: DIAGNOSTICS_CHECKS_MAP.SINGBOX.title, + order: DIAGNOSTICS_CHECKS_MAP.SINGBOX.order, + description: _("Queued"), + items: [], + state: "skipped" + }, + { + code: "NFT" /* NFT */, + title: DIAGNOSTICS_CHECKS_MAP.NFT.title, + order: DIAGNOSTICS_CHECKS_MAP.NFT.order, + description: _("Queued"), + items: [], + state: "skipped" + }, + { + code: "FAKEIP" /* FAKEIP */, + title: DIAGNOSTICS_CHECKS_MAP.FAKEIP.title, + order: DIAGNOSTICS_CHECKS_MAP.FAKEIP.order, + description: _("Queued"), + items: [], + state: "skipped" + } + ] +}; + // src/store.ts function jsonStableStringify(obj) { return JSON.stringify(obj, (_2, value) => { @@ -1399,9 +1554,16 @@ var Store = class { } this.listeners.forEach((cb) => cb(this.value, prev, diff)); } - reset() { + reset(keys) { const prev = this.value; - const next = structuredClone(this.initial); + const next = structuredClone(this.value); + if (keys && keys.length > 0) { + keys.forEach((key) => { + next[key] = structuredClone(this.initial[key]); + }); + } else { + Object.assign(next, structuredClone(this.initial)); + } if (jsonEqual(prev, next)) return; this.value = next; this.lastHash = jsonStableStringify(next); @@ -1468,7 +1630,7 @@ var initialStore = { latencyFetching: false, data: [] }, - diagnosticsChecks: [] + ...initialDiagnosticStore }; var store = new Store(initialStore); @@ -2163,7 +2325,13 @@ async function onStoreUpdate(next, prev, diff) { async function initDashboardController() { onMount("dashboard-status").then(() => { store.unsubscribe(onStoreUpdate); - store.reset(); + store.reset([ + "bandwidthWidget", + "trafficTotalWidget", + "systemInfoWidget", + "servicesInfoWidget", + "sectionsWidget" + ]); store.subscribe(onStoreUpdate); fetchDashboardSections(); fetchServicesInfo(); @@ -2173,49 +2341,19 @@ async function initDashboardController() { // src/podkop/tabs/diagnostic/renderDiagnostic.ts function renderDiagnostic() { - return E( - "div", - { id: "diagnostic-status", class: "pdk_diagnostic-page" }, - E( - "div", - { + return E("div", { id: "diagnostic-status", class: "pdk_diagnostic-page" }, [ + E("div", { class: "pdk_diagnostic-page__left-bar" }, [ + E("div", { id: "pdk_diagnostic-page-run-check" }), + E("div", { class: "pdk_diagnostic-page__checks", id: "pdk_diagnostic-page-checks" - } - // [ - // renderCheckSection({ - // state: 'loading', - // title: _('DNS Checks'), - // description: _('Checking, please wait'), - // items: [], - // }), - // renderCheckSection({ - // state: 'warning', - // title: _('DNS Checks'), - // description: _('Some checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'error', - // title: _('DNS Checks'), - // description: _('Checks was failed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'success', - // title: _('DNS Checks'), - // description: _('Checks was passed'), - // items: [], - // }), - // renderCheckSection({ - // state: 'skipped', - // title: _('DNS Checks'), - // description: _('Checks was skipped'), - // items: [], - // }), - // ], - ) - ); + }) + ]), + E("div", { class: "pdk_diagnostic-page__right-bar" }, [ + E("div", { id: "pdk_diagnostic-page-actions" }), + E("div", { id: "pdk_diagnostic-page-system-info" }) + ]) + ]); } // src/icons/renderLoaderCircleIcon24.ts @@ -2625,10 +2763,11 @@ function updateDiagnosticsCheck(check, minified) { // src/podkop/tabs/diagnostic/checks/runDnsCheck.ts async function runDnsCheck() { - const code = "dns_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.DNS; updateDiagnosticsCheck({ + order, code, - title: _("DNS checks"), + title, description: _("Checking dns, please wait"), state: "loading", items: [] @@ -2636,8 +2775,9 @@ async function runDnsCheck() { const dnsChecks = await getDNSCheck(); if (!dnsChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _("DNS checks"), + title, description: _("Cannot receive DNS checks result"), state: "error", items: [] @@ -2658,8 +2798,9 @@ async function runDnsCheck() { return "error"; } updateDiagnosticsCheck({ + order, code, - title: _("DNS checks"), + title, description: _("DNS checks passed"), state: getStatus(), items: [ @@ -2692,10 +2833,11 @@ async function runDnsCheck() { // src/podkop/tabs/diagnostic/checks/runSingBoxCheck.ts async function runSingBoxCheck() { - const code = "sing_box_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.SINGBOX; updateDiagnosticsCheck({ + order, code, - title: _("Sing-box checks"), + title, description: _("Checking sing-box, please wait"), state: "loading", items: [] @@ -2703,8 +2845,9 @@ async function runSingBoxCheck() { const singBoxChecks = await getSingBoxCheck(); if (!singBoxChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _("Sing-box checks"), + title, description: _("Cannot receive Sing-box checks result"), state: "error", items: [] @@ -2725,8 +2868,9 @@ async function runSingBoxCheck() { return "error"; } updateDiagnosticsCheck({ + order, code, - title: _("Sing-box checks"), + title, description: _("Sing-box checks passed"), state: getStatus(), items: [ @@ -2797,10 +2941,11 @@ async function getFakeIpCheck() { // src/podkop/tabs/diagnostic/checks/runNftCheck.ts async function runNftCheck() { - const code = "nft_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.NFT; updateDiagnosticsCheck({ + order, code, - title: _("Nftables checks"), + title, description: _("Checking nftables, please wait"), state: "loading", items: [] @@ -2810,8 +2955,9 @@ async function runNftCheck() { const nftablesChecks = await getNftRulesCheck(); if (!nftablesChecks.success) { updateDiagnosticsCheck({ + order, code, - title: _("Nftables checks"), + title, description: _("Cannot receive nftables checks result"), state: "error", items: [] @@ -2832,8 +2978,9 @@ async function runNftCheck() { return "error"; } updateDiagnosticsCheck({ + order, code, - title: _("Nftables checks"), + title, description: allGood ? _("Nftables checks passed") : _("Nftables checks partially passed"), state: getStatus(), items: [ @@ -2886,10 +3033,11 @@ async function runNftCheck() { // src/podkop/tabs/diagnostic/checks/runFakeIPCheck.ts async function runFakeIPCheck() { - const code = "fake_ip_check"; + const { order, title, code } = DIAGNOSTICS_CHECKS_MAP.FAKEIP; updateDiagnosticsCheck({ + order, code, - title: _("FakeIP checks"), + title, description: _("Checking FakeIP, please wait"), state: "loading", items: [] @@ -2930,8 +3078,9 @@ async function runFakeIPCheck() { } const { state, description } = getMeta(); updateDiagnosticsCheck({ + order, code, - title: _("FakeIP checks"), + title, description, state, items: [ @@ -2956,10 +3105,54 @@ async function runFakeIPCheck() { }); } +// src/podkop/tabs/diagnostic/renderDiagnosticRunAction.ts +function renderDiagnosticRunAction({ + loading, + click +}) { + return E("div", { class: "pdk_diagnostic-page__run_check_wrapper" }, [ + E( + "button", + { class: "btn", disabled: loading ? true : void 0, click }, + loading ? _("Running... please wait") : _("Run Diagnostic") + ) + ]); +} + +// src/podkop/tabs/diagnostic/renderAvailableActions.ts +function renderAvailableActions() { + return E("div", { class: "pdk_diagnostic-page__right-bar__actions" }, [ + E("b", {}, "Available actions"), + E("button", { class: "btn" }, "Restart podkop"), + E("button", { class: "btn" }, "Stop podkop"), + E("button", { class: "btn" }, "Disable podkop"), + E("button", { class: "btn" }, "Get global check"), + E("button", { class: "btn" }, "View logs"), + E("button", { class: "btn" }, "Show sing-box config") + ]); +} + +// src/podkop/tabs/diagnostic/renderSystemInfo.ts +function renderSystemInfo({ items }) { + return E("div", { class: "pdk_diagnostic-page__right-bar__system-info" }, [ + E( + "b", + { class: "pdk_diagnostic-page__right-bar__system-info__title" }, + "System information" + ), + ...items.map( + (item) => E("div", { class: "pdk_diagnostic-page__right-bar__system-info__row" }, [ + E("b", {}, item.key), + E("div", {}, item.value) + ]) + ) + ]); +} + // src/podkop/tabs/diagnostic/initDiagnosticController.ts -async function renderDiagnosticsChecks() { +function renderDiagnosticsChecks() { console.log("renderDiagnosticsChecks"); - const diagnosticsChecks = store.get().diagnosticsChecks; + const diagnosticsChecks = store.get().diagnosticsChecks.sort((a, b) => a.order - b.order); const container = document.getElementById("pdk_diagnostic-page-checks"); const renderedDiagnosticsChecks = diagnosticsChecks.map( (check) => renderCheckSection(check) @@ -2968,24 +3161,90 @@ async function renderDiagnosticsChecks() { container.replaceChildren(...renderedDiagnosticsChecks); }); } +function renderDiagnosticRunActionWidget() { + console.log("renderDiagnosticRunActionWidget"); + const { loading } = store.get().diagnosticsRunAction; + const container = document.getElementById("pdk_diagnostic-page-run-check"); + const renderedAction = renderDiagnosticRunAction({ + loading, + click: () => runChecks() + }); + return preserveScrollForPage(() => { + container.replaceChildren(renderedAction); + }); +} +function renderDiagnosticAvailableActionsWidget() { + console.log("renderDiagnosticActionsWidget"); + const container = document.getElementById("pdk_diagnostic-page-actions"); + const renderedActions = renderAvailableActions(); + return preserveScrollForPage(() => { + container.replaceChildren(renderedActions); + }); +} +function renderDiagnosticSystemInfoWidget() { + console.log("renderDiagnosticSystemInfoWidget"); + const container = document.getElementById("pdk_diagnostic-page-system-info"); + const renderedSystemInfo = renderSystemInfo({ + items: [ + { + key: "Podkop", + value: "1" + }, + { + key: "Luci App", + value: "1" + }, + { + key: "Sing-box", + value: "1" + }, + { + key: "OS", + value: "1" + }, + { + key: "Device", + value: "1" + } + ] + }); + return preserveScrollForPage(() => { + container.replaceChildren(renderedSystemInfo); + }); +} async function onStoreUpdate2(next, prev, diff) { if (diff.diagnosticsChecks) { renderDiagnosticsChecks(); } + if (diff.diagnosticsRunAction) { + renderDiagnosticRunActionWidget(); + } } async function runChecks() { - await runDnsCheck(); - await runSingBoxCheck(); - await runNftCheck(); - await runFakeIPCheck(); + try { + store.set({ + diagnosticsRunAction: { loading: true }, + diagnosticsChecks: loadingDiagnosticsChecksStore.diagnosticsChecks + }); + await runDnsCheck(); + await runSingBoxCheck(); + await runNftCheck(); + await runFakeIPCheck(); + } catch (e) { + console.log("runChecks - e", e); + } finally { + store.set({ diagnosticsRunAction: { loading: false } }); + } } async function initDiagnosticController() { onMount("diagnostic-status").then(() => { console.log("diagnostic controller initialized."); store.unsubscribe(onStoreUpdate2); - store.reset(); store.subscribe(onStoreUpdate2); - runChecks(); + renderDiagnosticsChecks(); + renderDiagnosticRunActionWidget(); + renderDiagnosticAvailableActionsWidget(); + renderDiagnosticSystemInfoWidget(); }); } return baseclass.extend({