dsh-provider-proxy 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/lib/client.js +328 -19
- package/lib/client.js.map +3 -3
- package/lib/index.js +108 -26
- package/lib/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,10 @@ Host catalog 是否列出某个 provider(`live`)不影响已保存路由的
|
|
|
86
86
|
| `localDirect` | `true` | 本机 / 内网目标是否直连(见「目标域名规则」一节) |
|
|
87
87
|
| `testUrl` | `https://api.ipify.org?format=json` | 自检的最后兜底目标;可写多个(逗号或空格分隔,上限 5 个),按序回退 |
|
|
88
88
|
|
|
89
|
+
其中的 `localDirect` 与 `testUrl` 也可以在默认代理区块下方的 **全局设置(高级)** 折叠块里修改:
|
|
90
|
+
`testUrl` 留空时 Host 使用内置默认值,非法候选或超过 5 项只会提示、不会阻止保存;
|
|
91
|
+
`localDirect` 默认勾选。修改任一项都会清空页面上所有自检结果,并且点「测试」前会把待写设置先落盘。
|
|
92
|
+
|
|
89
93
|
列表只包含**已激活**的 provider(`llm.listProviders()` 能列出的那些)。仅在配置里声明、
|
|
90
94
|
但没有实际注册的可配置 provider 不显示——它的请求根本不会被发出去。
|
|
91
95
|
已经在 `providers` 里配过代理的条目即使暂时不激活也仍然保留,避免已保存的设置悄悄消失;
|
|
@@ -93,10 +97,18 @@ catalog 的 `live` 只影响未配置 provider 是否出现在列表里,不参
|
|
|
93
97
|
|
|
94
98
|
保存立即对新请求生效,不需要重启;正在进行中的流会继续用它启动时的那条连接。
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
设置写入失败时,字段会回滚,并在页面顶部显示「保存失败:<字段> — <原因>」和关闭按钮;下一次成功写入或点击关闭后消失。多次失败只保留最新一条。写入成功与失败的诊断都不会包含密码值,密码字段固定显示「值已隐藏」。
|
|
101
|
+
|
|
102
|
+
每张卡片还有一个 **测试** 按钮:点它会让 Host 通过**该 provider 自己的代理**发一次请求,回报 HTTP 状态、延迟、脱敏后的探测目标与(探测目标返回时)出口 IP;冷却命中时标注「缓存」。只要该卡片有完整端点它就会出现——**不必先启用**;未启用的检查会标注为「未启用,仅测试」。代理不可达时它会明确失败,而不是假通过。结果与当前配置绑定:改端点 / 凭据 / 自检目标 / 域名规则 / 全局设置后旧结果会立即消失;点「测试」前会先把防抖中的设置写入,Host 测的是刚填的值。
|
|
103
|
+
|
|
104
|
+
探测目标在 Host 侧脱敏后才进入响应:合法 URL 只保留 `origin + pathname`,userinfo、query 与 fragment 都不会出现在结果、诊断或错误文本里;非法 URL 也会去掉 userinfo / query / fragment 并截断长度。网络错误同样不会回显原始目标。
|
|
97
105
|
|
|
98
106
|
**目标与判定口径**:目标按三级取用——卡片的 `自检目标` → 该 provider 的真实上游(见下一节)→ 顶层 `testUrl`(可写多个候选,按序回退,命中任一即停;`407` 会提前终止,因为那是代理的问题)。结果按状态码分类:
|
|
99
107
|
|
|
108
|
+
结果行会标出**这次用的是哪一级**:`(本卡片自检目标)` / `(该 provider 的真实上游)` / `(全局自检地址)`。
|
|
109
|
+
所以「改了 `testUrl` 但某张卡片没变」是可以解释的——该 provider 自己发布了真实上游,本就不读 `testUrl`;
|
|
110
|
+
要覆盖它,请在那张卡片的「高级 → 自检目标」里显式填写。
|
|
111
|
+
|
|
100
112
|
| 状态 | 含义 |
|
|
101
113
|
| --- | --- |
|
|
102
114
|
| `407` | 代理要求认证 |
|
|
@@ -104,7 +116,8 @@ catalog 的 `live` 只影响未配置 provider 是否出现在列表里,不参
|
|
|
104
116
|
| `5xx` | 疑似代理或上游故障。HTTP 代理够不到上游时会**自己**回 502/503/504 |
|
|
105
117
|
| 抛出(`ECONNREFUSED`、超时等) | 按失败原因分类,不会假通过 |
|
|
106
118
|
|
|
107
|
-
延迟数值按快慢着色——≤500 ms 绿、≤1500 ms
|
|
119
|
+
延迟数值按快慢着色——≤500 ms 绿、≤1500 ms 黄、更慢红——但**只有延迟数字**带这个颜色,
|
|
120
|
+
状态码、目标与其它文字用普通标签色。一次超过 1500 ms 的成功检查仍然是成功,不会整行变红。
|
|
108
121
|
|
|
109
122
|
**自检遵循与真实流量相同的代理 / 直连判定。** 目标命中本机 / 内网地址、`bypassHosts`,或落在 `onlyHosts` 之外时,自检与路由一样**直连**——不把一个到不了该目标的请求塞给代理,也就不会把一条健康路由报成「代理坏了」。直连成功时卡片会标注「· 直连(未经过代理)」,直连失败则报「目标不可达」而非指向从未使用的代理。
|
|
110
123
|
|
|
@@ -121,7 +134,7 @@ catalog 的 `live` 只影响未配置 provider 是否出现在列表里,不参
|
|
|
121
134
|
|
|
122
135
|
**指向本机或内网的请求默认直连。** 目标主机命中 `localhost`(含子域)、`127.0.0.0/8`、`::1`、`10.0.0.0/8`、`172.16.0.0/12`、`192.168.0.0/16`、`169.254.0.0/16` 时不经代理。这类目标通常是跑在同一台机器上的中转(LiteLLM、Ollama、本地 mock),正向代理根本到不了它,硬走代理只会失败,而且失败看起来像「代理坏了」。顶层 `localDirect: false` 可关掉这条(例如代理本身就是目标)。
|
|
123
136
|
|
|
124
|
-
带协议前缀(`https
|
|
137
|
+
带协议前缀(`https://…`)、路径(`example.com/v1`)、端口(`example.com:443`)或杂散通配符(`*foo`、`foo*`)的条目永远匹配不上——规则只认主机名,通配符只能是单独的 `*` 或以 `*.` 开头;单独的 `*.` 也视为死条目。方括号 IPv6 字面量(如 `[::1]`)是合法的。这类条目会在输入框下以错误色标出,因为后果是静默的:`onlyHosts` 里的死条目会让 fail-closed 把流量直连出去,而用户以为已经走代理。Host 与设置页使用同一份共享解析 / 判定逻辑。
|
|
125
138
|
|
|
126
139
|
当组成里存在 web 服务时,列表还会多出一张 **Web 搜索** 卡片(保留 id `web-search`,字段与其它卡片完全相同,也支持自检、计数与域名规则)。它承载 `web_search` 工具的流量;不启用它,搜索保持直连。
|
|
127
140
|
|
|
@@ -181,7 +194,7 @@ catalog 的 `live` 只影响未配置 provider 是否出现在列表里,不参
|
|
|
181
194
|
|
|
182
195
|
```sh
|
|
183
196
|
npm run build # 构建 lib/index.js 与 lib/client.js
|
|
184
|
-
npm test #
|
|
197
|
+
npm test # 248 个用例,跑的是构建产物
|
|
185
198
|
npm run typecheck # 类型检查(仅覆盖 src/)
|
|
186
199
|
npm run verify # 上面三件事,按顺序
|
|
187
200
|
```
|
package/lib/client.js
CHANGED
|
@@ -64,6 +64,31 @@ __export(client_exports, {
|
|
|
64
64
|
module.exports = __toCommonJS(client_exports);
|
|
65
65
|
var import_client = __toESM(require_client(), 1);
|
|
66
66
|
|
|
67
|
+
// src/host-rules.ts
|
|
68
|
+
function splitHostRules(raw) {
|
|
69
|
+
return raw.split(/[,\s]+/).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
70
|
+
}
|
|
71
|
+
function normalizeHostRule(raw) {
|
|
72
|
+
return raw.trim().replace(/^\*?\./, "").toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
function isBracketedIpv6(token) {
|
|
75
|
+
return /^\[[0-9a-f:.]+\]$/i.test(token);
|
|
76
|
+
}
|
|
77
|
+
function hostRuleProblem(raw) {
|
|
78
|
+
const token = raw.trim();
|
|
79
|
+
if (token.length === 0) return void 0;
|
|
80
|
+
if (token === "*." || normalizeHostRule(token).length === 0) return "\u7A7A\u901A\u914D\u7B26";
|
|
81
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(token)) return "\u5E26\u534F\u8BAE\u524D\u7F00";
|
|
82
|
+
if (token.includes("/")) return "\u5E26\u8DEF\u5F84";
|
|
83
|
+
if (token.includes(":") && !isBracketedIpv6(token)) return "\u5E26\u7AEF\u53E3\u6216\u975E\u6CD5\u5192\u53F7";
|
|
84
|
+
const withoutLeadingWildcard = token.replace(/^\*\./, "");
|
|
85
|
+
if (withoutLeadingWildcard.includes("*") && token !== "*") return "\u6742\u6563\u901A\u914D\u7B26";
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
function deadRuleEntries(raw) {
|
|
89
|
+
return splitHostRules(raw).filter((entry) => hostRuleProblem(entry) !== void 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
67
92
|
// src/proxy-address.ts
|
|
68
93
|
var PROXY_HOST_INVALID = "\u4EE3\u7406\u5730\u5740\u53EA\u80FD\u586B IPv4\u3001IPv6 \u6216\u57DF\u540D";
|
|
69
94
|
var IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
|
|
@@ -203,6 +228,8 @@ var CATALOG_PATH = "/dsh-provider-proxy/providers";
|
|
|
203
228
|
var TEST_PATH = "/dsh-provider-proxy/test";
|
|
204
229
|
var LATENCY_FAST_MS = 500;
|
|
205
230
|
var LATENCY_SLOW_MS = 1500;
|
|
231
|
+
var DEFAULT_TEST_URL = "https://api.ipify.org?format=json";
|
|
232
|
+
var MAX_TEST_URLS = 5;
|
|
206
233
|
function latencyColor(ms) {
|
|
207
234
|
if (typeof ms !== "number" || !Number.isFinite(ms)) return "var(--dsw-alias-label-secondary)";
|
|
208
235
|
if (ms <= LATENCY_FAST_MS) return "var(--dsw-alias-state-success-primary, #2ea043)";
|
|
@@ -216,6 +243,10 @@ function testResultFrom(body, strings) {
|
|
|
216
243
|
ok,
|
|
217
244
|
ms: typeof body?.ms === "number" && Number.isFinite(body.ms) ? body.ms : void 0,
|
|
218
245
|
ip: typeof body?.exitIp === "string" && body.exitIp.length > 0 ? body.exitIp : void 0,
|
|
246
|
+
status: typeof body?.status === "number" && Number.isInteger(body.status) ? body.status : void 0,
|
|
247
|
+
target: typeof body?.target === "string" && body.target.length > 0 ? body.target : void 0,
|
|
248
|
+
targetSource: body?.targetSource === "route" || body?.targetSource === "upstream" ? body.targetSource : body?.targetSource === "testUrl" ? "testUrl" : void 0,
|
|
249
|
+
cached: body?.cached === true,
|
|
219
250
|
// The Host probes a target the way routing would: a loopback/private upstream
|
|
220
251
|
// (or a bypassHosts match) is reached DIRECT, not through the proxy. The card
|
|
221
252
|
// says so, so a green check is not read as "the proxy works".
|
|
@@ -223,6 +254,57 @@ function testResultFrom(body, strings) {
|
|
|
223
254
|
error: ok ? void 0 : typeof body?.error === "string" && body.error.length > 0 ? body.error : strings.testFailed
|
|
224
255
|
};
|
|
225
256
|
}
|
|
257
|
+
function splitTestUrls(raw) {
|
|
258
|
+
return raw.split(/[,\s]+/).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
259
|
+
}
|
|
260
|
+
function testUrlHint(raw, t) {
|
|
261
|
+
const entries = splitTestUrls(raw);
|
|
262
|
+
if (entries.length === 0) return t.testUrlEmptyHint;
|
|
263
|
+
const invalid = entries.filter((entry) => !isAbsoluteHttpUrl(entry));
|
|
264
|
+
if (invalid.length > 0) return t.testUrlInvalidHint;
|
|
265
|
+
if (entries.length > MAX_TEST_URLS) return t.testUrlTooManyHint;
|
|
266
|
+
return void 0;
|
|
267
|
+
}
|
|
268
|
+
function isAbsoluteHttpUrl(value) {
|
|
269
|
+
try {
|
|
270
|
+
const url = new URL(value);
|
|
271
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.hostname.length > 0;
|
|
272
|
+
} catch {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function targetSourceSuffix(result, t) {
|
|
277
|
+
switch (result.targetSource) {
|
|
278
|
+
case "route":
|
|
279
|
+
return t.targetSourceRoute;
|
|
280
|
+
case "upstream":
|
|
281
|
+
return t.targetSourceUpstream;
|
|
282
|
+
case "testUrl":
|
|
283
|
+
return t.targetSourceTestUrl;
|
|
284
|
+
default:
|
|
285
|
+
return "";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function redactSensitiveText(text, secrets = []) {
|
|
289
|
+
let safe = text;
|
|
290
|
+
for (const secret of secrets) {
|
|
291
|
+
if (typeof secret !== "string" || secret.length === 0) continue;
|
|
292
|
+
safe = safe.split(secret).join("***");
|
|
293
|
+
}
|
|
294
|
+
safe = safe.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1");
|
|
295
|
+
safe = safe.replace(/([?&#])[^\s"'<>]*/g, "$1");
|
|
296
|
+
return safe.length > 500 ? `${safe.slice(0, 500)}\u2026` : safe;
|
|
297
|
+
}
|
|
298
|
+
function secretsFromOps(ops) {
|
|
299
|
+
const out = [];
|
|
300
|
+
for (const op of ops) {
|
|
301
|
+
const field = op?.path?.[op.path.length - 1];
|
|
302
|
+
if (field === "password" && typeof op?.value === "string" && op.value.length > 0) {
|
|
303
|
+
out.push(op.value);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
226
308
|
var catalogueSetter = () => {
|
|
227
309
|
};
|
|
228
310
|
function relativeTime(at, t) {
|
|
@@ -293,9 +375,6 @@ function touchCatalogPolling() {
|
|
|
293
375
|
}, CATALOG_REFRESH_MS);
|
|
294
376
|
}
|
|
295
377
|
var byName = (a, b) => a.name.localeCompare(b.name);
|
|
296
|
-
function deadRuleEntries(raw) {
|
|
297
|
-
return raw.split(/[,\s]+/).map((entry) => entry.trim()).filter((entry) => /^[a-z][a-z0-9+.-]*:\/\//i.test(entry) || entry.includes("/"));
|
|
298
|
-
}
|
|
299
378
|
function view(route) {
|
|
300
379
|
return {
|
|
301
380
|
enabled: route?.enabled === true,
|
|
@@ -462,6 +541,14 @@ var S = {
|
|
|
462
541
|
cursor: "pointer"
|
|
463
542
|
},
|
|
464
543
|
testResult: {
|
|
544
|
+
// A flex row so the target can drop onto its own line: the container was a
|
|
545
|
+
// plain span, where `flexBasis: 100%` does nothing and the target ran into
|
|
546
|
+
// the "cached" label.
|
|
547
|
+
display: "flex",
|
|
548
|
+
flexWrap: "wrap",
|
|
549
|
+
alignItems: "baseline",
|
|
550
|
+
columnGap: "6px",
|
|
551
|
+
rowGap: "2px",
|
|
465
552
|
fontSize: "11px",
|
|
466
553
|
color: "var(--dsw-alias-label-secondary)",
|
|
467
554
|
wordBreak: "break-all"
|
|
@@ -570,6 +657,19 @@ var S = {
|
|
|
570
657
|
color: "var(--dsw-alias-label-primary)",
|
|
571
658
|
font: "inherit",
|
|
572
659
|
fontSize: "12px"
|
|
660
|
+
},
|
|
661
|
+
diag: {
|
|
662
|
+
marginTop: "4px",
|
|
663
|
+
padding: "8px 10px",
|
|
664
|
+
border: "1px solid var(--dsw-alias-border-l1)",
|
|
665
|
+
borderRadius: "8px",
|
|
666
|
+
fontSize: "11px",
|
|
667
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
668
|
+
},
|
|
669
|
+
hint: {
|
|
670
|
+
gridColumn: "2",
|
|
671
|
+
fontSize: "11px",
|
|
672
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
573
673
|
}
|
|
574
674
|
};
|
|
575
675
|
function renderPage(ctx) {
|
|
@@ -641,6 +741,13 @@ function renderPage(ctx) {
|
|
|
641
741
|
const [defaultDraft, setDefaultDraft] = (React.useState ?? ((v) => [v, () => {
|
|
642
742
|
}]))({});
|
|
643
743
|
const editDefault = (field2, value) => setDefaultDraft((current) => ({ ...current, [field2]: value }));
|
|
744
|
+
const [globalDraft, setGlobalDraft] = (React.useState ?? ((v) => [v, () => {
|
|
745
|
+
}]))({});
|
|
746
|
+
const editGlobal = (field2, value) => setGlobalDraft((current) => ({ ...current, [field2]: value }));
|
|
747
|
+
const [saveError, setSaveError] = (React.useState ?? ((v) => [v, () => {
|
|
748
|
+
}]))(void 0);
|
|
749
|
+
const [savedNote, setSavedNote] = (React.useState ?? ((v) => [v, () => {
|
|
750
|
+
}]))(void 0);
|
|
644
751
|
const [tests, setTests] = (React.useState ?? ((v) => [v, () => {
|
|
645
752
|
}]))({});
|
|
646
753
|
const invalidateTests = (ids) => {
|
|
@@ -668,7 +775,7 @@ function renderPage(ctx) {
|
|
|
668
775
|
void (async () => {
|
|
669
776
|
try {
|
|
670
777
|
for (const [key, entry] of [...pendingWrites]) {
|
|
671
|
-
if (key.startsWith(`${id}\0`) || key.startsWith("\0default\0")) {
|
|
778
|
+
if (key.startsWith(`${id}\0`) || key.startsWith("\0default\0") || key.startsWith("\0global\0")) {
|
|
672
779
|
void entry.flush();
|
|
673
780
|
}
|
|
674
781
|
}
|
|
@@ -694,12 +801,20 @@ function renderPage(ctx) {
|
|
|
694
801
|
const rows = knownProviders({ ...configured, ...draft }, published);
|
|
695
802
|
const inheritingIds = () => rows.filter((row) => view({ ...configured[row.id], ...draft[row.id] }).useDefault === true).map((row) => row.id);
|
|
696
803
|
const save = (key, path, label, value, onRefused) => {
|
|
804
|
+
const field2 = path[path.length - 1];
|
|
805
|
+
const secret = field2 === "password" && typeof value === "string" ? value : void 0;
|
|
806
|
+
const safeLabel = secret === void 0 ? label : `${label}\uFF08\u503C\u5DF2\u9690\u85CF\uFF09`;
|
|
697
807
|
scheduleWrite(
|
|
698
808
|
key,
|
|
699
809
|
() => writeConfig(ctx, [{ op: "set", path, value }]).then(() => {
|
|
700
|
-
|
|
810
|
+
setSaveError(void 0);
|
|
811
|
+
setSavedNote(secret === void 0 ? `\u5DF2\u4FDD\u5B58 ${label}` : `\u5DF2\u4FDD\u5B58 ${safeLabel}`);
|
|
701
812
|
}).catch((error) => {
|
|
702
|
-
|
|
813
|
+
const message = redactSensitiveText(String(error?.message ?? error), [
|
|
814
|
+
...secret === void 0 ? [] : [secret]
|
|
815
|
+
]);
|
|
816
|
+
setSavedNote(void 0);
|
|
817
|
+
setSaveError({ label: safeLabel, message });
|
|
703
818
|
onRefused();
|
|
704
819
|
})
|
|
705
820
|
);
|
|
@@ -739,13 +854,31 @@ function renderPage(ctx) {
|
|
|
739
854
|
});
|
|
740
855
|
});
|
|
741
856
|
};
|
|
857
|
+
const bindGlobal = (field2) => (value) => {
|
|
858
|
+
editGlobal(field2, value);
|
|
859
|
+
invalidateTests(rows.map((row) => row.id));
|
|
860
|
+
save(`\0global\0${field2}`, [field2], field2, value, () => {
|
|
861
|
+
setGlobalDraft((current) => {
|
|
862
|
+
if (current[field2] === void 0) return current;
|
|
863
|
+
const next = { ...current };
|
|
864
|
+
delete next[field2];
|
|
865
|
+
return next;
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
};
|
|
742
869
|
const saveMany = (key, ops, label, onRefused) => {
|
|
870
|
+
const secrets = secretsFromOps(ops);
|
|
871
|
+
const hidesValues = ops.some((op) => op?.path?.[op.path.length - 1] === "password");
|
|
872
|
+
const safeLabel = hidesValues ? `${label}\uFF08\u503C\u5DF2\u9690\u85CF\uFF09` : label;
|
|
743
873
|
scheduleWrite(
|
|
744
874
|
key,
|
|
745
875
|
() => writeConfig(ctx, ops).then(() => {
|
|
746
|
-
|
|
876
|
+
setSaveError(void 0);
|
|
877
|
+
setSavedNote(`\u5DF2\u4FDD\u5B58 ${safeLabel}`);
|
|
747
878
|
}).catch((error) => {
|
|
748
|
-
|
|
879
|
+
const message = redactSensitiveText(String(error?.message ?? error), secrets);
|
|
880
|
+
setSavedNote(void 0);
|
|
881
|
+
setSaveError({ label: safeLabel, message });
|
|
749
882
|
onRefused();
|
|
750
883
|
})
|
|
751
884
|
);
|
|
@@ -968,6 +1101,75 @@ function renderPage(ctx) {
|
|
|
968
1101
|
)
|
|
969
1102
|
)
|
|
970
1103
|
);
|
|
1104
|
+
const storedTestUrl = typeof snapshot.value?.testUrl === "string" ? snapshot.value.testUrl : DEFAULT_TEST_URL;
|
|
1105
|
+
const storedLocalDirect = snapshot.value?.localDirect !== false;
|
|
1106
|
+
const globalTestUrl = typeof globalDraft.testUrl === "string" ? globalDraft.testUrl : storedTestUrl;
|
|
1107
|
+
const globalLocalDirect = typeof globalDraft.localDirect === "boolean" ? globalDraft.localDirect : storedLocalDirect;
|
|
1108
|
+
const globalTestUrlHint = testUrlHint(globalTestUrl, t);
|
|
1109
|
+
const globalBlock = h(
|
|
1110
|
+
"details",
|
|
1111
|
+
{
|
|
1112
|
+
className: import_client.default.diag,
|
|
1113
|
+
style: {
|
|
1114
|
+
...S.diag,
|
|
1115
|
+
marginTop: "0"
|
|
1116
|
+
},
|
|
1117
|
+
key: "global-settings"
|
|
1118
|
+
},
|
|
1119
|
+
h("summary", { className: import_client.default.diagSummary }, t.globalTitle),
|
|
1120
|
+
h("p", { className: import_client.default.defaultLead, style: S.defaultLead }, t.globalLead),
|
|
1121
|
+
h(
|
|
1122
|
+
"label",
|
|
1123
|
+
{ className: import_client.default.field, style: S.field },
|
|
1124
|
+
h("span", { className: import_client.default.label, style: S.label }, t.testUrl),
|
|
1125
|
+
h("input", {
|
|
1126
|
+
className: import_client.default.input,
|
|
1127
|
+
style: S.input,
|
|
1128
|
+
value: globalTestUrl,
|
|
1129
|
+
onChange: (event) => bindGlobal("testUrl")(event.target.value)
|
|
1130
|
+
}),
|
|
1131
|
+
globalTestUrlHint === void 0 ? h("span", { style: S.hint }, t.testUrlHint) : h(
|
|
1132
|
+
"span",
|
|
1133
|
+
{
|
|
1134
|
+
className: import_client.default.hint,
|
|
1135
|
+
style: {
|
|
1136
|
+
...S.hint,
|
|
1137
|
+
color: "var(--dsw-alias-state-error-primary, #f85149)"
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
globalTestUrlHint
|
|
1141
|
+
)
|
|
1142
|
+
),
|
|
1143
|
+
h(
|
|
1144
|
+
"div",
|
|
1145
|
+
{ style: S.testRow },
|
|
1146
|
+
h(
|
|
1147
|
+
"button",
|
|
1148
|
+
{
|
|
1149
|
+
className: import_client.default.testButton,
|
|
1150
|
+
style: S.testButton,
|
|
1151
|
+
type: "button",
|
|
1152
|
+
onClick: () => bindGlobal("testUrl")(DEFAULT_TEST_URL)
|
|
1153
|
+
},
|
|
1154
|
+
t.restoreDefaultTestUrl
|
|
1155
|
+
)
|
|
1156
|
+
),
|
|
1157
|
+
h(
|
|
1158
|
+
"label",
|
|
1159
|
+
{ className: import_client.default.toggle, style: S.toggle },
|
|
1160
|
+
h("input", {
|
|
1161
|
+
type: "checkbox",
|
|
1162
|
+
checked: globalLocalDirect,
|
|
1163
|
+
onChange: (event) => bindGlobal("localDirect")(event.target.checked === true)
|
|
1164
|
+
}),
|
|
1165
|
+
h("span", null, t.localDirect)
|
|
1166
|
+
),
|
|
1167
|
+
h(
|
|
1168
|
+
"p",
|
|
1169
|
+
{ style: S.hint },
|
|
1170
|
+
globalLocalDirect ? t.localDirectOnHint : t.localDirectOffHint
|
|
1171
|
+
)
|
|
1172
|
+
);
|
|
971
1173
|
const query = filter.trim().toLowerCase();
|
|
972
1174
|
const shown = query.length === 0 ? rows : rows.filter((row) => `${row.id} ${row.name}`.toLowerCase().includes(query));
|
|
973
1175
|
const filterRow = rows.length === 0 ? null : field(
|
|
@@ -1186,19 +1388,56 @@ function renderPage(ctx) {
|
|
|
1186
1388
|
result?.pending === true ? t.testRunning : t.testButton
|
|
1187
1389
|
),
|
|
1188
1390
|
result?.pending === false ? h(
|
|
1189
|
-
"
|
|
1391
|
+
"div",
|
|
1190
1392
|
{ style: S.testResult },
|
|
1191
1393
|
result.ok === true ? [
|
|
1192
|
-
//
|
|
1193
|
-
//
|
|
1394
|
+
// The band colour belongs to the LATENCY alone. The
|
|
1395
|
+
// status sits beside it in the plain label colour:
|
|
1396
|
+
// colouring the whole line made a successful check
|
|
1397
|
+
// over 1500 ms read as a failure.
|
|
1194
1398
|
h(
|
|
1195
1399
|
"span",
|
|
1196
1400
|
{ key: "lat", style: { color: latencyColor(result.ms) } },
|
|
1197
1401
|
`\u2713 ${typeof result.ms === "number" ? result.ms : "\u2014"} ms`
|
|
1198
1402
|
),
|
|
1199
|
-
result.
|
|
1200
|
-
|
|
1201
|
-
|
|
1403
|
+
typeof result.status === "number" ? h(
|
|
1404
|
+
"span",
|
|
1405
|
+
{ key: "status" },
|
|
1406
|
+
`HTTP ${result.status}`
|
|
1407
|
+
) : null,
|
|
1408
|
+
result.ip === void 0 ? null : h("span", { key: "ip" }, `${t.exitIpLabel}${result.ip}`),
|
|
1409
|
+
result.direct === true ? h("span", { key: "direct" }, t.directLabel) : null,
|
|
1410
|
+
result.cached === true ? h("span", { key: "cached" }, t.cachedLabel) : null,
|
|
1411
|
+
result.target === void 0 ? null : h(
|
|
1412
|
+
"span",
|
|
1413
|
+
{
|
|
1414
|
+
key: "target",
|
|
1415
|
+
style: {
|
|
1416
|
+
flexBasis: "100%",
|
|
1417
|
+
fontSize: "11px",
|
|
1418
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1419
|
+
wordBreak: "break-all"
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
`${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`
|
|
1423
|
+
)
|
|
1424
|
+
] : [
|
|
1425
|
+
h("span", { key: "err", style: S.testFailure }, `\u2717 ${result.error}`),
|
|
1426
|
+
typeof result.status === "number" ? h("span", { key: "status" }, `HTTP ${result.status}`) : null,
|
|
1427
|
+
result.target === void 0 ? null : h(
|
|
1428
|
+
"span",
|
|
1429
|
+
{
|
|
1430
|
+
key: "target",
|
|
1431
|
+
style: {
|
|
1432
|
+
flexBasis: "100%",
|
|
1433
|
+
fontSize: "11px",
|
|
1434
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1435
|
+
wordBreak: "break-all"
|
|
1436
|
+
}
|
|
1437
|
+
},
|
|
1438
|
+
`${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`
|
|
1439
|
+
)
|
|
1440
|
+
]
|
|
1202
1441
|
) : null,
|
|
1203
1442
|
// Promote this card's endpoint to the shared default. Offered only
|
|
1204
1443
|
// when the card carries its OWN endpoint: an inheriting card would
|
|
@@ -1264,7 +1503,40 @@ function renderPage(ctx) {
|
|
|
1264
1503
|
"div",
|
|
1265
1504
|
{ className: import_client.default.root, style: S.root },
|
|
1266
1505
|
h("p", { className: import_client.default.lead, style: { margin: "0 0 4px", fontSize: "12px", lineHeight: "1.6", color: "var(--dsw-alias-label-secondary)" } }, t.lead),
|
|
1506
|
+
saveError === void 0 ? null : h(
|
|
1507
|
+
"div",
|
|
1508
|
+
{
|
|
1509
|
+
role: "alert",
|
|
1510
|
+
key: "save-error",
|
|
1511
|
+
style: {
|
|
1512
|
+
display: "flex",
|
|
1513
|
+
alignItems: "flex-start",
|
|
1514
|
+
justifyContent: "space-between",
|
|
1515
|
+
gap: "10px",
|
|
1516
|
+
padding: "8px 10px",
|
|
1517
|
+
border: "1px solid var(--dsw-alias-state-error-primary, #f85149)",
|
|
1518
|
+
borderRadius: "8px",
|
|
1519
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
1520
|
+
color: "var(--dsw-alias-state-error-primary, #f85149)",
|
|
1521
|
+
fontSize: "12px",
|
|
1522
|
+
fontWeight: "500",
|
|
1523
|
+
wordBreak: "break-all"
|
|
1524
|
+
}
|
|
1525
|
+
},
|
|
1526
|
+
h("span", null, `${t.saveFailedPrefix}${saveError.label} \u2014 ${saveError.message}`),
|
|
1527
|
+
h(
|
|
1528
|
+
"button",
|
|
1529
|
+
{
|
|
1530
|
+
className: import_client.default.testButton,
|
|
1531
|
+
style: { ...S.testButton, flex: "none" },
|
|
1532
|
+
type: "button",
|
|
1533
|
+
onClick: () => setSaveError(void 0)
|
|
1534
|
+
},
|
|
1535
|
+
t.close
|
|
1536
|
+
)
|
|
1537
|
+
),
|
|
1267
1538
|
defaultBlock,
|
|
1539
|
+
globalBlock,
|
|
1268
1540
|
catalogueNotice,
|
|
1269
1541
|
filterRow,
|
|
1270
1542
|
...body,
|
|
@@ -1293,8 +1565,9 @@ function renderPage(ctx) {
|
|
|
1293
1565
|
h(
|
|
1294
1566
|
"ul",
|
|
1295
1567
|
{ className: import_client.default.diagList, style: { margin: "6px 0 0", paddingLeft: "18px" } },
|
|
1296
|
-
...problemCount === 0 && diagnostics.length === 0 ? [h("li", { key: "none" }, t.diagPending)] : [
|
|
1568
|
+
...problemCount === 0 && diagnostics.length === 0 && savedNote === void 0 ? [h("li", { key: "none" }, t.diagPending)] : [
|
|
1297
1569
|
...problemLines,
|
|
1570
|
+
...savedNote === void 0 ? [] : [h("li", { key: "saved" }, savedNote)],
|
|
1298
1571
|
...diagnostics.map((line, index) => h("li", { key: `d${index}` }, line))
|
|
1299
1572
|
]
|
|
1300
1573
|
)
|
|
@@ -1342,12 +1615,28 @@ var EN = {
|
|
|
1342
1615
|
testFailed: "Test failed",
|
|
1343
1616
|
exitIpLabel: "exit IP ",
|
|
1344
1617
|
directLabel: "direct (not through the proxy)",
|
|
1618
|
+
cachedLabel: "cached",
|
|
1619
|
+
testTargetLabel: "Target: ",
|
|
1620
|
+
targetSourceRoute: " (this card's own target)",
|
|
1621
|
+
targetSourceUpstream: " (this provider's real upstream)",
|
|
1622
|
+
targetSourceTestUrl: " (global self-check URL)",
|
|
1623
|
+
globalTitle: "Global settings (advanced)",
|
|
1624
|
+
globalLead: "These fields are saved with the plugin rather than per provider. Leave testUrl empty to use the built-in default; comma/space separated candidates are tried in order and only the first 5 are used. localDirect is on by default; turn it off only when the proxy itself is a loopback or private target.",
|
|
1625
|
+
testUrl: "Self-check URL",
|
|
1626
|
+
testUrlHint: "Comma/space separated; http(s) URLs in the order they should be tried.",
|
|
1627
|
+
testUrlEmptyHint: "Empty means the Host uses its built-in default URL.",
|
|
1628
|
+
testUrlInvalidHint: "Some candidates are not absolute http(s) URLs. They are saved, but those checks will fail.",
|
|
1629
|
+
testUrlTooManyHint: "More than 5 candidates are listed; only the first 5 take effect.",
|
|
1630
|
+
restoreDefaultTestUrl: "Restore default",
|
|
1631
|
+
localDirect: "Loopback / private targets go direct",
|
|
1632
|
+
localDirectOnHint: "On: localhost, 127.x and private ranges bypass the proxy.",
|
|
1633
|
+
localDirectOffHint: "Off: loopback/private targets also use the proxy. Enable only when the proxy itself is the target.",
|
|
1345
1634
|
advanced: "Advanced: target host rules",
|
|
1346
1635
|
onlyHosts: "Proxy only",
|
|
1347
1636
|
onlyHostsHint: "Comma/space separated; when set, only requests to these hosts are proxied (suffix match, * for all).",
|
|
1348
1637
|
bypassHosts: "Bypass",
|
|
1349
1638
|
bypassHostsHint: "Comma/space separated; requests to these hosts go direct, taking priority.",
|
|
1350
|
-
ruleDeadHint: "These entries never match \u2014
|
|
1639
|
+
ruleDeadHint: "These entries never match \u2014 use bare host names (no port, scheme or path); a wildcard must be `*` or start with `*.`: ",
|
|
1351
1640
|
testTarget: "Self-check target",
|
|
1352
1641
|
testTargetHint: "Empty uses the top-level testUrl; when set, this card's Test requests this URL instead \u2014 point it at a real upstream to verify the end-to-end path.",
|
|
1353
1642
|
socks5Hint: "SOCKS5 tunnels the connection; the proxy resolves the target host name.",
|
|
@@ -1357,7 +1646,9 @@ var EN = {
|
|
|
1357
1646
|
complete: "complete",
|
|
1358
1647
|
notConfigured: "not configured",
|
|
1359
1648
|
diagnostics: "Connection diagnostics",
|
|
1360
|
-
diagPending: "(collecting\u2026)"
|
|
1649
|
+
diagPending: "(collecting\u2026)",
|
|
1650
|
+
saveFailedPrefix: "Save failed: ",
|
|
1651
|
+
close: "Close"
|
|
1361
1652
|
};
|
|
1362
1653
|
var ZH = {
|
|
1363
1654
|
lead: "\u8BA9\u6307\u5B9A\u7684 provider \u8D70\u4F60\u81EA\u5DF1\u7684\u6B63\u5411\u4EE3\u7406\uFF0C\u5176\u5B83 provider \u4FDD\u6301\u76F4\u8FDE\u3002\u6307\u5411\u672C\u673A\u6216\u5185\u7F51\u5730\u5740\uFF08localhost\u3001127.x\u3001\u79C1\u6709\u7F51\u6BB5\uFF09\u7684\u8BF7\u6C42\u9ED8\u8BA4\u76F4\u8FDE\uFF0C\u9876\u5C42 localDirect \u8BBE\u4E3A false \u65F6\u6062\u590D\u8D70\u4EE3\u7406\u2014\u2014\u6B63\u5411\u4EE3\u7406\u901A\u5E38\u591F\u4E0D\u5230\u4F60\u81EA\u5DF1\u7684\u673A\u5668\u3002",
|
|
@@ -1400,12 +1691,28 @@ var ZH = {
|
|
|
1400
1691
|
testFailed: "\u6D4B\u8BD5\u5931\u8D25",
|
|
1401
1692
|
exitIpLabel: "\u51FA\u53E3 IP ",
|
|
1402
1693
|
directLabel: "\u76F4\u8FDE\uFF08\u672A\u7ECF\u8FC7\u4EE3\u7406\uFF09",
|
|
1694
|
+
cachedLabel: "\u7F13\u5B58",
|
|
1695
|
+
testTargetLabel: "\u76EE\u6807 ",
|
|
1696
|
+
targetSourceRoute: "\uFF08\u672C\u5361\u7247\u81EA\u68C0\u76EE\u6807\uFF09",
|
|
1697
|
+
targetSourceUpstream: "\uFF08\u8BE5 provider \u7684\u771F\u5B9E\u4E0A\u6E38\uFF09",
|
|
1698
|
+
targetSourceTestUrl: "\uFF08\u5168\u5C40\u81EA\u68C0\u5730\u5740\uFF09",
|
|
1699
|
+
globalTitle: "\u5168\u5C40\u8BBE\u7F6E\uFF08\u9AD8\u7EA7\uFF09",
|
|
1700
|
+
globalLead: "\u8FD9\u91CC\u7684\u4E24\u9879\u5C5E\u4E8E\u63D2\u4EF6\u5168\u5C40\u8BBE\u7F6E\u3002testUrl \u7559\u7A7A\u65F6 Host \u4F7F\u7528\u5185\u7F6E\u9ED8\u8BA4\u503C\uFF1B\u591A\u4E2A\u5019\u9009\u7528\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694\uFF0C\u6309\u987A\u5E8F\u56DE\u9000\uFF0C\u53EA\u6709\u524D 5 \u4E2A\u751F\u6548\u3002localDirect \u9ED8\u8BA4\u5F00\u542F\uFF1B\u53EA\u6709\u4EE3\u7406\u672C\u8EAB\u5C31\u662F\u672C\u673A\u6216\u5185\u7F51\u76EE\u6807\u65F6\u624D\u9700\u8981\u5173\u95ED\u3002",
|
|
1701
|
+
testUrl: "\u81EA\u68C0\u5730\u5740",
|
|
1702
|
+
testUrlHint: "\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694\uFF1B\u6309\u987A\u5E8F\u5C1D\u8BD5\u7684 http(s) URL\u3002",
|
|
1703
|
+
testUrlEmptyHint: "\u7559\u7A7A\u65F6 Host \u4F7F\u7528\u5185\u7F6E\u9ED8\u8BA4\u5730\u5740\u3002",
|
|
1704
|
+
testUrlInvalidHint: "\u90E8\u5206\u5019\u9009\u4E0D\u662F\u7EDD\u5BF9\u7684 http(s) URL\u3002\u4ECD\u4F1A\u4FDD\u5B58\uFF0C\u4F46\u8FD9\u4E9B\u5019\u9009\u4F1A\u6D4B\u8BD5\u5931\u8D25\u3002",
|
|
1705
|
+
testUrlTooManyHint: "\u5019\u9009\u8D85\u8FC7 5 \u4E2A\uFF0C\u53EA\u6709\u524D 5 \u4E2A\u751F\u6548\u3002",
|
|
1706
|
+
restoreDefaultTestUrl: "\u6062\u590D\u9ED8\u8BA4",
|
|
1707
|
+
localDirect: "\u672C\u673A / \u5185\u7F51\u76EE\u6807\u76F4\u8FDE",
|
|
1708
|
+
localDirectOnHint: "\u5F00\u542F\uFF1Alocalhost\u3001127.x \u4E0E\u79C1\u6709\u7F51\u6BB5\u7ED5\u8FC7\u4EE3\u7406\u3002",
|
|
1709
|
+
localDirectOffHint: "\u5173\u95ED\uFF1A\u672C\u673A / \u5185\u7F51\u76EE\u6807\u4E5F\u8D70\u4EE3\u7406\uFF1B\u4EC5\u5728\u4EE3\u7406\u672C\u8EAB\u5C31\u662F\u76EE\u6807\u65F6\u4F7F\u7528\u3002",
|
|
1403
1710
|
advanced: "\u9AD8\u7EA7\uFF1A\u76EE\u6807\u57DF\u540D\u89C4\u5219",
|
|
1404
1711
|
onlyHosts: "\u4EC5\u8D70\u4EE3\u7406",
|
|
1405
1712
|
onlyHostsHint: "\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694\uFF1B\u975E\u7A7A\u65F6\u53EA\u6709\u5339\u914D\u8FD9\u4E9B\u4E3B\u673A\u7684\u8BF7\u6C42\u8D70\u4EE3\u7406\uFF08\u540E\u7F00\u5339\u914D\uFF0C* \u8868\u793A\u5168\u90E8\uFF09\u3002",
|
|
1406
1713
|
bypassHosts: "\u76F4\u8FDE",
|
|
1407
1714
|
bypassHostsHint: "\u9017\u53F7\u6216\u7A7A\u683C\u5206\u9694\uFF1B\u5339\u914D\u8FD9\u4E9B\u4E3B\u673A\u7684\u8BF7\u6C42\u76F4\u8FDE\uFF0C\u4F18\u5148\u4E8E\u4E0A\u4E00\u9879\u3002",
|
|
1408
|
-
ruleDeadHint: "\u4EE5\u4E0B\u6761\u76EE\u6C38\u8FDC\u5339\u914D\u4E0D\u4E0A\u2014\u2014\u89C4\u5219\u53EA\u8BA4\u4E3B\u673A\u540D\
|
|
1715
|
+
ruleDeadHint: "\u4EE5\u4E0B\u6761\u76EE\u6C38\u8FDC\u5339\u914D\u4E0D\u4E0A\u2014\u2014\u89C4\u5219\u53EA\u8BA4\u4E3B\u673A\u540D\uFF08\u4E0D\u8981\u5E26\u7AEF\u53E3\u3001\u534F\u8BAE\u524D\u7F00\u6216\u8DEF\u5F84\uFF09\uFF0C\u901A\u914D\u7B26\u53EA\u80FD\u662F\u5355\u72EC\u7684 * \u6216\u4EE5 *. \u5F00\u5934\uFF1A",
|
|
1409
1716
|
testTarget: "\u81EA\u68C0\u76EE\u6807",
|
|
1410
1717
|
testTargetHint: "\u7559\u7A7A\u7528\u9876\u5C42 testUrl\uFF1B\u586B\u5199\u540E\u8BE5\u5361\u7247\u7684\u300C\u6D4B\u8BD5\u300D\u6539\u4E3A\u8BF7\u6C42\u8FD9\u4E2A\u5730\u5740\uFF0C\u53EF\u62FF\u771F\u5B9E\u4E0A\u6E38\u9A8C\u8BC1\u7AEF\u5230\u7AEF\u8DEF\u5F84\u3002",
|
|
1411
1718
|
socks5Hint: "SOCKS5 \u4F1A\u5EFA\u7ACB\u96A7\u9053\uFF0C\u76EE\u6807\u57DF\u540D\u7531\u4EE3\u7406\u7AEF\u89E3\u6790\u3002",
|
|
@@ -1415,7 +1722,9 @@ var ZH = {
|
|
|
1415
1722
|
complete: "\u914D\u7F6E\u5B8C\u6574",
|
|
1416
1723
|
notConfigured: "\u672A\u914D\u7F6E",
|
|
1417
1724
|
diagnostics: "\u8FDE\u63A5\u8BCA\u65AD",
|
|
1418
|
-
diagPending: "\uFF08\u6B63\u5728\u6536\u96C6\u2026\uFF09"
|
|
1725
|
+
diagPending: "\uFF08\u6B63\u5728\u6536\u96C6\u2026\uFF09",
|
|
1726
|
+
saveFailedPrefix: "\u4FDD\u5B58\u5931\u8D25\uFF1A",
|
|
1727
|
+
close: "\u5173\u95ED"
|
|
1419
1728
|
};
|
|
1420
1729
|
function stringsFor(ctx) {
|
|
1421
1730
|
const language = localeOf(ctx);
|