dsh-provider-proxy 0.6.1 → 0.8.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/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": "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" };
42
+ var __dshMap = { "root": "1c0df77c_root", "lead": "5d248416_lead", "empty": "1710aef8_empty", "card": "b4a56347_card", "cardActive": "5d17f1d6_cardActive", "cardRejected": "0b12be1b_cardRejected", "defaultCard": "4dc8a19a_defaultCard", "defaultLead": "593693ac_defaultLead", "cardHead": "0c3d7976_cardHead", "cardTitles": "ba5490f7_cardTitles", "name": "fc223200_name", "id": "b54fcb50_id", "pillOn": "2d0e393c_pillOn", "pillLive": "ed98388d_pillLive", "pillOff": "07959fb6_pillOff", "pillRejected": "23916369_pillRejected", "toggle": "44052e5b_toggle", "field": "7826b254_field", "label": "fd3de42a_label", "hint": "9c1dd04c_hint", "input": "0cb12e78_input", "passwordToggle": "e7ed8e99_passwordToggle", "testButton": "3ea31502_testButton", "diag": "8093bbdb_diag", "diagSummary": "bb056060_diagSummary", "diagList": "643d5158_diagList", "chipRow": "cf7604fc_chipRow", "chip": "69584a20_chip", "chipActive": "9e9e3b3e_chipActive", "formGrid": "07283bc3_formGrid", "formGridFull": "31635992_formGridFull", "compactRow": "10f37453_compactRow", "compactRowActive": "b8d0ab91_compactRowActive", "compactRowRejected": "518f0c81_compactRowRejected", "copyButton": "4b86d627_copyButton" };
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.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';
48
+ __dshStyle.textContent = '/* dsh-provider-proxy \u2014 provider proxy settings page.\r\n Tokens only: no literal colours, matching the shared --dsw-* system. */\r\n\r\n.1c0df77c_root {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n max-width: 640px;\r\n}\r\n\r\n.5d248416_lead {\r\n margin: 0 0 4px;\r\n font-size: 12px;\r\n line-height: 1.6;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.1710aef8_empty {\r\n margin: 0;\r\n padding: 16px;\r\n border: 1px dashed var(--dsw-alias-border-l1);\r\n border-radius: 8px;\r\n font-size: 12px;\r\n text-align: center;\r\n color: var(--dsw-alias-state-idle-primary);\r\n}\r\n\r\n/* ---- provider card ---- */\r\n\r\n.b4a56347_card,\r\n.5d17f1d6_cardActive,\r\n.0b12be1b_cardRejected {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 10px;\r\n padding: 12px 14px;\r\n border: 1px solid var(--dsw-alias-border-l1);\r\n border-radius: 10px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n}\r\n\r\n.5d17f1d6_cardActive {\r\n border-color: var(--dsw-alias-state-success-primary);\r\n background: var(--dsw-alias-bg-layer-2);\r\n}\r\n\r\n/* An enabled route the Host refused: never dress it as active. */\r\n.0b12be1b_cardRejected {\r\n border-color: var(--dsw-alias-state-error-primary, #f85149);\r\n background: var(--dsw-alias-bg-layer-2);\r\n}\r\n\r\n/* ---- shared default endpoint ---- */\r\n\r\n/* A distinct outer layer, so it reads as "one setting for the page" rather than\r\n as one more provider card. */\r\n.4dc8a19a_defaultCard {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 10px;\r\n padding: 14px 16px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 10px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n}\r\n\r\n.593693ac_defaultLead {\r\n margin: -2px 0 0;\r\n font-size: 11px;\r\n line-height: 1.5;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.0c3d7976_cardHead {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 12px;\r\n}\r\n\r\n.ba5490f7_cardTitles {\r\n display: flex;\r\n align-items: baseline;\r\n gap: 8px;\r\n min-width: 0;\r\n}\r\n\r\n.fc223200_name {\r\n overflow: hidden;\r\n font-size: 13px;\r\n font-weight: 600;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.b54fcb50_id {\r\n flex: none;\r\n padding: 1px 6px;\r\n border-radius: 4px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n font-family: ui-monospace, monospace;\r\n font-size: 11px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* ---- status pill ---- */\r\n\r\n.2d0e393c_pillOn,\r\n.ed98388d_pillLive,\r\n.07959fb6_pillOff,\r\n.23916369_pillRejected {\r\n flex: none;\r\n padding: 2px 8px;\r\n border-radius: 999px;\r\n font-size: 11px;\r\n font-weight: 500;\r\n white-space: nowrap;\r\n}\r\n\r\n.2d0e393c_pillOn {\r\n background: var(--dsw-alias-state-success-primary);\r\n color: var(--dsw-alias-bg-base);\r\n}\r\n\r\n.ed98388d_pillLive {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.07959fb6_pillOff {\r\n color: var(--dsw-alias-state-idle-primary);\r\n}\r\n\r\n.23916369_pillRejected {\r\n border: 1px solid var(--dsw-alias-state-error-primary, #f85149);\r\n color: var(--dsw-alias-state-error-primary, #f85149);\r\n}\r\n\r\n/* ---- toggle ---- */\r\n\r\n.44052e5b_toggle {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding-top: 2px;\r\n font-size: 12px;\r\n color: var(--dsw-alias-label-primary);\r\n cursor: pointer;\r\n}\r\n\r\n.44052e5b_toggle input {\r\n accent-color: var(--dsw-alias-brand-primary);\r\n cursor: pointer;\r\n}\r\n\r\n/* ---- fields ---- */\r\n\r\n.7826b254_field {\r\n display: grid;\r\n grid-template-columns: 76px 1fr;\r\n align-items: center;\r\n gap: 8px;\r\n font-size: 12px;\r\n min-width: 0;\r\n}\r\n\r\n.fd3de42a_label {\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.9c1dd04c_hint {\r\n grid-column: 1 / -1;\r\n margin-top: 2px;\r\n font-size: 11px;\r\n color: var(--dsw-alias-state-warn-primary, #d29922);\r\n}\r\n\r\n.0cb12e78_input {\r\n box-sizing: border-box;\r\n width: 100%;\r\n min-width: 0;\r\n padding: 5px 8px;\r\n border: 1px solid var(--dsw-alias-border-l1);\r\n border-radius: 6px;\r\n outline: none;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n font: inherit;\r\n font-size: 12px;\r\n transition: border-color 120ms ease, background 120ms ease;\r\n}\r\n\r\n/* The base look of every input is INLINE too, so these three state rules only\r\n ever take effect as author-important declarations \u2014 same reason as the\r\n self-check button above. `cursor` has no inline base and needs none, and the\r\n hover border uses `l3` because `l1`\u2192`l2` is a 4%\u219210% black step nobody can\r\n see. */\r\n\r\n.0cb12e78_input:hover:not(:disabled) {\r\n border-color: var(--dsw-alias-border-l3) !important;\r\n}\r\n\r\n.0cb12e78_input:focus {\r\n border-color: var(--dsw-alias-brand-primary) !important;\r\n}\r\n\r\n.0cb12e78_input:disabled {\r\n color: var(--dsw-alias-state-idle-primary) !important;\r\n background: var(--dsw-alias-bg-layer-2) !important;\r\n cursor: not-allowed;\r\n}\r\n\r\n/* The icon sits inside the input, so its hover/focus feedback must be local\r\n and must outrank the transparent inline base. */\r\n.e7ed8e99_passwordToggle {\r\n transition: background 120ms ease, color 120ms ease;\r\n}\r\n\r\n.e7ed8e99_passwordToggle:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover) !important;\r\n color: var(--dsw-alias-label-primary) !important;\r\n}\r\n\r\n.e7ed8e99_passwordToggle:focus-visible {\r\n outline: 2px solid var(--dsw-alias-brand-primary);\r\n outline-offset: 1px;\r\n}\r\n\r\n/* ---- self-check ---- */\r\n\r\n/* The hover state is the one thing an inline style cannot express, which is why\r\n this element carries a class at all. Layout stays inline.\r\n\r\n Two traps, both hit before this comment existed:\r\n - `!important` is load-bearing: the base border and background are INLINE, and\r\n the cascade sorts a style attribute after every author rule, so a plain\r\n declaration here would ship, match, and still lose.\r\n - the TOKENS must be visible ones. `bg-layer-1/2/3` all resolve to\r\n `neutral-bluish-00` in the light theme, so a layer swap is a no-op there, and\r\n `border-l1`\u2192`l2` is 4%\u219210% black on a 1px line. The app\'s own hovers use\r\n `interactive-bg-hover`; using it here is what makes the feedback exist. */\r\n.3ea31502_testButton {\r\n transition: border-color 120ms ease, background 120ms ease;\r\n}\r\n\r\n.3ea31502_testButton:hover:not(:disabled) {\r\n border-color: var(--dsw-alias-border-l3) !important;\r\n background: var(--dsw-alias-interactive-bg-hover) !important;\r\n}\r\n\r\n/* ---- diagnostics ---- */\r\n\r\n.8093bbdb_diag {\r\n margin-top: 2px;\r\n padding: 8px 10px;\r\n border: 1px solid var(--dsw-alias-border-l1);\r\n border-radius: 8px;\r\n font-size: 11px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.bb056060_diagSummary {\r\n cursor: pointer;\r\n color: var(--dsw-alias-state-warn-primary);\r\n}\r\n\r\n.643d5158_diagList {\r\n margin: 6px 0 0;\r\n padding-left: 18px;\r\n font-family: ui-monospace, monospace;\r\n word-break: break-all;\r\n}\r\n\r\n/* ---- v0.8.0 N9 additions: status filter chips ---- */\r\n\r\n.cf7604fc_chipRow {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.69584a20_chip {\r\n transition: border-color 120ms ease, background 120ms ease, color 120ms ease;\r\n}\r\n\r\n.69584a20_chip:hover:not(:disabled) {\r\n border-color: var(--dsw-alias-border-l3) !important;\r\n background: var(--dsw-alias-interactive-bg-hover) !important;\r\n color: var(--dsw-alias-label-primary) !important;\r\n}\r\n\r\n.9e9e3b3e_chipActive {\r\n border-color: var(--dsw-alias-brand-primary) !important;\r\n background: var(--dsw-alias-interactive-bg-hover) !important;\r\n color: var(--dsw-alias-brand-primary) !important;\r\n}\r\n\r\n/* ---- v0.8.0 N11 additions: form grid ---- */\r\n\r\n.07283bc3_formGrid {\r\n display: grid;\r\n grid-template-columns: repeat(2, minmax(0, 1fr));\r\n align-items: start;\r\n gap: 10px 14px;\r\n}\r\n\r\n.31635992_formGridFull {\r\n grid-column: 1 / -1;\r\n}\r\n\r\n@media (max-width: 520px) {\r\n .07283bc3_formGrid {\r\n grid-template-columns: 1fr !important;\r\n }\r\n .31635992_formGridFull {\r\n grid-column: auto !important;\r\n }\r\n}\r\n\r\n/* ---- v0.8.0 N10 additions: compact view ---- */\r\n\r\n.10f37453_compactRow,\r\n.b8d0ab91_compactRowActive,\r\n.518f0c81_compactRowRejected {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 10px;\r\n padding: 10px 14px;\r\n border: 1px solid var(--dsw-alias-border-l1);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n margin-bottom: 8px;\r\n}\r\n\r\n.b8d0ab91_compactRowActive {\r\n border-color: var(--dsw-alias-state-success-primary);\r\n background: var(--dsw-alias-bg-layer-2);\r\n}\r\n\r\n.518f0c81_compactRowRejected {\r\n border-color: var(--dsw-alias-state-error-primary, #f85149);\r\n background: var(--dsw-alias-bg-layer-2);\r\n}\r\n\r\n/* ---- v0.8.0 N13 additions: quick copy ---- */\r\n.4b86d627_copyButton {\r\n transition: background 120ms ease, color 120ms ease;\r\n}\r\n\r\n.4b86d627_copyButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover) !important;\r\n color: var(--dsw-alias-label-primary) !important;\r\n}\r\n';
49
49
  document.head.appendChild(__dshStyle);
