dsh-provider-proxy 0.5.0 → 0.5.1

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/lib/client.js CHANGED
@@ -39,13 +39,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
39
39
  var require_client = __commonJS({
40
40
  "src/client.css"(exports, module2) {
41
41
  "use strict";
42
- var __dshMap = { "root": "7a51762d_root", "lead": "c71659b2_lead", "empty": "c42c4abe_empty", "card": "53acf569_card", "cardActive": "bd4546f8_cardActive", "defaultCard": "1103ba6e_defaultCard", "defaultLead": "0b907dc6_defaultLead", "cardHead": "d06dccdb_cardHead", "cardTitles": "ff20149f_cardTitles", "name": "4e36a10e_name", "id": "79208064_id", "pillOn": "3e0969fd_pillOn", "pillLive": "895a1b6e_pillLive", "pillOff": "f5c4d227_pillOff", "toggle": "94e6424f_toggle", "field": "5dfb5268_field", "label": "2e13439d_label", "hint": "cdd952c6_hint", "input": "31dd2d99_input", "testButton": "028bc91c_testButton", "diag": "5d114b0b_diag", "diagSummary": "27518b39_diagSummary", "diagList": "b3005b62_diagList" };
42
+ var __dshMap = { "root": "bcf3ba25_root", "lead": "86cd7a9a_lead", "empty": "4ec3d74d_empty", "card": "61dfce67_card", "cardActive": "56b6c433_cardActive", "cardRejected": "24b300e2_cardRejected", "defaultCard": "665ef72b_defaultCard", "defaultLead": "351db191_defaultLead", "cardHead": "9ee651d9_cardHead", "cardTitles": "bb118443_cardTitles", "name": "a1b940d4_name", "id": "1870f756_id", "pillOn": "e9cfdcad_pillOn", "pillLive": "3d3b7405_pillLive", "pillOff": "a517f862_pillOff", "pillRejected": "3e11f4ea_pillRejected", "toggle": "31ab2dc8_toggle", "field": "52ffb01c_field", "label": "b8d05488_label", "hint": "d8b3bd46_hint", "input": "0b74e44b_input", "testButton": "47b71ed7_testButton", "diag": "f395d2a5_diag", "diagSummary": "84895b20_diagSummary", "diagList": "9ecca3cd_diagList" };
43
43
  var __dshStyles = globalThis.__dshPluginStyles || (globalThis.__dshPluginStyles = /* @__PURE__ */ new Set());
44
44
  if (!__dshStyles.has("dsh-provider-proxy/client.css")) {
45
45
  __dshStyles.add("dsh-provider-proxy/client.css");
46
46
  __dshStyle = document.createElement("style");
47
47
  __dshStyle.setAttribute("data-plugin-css", "dsh-provider-proxy/client.css");
48
- __dshStyle.textContent = '/* dsh-provider-proxy \u2014 provider proxy settings page.\n Tokens only: no literal colours, matching the shared --dsw-* system. */\n\n.7a51762d_root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-width: 640px;\n}\n\n.c71659b2_lead {\n margin: 0 0 4px;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\n}\n\n.c42c4abe_empty {\n margin: 0;\n padding: 16px;\n border: 1px dashed var(--dsw-alias-border-l1);\n border-radius: 8px;\n font-size: 12px;\n text-align: center;\n color: var(--dsw-alias-state-idle-primary);\n}\n\n/* ---- provider card ---- */\n\n.53acf569_card,\n.bd4546f8_cardActive {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.bd4546f8_cardActive {\n border-color: var(--dsw-alias-state-success-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* ---- shared default endpoint ---- */\n\n/* A distinct outer layer, so it reads as "one setting for the page" rather than\n as one more provider card. */\n.1103ba6e_defaultCard {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px 16px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.0b907dc6_defaultLead {\n margin: -2px 0 0;\n font-size: 11px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n\n.d06dccdb_cardHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.ff20149f_cardTitles {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.4e36a10e_name {\n overflow: hidden;\n font-size: 13px;\n font-weight: 600;\n white-space: nowrap;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.79208064_id {\n flex: none;\n padding: 1px 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n font-family: ui-monospace, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* ---- status pill ---- */\n\n.3e0969fd_pillOn,\n.895a1b6e_pillLive,\n.f5c4d227_pillOff {\n flex: none;\n padding: 2px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 500;\n white-space: nowrap;\n}\n\n.3e0969fd_pillOn {\n background: var(--dsw-alias-state-success-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.895a1b6e_pillLive {\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n\n.f5c4d227_pillOff {\n color: var(--dsw-alias-state-idle-primary);\n}\n\n/* ---- toggle ---- */\n\n.94e6424f_toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 2px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n.94e6424f_toggle input {\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n/* ---- fields ---- */\n\n.5dfb5268_field {\n display: grid;\n grid-template-columns: 96px 1fr;\n align-items: center;\n gap: 10px;\n font-size: 12px;\n}\n\n.2e13439d_label {\n color: var(--dsw-alias-label-secondary);\n}\n\n.cdd952c6_hint {\n grid-column: 2;\n margin-top: -2px;\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.31dd2d99_input {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n padding: 5px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n outline: none;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n transition: border-color 120ms ease, background 120ms ease;\n}\n\n/* The base look of every input is INLINE too, so these three state rules only\n ever take effect as author-important declarations \u2014 same reason as the\n self-check button above. `cursor` has no inline base and needs none, and the\n hover border uses `l3` because `l1`\u2192`l2` is a 4%\u219210% black step nobody can\n see. */\n\n.31dd2d99_input:hover:not(:disabled) {\n border-color: var(--dsw-alias-border-l3) !important;\n}\n\n.31dd2d99_input:focus {\n border-color: var(--dsw-alias-brand-primary) !important;\n}\n\n.31dd2d99_input:disabled {\n color: var(--dsw-alias-state-idle-primary) !important;\n background: var(--dsw-alias-bg-layer-2) !important;\n cursor: not-allowed;\n}\n\n/* ---- self-check ---- */\n\n/* The hover state is the one thing an inline style cannot express, which is why\n this element carries a class at all. Layout stays inline.\n\n Two traps, both hit before this comment existed:\n - `!important` is load-bearing: the base border and background are INLINE, and\n the cascade sorts a style attribute after every author rule, so a plain\n declaration here would ship, match, and still lose.\n - the TOKENS must be visible ones. `bg-layer-1/2/3` all resolve to\n `neutral-bluish-00` in the light theme, so a layer swap is a no-op there, and\n `border-l1`\u2192`l2` is 4%\u219210% black on a 1px line. The app\'s own hovers use\n `interactive-bg-hover`; using it here is what makes the feedback exist. */\n.028bc91c_testButton {\n transition: border-color 120ms ease, background 120ms ease;\n}\n\n.028bc91c_testButton:hover:not(:disabled) {\n border-color: var(--dsw-alias-border-l3) !important;\n background: var(--dsw-alias-interactive-bg-hover) !important;\n}\n\n/* ---- diagnostics ---- */\n\n.5d114b0b_diag {\n margin-top: 2px;\n padding: 8px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.27518b39_diagSummary {\n cursor: pointer;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.b3005b62_diagList {\n margin: 6px 0 0;\n padding-left: 18px;\n font-family: ui-monospace, monospace;\n word-break: break-all;\n}\n';
48
+ __dshStyle.textContent = '/* dsh-provider-proxy \u2014 provider proxy settings page.\n Tokens only: no literal colours, matching the shared --dsw-* system. */\n\n.bcf3ba25_root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-width: 640px;\n}\n\n.86cd7a9a_lead {\n margin: 0 0 4px;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\n}\n\n.4ec3d74d_empty {\n margin: 0;\n padding: 16px;\n border: 1px dashed var(--dsw-alias-border-l1);\n border-radius: 8px;\n font-size: 12px;\n text-align: center;\n color: var(--dsw-alias-state-idle-primary);\n}\n\n/* ---- provider card ---- */\n\n.61dfce67_card,\n.56b6c433_cardActive,\n.24b300e2_cardRejected {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 12px 14px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.56b6c433_cardActive {\n border-color: var(--dsw-alias-state-success-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An enabled route the Host refused: never dress it as active. */\n.24b300e2_cardRejected {\n border-color: var(--dsw-alias-state-error-primary, #f85149);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* ---- shared default endpoint ---- */\n\n/* A distinct outer layer, so it reads as "one setting for the page" rather than\n as one more provider card. */\n.665ef72b_defaultCard {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px 16px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.351db191_defaultLead {\n margin: -2px 0 0;\n font-size: 11px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n\n.9ee651d9_cardHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.bb118443_cardTitles {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.a1b940d4_name {\n overflow: hidden;\n font-size: 13px;\n font-weight: 600;\n white-space: nowrap;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.1870f756_id {\n flex: none;\n padding: 1px 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n font-family: ui-monospace, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* ---- status pill ---- */\n\n.e9cfdcad_pillOn,\n.3d3b7405_pillLive,\n.a517f862_pillOff,\n.3e11f4ea_pillRejected {\n flex: none;\n padding: 2px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 500;\n white-space: nowrap;\n}\n\n.e9cfdcad_pillOn {\n background: var(--dsw-alias-state-success-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.3d3b7405_pillLive {\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n\n.a517f862_pillOff {\n color: var(--dsw-alias-state-idle-primary);\n}\n\n.3e11f4ea_pillRejected {\n border: 1px solid var(--dsw-alias-state-error-primary, #f85149);\n color: var(--dsw-alias-state-error-primary, #f85149);\n}\n\n/* ---- toggle ---- */\n\n.31ab2dc8_toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-top: 2px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n\n.31ab2dc8_toggle input {\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n/* ---- fields ---- */\n\n.52ffb01c_field {\n display: grid;\n grid-template-columns: 96px 1fr;\n align-items: center;\n gap: 10px;\n font-size: 12px;\n}\n\n.b8d05488_label {\n color: var(--dsw-alias-label-secondary);\n}\n\n.d8b3bd46_hint {\n grid-column: 2;\n margin-top: -2px;\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.0b74e44b_input {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n padding: 5px 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 6px;\n outline: none;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n transition: border-color 120ms ease, background 120ms ease;\n}\n\n/* The base look of every input is INLINE too, so these three state rules only\n ever take effect as author-important declarations \u2014 same reason as the\n self-check button above. `cursor` has no inline base and needs none, and the\n hover border uses `l3` because `l1`\u2192`l2` is a 4%\u219210% black step nobody can\n see. */\n\n.0b74e44b_input:hover:not(:disabled) {\n border-color: var(--dsw-alias-border-l3) !important;\n}\n\n.0b74e44b_input:focus {\n border-color: var(--dsw-alias-brand-primary) !important;\n}\n\n.0b74e44b_input:disabled {\n color: var(--dsw-alias-state-idle-primary) !important;\n background: var(--dsw-alias-bg-layer-2) !important;\n cursor: not-allowed;\n}\n\n/* ---- self-check ---- */\n\n/* The hover state is the one thing an inline style cannot express, which is why\n this element carries a class at all. Layout stays inline.\n\n Two traps, both hit before this comment existed:\n - `!important` is load-bearing: the base border and background are INLINE, and\n the cascade sorts a style attribute after every author rule, so a plain\n declaration here would ship, match, and still lose.\n - the TOKENS must be visible ones. `bg-layer-1/2/3` all resolve to\n `neutral-bluish-00` in the light theme, so a layer swap is a no-op there, and\n `border-l1`\u2192`l2` is 4%\u219210% black on a 1px line. The app\'s own hovers use\n `interactive-bg-hover`; using it here is what makes the feedback exist. */\n.47b71ed7_testButton {\n transition: border-color 120ms ease, background 120ms ease;\n}\n\n.47b71ed7_testButton:hover:not(:disabled) {\n border-color: var(--dsw-alias-border-l3) !important;\n background: var(--dsw-alias-interactive-bg-hover) !important;\n}\n\n/* ---- diagnostics ---- */\n\n.f395d2a5_diag {\n margin-top: 2px;\n padding: 8px 10px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.84895b20_diagSummary {\n cursor: pointer;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.9ecca3cd_diagList {\n margin: 6px 0 0;\n padding-left: 18px;\n font-family: ui-monospace, monospace;\n word-break: break-all;\n}\n';
49
49
  document.head.appendChild(__dshStyle);
50
50
  }
51
51
  var __dshStyle;
@@ -65,17 +65,34 @@ module.exports = __toCommonJS(client_exports);
65
65
  var import_client = __toESM(require_client(), 1);
66
66
 
67
67
  // src/proxy-address.ts
68
- function defaultProxyProblem(endpoint) {
69
- const host = (endpoint.host ?? "").trim();
68
+ var PROXY_HOST_INVALID = "\u4EE3\u7406\u5730\u5740\u53EA\u80FD\u586B IPv4\u3001IPv6 \u6216\u57DF\u540D";
69
+ 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)$/;
70
+ var DOMAIN = /^(?=.*[a-z])(?=.{1,253}$)(?!.*\.\d+$)[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?)*$/i;
71
+ var IPV6_CHARS = /^[0-9a-f:.]+$/i;
72
+ function proxyHostProblem(rawHost) {
73
+ const host = (rawHost ?? "").trim().replace(/\.$/, "");
70
74
  if (host.length === 0) return "\u672A\u586B\u5199\u4EE3\u7406\u5730\u5740";
71
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(host) || host.includes("/")) {
72
- return "\u4EE3\u7406\u5730\u5740\u53EA\u586B\u4E3B\u673A\u540D\u6216 IP\uFF0C\u4E0D\u8981\u5E26\u534F\u8BAE\u524D\u7F00\u6216\u8DEF\u5F84";
75
+ if (IPV4.test(host)) return void 0;
76
+ if (host.includes(":") || host.startsWith("[")) {
77
+ const literal = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
78
+ if (!IPV6_CHARS.test(literal)) return PROXY_HOST_INVALID;
79
+ try {
80
+ new URL(`http://[${literal}]:1`);
81
+ return void 0;
82
+ } catch {
83
+ return PROXY_HOST_INVALID;
84
+ }
73
85
  }
86
+ return DOMAIN.test(host) ? void 0 : PROXY_HOST_INVALID;
87
+ }
88
+ function defaultProxyProblem(endpoint) {
89
+ const hostProblem = proxyHostProblem(endpoint.host);
90
+ if (hostProblem !== void 0) return hostProblem;
74
91
  const port = endpoint.port ?? 0;
75
92
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
76
93
  return "\u4EE3\u7406\u7AEF\u53E3\u65E0\u6548\uFF08\u9700\u4E3A 1-65535 \u7684\u6574\u6570\uFF09";
77
94
  }
78
- const hasUser = (endpoint.username ?? "").length > 0;
95
+ const hasUser = (endpoint.username ?? "").trim().length > 0;
79
96
  const hasPassword = (endpoint.password ?? "").length > 0;
80
97
  if (hasUser !== hasPassword) {
81
98
  return hasUser ? "\u914D\u7F6E\u4E86\u4EE3\u7406\u7528\u6237\u540D\u4F46\u6CA1\u6709\u5BC6\u7801\uFF1B\u8BF7\u8865\u5168\u5BC6\u7801\u6216\u6E05\u7A7A\u7528\u6237\u540D" : "\u914D\u7F6E\u4E86\u4EE3\u7406\u5BC6\u7801\u4F46\u6CA1\u6709\u7528\u6237\u540D\uFF1B\u8BF7\u8865\u5168\u7528\u6237\u540D\u6216\u6E05\u7A7A\u5BC6\u7801";
@@ -170,12 +187,15 @@ async function writeConfig(ctx, ops) {
170
187
  }
171
188
  function knownProviders(configured, published) {
172
189
  const rows = /* @__PURE__ */ new Map();
190
+ const byId = /* @__PURE__ */ new Map();
173
191
  for (const row of published) {
192
+ byId.set(row.id, row);
174
193
  if (row.live) rows.set(row.id, row);
175
194
  }
176
- for (const [id, route] of Object.entries(configured)) {
195
+ for (const id of Object.keys(configured)) {
177
196
  if (rows.has(id)) continue;
178
- rows.set(id, { id, name: id, live: route?.enabled === true });
197
+ const known = byId.get(id);
198
+ rows.set(id, { id, name: known?.name ?? id, live: known?.live === true });
179
199
  }
180
200
  return [...rows.values()].sort(byName);
181
201
  }
@@ -293,6 +313,14 @@ function view(route) {
293
313
  function unbracket(hostname) {
294
314
  return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
295
315
  }
316
+ function explicitPortOf(text) {
317
+ const authority = text.slice(text.indexOf("://") + 3).split(/[/?#]/, 1)[0];
318
+ const hostPort = authority.slice(authority.lastIndexOf("@") + 1);
319
+ const match = /^\[[^\]]+\]:(\d+)$/.exec(hostPort) ?? /^[^:]+:(\d+)$/.exec(hostPort);
320
+ if (match === null) return 0;
321
+ const port = Number.parseInt(match[1], 10);
322
+ return Number.isNaN(port) ? 0 : port;
323
+ }
296
324
  function splitProxyUrl(text) {
297
325
  const trimmed = text.trim();
298
326
  if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return void 0;
@@ -313,7 +341,7 @@ function splitProxyUrl(text) {
313
341
  return value;
314
342
  }
315
343
  };
316
- const parsedPort = url.port.length > 0 ? Number.parseInt(url.port, 10) : 0;
344
+ const parsedPort = url.port.length > 0 ? Number.parseInt(url.port, 10) : explicitPortOf(trimmed);
317
345
  return {
318
346
  protocol,
319
347
  host: unbracket(url.hostname),
@@ -324,6 +352,39 @@ function splitProxyUrl(text) {
324
352
  }
325
353
  var WRITE_DELAY_MS = 300;
326
354
  var pendingWrites = /* @__PURE__ */ new Map();
355
+ var writeQueue = Promise.resolve();
356
+ function enqueueWrite(run) {
357
+ const next = writeQueue.then(run, run);
358
+ writeQueue = next.catch(() => void 0);
359
+ return next;
360
+ }
361
+ function scheduleWrite(key, run) {
362
+ const pending = pendingWrites.get(key);
363
+ if (pending?.timer !== void 0) clearTimeout(pending.timer);
364
+ const entry = {
365
+ flush: () => {
366
+ if (entry.timer !== void 0) clearTimeout(entry.timer);
367
+ pendingWrites.delete(key);
368
+ return enqueueWrite(run);
369
+ }
370
+ };
371
+ entry.timer = setTimeout(() => {
372
+ void entry.flush();
373
+ }, WRITE_DELAY_MS);
374
+ pendingWrites.set(key, entry);
375
+ }
376
+ var testGeneration = /* @__PURE__ */ new Map();
377
+ var TEST_RELEVANT_FIELDS = /* @__PURE__ */ new Set([
378
+ "protocol",
379
+ "host",
380
+ "port",
381
+ "username",
382
+ "password",
383
+ "onlyHosts",
384
+ "bypassHosts",
385
+ "testTarget",
386
+ "useDefault"
387
+ ]);
327
388
  var diagnostics = [];
328
389
  function note(message) {
329
390
  if (!diagnostics.includes(message)) diagnostics.push(message);
@@ -349,6 +410,11 @@ var S = {
349
410
  borderColor: "var(--dsw-alias-state-success-primary)",
350
411
  background: "var(--dsw-alias-bg-layer-2)"
351
412
  },
413
+ /** An enabled route the Host refused: active-looking styling would be a lie. */
414
+ cardRejected: {
415
+ borderColor: "var(--dsw-alias-state-error-primary, #f85149)",
416
+ background: "var(--dsw-alias-bg-layer-2)"
417
+ },
352
418
  head: {
353
419
  display: "flex",
354
420
  alignItems: "center",
@@ -439,6 +505,10 @@ var S = {
439
505
  color: "var(--dsw-alias-label-secondary)"
440
506
  },
441
507
  pillOff: { color: "var(--dsw-alias-state-idle-primary)" },
508
+ pillRejected: {
509
+ border: "1px solid var(--dsw-alias-state-error-primary, #f85149)",
510
+ color: "var(--dsw-alias-state-error-primary, #f85149)"
511
+ },
442
512
  /**
443
513
  * The shared default endpoint sits on its own layer so it reads as global
444
514
  * rather than as one more provider card — the mistake it exists to prevent is
@@ -521,14 +591,20 @@ function renderPage(ctx) {
521
591
  const [state, setState] = (React.useState ?? ((fn) => [fn(), () => {
522
592
  }]))(() => {
523
593
  void fetchCatalog().then((catalog) => catalogueSetter?.(catalog)).catch((error) => {
524
- note(`provider \u5217\u8868\u83B7\u53D6\u5931\u8D25\uFF1A${error?.message ?? error}`);
525
- catalogueSetter?.({ rows: [], hits: {}, problems: {}, lastUse: {} });
594
+ catalogueSetter?.({
595
+ rows: [],
596
+ hits: {},
597
+ problems: {},
598
+ lastUse: {},
599
+ error: String(error?.message ?? error)
600
+ });
526
601
  });
527
602
  return {
528
603
  rows: void 0,
529
604
  hits: {},
530
605
  problems: {},
531
606
  lastUse: {},
607
+ error: void 0,
532
608
  pending: true
533
609
  };
534
610
  });
@@ -537,6 +613,7 @@ function renderPage(ctx) {
537
613
  hits: catalog.hits,
538
614
  problems: catalog.problems,
539
615
  lastUse: catalog.lastUse,
616
+ error: catalog.error,
540
617
  pending: false
541
618
  });
542
619
  touchCatalogPolling();
@@ -566,20 +643,47 @@ function renderPage(ctx) {
566
643
  const editDefault = (field2, value) => setDefaultDraft((current) => ({ ...current, [field2]: value }));
567
644
  const [tests, setTests] = (React.useState ?? ((v) => [v, () => {
568
645
  }]))({});
646
+ const invalidateTests = (ids) => {
647
+ if (ids.length === 0) return;
648
+ for (const id of ids) {
649
+ testGeneration.set(id, (testGeneration.get(id) ?? 0) + 1);
650
+ }
651
+ setTests((current) => {
652
+ let changed = false;
653
+ const next = { ...current };
654
+ for (const id of ids) {
655
+ if (next[id] === void 0) continue;
656
+ delete next[id];
657
+ changed = true;
658
+ }
659
+ return changed ? next : current;
660
+ });
661
+ };
569
662
  const [filter, setFilter] = (React.useState ?? ((v) => [v, () => {
570
663
  }]))("");
571
664
  const runTest = (id) => {
665
+ const generation = (testGeneration.get(id) ?? 0) + 1;
666
+ testGeneration.set(id, generation);
572
667
  setTests((current) => ({ ...current, [id]: { pending: true } }));
573
668
  void (async () => {
574
669
  try {
670
+ for (const [key, entry] of [...pendingWrites]) {
671
+ if (key.startsWith(`${id}\0`) || key.startsWith("\0default\0")) {
672
+ void entry.flush();
673
+ }
674
+ }
675
+ await writeQueue;
676
+ if (testGeneration.get(id) !== generation) return;
575
677
  const response = await globalThis.fetch(TEST_PATH, {
576
678
  method: "POST",
577
679
  headers: { "content-type": "application/json", accept: "application/json" },
578
680
  body: JSON.stringify({ provider: id })
579
681
  });
580
682
  const body2 = await response.json();
683
+ if (testGeneration.get(id) !== generation) return;
581
684
  setTests((current) => ({ ...current, [id]: testResultFrom(body2, t) }));
582
685
  } catch (error) {
686
+ if (testGeneration.get(id) !== generation) return;
583
687
  setTests((current) => ({
584
688
  ...current,
585
689
  [id]: { pending: false, ok: false, error: String(error?.message ?? error) }
@@ -588,19 +692,17 @@ function renderPage(ctx) {
588
692
  })();
589
693
  };
590
694
  const rows = knownProviders({ ...configured, ...draft }, published);
695
+ const inheritingIds = () => rows.filter((row) => view({ ...configured[row.id], ...draft[row.id] }).useDefault === true).map((row) => row.id);
591
696
  const save = (key, path, label, value, onRefused) => {
592
- const pending = pendingWrites.get(key);
593
- if (pending !== void 0) clearTimeout(pending.timer);
594
- const timer = setTimeout(() => {
595
- pendingWrites.delete(key);
596
- void writeConfig(ctx, [{ op: "set", path, value }]).then(() => {
697
+ scheduleWrite(
698
+ key,
699
+ () => writeConfig(ctx, [{ op: "set", path, value }]).then(() => {
597
700
  note(`\u5DF2\u4FDD\u5B58 ${label} = ${JSON.stringify(value)}`);
598
701
  }).catch((error) => {
599
702
  note(`\u4FDD\u5B58\u5931\u8D25 ${label}\uFF1A${error?.message ?? error}`);
600
703
  onRefused();
601
- });
602
- }, WRITE_DELAY_MS);
603
- pendingWrites.set(key, { timer });
704
+ })
705
+ );
604
706
  };
605
707
  const rollbackRouteField = (id, field2) => {
606
708
  setDraft((current) => {
@@ -616,6 +718,7 @@ function renderPage(ctx) {
616
718
  };
617
719
  const bind = (id, field2) => (value) => {
618
720
  edit(id, field2, value);
721
+ if (TEST_RELEVANT_FIELDS.has(field2)) invalidateTests([id]);
619
722
  save(
620
723
  `${id}\0${field2}`,
621
724
  ["providers", id, field2],
@@ -626,6 +729,7 @@ function renderPage(ctx) {
626
729
  };
627
730
  const bindDefault = (field2) => (value) => {
628
731
  editDefault(field2, value);
732
+ invalidateTests(inheritingIds());
629
733
  save(`\0default\0${field2}`, ["defaultProxy", field2], `defaultProxy.${field2}`, value, () => {
630
734
  setDefaultDraft((current) => {
631
735
  if (current[field2] === void 0) return current;
@@ -636,18 +740,15 @@ function renderPage(ctx) {
636
740
  });
637
741
  };
638
742
  const saveMany = (key, ops, label, onRefused) => {
639
- const pending = pendingWrites.get(key);
640
- if (pending !== void 0) clearTimeout(pending.timer);
641
- const timer = setTimeout(() => {
642
- pendingWrites.delete(key);
643
- void writeConfig(ctx, ops).then(() => {
743
+ scheduleWrite(
744
+ key,
745
+ () => writeConfig(ctx, ops).then(() => {
644
746
  note(`\u5DF2\u4FDD\u5B58 ${label}`);
645
747
  }).catch((error) => {
646
748
  note(`\u4FDD\u5B58\u5931\u8D25 ${label}\uFF1A${error?.message ?? error}`);
647
749
  onRefused();
648
- });
649
- }, WRITE_DELAY_MS);
650
- pendingWrites.set(key, { timer });
750
+ })
751
+ );
651
752
  };
652
753
  const rollbackRouteFields = (id, fields) => {
653
754
  setDraft((current) => {
@@ -663,6 +764,7 @@ function renderPage(ctx) {
663
764
  };
664
765
  const applyCardUrl = (id, parts) => {
665
766
  setDraft((current) => ({ ...current, [id]: { ...current[id], ...parts } }));
767
+ invalidateTests([id]);
666
768
  const fields = Object.keys(parts);
667
769
  saveMany(
668
770
  `${id}\0*paste`,
@@ -673,6 +775,7 @@ function renderPage(ctx) {
673
775
  };
674
776
  const applyDefaultUrl = (parts) => {
675
777
  setDefaultDraft((current) => ({ ...current, ...parts }));
778
+ invalidateTests(inheritingIds());
676
779
  const fields = Object.keys(parts);
677
780
  saveMany(
678
781
  "\0default\0*paste",
@@ -705,6 +808,7 @@ function renderPage(ctx) {
705
808
  for (const id of ids) next[id] = { ...next[id], useDefault: true };
706
809
  return next;
707
810
  });
811
+ invalidateTests(ids);
708
812
  saveMany(
709
813
  "\0default\0applyAll",
710
814
  ids.map((id) => ({ op: "set", path: ["providers", id, "useDefault"], value: true })),
@@ -754,7 +858,8 @@ function renderPage(ctx) {
754
858
  const storedDefault = snapshot.value?.defaultProxy ?? {};
755
859
  const defaultView = view({ ...storedDefault, ...defaultDraft });
756
860
  const defaultWhy = defaultProxyProblem(defaultView);
757
- const defaultUsable = defaultWhy === void 0;
861
+ const defaultHostWhy = defaultView.host.trim().length === 0 ? void 0 : proxyHostProblem(defaultView.host);
862
+ const defaultComplete = defaultWhy === void 0;
758
863
  const defaultOrigin = `${defaultView.protocol}://${defaultView.host}:${defaultView.port}`;
759
864
  const defaultBlock = h(
760
865
  "div",
@@ -770,10 +875,10 @@ function renderPage(ctx) {
770
875
  h(
771
876
  "span",
772
877
  {
773
- className: defaultUsable ? import_client.default.pillOn : import_client.default.pillOff,
774
- style: { ...S.pill, ...defaultUsable ? S.pillOn : S.pillOff }
878
+ className: defaultComplete ? import_client.default.pillOn : import_client.default.pillOff,
879
+ style: { ...S.pill, ...defaultComplete ? S.pillOn : S.pillOff }
775
880
  },
776
- defaultUsable ? t.on : t.off
881
+ defaultComplete ? t.complete : t.notConfigured
777
882
  )
778
883
  ),
779
884
  h("p", { className: import_client.default.defaultLead, style: S.defaultLead }, t.defaultLead),
@@ -803,7 +908,8 @@ function renderPage(ctx) {
803
908
  // Same convenience as the cards: a full URL fills the whole block.
804
909
  onPaste: onProxyUrlPaste(applyDefaultUrl)
805
910
  }),
806
- t.hostHint
911
+ defaultHostWhy ?? t.hostHint,
912
+ defaultHostWhy !== void 0
807
913
  ),
808
914
  field(
809
915
  t.port,
@@ -844,7 +950,7 @@ function renderPage(ctx) {
844
950
  t.passwordHintProxy
845
951
  ),
846
952
  // Bulk opt-in, in ONE write: a per-card walk would rebuild the table N times.
847
- // Disabled while the default is unusable, because pointing every card at an
953
+ // Disabled while the default configuration is incomplete, because pointing every card at an
848
954
  // empty endpoint would just refuse them all.
849
955
  h(
850
956
  "div",
@@ -855,7 +961,7 @@ function renderPage(ctx) {
855
961
  className: import_client.default.testButton,
856
962
  style: S.testButton,
857
963
  type: "button",
858
- disabled: !defaultUsable || rows.length === 0,
964
+ disabled: !defaultComplete || rows.length === 0,
859
965
  onClick: applyDefaultToAll
860
966
  },
861
967
  t.applyDefaultAll
@@ -874,6 +980,12 @@ function renderPage(ctx) {
874
980
  onChange: (event) => setFilter(event.target.value)
875
981
  })
876
982
  );
983
+ const catalogueStatus = rows.length === 0 ? state.pending ? { text: t.catalogueLoading, error: false } : state.error !== void 0 ? { text: `${t.catalogueError}${state.error}`, error: true } : void 0 : void 0;
984
+ const catalogueNotice = state.error !== void 0 && rows.length > 0 ? h(
985
+ "p",
986
+ { key: "catalogue-error", style: S.inheritWarning },
987
+ `${t.catalogueStale}${state.error}`
988
+ ) : null;
877
989
  const body = shown.length === 0 ? [
878
990
  h(
879
991
  "p",
@@ -887,10 +999,11 @@ function renderPage(ctx) {
887
999
  borderRadius: "8px",
888
1000
  fontSize: "12px",
889
1001
  textAlign: "center",
890
- color: "var(--dsw-alias-state-idle-primary)"
1002
+ color: catalogueStatus?.error === true ? "var(--dsw-alias-state-error-primary, #f85149)" : "var(--dsw-alias-state-idle-primary)",
1003
+ ...catalogueStatus?.error === true ? { fontWeight: "500" } : {}
891
1004
  }
892
1005
  },
893
- query.length > 0 ? t.noMatch : t.empty
1006
+ catalogueStatus?.text ?? (query.length > 0 ? t.noMatch : t.empty)
894
1007
  )
895
1008
  ] : shown.map((row) => {
896
1009
  const route = view({ ...configured[row.id], ...draft[row.id] });
@@ -899,8 +1012,15 @@ function renderPage(ctx) {
899
1012
  const onlyDead = deadRuleEntries(route.onlyHosts);
900
1013
  const bypassDead = deadRuleEntries(route.bypassHosts);
901
1014
  const useDefault = route.useDefault === true;
902
- const endpointStored = route.useDefault ? defaultUsable : route.host.trim().length > 0 && Number.isInteger(route.port) && route.port >= 1 && route.port <= 65535;
1015
+ const endpointStored = route.useDefault ? defaultComplete : route.host.trim().length > 0 && Number.isInteger(route.port) && route.port >= 1 && route.port <= 65535;
903
1016
  const showTest = route.enabled || endpointStored;
1017
+ const problemText = (() => {
1018
+ const recorded = problems[row.id];
1019
+ if (recorded !== void 0) return recorded;
1020
+ if (useDefault) return defaultComplete ? void 0 : `${t.defaultMissing}${defaultWhy}`;
1021
+ return route.enabled ? defaultProxyProblem(route) : void 0;
1022
+ })();
1023
+ const rejected = route.enabled && problemText !== void 0;
904
1024
  const endpointFields = [
905
1025
  field(
906
1026
  t.protocol,
@@ -975,11 +1095,12 @@ function renderPage(ctx) {
975
1095
  route.protocol === "socks5" ? t.passwordHint : t.passwordHintProxy
976
1096
  )
977
1097
  ];
1098
+ const pill = rejected ? { className: import_client.default.pillRejected, style: { ...S.pill, ...S.pillRejected }, text: t.rejected } : route.enabled ? { className: import_client.default.pillOn, style: { ...S.pill, ...S.pillOn }, text: t.on } : { className: import_client.default.pillLive, style: { ...S.pill, ...S.pillLive }, text: t.off };
978
1099
  return h(
979
1100
  "section",
980
1101
  {
981
- className: route.enabled ? import_client.default.cardActive : import_client.default.card,
982
- style: route.enabled ? { ...S.card, ...S.cardActive } : S.card,
1102
+ className: rejected ? import_client.default.cardRejected : route.enabled ? import_client.default.cardActive : import_client.default.card,
1103
+ style: rejected ? { ...S.card, ...S.cardRejected } : route.enabled ? { ...S.card, ...S.cardActive } : S.card,
983
1104
  key: row.id
984
1105
  },
985
1106
  h(
@@ -1010,17 +1131,7 @@ function renderPage(ctx) {
1010
1131
  lastText
1011
1132
  )
1012
1133
  ),
1013
- h(
1014
- "span",
1015
- {
1016
- className: route.enabled ? import_client.default.pillOn : row.live ? import_client.default.pillLive : import_client.default.pillOff,
1017
- style: {
1018
- ...S.pill,
1019
- ...route.enabled ? S.pillOn : row.live ? S.pillLive : S.pillOff
1020
- }
1021
- },
1022
- route.enabled ? t.on : row.live ? t.live : t.off
1023
- )
1134
+ h("span", { className: pill.className, style: pill.style }, pill.text)
1024
1135
  ),
1025
1136
  h(
1026
1137
  "label",
@@ -1046,16 +1157,12 @@ function renderPage(ctx) {
1046
1157
  }),
1047
1158
  h("span", null, t.useDefault)
1048
1159
  ),
1049
- ...useDefault ? [
1050
- h(
1051
- "p",
1052
- {
1053
- key: `${row.id}:inherit`,
1054
- style: defaultUsable ? S.inheritNote : S.inheritWarning
1055
- },
1056
- defaultUsable ? `${t.inheritingDefault}${defaultOrigin}` : `${t.defaultMissing}${defaultWhy}`
1057
- )
1058
- ] : endpointFields,
1160
+ problemText !== void 0 ? h("p", { key: `${row.id}:problem`, style: S.inheritWarning }, problemText) : useDefault ? h(
1161
+ "p",
1162
+ { key: `${row.id}:inherit`, style: S.inheritNote },
1163
+ `${t.inheritingDefault}${defaultOrigin}`
1164
+ ) : null,
1165
+ ...useDefault ? [] : endpointFields,
1059
1166
  // The self-check is offered whenever there is something to check: an
1060
1167
  // enabled route (the Host's table carries it, and a rejected one still
1061
1168
  // reports WHY), or a disabled one whose endpoint is already complete —
@@ -1158,6 +1265,7 @@ function renderPage(ctx) {
1158
1265
  { className: import_client.default.root, style: S.root },
1159
1266
  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),
1160
1267
  defaultBlock,
1268
+ catalogueNotice,
1161
1269
  filterRow,
1162
1270
  ...body,
1163
1271
  h(
@@ -1194,14 +1302,17 @@ function renderPage(ctx) {
1194
1302
  );
1195
1303
  }
1196
1304
  var EN = {
1197
- lead: "Route selected model providers through your own forward proxy. Other providers stay direct. Requests to loopback or private addresses (localhost, 127.x, private ranges) always go direct \u2014 a forward proxy cannot reach your own machine.",
1305
+ lead: "Route selected model providers through your own forward proxy. Other providers stay direct. Requests to loopback or private addresses (localhost, 127.x, private ranges) go direct by default; set the top-level localDirect to false to proxy them instead \u2014 a forward proxy normally cannot reach your own machine.",
1198
1306
  empty: "No providers found yet.",
1307
+ catalogueLoading: "Loading providers\u2026",
1308
+ catalogueError: "Could not load the provider list: ",
1309
+ catalogueStale: "Could not load the provider list; showing the saved configuration: ",
1199
1310
  useProxy: "Use a proxy for this provider",
1200
1311
  useDefault: "Use the default proxy",
1201
1312
  defaultTitle: "Default proxy",
1202
- defaultLead: 'One endpoint, reused by every provider that turns on "Use the default proxy".',
1313
+ defaultLead: 'One endpoint, reused by every provider that turns on "Use the default proxy". The status only reports configuration completeness; use the Test button on a card to verify reachability.',
1203
1314
  inheritingDefault: "Using the default proxy: ",
1204
- defaultMissing: "The default proxy cannot be used, so this provider is not proxied: ",
1315
+ defaultMissing: "The default proxy configuration is incomplete, so this provider is not proxied: ",
1205
1316
  filterLabel: "Filter",
1206
1317
  filterPlaceholder: "Filter by name or id\u2026",
1207
1318
  noMatch: "No provider matches the filter.",
@@ -1212,7 +1323,7 @@ var EN = {
1212
1323
  port: "Port",
1213
1324
  username: "Username",
1214
1325
  password: "Password",
1215
- hostHint: "Host or IP only, without scheme or path. A full proxy URL (socks5://user:pass@host:1080) can be pasted instead \u2014 it is split into the fields.",
1326
+ hostHint: "Only an IPv4, IPv6, or domain name is allowed. A full proxy URL (socks5://user:pass@host:1080) can be pasted instead \u2014 it is split into the fields.",
1216
1327
  passwordHint: "Sent to the SOCKS5 proxy for authentication; stored with this setting.",
1217
1328
  passwordHintProxy: "Sent to the proxy as an authentication password; stored with this setting.",
1218
1329
  hitCountPrefix: "",
@@ -1241,20 +1352,25 @@ var EN = {
1241
1352
  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.",
1242
1353
  socks5Hint: "SOCKS5 tunnels the connection; the proxy resolves the target host name.",
1243
1354
  on: "proxied",
1244
- live: "active",
1355
+ rejected: "config rejected",
1245
1356
  off: "direct",
1357
+ complete: "complete",
1358
+ notConfigured: "not configured",
1246
1359
  diagnostics: "Connection diagnostics",
1247
1360
  diagPending: "(collecting\u2026)"
1248
1361
  };
1249
1362
  var ZH = {
1250
- 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\u59CB\u7EC8\u76F4\u8FDE\u2014\u2014\u6B63\u5411\u4EE3\u7406\u591F\u4E0D\u5230\u4F60\u81EA\u5DF1\u7684\u673A\u5668\u3002",
1363
+ 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",
1251
1364
  empty: "\u6CA1\u6709\u68C0\u6D4B\u5230\u4EFB\u4F55 provider\u3002",
1365
+ catalogueLoading: "\u6B63\u5728\u8BFB\u53D6 provider \u5217\u8868\u2026",
1366
+ catalogueError: "\u8BFB\u53D6 provider \u5217\u8868\u5931\u8D25\uFF1A",
1367
+ catalogueStale: "provider \u5217\u8868\u8BFB\u53D6\u5931\u8D25\uFF0C\u4E0B\u9762\u663E\u793A\u7684\u662F\u5DF2\u4FDD\u5B58\u7684\u914D\u7F6E\uFF1A",
1252
1368
  useProxy: "\u4E3A\u8BE5 provider \u542F\u7528\u4EE3\u7406",
1253
1369
  useDefault: "\u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406",
1254
1370
  defaultTitle: "\u9ED8\u8BA4\u4EE3\u7406",
1255
- defaultLead: "\u4E3A\u52FE\u9009\u300C\u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406\u300D\u7684 provider \u590D\u7528\u7684\u7AEF\u70B9\uFF0C\u53EA\u586B\u4E00\u6B21\u3002",
1371
+ defaultLead: "\u4E3A\u52FE\u9009\u300C\u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406\u300D\u7684 provider \u590D\u7528\u7684\u7AEF\u70B9\uFF0C\u53EA\u586B\u4E00\u6B21\u3002\u72B6\u6001\u53EA\u8868\u793A\u914D\u7F6E\u662F\u5426\u5B8C\u6574\uFF1B\u4EE3\u7406\u662F\u5426\u53EF\u8FBE\u8BF7\u7528\u5361\u7247\u4E0A\u7684\u300C\u6D4B\u8BD5\u300D\u9A8C\u8BC1\u3002",
1256
1372
  inheritingDefault: "\u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406\uFF1A",
1257
- defaultMissing: "\u9876\u5C42\u9ED8\u8BA4\u4EE3\u7406\u4E0D\u53EF\u7528\uFF0C\u8BE5 provider \u4E0D\u4F1A\u8D70\u4EE3\u7406\uFF1A",
1373
+ defaultMissing: "\u9876\u5C42\u9ED8\u8BA4\u4EE3\u7406\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF0C\u8BE5 provider \u4E0D\u4F1A\u8D70\u4EE3\u7406\uFF1A",
1258
1374
  filterLabel: "\u8FC7\u6EE4",
1259
1375
  filterPlaceholder: "\u6309\u540D\u79F0\u6216 id \u8FC7\u6EE4\u2026",
1260
1376
  noMatch: "\u6CA1\u6709\u5339\u914D\u7684 provider\u3002",
@@ -1265,7 +1381,7 @@ var ZH = {
1265
1381
  port: "\u7AEF\u53E3",
1266
1382
  username: "\u7528\u6237\u540D",
1267
1383
  password: "\u5BC6\u7801",
1268
- hostHint: "\u53EA\u586B\u4E3B\u673A\u540D\u6216 IP\uFF0C\u4E0D\u8981\u5E26\u534F\u8BAE\u524D\u7F00\u6216\u8DEF\u5F84\uFF1B\u4E5F\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u5B8C\u6574\u4EE3\u7406 URL\uFF08\u5982 socks5://user:pass@host:1080\uFF09\uFF0C\u4F1A\u81EA\u52A8\u62C6\u5206\u5230\u5404\u5B57\u6BB5\u3002",
1384
+ hostHint: "\u53EA\u5141\u8BB8\u586B\u5199 IPv4\u3001IPv6 \u6216\u57DF\u540D\uFF1B\u4E5F\u53EF\u4EE5\u76F4\u63A5\u7C98\u8D34\u5B8C\u6574\u4EE3\u7406 URL\uFF08\u5982 socks5://user:pass@host:1080\uFF09\uFF0C\u4F1A\u81EA\u52A8\u62C6\u5206\u5230\u5404\u5B57\u6BB5\u3002",
1269
1385
  passwordHint: "\u4EC5\u4F5C\u4E3A SOCKS5 \u8BA4\u8BC1\u53E3\u4EE4\u53D1\u9001\u7ED9\u4EE3\u7406\uFF1B\u968F\u8BE5\u8BBE\u7F6E\u4E00\u8D77\u4FDD\u5B58\u3002",
1270
1386
  passwordHintProxy: "\u4F5C\u4E3A\u4EE3\u7406\u8BA4\u8BC1\u53E3\u4EE4\u53D1\u9001\u7ED9\u4EE3\u7406\uFF1B\u968F\u8BE5\u8BBE\u7F6E\u4E00\u8D77\u4FDD\u5B58\u3002",
1271
1387
  hitCountPrefix: "\u5DF2\u4EE3\u7406 ",
@@ -1294,8 +1410,10 @@ var ZH = {
1294
1410
  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",
1295
1411
  socks5Hint: "SOCKS5 \u4F1A\u5EFA\u7ACB\u96A7\u9053\uFF0C\u76EE\u6807\u57DF\u540D\u7531\u4EE3\u7406\u7AEF\u89E3\u6790\u3002",
1296
1412
  on: "\u8D70\u4EE3\u7406",
1297
- live: "\u542F\u7528\u4E2D",
1413
+ rejected: "\u914D\u7F6E\u88AB\u62D2",
1298
1414
  off: "\u76F4\u8FDE",
1415
+ complete: "\u914D\u7F6E\u5B8C\u6574",
1416
+ notConfigured: "\u672A\u914D\u7F6E",
1299
1417
  diagnostics: "\u8FDE\u63A5\u8BCA\u65AD",
1300
1418
  diagPending: "\uFF08\u6B63\u5728\u6536\u96C6\u2026\uFF09"
1301
1419
  };