mirror of
https://github.com/itdoginfo/podkop.git
synced 2025-12-06 03:26:51 +03:00
@@ -7,3 +7,4 @@ export * from './maskIP';
|
||||
export * from './getProxyUrlName';
|
||||
export * from './onMount';
|
||||
export * from './getClashApiUrl';
|
||||
export * from './splitProxyString';
|
||||
|
||||
7
fe-app-podkop/src/helpers/splitProxyString.ts
Normal file
7
fe-app-podkop/src/helpers/splitProxyString.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function splitProxyString(str: string) {
|
||||
return str
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => !line.startsWith('//'))
|
||||
.filter(Boolean);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -101,7 +101,7 @@ export function renderDefaultState({
|
||||
class: 'btn dashboard-sections-grid-item-test-latency',
|
||||
click: () => testLatency(),
|
||||
},
|
||||
'Test latency',
|
||||
_('Test latency'),
|
||||
),
|
||||
]),
|
||||
E(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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') };
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PODIR="po"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user