Improve URLTest details and Xray tag handling

This commit is contained in:
ushan0v
2026-07-11 00:10:55 +03:00
parent f7a2fc3e4e
commit c0cf425fb5
12 changed files with 1122 additions and 740 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -10
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-02 17:43+0300\n"
"PO-Revision-Date: 2026-07-02 17:43+0300\n"
"POT-Creation-Date: 2026-07-02 23:54+0300\n"
"PO-Revision-Date: 2026-07-02 23:54+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -281,9 +281,6 @@ msgstr "Копировать"
msgid "Copy proxy link"
msgstr "Скопировать ссылку прокси"
msgid "Copy URL"
msgstr "Скопировать URL"
msgid "Could not compare FakeIP and control public IPs"
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
@@ -623,8 +620,8 @@ msgstr "Задержка при мониторинге интерфейсов"
msgid "Interface monitoring for Bad WAN"
msgstr "Мониторинг интерфейса для Bad WAN"
msgid "Interrupt existing connections"
msgstr "Прерывать существующие соединения"
msgid "Interrupt connections"
msgstr "Прерывать соединения"
msgid "Interval"
msgstr "Интервал"
@@ -911,9 +908,6 @@ msgstr "Мониторинг"
msgid "Must be a number in the range of 50 - 1000"
msgstr "Должно быть числом от 50 до 1000"
msgid "N/A"
msgstr "Н/Д"
msgid "Network Interface"
msgstr "Сетевой интерфейс"
@@ -207,7 +207,7 @@ function isUrlTestProxyEntry(entry?: ClashProxyEntry) {
return entry?.value?.type?.toLowerCase() === 'urltest';
}
function getLatencySortValue(outbound: Podkop.Outbound) {
function getLatencySortValue(outbound: { latency: number }) {
const latency = Number(outbound.latency);
return Number.isFinite(latency) && latency > 0
@@ -248,6 +248,27 @@ function sortOutboundsForDashboard(
.map((item) => item.outbound);
}
function sortUrlTestMembers(outbounds: Podkop.UrlTestMember[]) {
return outbounds
.map((outbound, index) => ({ outbound, index }))
.sort((left, right) => {
if (left.outbound.selected !== right.outbound.selected) {
return left.outbound.selected ? -1 : 1;
}
const latencyDiff =
getLatencySortValue(left.outbound) -
getLatencySortValue(right.outbound);
if (latencyDiff !== 0) {
return latencyDiff;
}
return left.index - right.index;
})
.map((item) => item.outbound);
}
function isSafeSectionName(sectionName: string) {
return /^[A-Za-z0-9_-]+$/.test(sectionName);
}
@@ -338,32 +359,34 @@ function buildUrlTestInfo({
: entry.value.all || [],
);
const selectedCode = entry.value.now || '';
const outbounds = childCodes.flatMap((childCode) => {
const childEntry = proxyByCode.get(childCode);
const link = manualLinkByCode.get(childCode) || '';
const canCopyLink =
isCopyableProxyLink(link) || subscriptionCopyableCodes.has(childCode);
const outbounds = sortUrlTestMembers(
childCodes.flatMap((childCode) => {
const childEntry = proxyByCode.get(childCode);
const link = manualLinkByCode.get(childCode) || '';
const canCopyLink =
isCopyableProxyLink(link) || subscriptionCopyableCodes.has(childCode);
return [
{
code: childCode,
displayName: getOutboundDisplayName(
childCode,
childEntry,
return [
{
code: childCode,
displayName: getOutboundDisplayName(
childCode,
childEntry,
link,
outboundMetadata,
),
latency: childEntry?.value?.history?.[0]?.delay || 0,
type: childEntry?.value?.type || '',
selected: selectedCode === childCode,
link,
outboundMetadata,
),
latency: childEntry?.value?.history?.[0]?.delay || 0,
type: childEntry?.value?.type || '',
selected: selectedCode === childCode,
link,
canCopyLink,
country: showDetectedCountries
? outboundMetadata?.countries?.[childCode]
: undefined,
},
];
});
canCopyLink,
country: showDetectedCountries
? outboundMetadata?.countries?.[childCode]
: undefined,
},
];
}),
);
const selectedName =
outbounds.find((outbound) => outbound.code === selectedCode)?.displayName ||
selectedCode;
@@ -125,6 +125,20 @@ describe('getDashboardSections', () => {
it('hydrates URLTest details from the section cache and Clash API', async () => {
mocks.getConfigSections.mockResolvedValue([proxySection()]);
mocks.getClashApiProxies.mockResolvedValue({
success: true,
data: {
proxies: {
...clashProxies,
'main-urltest-out': proxy('URLTest', {
name: 'main-urltest-out',
now: 'main-3-out',
history: [{ time: '2026-05-27T00:00:00Z', delay: 10 }],
all: ['main-1-out', 'main-3-out'],
}),
},
},
});
mocks.fsRead.mockResolvedValue(
JSON.stringify({
outboundMetadata: {
@@ -137,7 +151,7 @@ describe('getDashboardSections', () => {
urltestGroups: {
'main-urltest-out': {
displayName: 'Fastest',
outbounds: ['main-1-out', 'main-3-out'],
outbounds: ['main-1-out', 'main-3-out', 'main-2-out'],
url: 'https://probe.example/204',
interval: '3m',
tolerance: 50,
@@ -161,15 +175,24 @@ describe('getDashboardSections', () => {
interval: '3m',
tolerance: 50,
interruptExistConnections: true,
selectedCode: 'main-3-out',
selectedName: 'Third cached',
});
expect(urltest?.urlTestInfo?.outbounds.map((item) => item.code)).toEqual([
'main-1-out',
'main-3-out',
'main-1-out',
'main-2-out',
]);
expect(urltest?.urlTestInfo?.outbounds[0]).toMatchObject({
displayName: 'Third cached',
latency: 300,
type: 'VLESS',
selected: true,
});
expect(urltest?.urlTestInfo?.outbounds[1]).toMatchObject({
displayName: 'one',
latency: 100,
type: 'VLESS',
selected: false,
});
});
@@ -613,7 +613,68 @@ function formatUrlTestModalValue(value: unknown) {
}
const text = `${value ?? ''}`.trim();
return text || _('N/A');
return text || _('No');
}
function getUrlTestLatencyClass(latency: number) {
if (!latency) {
return 'pdk_dashboard-page__outbound-grid__item__latency--empty';
}
if (latency < 800) {
return 'pdk_dashboard-page__outbound-grid__item__latency--green';
}
if (latency < 1500) {
return 'pdk_dashboard-page__outbound-grid__item__latency--yellow';
}
return 'pdk_dashboard-page__outbound-grid__item__latency--red';
}
function formatUrlTestLatency(latency: number) {
return latency ? `${latency}ms` : 'N/A';
}
function renderUrlTestSelectedValue(info: Podkop.UrlTestInfo) {
const selectedMember = info.outbounds.find((member) => member.selected);
const selectedName =
selectedMember?.displayName || info.selectedName || info.selectedCode || '';
const name = formatUrlTestModalValue(selectedName);
if (name === _('No')) {
return E('span', {}, name);
}
return E(
'span',
{ class: 'pdk_dashboard-page__urltest-details__selected-value' },
[
E(
'span',
{ class: 'pdk_dashboard-page__urltest-details__selected-name' },
name,
),
...(selectedMember?.type
? [
E(
'span',
{ class: 'pdk_dashboard-page__urltest-details__selected-type' },
selectedMember.type,
),
]
: []),
...(selectedMember
? [
E(
'span',
{ class: getUrlTestLatencyClass(selectedMember.latency) },
formatUrlTestLatency(selectedMember.latency),
),
]
: []),
],
);
}
function renderUrlTestCopyButton(
@@ -624,7 +685,7 @@ function renderUrlTestCopyButton(
'button',
{
type: 'button',
class: 'btn pdk_dashboard-page__outbound-grid__item__copy-button',
class: 'btn pdk_dashboard-page__urltest-details__copy-button',
title,
'aria-label': title,
click: onClick,
@@ -643,33 +704,37 @@ function renderUrlTestInfoModal(
return E('div', {}, _('URLTest details are unavailable'));
}
const fields: Array<[string, unknown, string?]> = [
[_('Selected'), info.selectedName || info.selectedCode],
[_('Testing URL'), info.url, info.url],
[_('Interval'), info.interval],
[_('Tolerance'), info.tolerance],
[_('Idle timeout'), info.idleTimeout],
[_('Interrupt existing connections'), info.interruptExistConnections],
const fields: Array<{
label: string;
value?: unknown;
children?: Array<HTMLElement | string>;
}> = [
{
label: _('Selected'),
children: [renderUrlTestSelectedValue(info)],
},
{ label: _('Testing URL'), value: info.url },
{ label: _('Interval'), value: info.interval },
{ label: _('Tolerance'), value: info.tolerance },
{ label: _('Idle timeout'), value: info.idleTimeout },
{
label: _('Interrupt connections'),
value: info.interruptExistConnections,
},
];
return E('div', { class: 'pdk_dashboard-page__urltest-details' }, [
E(
'dl',
{ class: 'pdk_dashboard-page__urltest-details__params' },
fields.map(([label, value, copyValue]) =>
fields.map(({ label, value, children }) =>
E('div', { class: 'pdk_dashboard-page__urltest-details__param' }, [
E('dt', {}, label),
E('dd', {}, [
E('span', {}, formatUrlTestModalValue(value)),
...(copyValue
? [
renderUrlTestCopyButton(_('Copy URL'), (event) => {
event.preventDefault();
copyToClipboard(copyValue);
}),
]
: []),
]),
E(
'dd',
{},
children || [E('span', {}, formatUrlTestModalValue(value))],
),
]),
),
),
@@ -704,7 +769,18 @@ function renderUrlTestInfoModal(
},
[
E('b', {}, member.displayName),
member.selected ? E('span', {}, _('Selected')) : '',
...(member.type
? [
E(
'span',
{
class:
'pdk_dashboard-page__urltest-details__row-type',
},
member.type,
),
]
: []),
],
),
E(
@@ -713,29 +789,45 @@ function renderUrlTestInfoModal(
class: 'pdk_dashboard-page__urltest-details__row-meta',
},
[
E('span', {}, member.type || _('N/A')),
E(
'span',
{},
member.latency ? `${member.latency}ms` : 'N/A',
{ class: getUrlTestLatencyClass(member.latency) },
formatUrlTestLatency(member.latency),
),
],
),
...(member.canCopyLink
? [
renderUrlTestCopyButton(
_('Copy proxy link'),
(event) => {
event.preventDefault();
void handleCopyOutbound(section, member);
},
),
]
: []),
member.canCopyLink
? renderUrlTestCopyButton(_('Copy proxy link'), (event) => {
event.preventDefault();
void handleCopyOutbound(section, member);
})
: E('span', {
class:
'pdk_dashboard-page__urltest-details__copy-placeholder',
}),
],
),
)
: [E('div', {}, _('No outbounds'))],
: [
E(
'div',
{ class: 'pdk_dashboard-page__urltest-details__empty' },
_('No outbounds'),
),
],
),
]),
E('div', { class: 'pdk_dashboard-page__urltest-details__footer' }, [
E(
'button',
{
type: 'button',
class: 'btn cbi-button cbi-button-neutral',
click: () => {
ui.hideModal();
},
},
_('Close'),
),
]),
]);
@@ -749,7 +841,12 @@ function handleShowUrlTestInfo(
return;
}
ui.showModal(_('URLTest details'), renderUrlTestInfoModal(section, outbound));
ui.showModal(
`${_('URLTest details')}: ${
outbound.urlTestInfo.displayName || outbound.displayName
}`,
renderUrlTestInfoModal(section, outbound),
);
}
async function handleUpdateSubscription(section: Podkop.OutboundGroup) {
+102 -11
View File
@@ -392,14 +392,17 @@ export const styles = `
}
.pdk_dashboard-page__urltest-details {
min-width: min(720px, 86vw);
box-sizing: border-box;
width: min(760px, calc(100vw - 56px));
max-width: 100%;
padding-top: 10px;
}
.pdk_dashboard-page__urltest-details__params {
display: grid;
grid-template-columns: minmax(120px, max-content) minmax(0, 1fr);
gap: 8px 16px;
margin: 0 0 16px;
margin: 0 0 18px;
}
.pdk_dashboard-page__urltest-details__param {
@@ -408,6 +411,7 @@ export const styles = `
.pdk_dashboard-page__urltest-details__param dt {
color: var(--text-color-medium, #666);
line-height: 1.35;
}
.pdk_dashboard-page__urltest-details__param dd {
@@ -423,6 +427,30 @@ export const styles = `
overflow-wrap: anywhere;
}
.pdk_dashboard-page__urltest-details__selected-value {
display: inline-flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
max-width: 100%;
padding: 1px 8px;
border: 1px solid var(--success-color-low, #2d7d46);
border-radius: 4px;
color: inherit;
background: transparent;
box-sizing: border-box;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__selected-name {
min-width: 0;
overflow-wrap: anywhere;
}
.pdk_dashboard-page__urltest-details__selected-type {
color: var(--text-color-medium, #666);
}
.pdk_dashboard-page__urltest-details__outbounds-title {
margin-bottom: 8px;
font-weight: 600;
@@ -431,52 +459,115 @@ export const styles = `
.pdk_dashboard-page__urltest-details__table {
display: grid;
gap: 6px;
width: calc(100% + 14px);
box-sizing: border-box;
max-height: min(46vh, 460px);
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
padding-right: 14px;
scrollbar-gutter: auto;
}
.pdk_dashboard-page__urltest-details__row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
grid-template-columns: minmax(0, 1fr) minmax(54px, max-content) 20px;
align-items: center;
gap: 12px;
padding: 8px 0;
gap: 8px;
width: 100%;
min-width: 0;
padding: 7px 8px;
box-sizing: border-box;
border: 1px solid transparent;
border-bottom: 1px solid var(--border-color-low, #eee);
border-radius: 4px;
}
.pdk_dashboard-page__urltest-details__row--active {
font-weight: 600;
border-color: var(--success-color-low, #2d7d46);
background: transparent;
}
.pdk_dashboard-page__urltest-details__row-name,
.pdk_dashboard-page__urltest-details__row-meta {
display: flex;
align-items: center;
gap: 8px;
gap: 6px;
min-width: 0;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__row-name {
flex-wrap: wrap;
}
.pdk_dashboard-page__urltest-details__row-name b {
min-width: 0;
overflow-wrap: anywhere;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__row-name span,
.pdk_dashboard-page__urltest-details__row-type,
.pdk_dashboard-page__urltest-details__row-meta {
color: var(--text-color-medium, #666);
}
@media (max-width: 480px) {
.pdk_dashboard-page__urltest-details__row-type {
white-space: nowrap;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__row-meta {
justify-content: flex-end;
white-space: nowrap;
}
.pdk_dashboard-page__urltest-details__copy-button {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 20px;
width: 20px;
min-width: 20px;
height: 20px;
padding: 0;
box-sizing: border-box;
}
.pdk_dashboard-page__urltest-details__copy-button svg {
width: 12px;
height: 12px;
}
.pdk_dashboard-page__urltest-details__copy-placeholder {
display: block;
width: 20px;
min-width: 20px;
height: 1px;
}
.pdk_dashboard-page__urltest-details__empty {
padding: 8px;
color: var(--text-color-medium, #666);
}
.pdk_dashboard-page__urltest-details__footer {
display: flex;
justify-content: flex-end;
margin-top: 14px;
}
@media (max-width: 560px) {
.pdk_dashboard-page__urltest-details__params {
grid-template-columns: 1fr;
}
.pdk_dashboard-page__urltest-details__row {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-columns: minmax(0, 1fr) 20px;
}
.pdk_dashboard-page__urltest-details__row-meta {
grid-column: 1 / -1;
justify-content: flex-start;
}
}
@@ -3239,6 +3239,18 @@ function sortOutboundsForDashboard(outbounds, options = {}) {
return left.index - right.index;
}).map((item) => item.outbound);
}
function sortUrlTestMembers(outbounds) {
return outbounds.map((outbound, index) => ({ outbound, index })).sort((left, right) => {
if (left.outbound.selected !== right.outbound.selected) {
return left.outbound.selected ? -1 : 1;
}
const latencyDiff = getLatencySortValue(left.outbound) - getLatencySortValue(right.outbound);
if (latencyDiff !== 0) {
return latencyDiff;
}
return left.index - right.index;
}).map((item) => item.outbound);
}
function isSafeSectionName(sectionName) {
return /^[A-Za-z0-9_-]+$/.test(sectionName);
}
@@ -3292,28 +3304,30 @@ function buildUrlTestInfo({
groupCache?.outbounds?.length ? groupCache.outbounds : entry.value.all || []
);
const selectedCode = entry.value.now || "";
const outbounds = childCodes.flatMap((childCode) => {
const childEntry = proxyByCode.get(childCode);
const link = manualLinkByCode.get(childCode) || "";
const canCopyLink = isCopyableProxyLink(link) || subscriptionCopyableCodes.has(childCode);
return [
{
code: childCode,
displayName: getOutboundDisplayName(
childCode,
childEntry,
const outbounds = sortUrlTestMembers(
childCodes.flatMap((childCode) => {
const childEntry = proxyByCode.get(childCode);
const link = manualLinkByCode.get(childCode) || "";
const canCopyLink = isCopyableProxyLink(link) || subscriptionCopyableCodes.has(childCode);
return [
{
code: childCode,
displayName: getOutboundDisplayName(
childCode,
childEntry,
link,
outboundMetadata
),
latency: childEntry?.value?.history?.[0]?.delay || 0,
type: childEntry?.value?.type || "",
selected: selectedCode === childCode,
link,
outboundMetadata
),
latency: childEntry?.value?.history?.[0]?.delay || 0,
type: childEntry?.value?.type || "",
selected: selectedCode === childCode,
link,
canCopyLink,
country: showDetectedCountries ? outboundMetadata?.countries?.[childCode] : void 0
}
];
});
canCopyLink,
country: showDetectedCountries ? outboundMetadata?.countries?.[childCode] : void 0
}
];
})
);
const selectedName = outbounds.find((outbound) => outbound.code === selectedCode)?.displayName || selectedCode;
return {
code,
@@ -5358,14 +5372,62 @@ function formatUrlTestModalValue(value) {
return value ? _("Yes") : _("No");
}
const text = `${value ?? ""}`.trim();
return text || _("N/A");
return text || _("No");
}
function getUrlTestLatencyClass(latency) {
if (!latency) {
return "pdk_dashboard-page__outbound-grid__item__latency--empty";
}
if (latency < 800) {
return "pdk_dashboard-page__outbound-grid__item__latency--green";
}
if (latency < 1500) {
return "pdk_dashboard-page__outbound-grid__item__latency--yellow";
}
return "pdk_dashboard-page__outbound-grid__item__latency--red";
}
function formatUrlTestLatency(latency) {
return latency ? `${latency}ms` : "N/A";
}
function renderUrlTestSelectedValue(info) {
const selectedMember = info.outbounds.find((member) => member.selected);
const selectedName = selectedMember?.displayName || info.selectedName || info.selectedCode || "";
const name = formatUrlTestModalValue(selectedName);
if (name === _("No")) {
return E("span", {}, name);
}
return E(
"span",
{ class: "pdk_dashboard-page__urltest-details__selected-value" },
[
E(
"span",
{ class: "pdk_dashboard-page__urltest-details__selected-name" },
name
),
...selectedMember?.type ? [
E(
"span",
{ class: "pdk_dashboard-page__urltest-details__selected-type" },
selectedMember.type
)
] : [],
...selectedMember ? [
E(
"span",
{ class: getUrlTestLatencyClass(selectedMember.latency) },
formatUrlTestLatency(selectedMember.latency)
)
] : []
]
);
}
function renderUrlTestCopyButton(title, onClick) {
return E(
"button",
{
type: "button",
class: "btn pdk_dashboard-page__outbound-grid__item__copy-button",
class: "btn pdk_dashboard-page__urltest-details__copy-button",
title,
"aria-label": title,
click: onClick
@@ -5379,29 +5441,31 @@ function renderUrlTestInfoModal(section, outbound) {
return E("div", {}, _("URLTest details are unavailable"));
}
const fields = [
[_("Selected"), info.selectedName || info.selectedCode],
[_("Testing URL"), info.url, info.url],
[_("Interval"), info.interval],
[_("Tolerance"), info.tolerance],
[_("Idle timeout"), info.idleTimeout],
[_("Interrupt existing connections"), info.interruptExistConnections]
{
label: _("Selected"),
children: [renderUrlTestSelectedValue(info)]
},
{ label: _("Testing URL"), value: info.url },
{ label: _("Interval"), value: info.interval },
{ label: _("Tolerance"), value: info.tolerance },
{ label: _("Idle timeout"), value: info.idleTimeout },
{
label: _("Interrupt connections"),
value: info.interruptExistConnections
}
];
return E("div", { class: "pdk_dashboard-page__urltest-details" }, [
E(
"dl",
{ class: "pdk_dashboard-page__urltest-details__params" },
fields.map(
([label, value, copyValue]) => E("div", { class: "pdk_dashboard-page__urltest-details__param" }, [
({ label, value, children }) => E("div", { class: "pdk_dashboard-page__urltest-details__param" }, [
E("dt", {}, label),
E("dd", {}, [
E("span", {}, formatUrlTestModalValue(value)),
...copyValue ? [
renderUrlTestCopyButton(_("Copy URL"), (event) => {
event.preventDefault();
copyToClipboard(copyValue);
})
] : []
])
E(
"dd",
{},
children || [E("span", {}, formatUrlTestModalValue(value))]
)
])
)
),
@@ -5431,7 +5495,15 @@ function renderUrlTestInfoModal(section, outbound) {
},
[
E("b", {}, member.displayName),
member.selected ? E("span", {}, _("Selected")) : ""
...member.type ? [
E(
"span",
{
class: "pdk_dashboard-page__urltest-details__row-type"
},
member.type
)
] : []
]
),
E(
@@ -5440,26 +5512,41 @@ function renderUrlTestInfoModal(section, outbound) {
class: "pdk_dashboard-page__urltest-details__row-meta"
},
[
E("span", {}, member.type || _("N/A")),
E(
"span",
{},
member.latency ? `${member.latency}ms` : "N/A"
{ class: getUrlTestLatencyClass(member.latency) },
formatUrlTestLatency(member.latency)
)
]
),
...member.canCopyLink ? [
renderUrlTestCopyButton(
_("Copy proxy link"),
(event) => {
event.preventDefault();
void handleCopyOutbound(section, member);
}
)
] : []
member.canCopyLink ? renderUrlTestCopyButton(_("Copy proxy link"), (event) => {
event.preventDefault();
void handleCopyOutbound(section, member);
}) : E("span", {
class: "pdk_dashboard-page__urltest-details__copy-placeholder"
})
]
)
) : [E("div", {}, _("No outbounds"))]
) : [
E(
"div",
{ class: "pdk_dashboard-page__urltest-details__empty" },
_("No outbounds")
)
]
)
]),
E("div", { class: "pdk_dashboard-page__urltest-details__footer" }, [
E(
"button",
{
type: "button",
class: "btn cbi-button cbi-button-neutral",
click: () => {
ui.hideModal();
}
},
_("Close")
)
])
]);
@@ -5468,7 +5555,10 @@ function handleShowUrlTestInfo(section, outbound) {
if (!outbound.urlTestInfo) {
return;
}
ui.showModal(_("URLTest details"), renderUrlTestInfoModal(section, outbound));
ui.showModal(
`${_("URLTest details")}: ${outbound.urlTestInfo.displayName || outbound.displayName}`,
renderUrlTestInfoModal(section, outbound)
);
}
async function handleUpdateSubscription(section) {
if (store.get().sectionsWidget.subscriptionUpdatingSections[section.sectionName]) {
@@ -6226,14 +6316,17 @@ var styles = `
}
.pdk_dashboard-page__urltest-details {
min-width: min(720px, 86vw);
box-sizing: border-box;
width: min(760px, calc(100vw - 56px));
max-width: 100%;
padding-top: 10px;
}
.pdk_dashboard-page__urltest-details__params {
display: grid;
grid-template-columns: minmax(120px, max-content) minmax(0, 1fr);
gap: 8px 16px;
margin: 0 0 16px;
margin: 0 0 18px;
}
.pdk_dashboard-page__urltest-details__param {
@@ -6242,6 +6335,7 @@ var styles = `
.pdk_dashboard-page__urltest-details__param dt {
color: var(--text-color-medium, #666);
line-height: 1.35;
}
.pdk_dashboard-page__urltest-details__param dd {
@@ -6257,6 +6351,30 @@ var styles = `
overflow-wrap: anywhere;
}
.pdk_dashboard-page__urltest-details__selected-value {
display: inline-flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
max-width: 100%;
padding: 1px 8px;
border: 1px solid var(--success-color-low, #2d7d46);
border-radius: 4px;
color: inherit;
background: transparent;
box-sizing: border-box;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__selected-name {
min-width: 0;
overflow-wrap: anywhere;
}
.pdk_dashboard-page__urltest-details__selected-type {
color: var(--text-color-medium, #666);
}
.pdk_dashboard-page__urltest-details__outbounds-title {
margin-bottom: 8px;
font-weight: 600;
@@ -6265,52 +6383,115 @@ var styles = `
.pdk_dashboard-page__urltest-details__table {
display: grid;
gap: 6px;
width: calc(100% + 14px);
box-sizing: border-box;
max-height: min(46vh, 460px);
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
padding-right: 14px;
scrollbar-gutter: auto;
}
.pdk_dashboard-page__urltest-details__row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
grid-template-columns: minmax(0, 1fr) minmax(54px, max-content) 20px;
align-items: center;
gap: 12px;
padding: 8px 0;
gap: 8px;
width: 100%;
min-width: 0;
padding: 7px 8px;
box-sizing: border-box;
border: 1px solid transparent;
border-bottom: 1px solid var(--border-color-low, #eee);
border-radius: 4px;
}
.pdk_dashboard-page__urltest-details__row--active {
font-weight: 600;
border-color: var(--success-color-low, #2d7d46);
background: transparent;
}
.pdk_dashboard-page__urltest-details__row-name,
.pdk_dashboard-page__urltest-details__row-meta {
display: flex;
align-items: center;
gap: 8px;
gap: 6px;
min-width: 0;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__row-name {
flex-wrap: wrap;
}
.pdk_dashboard-page__urltest-details__row-name b {
min-width: 0;
overflow-wrap: anywhere;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__row-name span,
.pdk_dashboard-page__urltest-details__row-type,
.pdk_dashboard-page__urltest-details__row-meta {
color: var(--text-color-medium, #666);
}
@media (max-width: 480px) {
.pdk_dashboard-page__urltest-details__row-type {
white-space: nowrap;
line-height: 1.3;
}
.pdk_dashboard-page__urltest-details__row-meta {
justify-content: flex-end;
white-space: nowrap;
}
.pdk_dashboard-page__urltest-details__copy-button {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 20px;
width: 20px;
min-width: 20px;
height: 20px;
padding: 0;
box-sizing: border-box;
}
.pdk_dashboard-page__urltest-details__copy-button svg {
width: 12px;
height: 12px;
}
.pdk_dashboard-page__urltest-details__copy-placeholder {
display: block;
width: 20px;
min-width: 20px;
height: 1px;
}
.pdk_dashboard-page__urltest-details__empty {
padding: 8px;
color: var(--text-color-medium, #666);
}
.pdk_dashboard-page__urltest-details__footer {
display: flex;
justify-content: flex-end;
margin-top: 14px;
}
@media (max-width: 560px) {
.pdk_dashboard-page__urltest-details__params {
grid-template-columns: 1fr;
}
.pdk_dashboard-page__urltest-details__row {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-columns: minmax(0, 1fr) 20px;
}
.pdk_dashboard-page__urltest-details__row-meta {
grid-column: 1 / -1;
justify-content: flex-start;
}
}
+4 -10
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-02 17:43+0300\n"
"PO-Revision-Date: 2026-07-02 17:43+0300\n"
"POT-Creation-Date: 2026-07-02 23:54+0300\n"
"PO-Revision-Date: 2026-07-02 23:54+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -281,9 +281,6 @@ msgstr "Копировать"
msgid "Copy proxy link"
msgstr "Скопировать ссылку прокси"
msgid "Copy URL"
msgstr "Скопировать URL"
msgid "Could not compare FakeIP and control public IPs"
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
@@ -623,8 +620,8 @@ msgstr "Задержка при мониторинге интерфейсов"
msgid "Interface monitoring for Bad WAN"
msgstr "Мониторинг интерфейса для Bad WAN"
msgid "Interrupt existing connections"
msgstr "Прерывать существующие соединения"
msgid "Interrupt connections"
msgstr "Прерывать соединения"
msgid "Interval"
msgstr "Интервал"
@@ -911,9 +908,6 @@ msgstr "Мониторинг"
msgid "Must be a number in the range of 50 - 1000"
msgstr "Должно быть числом от 50 до 1000"
msgid "N/A"
msgstr "Н/Д"
msgid "Network Interface"
msgstr "Сетевой интерфейс"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -2219,7 +2219,7 @@ function xray_tag_base(original_tag, config_index, config_count) {
original_tag = as_string(original_tag);
if (original_tag == "")
original_tag = "server";
return config_count > 1 ? ("xray-" + (config_index + 1) + "-" + original_tag) : original_tag;
return original_tag;
}
function xray_protocol_supported(protocol) {
@@ -135,6 +135,9 @@ if (group.interval != "45s")
die("xray urltest interval was not preserved\n");
if (group.tolerance != null)
die("xray urltest should not receive hardcoded tolerance\n");
for (let child in group.outbounds || [])
if (substr(child, 0, 5) == "xray-")
die("xray urltest child tags should preserve source names when unique\n");
' "$xray_normalized" || fail "xray normalized URLTest fields"
prepare_subscription_cache proxy 1 "https://xray.example/sub" "$xray_normalized"
@@ -186,6 +189,9 @@ if (!builtin || length(builtin.outbounds || []) != 2)
for (let child in builtin.outbounds || [])
if (child == "Latvia group")
die("built-in URLTest must not use the xray group tag as a child\n");
for (let child in builtin.outbounds || [])
if (substr(child, 0, 5) == "xray-")
die("built-in URLTest must not use artificial xray child tag prefixes\n");
if (object_or_empty(cache.urltestGroups)["Latvia group"].url != "https://probe.example/204")
die("section cache is missing imported xray URLTest params\n");
if (length(object_or_empty(cache.urltestGroups)["proxy-urltest-out"].outbounds || []) != 2)