Merge pull request #205 from itdoginfo/hotfix

Hotfix
This commit is contained in:
Kirill Sobakin
2025-10-07 21:34:26 +03:00
committed by GitHub
11 changed files with 201 additions and 31 deletions

View File

@@ -7,3 +7,4 @@ export * from './maskIP';
export * from './getProxyUrlName';
export * from './onMount';
export * from './getClashApiUrl';
export * from './splitProxyString';

View File

@@ -0,0 +1,7 @@
export function splitProxyString(str: string) {
return str
.split('\n')
.map((line) => line.trim())
.filter((line) => !line.startsWith('//'))
.filter(Boolean);
}

View File

@@ -1,7 +1,7 @@
import { Podkop } from '../types';
import { getConfigSections } from './getConfigSections';
import { getClashProxies } from '../../clash';
import { getProxyUrlName } from '../../helpers';
import { getProxyUrlName, splitProxyString } from '../../helpers';
interface IGetDashboardSectionsResponse {
success: boolean;
@@ -35,6 +35,11 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
(proxy) => proxy.code === `${section['.name']}-out`,
);
const activeConfigs = splitProxyString(section.proxy_string);
const proxyDisplayName =
getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || '';
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
@@ -42,10 +47,7 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
outbounds: [
{
code: outbound?.code || section['.name'],
displayName:
getProxyUrlName(section.proxy_string) ||
outbound?.value?.name ||
'',
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,

View File

@@ -101,7 +101,7 @@ export function renderDefaultState({
class: 'btn dashboard-sections-grid-item-test-latency',
click: () => testLatency(),
},
'Test latency',
_('Test latency'),
),
]),
E(

View File

@@ -29,6 +29,13 @@ export const invalidDomains = [
['Too long domain (>253 chars)', Array(40).fill('abcdef').join('.') + '.com'],
];
export const dotTLDTests = [
['Dot TLD allowed (.net)', '.net', true, true],
['Dot TLD not allowed (.net)', '.net', false, false],
['Invalid with double dot', '..net', true, false],
['Invalid single word TLD (net)', 'net', true, false],
];
describe('validateDomain', () => {
describe.each(validDomains)('Valid domain: %s', (_desc, domain) => {
it(`returns valid=true for "${domain}"`, () => {
@@ -43,4 +50,14 @@ describe('validateDomain', () => {
expect(res.valid).toBe(false);
});
});
describe.each(dotTLDTests)(
'Dot TLD toggle: %s',
(_desc, domain, allowDotTLD, expected) => {
it(`"${domain}" with allowDotTLD=${allowDotTLD} → valid=${expected}`, () => {
const res = validateDomain(domain, allowDotTLD);
expect(res.valid).toBe(expected);
});
},
);
});

View File

@@ -1,9 +1,19 @@
import { ValidationResult } from './types';
export function validateDomain(domain: string): ValidationResult {
export function validateDomain(
domain: string,
allowDotTLD = false,
): ValidationResult {
const domainRegex =
/^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/;
if (allowDotTLD) {
const dotTLD = /^\.[a-zA-Z]{2,}$/;
if (dotTLD.test(domain)) {
return { valid: true, message: _('Valid') };
}
}
if (!domainRegex.test(domain)) {
return { valid: false, message: _('Invalid domain address') };
}

View File

@@ -130,11 +130,7 @@ function createConfigSection(section) {
}
try {
const activeConfigs = value
.split('\n')
.map((line) => line.trim())
.filter((line) => !line.startsWith('//'))
.filter(Boolean);
const activeConfigs = main.splitProxyString(value);
if (!activeConfigs.length) {
return _(
@@ -455,7 +451,7 @@ function createConfigSection(section) {
return true;
}
const validation = main.validateDomain(value);
const validation = main.validateDomain(value, true);
if (validation.valid) {
return true;
@@ -493,7 +489,7 @@ function createConfigSection(section) {
);
}
const { valid, results } = main.bulkValidate(domains, main.validateDomain);
const { valid, results } = main.bulkValidate(domains, row => main.validateDomain(row, true));
if (!valid) {
const errors = results

View File

@@ -14,8 +14,14 @@ function validateIPV4(ip) {
}
// src/validators/validateDomain.ts
function validateDomain(domain) {
function validateDomain(domain, allowDotTLD = false) {
const domainRegex = /^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$/;
if (allowDotTLD) {
const dotTLD = /^\.[a-zA-Z]{2,}$/;
if (dotTLD.test(domain)) {
return { valid: true, message: _("Valid") };
}
}
if (!domainRegex.test(domain)) {
return { valid: false, message: _("Invalid domain address") };
}
@@ -765,6 +771,11 @@ function getClashWsUrl() {
return `ws://${hostname}:9090`;
}
// src/helpers/splitProxyString.ts
function splitProxyString(str) {
return str.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).filter(Boolean);
}
// src/clash/methods/createBaseApiRequest.ts
async function createBaseApiRequest(fetchFn) {
try {
@@ -893,6 +904,8 @@ async function getDashboardSections() {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const activeConfigs = splitProxyString(section.proxy_string);
const proxyDisplayName = getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || "";
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
@@ -900,7 +913,7 @@ async function getDashboardSections() {
outbounds: [
{
code: outbound?.code || section[".name"],
displayName: getProxyUrlName(section.proxy_string) || outbound?.value?.name || "",
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true
@@ -1294,7 +1307,7 @@ function renderDefaultState({
class: "btn dashboard-sections-grid-item-test-latency",
click: () => testLatency()
},
"Test latency"
_("Test latency")
)
]),
E(
@@ -1891,6 +1904,7 @@ return baseclass.extend({
onMount,
parseValueList,
renderDashboard,
splitProxyString,
triggerLatencyGroupTest,
triggerLatencyProxyTest,
triggerProxySelector,

View File

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
PODIR="po"

View File

@@ -51,9 +51,11 @@ msgid "Config without description"
msgstr "Конфигурация без описания"
msgid ""
"Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup configs"
"Enter connection string starting with vless:// or ss:// for proxy configuration. Add comments with // for backup "
"configs"
msgstr ""
"Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для резервных конфигураций"
"Введите строку подключения, начинающуюся с vless:// или ss:// для настройки прокси. Добавляйте комментарии с // для "
"резервных конфигураций"
msgid "No active configuration found. One configuration is required."
msgstr "Активная конфигурация не найдена. Требуется хотя бы одна незакомментированная строка."
@@ -124,14 +126,18 @@ msgstr "Выберите предустановленные сервисы дл
msgid "Regional options cannot be used together"
msgstr "Нельзя использовать несколько региональных опций одновременно"
#, javascript-format
msgid "Warning: %s cannot be used together with %s. Previous selections have been removed."
msgstr "Предупреждение: %s нельзя использовать вместе с %s. Предыдущие варианты были удалены."
msgid "Russia inside restrictions"
msgstr "Ограничения Russia inside"
msgid "Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
msgstr "Внимание: «Russia inside» может использоваться только с %s. %s уже находится в «Russia inside» и был удалён из выбора."
#, javascript-format
msgid ""
"Warning: Russia inside can only be used with %s. %s already in Russia inside and have been removed from selection."
msgstr ""
"Внимание: «Russia inside» может использоваться только с %s. %s уже находится в «Russia inside» и был удалён из выбора."
msgid "User Domain List Type"
msgstr "Тип пользовательского списка доменов"
@@ -214,8 +220,12 @@ msgstr "Введите подсети в нотации CIDR (например:
msgid "User Subnets List"
msgstr "Список пользовательских подсетей"
msgid "Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments after //"
msgstr "Введите подсети в нотации CIDR или IP-адреса через запятую, пробел или новую строку. Можно добавлять комментарии после //"
msgid ""
"Enter subnets in CIDR notation or single IP addresses, separated by comma, space or newline. You can add comments "
"after //"
msgstr ""
"Введите подсети в нотации CIDR или IP-адреса через запятую, пробел или новую строку. Можно добавлять комментарии "
"после //"
msgid "At least one valid subnet or IP must be specified. Comments-only content is not allowed."
msgstr "Необходимо указать хотя бы одну действительную подсеть или IP. Только комментарии недопустимы."
@@ -568,11 +578,122 @@ msgstr "Конфигурация: "
msgid "Diagnostics"
msgstr "Диагностика"
msgid "Podkop"
msgstr "Podkop"
msgid "Additional Settings"
msgstr "Дополнительные настройки"
msgid "Extra configurations"
msgstr "Дополнительные конфигурации"
msgid "Yacd enable"
msgstr "Включить YACD"
msgid "Add Section"
msgstr "Добавить раздел"
msgid "Exclude NTP"
msgstr "Исключить NTP"
msgid "Allows you to exclude NTP protocol traffic from the tunnel"
msgstr "Позволяет исключить направление трафика NTP-протокола в туннель"
msgid "QUIC disable"
msgstr "Отключить QUIC"
msgid "For issues with the video stream"
msgstr "Для проблем с видеопотоком"
msgid "List Update Frequency"
msgstr "Частота обновления списков"
msgid "Select how often the lists will be updated"
msgstr "Выберите как часто будут обновляться списки"
msgid "Select DNS protocol to use"
msgstr "Выберите протокол DNS"
msgid "Bootstrap DNS server"
msgstr "Bootstrap DNS-сервер"
msgid "The DNS server used to look up the IP address of an upstream DNS server"
msgstr "DNS-сервер, используемый для поиска IP-адреса вышестоящего DNS-сервера"
msgid "DNS Rewrite TTL"
msgstr "Перезапись TTL для DNS"
msgid "Time in seconds for DNS record caching (default: 60)"
msgstr "Время в секундах для кэширования DNS записей (по умолчанию: 60)"
msgid "TTL value cannot be empty"
msgstr "Значение TTL не может быть пустым"
msgid "TTL must be a positive number"
msgstr "TTL должно быть положительным числом"
msgid "Config File Path"
msgstr "Путь к файлу конфигурации"
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
msgstr "Выберите путь к файлу конфигурации sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете"
msgid "Cache File Path"
msgstr "Путь к файлу кэша"
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
msgstr "Выберите или введите путь к файлу кеша sing-box. Изменяйте это, ТОЛЬКО если вы знаете, что делаете"
msgid "Cache file path cannot be empty"
msgstr "Путь к файлу кэша не может быть пустым"
msgid "Path must be absolute (start with /)"
msgstr "Путь должен быть абсолютным (начинаться с /)"
msgid "Path must end with cache.db"
msgstr "Путь должен заканчиваться на cache.db"
msgid "Path must contain at least one directory (like /tmp/cache.db)"
msgstr "Путь должен содержать хотя бы одну директорию (например /tmp/cache.db)"
msgid "Source Network Interface"
msgstr "Сетевой интерфейс источника"
msgid "Select the network interface from which the traffic will originate"
msgstr "Выберите сетевой интерфейс, с которого будет исходить трафик"
msgid "Interface monitoring"
msgstr "Мониторинг интерфейсов"
msgid "Interface monitoring for bad WAN"
msgstr "Мониторинг интерфейсов для плохого WAN"
msgid "Interface for monitoring"
msgstr "Интерфейс для мониторинга"
msgid "Select the WAN interfaces to be monitored"
msgstr "Выберите WAN интерфейсы для мониторинга"
msgid "Interface Monitoring Delay"
msgstr "Задержка при мониторинге интерфейсов"
msgid "Delay in milliseconds before reloading podkop after interface UP"
msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса"
msgid "Delay value cannot be empty"
msgstr "Значение задержки не может быть пустым"
msgid "Dont touch my DHCP!"
msgstr "Не трогать мой DHCP!"
msgid "Podkop will not change the DHCP config"
msgstr "Podkop не будет изменять конфигурацию DHCP"
msgid "Proxy download of lists"
msgstr "Загрузка списков через прокси"
msgid "Downloading all lists via main Proxy/VPN"
msgstr "Загрузка всех списков через основной прокси/VPN"
msgid "IP for exclusion"
msgstr "IP для исключения"
msgid "Specify local IP addresses that will never use the configured route"
msgstr "Укажите локальные IP-адреса, которые никогда не будут использовать настроенный маршрут"
msgid "Mixed enable"
msgstr "Включить смешанный режим"
msgid "Browser port: 2080"
msgstr "Порт браузера: 2080"

View File

@@ -1,4 +1,5 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
SRC_DIR="htdocs/luci-static/resources/view/podkop"
OUT_POT="po/templates/podkop.pot"
@@ -11,6 +12,7 @@ if [ ${#FILES[@]} -eq 0 ]; then
exit 1
fi
mapfile -t FILES < <(printf '%s\n' "${FILES[@]}" | sort)
mkdir -p "$(dirname "$OUT_POT")"
echo "Generating POT template from JS files in $SRC_DIR"