50
50
  }
51
51
  var __dshStyle;
@@ -357,7 +357,7 @@ async function fetchCatalog() {
357
357
  };
358
358
  }
359
359
  }
360
- return { rows, hits, problems, lastUse };
360
+ return { rows, publishedRows: rows, hits, problems, lastUse };
361
361
  }
362
362
  var CATALOG_REFRESH_MS = 1e4;
363
363
  var catalogPoll;
@@ -540,6 +540,29 @@ var S = {
540
540
  fontSize: "12px",
541
541
  cursor: "pointer"
542
542
  },
543
+ /** The reveal/hide control sits inside the password field's right edge. */
544
+ passwordControl: {
545
+ position: "relative",
546
+ minWidth: "0"
547
+ },
548
+ passwordToggle: {
549
+ position: "absolute",
550
+ top: "50%",
551
+ right: "4px",
552
+ display: "flex",
553
+ alignItems: "center",
554
+ justifyContent: "center",
555
+ width: "26px",
556
+ height: "26px",
557
+ padding: "0",
558
+ border: "0",
559
+ borderRadius: "6px",
560
+ background: "transparent",
561
+ color: "var(--dsw-alias-label-secondary)",
562
+ lineHeight: "1",
563
+ transform: "translateY(-50%)",
564
+ cursor: "pointer"
565
+ },
543
566
  testResult: {
544
567
  // A flex row so the target can drop onto its own line: the container was a
545
568
  // plain span, where `flexBasis: 100%` does nothing and the target ran into
@@ -566,6 +589,10 @@ var S = {
566
589
  diagProblem: {
567
590
  color: "var(--dsw-alias-state-error-primary, #f85149)"
568
591
  },
592
+ /** Informational catalogue mismatch: not a rejected route, so no error red. */
593
+ diagNote: {
594
+ color: "var(--dsw-alias-label-secondary)"
595
+ },
569
596
  advanced: {
570
597
  marginTop: "4px",
571
598
  fontSize: "12px"
@@ -639,10 +666,11 @@ var S = {
639
666
  },
640
667
  field: {
641
668
  display: "grid",
642
- gridTemplateColumns: "88px 1fr",
669
+ gridTemplateColumns: "76px 1fr",
643
670
  alignItems: "center",
644
- gap: "10px",
645
- fontSize: "12px"
671
+ gap: "8px",
672
+ fontSize: "12px",
673
+ minWidth: "0"
646
674
  },
647
675
  label: { color: "var(--dsw-alias-label-secondary)" },
648
676
  input: {
@@ -667,9 +695,83 @@ var S = {
667
695
  color: "var(--dsw-alias-label-secondary)"
668
696
  },
669
697
  hint: {
670
- gridColumn: "2",
698
+ gridColumn: "1 / -1",
671
699
  fontSize: "11px",
672
- color: "var(--dsw-alias-label-secondary)"
700
+ color: "var(--dsw-alias-state-warn-primary, #d29922)"
701
+ },
702
+ chipRow: {
703
+ display: "flex",
704
+ alignItems: "center",
705
+ gap: "6px",
706
+ flexWrap: "wrap",
707
+ marginBottom: "2px"
708
+ },
709
+ chip: {
710
+ display: "inline-flex",
711
+ alignItems: "center",
712
+ padding: "3px 10px",
713
+ borderRadius: "999px",
714
+ border: "1px solid var(--dsw-alias-border-l1)",
715
+ background: "var(--dsw-alias-bg-layer-1)",
716
+ color: "var(--dsw-alias-label-secondary)",
717
+ fontSize: "11px",
718
+ fontWeight: "500",
719
+ cursor: "pointer",
720
+ userSelect: "none"
721
+ },
722
+ chipActive: {
723
+ borderColor: "var(--dsw-alias-brand-primary)",
724
+ background: "var(--dsw-alias-interactive-bg-hover)",
725
+ color: "var(--dsw-alias-brand-primary)",
726
+ fontWeight: "600"
727
+ },
728
+ compactRow: {
729
+ display: "flex",
730
+ alignItems: "center",
731
+ justifyContent: "space-between",
732
+ gap: "10px",
733
+ padding: "10px 14px",
734
+ border: "1px solid var(--dsw-alias-border-l1)",
735
+ borderRadius: "8px",
736
+ background: "var(--dsw-alias-bg-layer-1)",
737
+ marginBottom: "8px"
738
+ },
739
+ compactRowActive: {
740
+ borderColor: "var(--dsw-alias-state-success-primary)",
741
+ background: "var(--dsw-alias-bg-layer-2)"
742
+ },
743
+ compactRowRejected: {
744
+ borderColor: "var(--dsw-alias-state-error-primary, #f85149)",
745
+ background: "var(--dsw-alias-bg-layer-2)"
746
+ },
747
+ compactEndpoint: {
748
+ fontSize: "11px",
749
+ color: "var(--dsw-alias-label-secondary)",
750
+ overflow: "hidden",
751
+ textOverflow: "ellipsis",
752
+ whiteSpace: "nowrap",
753
+ maxWidth: "180px"
754
+ },
755
+ formGrid: {
756
+ display: "grid",
757
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
758
+ alignItems: "start",
759
+ gap: "10px 14px"
760
+ },
761
+ formGridFull: {
762
+ gridColumn: "1 / -1"
763
+ },
764
+ copyButton: {
765
+ display: "inline-flex",
766
+ alignItems: "center",
767
+ padding: "2px 4px",
768
+ border: "none",
769
+ borderRadius: "4px",
770
+ background: "transparent",
771
+ color: "var(--dsw-alias-label-secondary)",
772
+ cursor: "pointer",
773
+ fontSize: "11px",
774
+ lineHeight: "1"
673
775
  }
674
776
  };
675
777
  function renderPage(ctx) {
@@ -693,6 +795,7 @@ function renderPage(ctx) {
693
795
  void fetchCatalog().then((catalog) => catalogueSetter?.(catalog)).catch((error) => {
694
796
  catalogueSetter?.({
695
797
  rows: [],
798
+ publishedRows: [],
696
799
  hits: {},
697
800
  problems: {},
698
801
  lastUse: {},
@@ -701,6 +804,7 @@ function renderPage(ctx) {
701
804
  });
702
805
  return {
703
806
  rows: void 0,
807
+ publishedRows: void 0,
704
808
  hits: {},
705
809
  problems: {},
706
810
  lastUse: {},
@@ -710,6 +814,7 @@ function renderPage(ctx) {
710
814
  });
711
815
  catalogueSetter = (catalog) => setState({
712
816
  rows: catalog.rows,
817
+ publishedRows: catalog.publishedRows,
713
818
  hits: catalog.hits,
714
819
  problems: catalog.problems,
715
820
  lastUse: catalog.lastUse,
@@ -718,6 +823,7 @@ function renderPage(ctx) {
718
823
  });
719
824
  touchCatalogPolling();
720
825
  const published = state.rows ?? [];
826
+ const publishedRows = state.publishedRows ?? [];
721
827
  const hits = state.hits ?? {};
722
828
  const lastUse = state.lastUse ?? {};
723
829
  const lastUseText = (id) => {
@@ -731,7 +837,14 @@ function renderPage(ctx) {
731
837
  const problemLines = Object.entries(problems).map(
732
838
  ([id, message]) => h("li", { key: `p:${id}`, style: S.diagProblem }, `${id}\uFF1A${message}`)
733
839
  );
734
- const problemCount = problemLines.length;
840
+ const publishedIds = new Set(publishedRows.map((row) => row.id));
841
+ const missingFromCatalog = state.pending === true || state.error !== void 0 ? [] : Object.keys(configured).filter((id) => !publishedIds.has(id)).sort();
842
+ const catalogueMismatchLine = missingFromCatalog.length === 0 ? void 0 : h(
843
+ "li",
844
+ { key: "catalogue-mismatch", style: S.diagNote },
845
+ `${t.catalogueMismatch}${missingFromCatalog.join("\u3001")}${t.catalogueMismatchSuffix}`
846
+ );
847
+ const problemCount = problemLines.length + (catalogueMismatchLine === void 0 ? 0 : 1);
735
848
  note(
736
849
  `provider \u5217\u8868\uFF1A\u6765\u81EA Host \u5171 ${published.length} \u4E2A\uFF08${published.map((r) => r.id).join("\u3001") || "\u65E0"}\uFF09`
737
850
  );
@@ -741,6 +854,45 @@ function renderPage(ctx) {
741
854
  const [defaultDraft, setDefaultDraft] = (React.useState ?? ((v) => [v, () => {
742
855
  }]))({});
743
856
  const editDefault = (field2, value) => setDefaultDraft((current) => ({ ...current, [field2]: value }));
857
+ const [passwordVisible, setPasswordVisible] = (React.useState ?? ((v) => [v, () => {
858
+ }]))({});
859
+ const [defaultPasswordVisible, setDefaultPasswordVisible] = (React.useState ?? ((v) => [v, () => {
860
+ }]))(false);
861
+ const togglePassword = (id) => setPasswordVisible((current) => ({ ...current, [id]: current[id] !== true }));
862
+ const toggleDefaultPassword = () => setDefaultPasswordVisible((current) => current !== true);
863
+ const passwordToggle = (visible, onToggle) => h(
864
+ "button",
865
+ {
866
+ className: import_client.default.passwordToggle,
867
+ style: S.passwordToggle,
868
+ type: "button",
869
+ "aria-label": visible ? t.hidePassword : t.showPassword,
870
+ "aria-pressed": visible,
871
+ title: visible ? t.hidePassword : t.showPassword,
872
+ onClick: onToggle
873
+ },
874
+ h(
875
+ "svg",
876
+ {
877
+ width: 16,
878
+ height: 16,
879
+ viewBox: "0 0 24 24",
880
+ fill: "none",
881
+ stroke: "currentColor",
882
+ strokeWidth: 2,
883
+ strokeLinecap: "round",
884
+ strokeLinejoin: "round",
885
+ "aria-hidden": "true",
886
+ focusable: "false"
887
+ },
888
+ h("path", {
889
+ key: "eye",
890
+ d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"
891
+ }),
892
+ h("circle", { key: "pupil", cx: 12, cy: 12, r: 3 }),
893
+ visible ? h("path", { key: "slash", d: "m3 3 18 18" }) : null
894
+ )
895
+ );
744
896
  const [globalDraft, setGlobalDraft] = (React.useState ?? ((v) => [v, () => {
745
897
  }]))({});
746
898
  const editGlobal = (field2, value) => setGlobalDraft((current) => ({ ...current, [field2]: value }));
@@ -768,11 +920,77 @@ function renderPage(ctx) {
768
920
  };
769
921
  const [filter, setFilter] = (React.useState ?? ((v) => [v, () => {
770
922
  }]))("");
923
+ const [statusFilter, setStatusFilter] = (React.useState ?? ((v) => [v, () => {
924
+ }]))("all");
925
+ const [viewMode, setViewMode] = (React.useState ?? ((v) => [v, () => {
926
+ }]))("card");
927
+ const [expandedCards, setExpandedCards] = (React.useState ?? ((v) => [v, () => {
928
+ }]))({});
929
+ const [batchProgress, setBatchProgress] = (React.useState ?? ((v) => [v, () => {
930
+ }]))(null);
931
+ const [copiedKey, setCopiedKey] = (React.useState ?? ((v) => [v, () => {
932
+ }]))(void 0);
933
+ const copyToClipboard = (key, text) => {
934
+ try {
935
+ if (typeof globalThis.navigator?.clipboard?.writeText === "function") {
936
+ void globalThis.navigator.clipboard.writeText(text).then(() => {
937
+ setCopiedKey(key);
938
+ setTimeout(() => setCopiedKey((prev) => prev === key ? void 0 : prev), 1500);
939
+ }).catch(() => void 0);
940
+ }
941
+ } catch {
942
+ }
943
+ };
944
+ const copyButton = (key, text, ariaLabel) => h(
945
+ "button",
946
+ {
947
+ className: import_client.default.copyButton,
948
+ style: S.copyButton,
949
+ type: "button",
950
+ "aria-label": ariaLabel,
951
+ title: copiedKey === key ? t.copied : ariaLabel,
952
+ onClick: (e) => {
953
+ e.stopPropagation?.();
954
+ copyToClipboard(key, text);
955
+ }
956
+ },
957
+ copiedKey === key ? h(
958
+ "svg",
959
+ {
960
+ width: 12,
961
+ height: 12,
962
+ viewBox: "0 0 24 24",
963
+ fill: "none",
964
+ stroke: "var(--dsw-alias-state-success-primary, #2ea043)",
965
+ strokeWidth: 2,
966
+ strokeLinecap: "round",
967
+ strokeLinejoin: "round",
968
+ "aria-hidden": "true"
969
+ },
970
+ h("path", { d: "M20 6 9 17l-5-5" })
971
+ ) : h(
972
+ "svg",
973
+ {
974
+ width: 12,
975
+ height: 12,
976
+ viewBox: "0 0 24 24",
977
+ fill: "none",
978
+ stroke: "currentColor",
979
+ strokeWidth: 2,
980
+ strokeLinecap: "round",
981
+ strokeLinejoin: "round",
982
+ "aria-hidden": "true"
983
+ },
984
+ h("rect", { width: 14, height: 14, x: 8, y: 8, rx: 2, ry: 2 }),
985
+ h("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
986
+ ),
987
+ copiedKey === key ? h("span", { style: { marginLeft: "3px", fontSize: "10px", color: "var(--dsw-alias-state-success-primary, #2ea043)" } }, t.copied) : null
988
+ );
771
989
  const runTest = (id) => {
772
990
  const generation = (testGeneration.get(id) ?? 0) + 1;
773
991
  testGeneration.set(id, generation);
774
992
  setTests((current) => ({ ...current, [id]: { pending: true } }));
775
- void (async () => {
993
+ return (async () => {
776
994
  try {
777
995
  for (const [key, entry] of [...pendingWrites]) {
778
996
  if (key.startsWith(`${id}\0`) || key.startsWith("\0default\0") || key.startsWith("\0global\0")) {
@@ -798,6 +1016,28 @@ function renderPage(ctx) {
798
1016
  }
799
1017
  })();
800
1018
  };
1019
+ const runBatchTest = () => {
1020
+ const activeIds = rows.filter((row) => view({ ...configured[row.id], ...draft[row.id] }).enabled).map((row) => row.id);
1021
+ if (activeIds.length === 0 || batchProgress !== null) return;
1022
+ setBatchProgress({ done: 0, total: activeIds.length });
1023
+ void (async () => {
1024
+ let index = 0;
1025
+ let completed = 0;
1026
+ const concurrency = Math.min(3, activeIds.length);
1027
+ const worker = async () => {
1028
+ while (index < activeIds.length) {
1029
+ const id = activeIds[index++];
1030
+ await runTest(id);
1031
+ completed++;
1032
+ setBatchProgress({ done: completed, total: activeIds.length });
1033
+ }
1034
+ };
1035
+ await Promise.all(Array.from({ length: concurrency }, () => worker()));
1036
+ setTimeout(() => {
1037
+ setBatchProgress(null);
1038
+ }, 2e3);
1039
+ })();
1040
+ };
801
1041
  const rows = knownProviders({ ...configured, ...draft }, published);
802
1042
  const inheritingIds = () => rows.filter((row) => view({ ...configured[row.id], ...draft[row.id] }).useDefault === true).map((row) => row.id);
803
1043
  const save = (key, path, label, value, onRefused) => {
@@ -924,6 +1164,9 @@ function renderPage(ctx) {
924
1164
  );
925
1165
  };
926
1166
  const promoteToDefault = (id) => {
1167
+ if (defaultComplete && inheritingIds().length > 0 && globalThis.window?.confirm?.(t.confirmPromoteToDefault) !== true) {
1168
+ return;
1169
+ }
927
1170
  const route = view({ ...configured[id], ...draft[id] });
928
1171
  applyDefaultUrl({
929
1172
  protocol: route.protocol,
@@ -945,7 +1188,7 @@ function renderPage(ctx) {
945
1188
  saveMany(
946
1189
  "\0default\0applyAll",
947
1190
  ids.map((id) => ({ op: "set", path: ["providers", id, "useDefault"], value: true })),
948
- "\u6240\u6709 provider \u7684 useDefault",
1191
+ `\u6240\u6709 provider \u7684 useDefault\uFF08${ids.length} \u4E2A\uFF09`,
949
1192
  () => setDraft((current) => {
950
1193
  const next = { ...current };
951
1194
  for (const id of ids) {
@@ -968,9 +1211,16 @@ function renderPage(ctx) {
968
1211
  event.preventDefault?.();
969
1212
  apply2(parts);
970
1213
  };
971
- const field = (label, control, hint, hintError) => h(
1214
+ const field = (label, control, hint, hintError, fullWidth) => h(
972
1215
  "label",
973
- { className: import_client.default.field, style: S.field, key: label },
1216
+ {
1217
+ className: `${import_client.default.field}${fullWidth ? ` ${import_client.default.formGridFull}` : ""}`,
1218
+ style: {
1219
+ ...S.field,
1220
+ ...fullWidth ? S.formGridFull : {}
1221
+ },
1222
+ key: label
1223
+ },
974
1224
  h("span", { className: import_client.default.label, style: S.label }, label),
975
1225
  control,
976
1226
  hint === void 0 ? null : h(
@@ -978,8 +1228,8 @@ function renderPage(ctx) {
978
1228
  {
979
1229
  className: import_client.default.hint,
980
1230
  style: {
981
- gridColumn: "2",
982
- fontSize: "11px",
1231
+ ...S.hint,
1232
+ marginTop: "2px",
983
1233
  // A warning that a value can never work is an answer, not
984
1234
  // decoration — same rule as the failed self-check text.
985
1235
  ...hintError === true ? { color: "var(--dsw-alias-state-error-primary, #f85149)" } : {}
@@ -1015,73 +1265,84 @@ function renderPage(ctx) {
1015
1265
  )
1016
1266
  ),
1017
1267
  h("p", { className: import_client.default.defaultLead, style: S.defaultLead }, t.defaultLead),
1018
- field(
1019
- t.protocol,
1020
- h(
1021
- "select",
1022
- {
1268
+ h(
1269
+ "div",
1270
+ { className: import_client.default.formGrid, style: S.formGrid, key: "default:form-grid" },
1271
+ field(
1272
+ t.protocol,
1273
+ h(
1274
+ "select",
1275
+ {
1276
+ className: import_client.default.input,
1277
+ style: S.input,
1278
+ value: defaultView.protocol,
1279
+ onChange: (event) => bindDefault("protocol")(event.target.value)
1280
+ },
1281
+ ...PROTOCOLS.map(
1282
+ (protocol) => h("option", { key: protocol, value: protocol }, protocol.toUpperCase())
1283
+ )
1284
+ )
1285
+ ),
1286
+ field(
1287
+ t.host,
1288
+ h("input", {
1023
1289
  className: import_client.default.input,
1024
1290
  style: S.input,
1025
- value: defaultView.protocol,
1026
- onChange: (event) => bindDefault("protocol")(event.target.value)
1027
- },
1028
- ...PROTOCOLS.map(
1029
- (protocol) => h("option", { key: protocol, value: protocol }, protocol.toUpperCase())
1030
- )
1291
+ placeholder: "127.0.0.1",
1292
+ value: defaultView.host,
1293
+ onChange: (event) => bindDefault("host")(event.target.value),
1294
+ // Same convenience as the cards: a full URL fills the whole block.
1295
+ onPaste: onProxyUrlPaste(applyDefaultUrl)
1296
+ }),
1297
+ defaultHostWhy ?? t.hostHint,
1298
+ defaultHostWhy !== void 0
1299
+ ),
1300
+ field(
1301
+ t.port,
1302
+ h("input", {
1303
+ className: import_client.default.input,
1304
+ style: S.input,
1305
+ type: "number",
1306
+ min: 1,
1307
+ max: 65535,
1308
+ placeholder: "7890",
1309
+ value: defaultView.port === 0 ? "" : String(defaultView.port),
1310
+ onChange: (event) => {
1311
+ const parsed = Number.parseInt(event.target.value, 10);
1312
+ bindDefault("port")(Number.isNaN(parsed) ? 0 : parsed);
1313
+ }
1314
+ })
1315
+ ),
1316
+ field(
1317
+ t.username,
1318
+ h("input", {
1319
+ className: import_client.default.input,
1320
+ style: S.input,
1321
+ autoComplete: "off",
1322
+ value: defaultView.username,
1323
+ onChange: (event) => bindDefault("username")(event.target.value)
1324
+ })
1325
+ ),
1326
+ field(
1327
+ t.password,
1328
+ h(
1329
+ "div",
1330
+ { style: S.passwordControl },
1331
+ h("input", {
1332
+ className: import_client.default.input,
1333
+ style: { ...S.input, paddingRight: "36px" },
1334
+ type: defaultPasswordVisible === true ? "text" : "password",
1335
+ autoComplete: "off",
1336
+ value: defaultView.password,
1337
+ onChange: (event) => bindDefault("password")(event.target.value)
1338
+ }),
1339
+ passwordToggle(defaultPasswordVisible === true, toggleDefaultPassword)
1340
+ ),
1341
+ t.passwordHintProxy,
1342
+ false,
1343
+ true
1031
1344
  )
1032
1345
  ),
1033
- field(
1034
- t.host,
1035
- h("input", {
1036
- className: import_client.default.input,
1037
- style: S.input,
1038
- placeholder: "127.0.0.1",
1039
- value: defaultView.host,
1040
- onChange: (event) => bindDefault("host")(event.target.value),
1041
- // Same convenience as the cards: a full URL fills the whole block.
1042
- onPaste: onProxyUrlPaste(applyDefaultUrl)
1043
- }),
1044
- defaultHostWhy ?? t.hostHint,
1045
- defaultHostWhy !== void 0
1046
- ),
1047
- field(
1048
- t.port,
1049
- h("input", {
1050
- className: import_client.default.input,
1051
- style: S.input,
1052
- type: "number",
1053
- min: 1,
1054
- max: 65535,
1055
- placeholder: "7890",
1056
- value: defaultView.port === 0 ? "" : String(defaultView.port),
1057
- onChange: (event) => {
1058
- const parsed = Number.parseInt(event.target.value, 10);
1059
- bindDefault("port")(Number.isNaN(parsed) ? 0 : parsed);
1060
- }
1061
- })
1062
- ),
1063
- field(
1064
- t.username,
1065
- h("input", {
1066
- className: import_client.default.input,
1067
- style: S.input,
1068
- autocomplete: "off",
1069
- value: defaultView.username,
1070
- onChange: (event) => bindDefault("username")(event.target.value)
1071
- })
1072
- ),
1073
- field(
1074
- t.password,
1075
- h("input", {
1076
- className: import_client.default.input,
1077
- style: S.input,
1078
- type: "password",
1079
- autocomplete: "off",
1080
- value: defaultView.password,
1081
- onChange: (event) => bindDefault("password")(event.target.value)
1082
- }),
1083
- t.passwordHintProxy
1084
- ),
1085
1346
  // Bulk opt-in, in ONE write: a per-card walk would rebuild the table N times.
1086
1347
  // Disabled while the default configuration is incomplete, because pointing every card at an
1087
1348
  // empty endpoint would just refuse them all.
@@ -1097,7 +1358,7 @@ function renderPage(ctx) {
1097
1358
  disabled: !defaultComplete || rows.length === 0,
1098
1359
  onClick: applyDefaultToAll
1099
1360
  },
1100
- t.applyDefaultAll
1361
+ `${t.applyDefaultAll}\uFF08${rows.length}\uFF09`
1101
1362
  )
1102
1363
  )
1103
1364
  );
@@ -1128,7 +1389,7 @@ function renderPage(ctx) {
1128
1389
  value: globalTestUrl,
1129
1390
  onChange: (event) => bindGlobal("testUrl")(event.target.value)
1130
1391
  }),
1131
- globalTestUrlHint === void 0 ? h("span", { style: S.hint }, t.testUrlHint) : h(
1392
+ globalTestUrlHint === void 0 ? h("span", { className: import_client.default.hint, style: S.hint }, t.testUrlHint) : h(
1132
1393
  "span",
1133
1394
  {
1134
1395
  className: import_client.default.hint,
@@ -1166,21 +1427,87 @@ function renderPage(ctx) {
1166
1427
  ),
1167
1428
  h(
1168
1429
  "p",
1169
- { style: S.hint },
1430
+ { className: import_client.default.hint, style: { ...S.hint, margin: "2px 0 0" } },
1170
1431
  globalLocalDirect ? t.localDirectOnHint : t.localDirectOffHint
1171
1432
  )
1172
1433
  );
1434
+ const routeStateOf = (row) => {
1435
+ const route = view({ ...configured[row.id], ...draft[row.id] });
1436
+ const recorded = problems[row.id];
1437
+ const useDefault = route.useDefault === true;
1438
+ const problemText = recorded !== void 0 ? recorded : useDefault ? defaultComplete ? void 0 : `${t.defaultMissing}${defaultWhy}` : route.enabled ? defaultProxyProblem(route) : void 0;
1439
+ if (route.enabled && problemText !== void 0) return "rejected";
1440
+ if (route.enabled) return "proxied";
1441
+ return "direct";
1442
+ };
1173
1443
  const query = filter.trim().toLowerCase();
1174
- const shown = query.length === 0 ? rows : rows.filter((row) => `${row.id} ${row.name}`.toLowerCase().includes(query));
1444
+ const matchedKeywordRows = query.length === 0 ? rows : rows.filter((row) => `${row.id} ${row.name}`.toLowerCase().includes(query));
1445
+ const countActive = matchedKeywordRows.filter((r) => routeStateOf(r) === "proxied").length;
1446
+ const countDirect = matchedKeywordRows.filter((r) => routeStateOf(r) === "direct").length;
1447
+ const countRejected = matchedKeywordRows.filter((r) => routeStateOf(r) === "rejected").length;
1448
+ const shown = matchedKeywordRows.filter((row) => {
1449
+ if (statusFilter === "all") return true;
1450
+ return routeStateOf(row) === statusFilter;
1451
+ });
1452
+ const chipRow = rows.length === 0 ? null : h(
1453
+ "div",
1454
+ { className: import_client.default.chipRow, style: S.chipRow, key: "status-chips" },
1455
+ [
1456
+ { id: "all", label: `${t.filterStatusAll} (${matchedKeywordRows.length})` },
1457
+ { id: "proxied", label: `${t.filterStatusProxied} (${countActive})` },
1458
+ { id: "direct", label: `${t.filterStatusDirect} (${countDirect})` },
1459
+ { id: "rejected", label: `${t.filterStatusRejected} (${countRejected})` }
1460
+ ].map(
1461
+ ({ id, label }) => h(
1462
+ "button",
1463
+ {
1464
+ key: id,
1465
+ type: "button",
1466
+ className: statusFilter === id ? import_client.default.chipActive : import_client.default.chip,
1467
+ style: {
1468
+ ...S.chip,
1469
+ ...statusFilter === id ? S.chipActive : {}
1470
+ },
1471
+ onClick: () => setStatusFilter(id)
1472
+ },
1473
+ label
1474
+ )
1475
+ )
1476
+ );
1175
1477
  const filterRow = rows.length === 0 ? null : field(
1176
1478
  t.filterLabel,
1177
- h("input", {
1178
- className: import_client.default.input,
1179
- style: S.input,
1180
- placeholder: t.filterPlaceholder,
1181
- value: filter,
1182
- onChange: (event) => setFilter(event.target.value)
1183
- })
1479
+ h(
1480
+ "div",
1481
+ { style: { display: "flex", alignItems: "center", gap: "8px" } },
1482
+ h("input", {
1483
+ className: import_client.default.input,
1484
+ style: S.input,
1485
+ placeholder: t.filterPlaceholder,
1486
+ value: filter,
1487
+ onChange: (event) => setFilter(event.target.value)
1488
+ }),
1489
+ h(
1490
+ "button",
1491
+ {
1492
+ className: import_client.default.testButton,
1493
+ style: { ...S.testButton, flex: "none", whiteSpace: "nowrap" },
1494
+ type: "button",
1495
+ disabled: countActive === 0 || batchProgress !== null,
1496
+ onClick: runBatchTest
1497
+ },
1498
+ batchProgress !== null ? `${t.batchTesting} (${batchProgress.done}/${batchProgress.total})` : `${t.batchTest} (${countActive})`
1499
+ ),
1500
+ h(
1501
+ "button",
1502
+ {
1503
+ className: import_client.default.testButton,
1504
+ style: { ...S.testButton, flex: "none", whiteSpace: "nowrap" },
1505
+ type: "button",
1506
+ onClick: () => setViewMode((m) => m === "card" ? "compact" : "card")
1507
+ },
1508
+ viewMode === "card" ? t.viewCompact : t.viewCard
1509
+ )
1510
+ )
1184
1511
  );
1185
1512
  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;
1186
1513
  const catalogueNotice = state.error !== void 0 && rows.length > 0 ? h(
@@ -1274,7 +1601,7 @@ function renderPage(ctx) {
1274
1601
  h("input", {
1275
1602
  className: import_client.default.input,
1276
1603
  style: S.input,
1277
- autocomplete: "off",
1604
+ autoComplete: "off",
1278
1605
  value: route.username,
1279
1606
  onChange: (event) => bind(row.id, "username")(event.target.value)
1280
1607
  })
@@ -1286,19 +1613,26 @@ function renderPage(ctx) {
1286
1613
  // config; the diagnostics only ever surface a credential-free origin.
1287
1614
  field(
1288
1615
  t.password,
1289
- h("input", {
1290
- className: import_client.default.input,
1291
- style: S.input,
1292
- type: "password",
1293
- autocomplete: "off",
1294
- value: route.password,
1295
- onChange: (event) => bind(row.id, "password")(event.target.value)
1296
- }),
1297
- route.protocol === "socks5" ? t.passwordHint : t.passwordHintProxy
1616
+ h(
1617
+ "div",
1618
+ { style: S.passwordControl },
1619
+ h("input", {
1620
+ className: import_client.default.input,
1621
+ style: { ...S.input, paddingRight: "36px" },
1622
+ type: passwordVisible[row.id] === true ? "text" : "password",
1623
+ autoComplete: "off",
1624
+ value: route.password,
1625
+ onChange: (event) => bind(row.id, "password")(event.target.value)
1626
+ }),
1627
+ passwordToggle(passwordVisible[row.id] === true, () => togglePassword(row.id))
1628
+ ),
1629
+ route.protocol === "socks5" ? t.passwordHint : t.passwordHintProxy,
1630
+ false,
1631
+ true
1298
1632
  )
1299
1633
  ];
1300
1634
  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 };
1301
- return h(
1635
+ const cardSection = h(
1302
1636
  "section",
1303
1637
  {
1304
1638
  className: rejected ? import_client.default.cardRejected : route.enabled ? import_client.default.cardActive : import_client.default.card,
@@ -1364,7 +1698,7 @@ function renderPage(ctx) {
1364
1698
  { key: `${row.id}:inherit`, style: S.inheritNote },
1365
1699
  `${t.inheritingDefault}${defaultOrigin}`
1366
1700
  ) : null,
1367
- ...useDefault ? [] : endpointFields,
1701
+ ...useDefault ? [] : [h("div", { className: import_client.default.formGrid, style: S.formGrid, key: `${row.id}:form-grid` }, ...endpointFields)],
1368
1702
  // The self-check is offered whenever there is something to check: an
1369
1703
  // enabled route (the Host's table carries it, and a rejected one still
1370
1704
  // reports WHY), or a disabled one whose endpoint is already complete —
@@ -1405,7 +1739,12 @@ function renderPage(ctx) {
1405
1739
  { key: "status" },
1406
1740
  `HTTP ${result.status}`
1407
1741
  ) : null,
1408
- result.ip === void 0 ? null : h("span", { key: "ip" }, `${t.exitIpLabel}${result.ip}`),
1742
+ result.ip === void 0 ? null : h(
1743
+ "span",
1744
+ { key: "ip", style: { display: "inline-flex", alignItems: "center", gap: "3px" } },
1745
+ `${t.exitIpLabel}${result.ip}`,
1746
+ copyButton(`${row.id}:ip`, result.ip, t.copy)
1747
+ ),
1409
1748
  result.direct === true ? h("span", { key: "direct" }, t.directLabel) : null,
1410
1749
  result.cached === true ? h("span", { key: "cached" }, t.cachedLabel) : null,
1411
1750
  result.target === void 0 ? null : h(
@@ -1416,13 +1755,22 @@ function renderPage(ctx) {
1416
1755
  flexBasis: "100%",
1417
1756
  fontSize: "11px",
1418
1757
  color: "var(--dsw-alias-label-secondary)",
1419
- wordBreak: "break-all"
1758
+ wordBreak: "break-all",
1759
+ display: "inline-flex",
1760
+ alignItems: "center",
1761
+ gap: "3px"
1420
1762
  }
1421
1763
  },
1422
- `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`
1764
+ `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`,
1765
+ copyButton(`${row.id}:target`, result.target, t.copy)
1423
1766
  )
1424
1767
  ] : [
1425
- h("span", { key: "err", style: S.testFailure }, `\u2717 ${result.error}`),
1768
+ h(
1769
+ "span",
1770
+ { key: "err", style: { ...S.testFailure, display: "inline-flex", alignItems: "center", gap: "3px" } },
1771
+ `\u2717 ${result.error}`,
1772
+ copyButton(`${row.id}:err`, String(result.error), t.copy)
1773
+ ),
1426
1774
  typeof result.status === "number" ? h("span", { key: "status" }, `HTTP ${result.status}`) : null,
1427
1775
  result.target === void 0 ? null : h(
1428
1776
  "span",
@@ -1432,10 +1780,14 @@ function renderPage(ctx) {
1432
1780
  flexBasis: "100%",
1433
1781
  fontSize: "11px",
1434
1782
  color: "var(--dsw-alias-label-secondary)",
1435
- wordBreak: "break-all"
1783
+ wordBreak: "break-all",
1784
+ display: "inline-flex",
1785
+ alignItems: "center",
1786
+ gap: "3px"
1436
1787
  }
1437
1788
  },
1438
- `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`
1789
+ `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`,
1790
+ copyButton(`${row.id}:target`, result.target, t.copy)
1439
1791
  )
1440
1792
  ]
1441
1793
  ) : null,
@@ -1498,6 +1850,58 @@ function renderPage(ctx) {
1498
1850
  )
1499
1851
  )
1500
1852
  );
1853
+ const isExpanded = expandedCards[row.id] === true;
1854
+ const compactRowElement = h(
1855
+ "section",
1856
+ {
1857
+ className: rejected ? import_client.default.compactRowRejected : route.enabled ? import_client.default.compactRowActive : import_client.default.compactRow,
1858
+ style: rejected ? { ...S.compactRow, ...S.compactRowRejected } : route.enabled ? { ...S.compactRow, ...S.compactRowActive } : S.compactRow,
1859
+ key: `compact:${row.id}`
1860
+ },
1861
+ h(
1862
+ "div",
1863
+ { style: { display: "flex", alignItems: "center", gap: "8px", minWidth: "0" } },
1864
+ h("input", {
1865
+ type: "checkbox",
1866
+ checked: route.enabled,
1867
+ onChange: (event) => bind(row.id, "enabled")(event.target.checked === true)
1868
+ }),
1869
+ h("span", { className: import_client.default.name, style: { ...S.name, maxWidth: "140px" } }, row.name),
1870
+ h("span", { className: import_client.default.id, style: S.id }, row.id),
1871
+ useDefault ? h("span", { style: S.compactEndpoint }, `${t.useDefault}: ${defaultOrigin}`) : h("span", { style: S.compactEndpoint }, `${route.protocol.toUpperCase()}://${route.host || "\u2014"}:${route.port || "\u2014"}`)
1872
+ ),
1873
+ h(
1874
+ "div",
1875
+ { style: { display: "flex", alignItems: "center", gap: "8px", flex: "none" } },
1876
+ result?.pending === true ? h("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary)" } }, t.testRunning) : result?.ok === true ? h("span", { style: { fontSize: "11px", color: latencyColor(result.ms), fontWeight: "500" } }, `\u2713 ${result.ms ?? "\u2014"} ms`) : result?.ok === false ? h("span", { style: { fontSize: "11px", color: "var(--dsw-alias-state-error-primary, #f85149)", fontWeight: "500" } }, `\u2717 ${result.error}`) : lastText !== void 0 ? h("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary)" } }, lastText) : null,
1877
+ h("span", { className: pill.className, style: pill.style }, pill.text),
1878
+ showTest ? h(
1879
+ "button",
1880
+ {
1881
+ className: import_client.default.testButton,
1882
+ style: { ...S.testButton, padding: "3px 8px", fontSize: "11px" },
1883
+ type: "button",
1884
+ disabled: result?.pending === true,
1885
+ onClick: () => runTest(row.id)
1886
+ },
1887
+ result?.pending === true ? t.testRunning : t.testButton
1888
+ ) : null,
1889
+ h(
1890
+ "button",
1891
+ {
1892
+ className: import_client.default.testButton,
1893
+ style: { ...S.testButton, padding: "3px 8px", fontSize: "11px" },
1894
+ type: "button",
1895
+ onClick: () => setExpandedCards((curr) => ({ ...curr, [row.id]: !curr[row.id] }))
1896
+ },
1897
+ isExpanded ? t.collapseDetails : t.expandDetails
1898
+ )
1899
+ )
1900
+ );
1901
+ if (viewMode === "compact") {
1902
+ return isExpanded ? h("div", { key: `compact-wrap:${row.id}` }, compactRowElement, cardSection) : compactRowElement;
1903
+ }
1904
+ return cardSection;
1501
1905
  });
1502
1906
  return h(
1503
1907
  "div",
@@ -1538,6 +1942,7 @@ function renderPage(ctx) {
1538
1942
  defaultBlock,
1539
1943
  globalBlock,
1540
1944
  catalogueNotice,
1945
+ chipRow,
1541
1946
  filterRow,
1542
1947
  ...body,
1543
1948
  h(
@@ -1567,6 +1972,7 @@ function renderPage(ctx) {
1567
1972
  { className: import_client.default.diagList, style: { margin: "6px 0 0", paddingLeft: "18px" } },
1568
1973
  ...problemCount === 0 && diagnostics.length === 0 && savedNote === void 0 ? [h("li", { key: "none" }, t.diagPending)] : [
1569
1974
  ...problemLines,
1975
+ ...catalogueMismatchLine === void 0 ? [] : [catalogueMismatchLine],
1570
1976
  ...savedNote === void 0 ? [] : [h("li", { key: "saved" }, savedNote)],
1571
1977
  ...diagnostics.map((line, index) => h("li", { key: `d${index}` }, line))
1572
1978
  ]
@@ -1588,6 +1994,10 @@ var EN = {
1588
1994
  defaultMissing: "The default proxy configuration is incomplete, so this provider is not proxied: ",
1589
1995
  filterLabel: "Filter",
1590
1996
  filterPlaceholder: "Filter by name or id\u2026",
1997
+ filterStatusAll: "All",
1998
+ filterStatusProxied: "Proxied",
1999
+ filterStatusDirect: "Direct",
2000
+ filterStatusRejected: "Rejected",
1591
2001
  noMatch: "No provider matches the filter.",
1592
2002
  setAsDefault: "Set as the default proxy",
1593
2003
  applyDefaultAll: "Use the default proxy for all providers",
@@ -1596,7 +2006,10 @@ var EN = {
1596
2006
  port: "Port",
1597
2007
  username: "Username",
1598
2008
  password: "Password",
1599
- 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.",
2009
+ showPassword: "Show",
2010
+ hidePassword: "Hide",
2011
+ confirmPromoteToDefault: "The default proxy is already in use by one or more providers. Replace it with this card's endpoint?",
2012
+ hostHint: "IP or domain only; paste full URL supported",
1600
2013
  passwordHint: "Sent to the SOCKS5 proxy for authentication; stored with this setting.",
1601
2014
  passwordHintProxy: "Sent to the proxy as an authentication password; stored with this setting.",
1602
2015
  hitCountPrefix: "",
@@ -1609,6 +2022,12 @@ var EN = {
1609
2022
  minutesAgo: " min ago",
1610
2023
  hoursAgo: " h ago",
1611
2024
  daysAgo: " d ago",
2025
+ viewCard: "Card view",
2026
+ viewCompact: "Compact view",
2027
+ expandDetails: "Edit",
2028
+ collapseDetails: "Collapse",
2029
+ batchTest: "Test active",
2030
+ batchTesting: "Testing",
1612
2031
  testButton: "Test",
1613
2032
  testRunning: "Testing\u2026",
1614
2033
  testDisabledNote: "Not enabled \u2014 test only: ",
@@ -1647,8 +2066,12 @@ var EN = {
1647
2066
  notConfigured: "not configured",
1648
2067
  diagnostics: "Connection diagnostics",
1649
2068
  diagPending: "(collecting\u2026)",
2069
+ catalogueMismatch: "Saved but not listed by the Host catalogue: ",
2070
+ catalogueMismatchSuffix: ". Their proxy settings are kept.",
1650
2071
  saveFailedPrefix: "Save failed: ",
1651
- close: "Close"
2072
+ close: "Close",
2073
+ copy: "Copy",
2074
+ copied: "Copied"
1652
2075
  };
1653
2076
  var ZH = {
1654
2077
  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",
@@ -1664,6 +2087,10 @@ var ZH = {
1664
2087
  defaultMissing: "\u9876\u5C42\u9ED8\u8BA4\u4EE3\u7406\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF0C\u8BE5 provider \u4E0D\u4F1A\u8D70\u4EE3\u7406\uFF1A",
1665
2088
  filterLabel: "\u8FC7\u6EE4",
1666
2089
  filterPlaceholder: "\u6309\u540D\u79F0\u6216 id \u8FC7\u6EE4\u2026",
2090
+ filterStatusAll: "\u5168\u90E8",
2091
+ filterStatusProxied: "\u8D70\u4EE3\u7406",
2092
+ filterStatusDirect: "\u76F4\u8FDE",
2093
+ filterStatusRejected: "\u914D\u7F6E\u88AB\u62D2",
1667
2094
  noMatch: "\u6CA1\u6709\u5339\u914D\u7684 provider\u3002",
1668
2095
  setAsDefault: "\u8BBE\u4E3A\u9ED8\u8BA4\u4EE3\u7406",
1669
2096
  applyDefaultAll: "\u6240\u6709 provider \u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406",
@@ -1672,7 +2099,10 @@ var ZH = {
1672
2099
  port: "\u7AEF\u53E3",
1673
2100
  username: "\u7528\u6237\u540D",
1674
2101
  password: "\u5BC6\u7801",
1675
- 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",
2102
+ showPassword: "\u663E\u793A",
2103
+ hidePassword: "\u9690\u85CF",
2104
+ confirmPromoteToDefault: "\u5DF2\u6709 provider \u6B63\u5728\u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406\u3002\u8981\u7528\u8FD9\u5F20\u5361\u7247\u7684\u7AEF\u70B9\u8986\u76D6\u5B83\u5417\uFF1F",
2105
+ hostHint: "\u4EC5\u9650 IP \u6216\u57DF\u540D\uFF0C\u652F\u6301\u7C98\u8D34\u5B8C\u6574\u4EE3\u7406 URL",
1676
2106
  passwordHint: "\u4EC5\u4F5C\u4E3A SOCKS5 \u8BA4\u8BC1\u53E3\u4EE4\u53D1\u9001\u7ED9\u4EE3\u7406\uFF1B\u968F\u8BE5\u8BBE\u7F6E\u4E00\u8D77\u4FDD\u5B58\u3002",
1677
2107
  passwordHintProxy: "\u4F5C\u4E3A\u4EE3\u7406\u8BA4\u8BC1\u53E3\u4EE4\u53D1\u9001\u7ED9\u4EE3\u7406\uFF1B\u968F\u8BE5\u8BBE\u7F6E\u4E00\u8D77\u4FDD\u5B58\u3002",
1678
2108
  hitCountPrefix: "\u5DF2\u4EE3\u7406 ",
@@ -1685,6 +2115,12 @@ var ZH = {
1685
2115
  minutesAgo: " \u5206\u949F\u524D",
1686
2116
  hoursAgo: " \u5C0F\u65F6\u524D",
1687
2117
  daysAgo: " \u5929\u524D",
2118
+ viewCard: "\u5361\u7247\u89C6\u56FE",
2119
+ viewCompact: "\u7D27\u51D1\u89C6\u56FE",
2120
+ expandDetails: "\u7F16\u8F91",
2121
+ collapseDetails: "\u6536\u8D77",
2122
+ batchTest: "\u6D4B\u8BD5\u5DF2\u542F\u7528",
2123
+ batchTesting: "\u6B63\u5728\u6D4B\u8BD5",
1688
2124
  testButton: "\u6D4B\u8BD5",
1689
2125
  testRunning: "\u6D4B\u8BD5\u4E2D\u2026",
1690
2126
  testDisabledNote: "\u672A\u542F\u7528\uFF0C\u4EC5\u6D4B\u8BD5\uFF1A",
@@ -1723,8 +2159,12 @@ var ZH = {
1723
2159
  notConfigured: "\u672A\u914D\u7F6E",
1724
2160
  diagnostics: "\u8FDE\u63A5\u8BCA\u65AD",
1725
2161
  diagPending: "\uFF08\u6B63\u5728\u6536\u96C6\u2026\uFF09",
2162
+ catalogueMismatch: "\u5DF2\u4FDD\u5B58\u4F46\u672A\u51FA\u73B0\u5728 Host \u76EE\u5F55\u4E2D\u7684 provider\uFF1A",
2163
+ catalogueMismatchSuffix: "\u3002\u4EE3\u7406\u914D\u7F6E\u4ECD\u4F1A\u4FDD\u7559\u3002",
1726
2164
  saveFailedPrefix: "\u4FDD\u5B58\u5931\u8D25\uFF1A",
1727
- close: "\u5173\u95ED"
2165
+ close: "\u5173\u95ED",
2166
+ copy: "\u590D\u5236",
2167
+ copied: "\u5DF2\u590D\u5236"
1728
2168
  };
1729
2169
  function stringsFor(ctx) {
1730
2170
  const language = localeOf(ctx);