diff --git a/fe-app-podkop/src/clash/methods/index.ts b/fe-app-podkop/src/clash/methods/index.ts index 77f254b..1feccdb 100644 --- a/fe-app-podkop/src/clash/methods/index.ts +++ b/fe-app-podkop/src/clash/methods/index.ts @@ -4,3 +4,4 @@ export * from './getGroupDelay'; export * from './getProxies'; export * from './getVersion'; export * from './triggerProxySelector'; +export * from './triggerLatencyTest'; diff --git a/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts b/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts new file mode 100644 index 0000000..94bf335 --- /dev/null +++ b/fe-app-podkop/src/clash/methods/triggerLatencyTest.ts @@ -0,0 +1,35 @@ +import { IBaseApiResponse } from '../types'; +import { createBaseApiRequest } from './createBaseApiRequest'; +import { getClashApiUrl } from '../../helpers'; + +export async function triggerLatencyGroupTest( + tag: string, + timeout: number = 2000, + url: string = 'https://www.gstatic.com/generate_204', +): Promise> { + return createBaseApiRequest(() => + fetch( + `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, + { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }, + ), + ); +} + +export async function triggerLatencyProxyTest( + tag: string, + timeout: number = 2000, + url: string = 'https://www.gstatic.com/generate_204', +): Promise> { + return createBaseApiRequest(() => + fetch( + `${getClashApiUrl()}/proxies/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, + { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }, + ), + ); +} diff --git a/fe-app-podkop/src/dashboard/initDashboardController.ts b/fe-app-podkop/src/dashboard/initDashboardController.ts index a85a130..aa457df 100644 --- a/fe-app-podkop/src/dashboard/initDashboardController.ts +++ b/fe-app-podkop/src/dashboard/initDashboardController.ts @@ -9,6 +9,11 @@ import { store } from '../store'; import { socket } from '../socket'; import { renderDashboardWidget } from './renderer/renderWidget'; import { prettyBytes } from '../helpers/prettyBytes'; +import { + triggerLatencyGroupTest, + triggerLatencyProxyTest, + triggerProxySelector, +} from '../clash'; // Fetchers @@ -63,11 +68,40 @@ async function connectToClashSockets() { // Renderer +async function handleChooseOutbound(selector: string, tag: string) { + await triggerProxySelector(selector, tag); + await fetchDashboardSections(); +} + +async function handleTestGroupLatency(tag: string) { + await triggerLatencyGroupTest(tag); + await fetchDashboardSections(); +} + +async function handleTestProxyLatency(tag: string) { + await triggerLatencyProxyTest(tag); + await fetchDashboardSections(); +} + async function renderDashboardSections() { const sections = store.get().sections; console.log('render dashboard sections group'); const container = document.getElementById('dashboard-sections-grid'); - const renderedOutboundGroups = sections.map(renderOutboundGroup); + const renderedOutboundGroups = sections.map((section) => + renderOutboundGroup({ + section, + onTestLatency: (tag) => { + if (section.withTagSelect) { + return handleTestGroupLatency(tag); + } + + return handleTestProxyLatency(tag); + }, + onChooseOutbound: (selector, tag) => { + handleChooseOutbound(selector, tag); + }, + }), + ); container!.replaceChildren(...renderedOutboundGroups); } diff --git a/fe-app-podkop/src/dashboard/renderer/renderOutboundGroup.ts b/fe-app-podkop/src/dashboard/renderer/renderOutboundGroup.ts index 0b8ad3f..b982dd9 100644 --- a/fe-app-podkop/src/dashboard/renderer/renderOutboundGroup.ts +++ b/fe-app-podkop/src/dashboard/renderer/renderOutboundGroup.ts @@ -1,12 +1,28 @@ import { Podkop } from '../../podkop/types'; +interface IRenderOutboundGroupProps { + section: Podkop.OutboundGroup; + onTestLatency: (tag: string) => void; + onChooseOutbound: (selector: string, tag: string) => void; +} + export function renderOutboundGroup({ - outbounds, - displayName, -}: Podkop.OutboundGroup) { + section, + onTestLatency, + onChooseOutbound, +}: IRenderOutboundGroupProps) { + function testLatency() { + if (section.withTagSelect) { + return onTestLatency(section.code); + } + + if (section.outbounds.length) { + return onTestLatency(section.outbounds[0].code); + } + } + function renderOutbound(outbound: Podkop.Outbound) { function getLatencyClass() { - if (!outbound.latency) { return 'pdk_dashboard-page__outbound-grid__item__latency--empty'; } @@ -25,7 +41,10 @@ export function renderOutboundGroup({ return E( 'div', { - class: `pdk_dashboard-page__outbound-grid__item ${outbound.selected ? 'pdk_dashboard-page__outbound-grid__item--active' : ''}`, + class: `pdk_dashboard-page__outbound-grid__item ${outbound.selected ? 'pdk_dashboard-page__outbound-grid__item--active' : ''} ${section.withTagSelect ? 'pdk_dashboard-page__outbound-grid__item--selectable' : ''}`, + click: () => + section.withTagSelect && + onChooseOutbound(section.code, outbound.code), }, [ E('b', {}, outbound.displayName), @@ -53,14 +72,14 @@ export function renderOutboundGroup({ { class: 'pdk_dashboard-page__outbound-section__title-section__title', }, - displayName, + section.displayName, ), - E('button', { class: 'btn' }, 'Test latency'), + E('button', { class: 'btn', click: () => testLatency() }, 'Test latency'), ]), E( 'div', { class: 'pdk_dashboard-page__outbound-grid' }, - outbounds.map((outbound) => renderOutbound(outbound)), + section.outbounds.map((outbound) => renderOutbound(outbound)), ), ]); } diff --git a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts index aaa0c52..5f57512 100644 --- a/fe-app-podkop/src/podkop/methods/getDashboardSections.ts +++ b/fe-app-podkop/src/podkop/methods/getDashboardSections.ts @@ -28,7 +28,8 @@ export async function getDashboardSections(): Promise { ); return { - code: section['.name'], + withTagSelect: false, + code: outbound?.code || section['.name'], displayName: section['.name'], outbounds: [ { @@ -51,7 +52,8 @@ export async function getDashboardSections(): Promise { ); return { - code: section['.name'], + withTagSelect: false, + code: outbound?.code || section['.name'], displayName: section['.name'], outbounds: [ { @@ -90,7 +92,8 @@ export async function getDashboardSections(): Promise { })); return { - code: section['.name'], + withTagSelect: true, + code: selector?.code || section['.name'], displayName: section['.name'], outbounds: [ { @@ -112,7 +115,8 @@ export async function getDashboardSections(): Promise { ); return { - code: section['.name'], + withTagSelect: false, + code: outbound?.code || section['.name'], displayName: section['.name'], outbounds: [ { @@ -127,6 +131,7 @@ export async function getDashboardSections(): Promise { } return { + withTagSelect: false, code: section['.name'], displayName: section['.name'], outbounds: [], diff --git a/fe-app-podkop/src/podkop/types.ts b/fe-app-podkop/src/podkop/types.ts index c715b61..531f648 100644 --- a/fe-app-podkop/src/podkop/types.ts +++ b/fe-app-podkop/src/podkop/types.ts @@ -9,6 +9,7 @@ export namespace Podkop { } export interface OutboundGroup { + withTagSelect: boolean; code: string; displayName: string; outbounds: Outbound[]; diff --git a/fe-app-podkop/src/styles.ts b/fe-app-podkop/src/styles.ts index 836baab..5337c10 100644 --- a/fe-app-podkop/src/styles.ts +++ b/fe-app-podkop/src/styles.ts @@ -122,13 +122,17 @@ export const GlobalStyles = ` } .pdk_dashboard-page__outbound-grid__item { - cursor: pointer; border: 2px var(--background-color-low) solid; border-radius: 4px; padding: 10px; transition: border 0.2s ease; } -.pdk_dashboard-page__outbound-grid__item:hover { + +.pdk_dashboard-page__outbound-grid__item--selectable { + cursor: pointer; +} + +.pdk_dashboard-page__outbound-grid__item--selectable:hover { border-color: var(--primary-color-high); } 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 3fc7fc3..197deb7 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 @@ -470,13 +470,17 @@ var GlobalStyles = ` } .pdk_dashboard-page__outbound-grid__item { - cursor: pointer; border: 2px var(--background-color-low) solid; border-radius: 4px; padding: 10px; transition: border 0.2s ease; } -.pdk_dashboard-page__outbound-grid__item:hover { + +.pdk_dashboard-page__outbound-grid__item--selectable { + cursor: pointer; +} + +.pdk_dashboard-page__outbound-grid__item--selectable:hover { border-color: var(--primary-color-high); } @@ -855,6 +859,30 @@ async function triggerProxySelector(selector, outbound) { ); } +// src/clash/methods/triggerLatencyTest.ts +async function triggerLatencyGroupTest(tag, timeout = 2e3, url = "https://www.gstatic.com/generate_204") { + return createBaseApiRequest( + () => fetch( + `${getClashApiUrl()}/group/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, + { + method: "GET", + headers: { "Content-Type": "application/json" } + } + ) + ); +} +async function triggerLatencyProxyTest(tag, timeout = 2e3, url = "https://www.gstatic.com/generate_204") { + return createBaseApiRequest( + () => fetch( + `${getClashApiUrl()}/proxies/${tag}/delay?url=${encodeURIComponent(url)}&timeout=${timeout}`, + { + method: "GET", + headers: { "Content-Type": "application/json" } + } + ) + ); +} + // src/dashboard/renderDashboard.ts function renderDashboard() { return E( @@ -958,7 +986,8 @@ async function getDashboardSections() { (proxy) => proxy.code === `${section[".name"]}-out` ); return { - code: section[".name"], + withTagSelect: false, + code: outbound?.code || section[".name"], displayName: section[".name"], outbounds: [ { @@ -976,7 +1005,8 @@ async function getDashboardSections() { (proxy) => proxy.code === `${section[".name"]}-out` ); return { - code: section[".name"], + withTagSelect: false, + code: outbound?.code || section[".name"], displayName: section[".name"], outbounds: [ { @@ -1004,7 +1034,8 @@ async function getDashboardSections() { selected: selector?.value?.now === item?.code })); return { - code: section[".name"], + withTagSelect: true, + code: selector?.code || section[".name"], displayName: section[".name"], outbounds: [ { @@ -1024,7 +1055,8 @@ async function getDashboardSections() { (proxy) => proxy.code === `${section[".name"]}-out` ); return { - code: section[".name"], + withTagSelect: false, + code: outbound?.code || section[".name"], displayName: section[".name"], outbounds: [ { @@ -1038,6 +1070,7 @@ async function getDashboardSections() { }; } return { + withTagSelect: false, code: section[".name"], displayName: section[".name"], outbounds: [] @@ -1073,9 +1106,18 @@ async function getSingboxStatus() { // src/dashboard/renderer/renderOutboundGroup.ts function renderOutboundGroup({ - outbounds, - displayName + section, + onTestLatency, + onChooseOutbound }) { + function testLatency() { + if (section.withTagSelect) { + return onTestLatency(section.code); + } + if (section.outbounds.length) { + return onTestLatency(section.outbounds[0].code); + } + } function renderOutbound(outbound) { function getLatencyClass() { if (!outbound.latency) { @@ -1092,7 +1134,8 @@ function renderOutboundGroup({ return E( "div", { - class: `pdk_dashboard-page__outbound-grid__item ${outbound.selected ? "pdk_dashboard-page__outbound-grid__item--active" : ""}` + class: `pdk_dashboard-page__outbound-grid__item ${outbound.selected ? "pdk_dashboard-page__outbound-grid__item--active" : ""} ${section.withTagSelect ? "pdk_dashboard-page__outbound-grid__item--selectable" : ""}`, + click: () => section.withTagSelect && onChooseOutbound(section.code, outbound.code) }, [ E("b", {}, outbound.displayName), @@ -1119,14 +1162,14 @@ function renderOutboundGroup({ { class: "pdk_dashboard-page__outbound-section__title-section__title" }, - displayName + section.displayName ), - E("button", { class: "btn" }, "Test latency") + E("button", { class: "btn", click: () => testLatency() }, "Test latency") ]), E( "div", { class: "pdk_dashboard-page__outbound-grid" }, - outbounds.map((outbound) => renderOutbound(outbound)) + section.outbounds.map((outbound) => renderOutbound(outbound)) ) ]); } @@ -1343,11 +1386,36 @@ async function connectToClashSockets() { }); }); } +async function handleChooseOutbound(selector, tag) { + await triggerProxySelector(selector, tag); + await fetchDashboardSections(); +} +async function handleTestGroupLatency(tag) { + await triggerLatencyGroupTest(tag); + await fetchDashboardSections(); +} +async function handleTestProxyLatency(tag) { + await triggerLatencyProxyTest(tag); + await fetchDashboardSections(); +} async function renderDashboardSections() { const sections = store.get().sections; console.log("render dashboard sections group"); const container = document.getElementById("dashboard-sections-grid"); - const renderedOutboundGroups = sections.map(renderOutboundGroup); + const renderedOutboundGroups = sections.map( + (section) => renderOutboundGroup({ + section, + onTestLatency: (tag) => { + if (section.withTagSelect) { + return handleTestGroupLatency(tag); + } + return handleTestProxyLatency(tag); + }, + onChooseOutbound: (selector, tag) => { + handleChooseOutbound(selector, tag); + } + }) + ); container.replaceChildren(...renderedOutboundGroups); } async function renderTrafficWidget() { @@ -1480,6 +1548,8 @@ return baseclass.extend({ onMount, parseValueList, renderDashboard, + triggerLatencyGroupTest, + triggerLatencyProxyTest, triggerProxySelector, validateDNS, validateDomain,