dsh-provider-proxy 0.7.0 → 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": "b4bf55bf_root", "lead": "03b72018_lead", "empty": "9a9a9c27_empty", "card": "6d6907d5_card", "cardActive": "a287416b_cardActive", "cardRejected": "aabaa9b3_cardRejected", "defaultCard": "ebf26189_defaultCard", "defaultLead": "5b9e9eb5_defaultLead", "cardHead": "a3319282_cardHead", "cardTitles": "6f64c28d_cardTitles", "name": "847f7e47_name", "id": "7864474d_id", "pillOn": "7ed5399b_pillOn", "pillLive": "0ccb6da5_pillLive", "pillOff": "3accadc4_pillOff", "pillRejected": "86897991_pillRejected", "toggle": "81025292_toggle", "field": "5e2577de_field", "label": "d9087e9b_label", "hint": "aae2a8a2_hint", "input": "feb8c902_input", "passwordToggle": "5645283d_passwordToggle", "testButton": "de8ca09a_testButton", "diag": "4ad17b9d_diag", "diagSummary": "b2f0759b_diagSummary", "diagList": "f49699bb_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.b4bf55bf_root {\n display: flex;\n flex-direction: column;\n gap: 12px;\n max-width: 640px;\n}\n\n.03b72018_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.9a9a9c27_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.6d6907d5_card,\n.a287416b_cardActive,\n.aabaa9b3_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.a287416b_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.aabaa9b3_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.ebf26189_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.5b9e9eb5_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.a3319282_cardHead {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.6f64c28d_cardTitles {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.847f7e47_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.7864474d_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.7ed5399b_pillOn,\n.0ccb6da5_pillLive,\n.3accadc4_pillOff,\n.86897991_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.7ed5399b_pillOn {\n background: var(--dsw-alias-state-success-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.0ccb6da5_pillLive {\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-secondary);\n}\n\n.3accadc4_pillOff {\n color: var(--dsw-alias-state-idle-primary);\n}\n\n.86897991_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.81025292_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.81025292_toggle input {\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n/* ---- fields ---- */\n\n.5e2577de_field {\n display: grid;\n grid-template-columns: 96px 1fr;\n align-items: center;\n gap: 10px;\n font-size: 12px;\n}\n\n.d9087e9b_label {\n color: var(--dsw-alias-label-secondary);\n}\n\n.aae2a8a2_hint {\n grid-column: 2;\n margin-top: -2px;\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.feb8c902_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.feb8c902_input:hover:not(:disabled) {\n border-color: var(--dsw-alias-border-l3) !important;\n}\n\n.feb8c902_input:focus {\n border-color: var(--dsw-alias-brand-primary) !important;\n}\n\n.feb8c902_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/* The icon sits inside the input, so its hover/focus feedback must be local\n and must outrank the transparent inline base. */\n.5645283d_passwordToggle {\n transition: background 120ms ease, color 120ms ease;\n}\n\n.5645283d_passwordToggle:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover) !important;\n color: var(--dsw-alias-label-primary) !important;\n}\n\n.5645283d_passwordToggle:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\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.de8ca09a_testButton {\n transition: border-color 120ms ease, background 120ms ease;\n}\n\n.de8ca09a_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.4ad17b9d_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.b2f0759b_diagSummary {\n cursor: pointer;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.f49699bb_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;
@@ -666,10 +666,11 @@ var S = {
666
666
  },
667
667
  field: {
668
668
  display: "grid",
669
- gridTemplateColumns: "88px 1fr",
669
+ gridTemplateColumns: "76px 1fr",
670
670
  alignItems: "center",
671
- gap: "10px",
672
- fontSize: "12px"
671
+ gap: "8px",
672
+ fontSize: "12px",
673
+ minWidth: "0"
673
674
  },
674
675
  label: { color: "var(--dsw-alias-label-secondary)" },
675
676
  input: {
@@ -694,9 +695,83 @@ var S = {
694
695
  color: "var(--dsw-alias-label-secondary)"
695
696
  },
696
697
  hint: {
697
- gridColumn: "2",
698
+ gridColumn: "1 / -1",
698
699
  fontSize: "11px",
699
- 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"
700
775
  }
701
776
  };
702
777
  function renderPage(ctx) {
@@ -845,11 +920,77 @@ function renderPage(ctx) {
845
920
  };
846
921
  const [filter, setFilter] = (React.useState ?? ((v) => [v, () => {
847
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
+ );
848
989
  const runTest = (id) => {
849
990
  const generation = (testGeneration.get(id) ?? 0) + 1;
850
991
  testGeneration.set(id, generation);
851
992
  setTests((current) => ({ ...current, [id]: { pending: true } }));
852
- void (async () => {
993
+ return (async () => {
853
994
  try {
854
995
  for (const [key, entry] of [...pendingWrites]) {
855
996
  if (key.startsWith(`${id}\0`) || key.startsWith("\0default\0") || key.startsWith("\0global\0")) {
@@ -875,6 +1016,28 @@ function renderPage(ctx) {
875
1016
  }
876
1017
  })();
877
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
+ };
878
1041
  const rows = knownProviders({ ...configured, ...draft }, published);
879
1042
  const inheritingIds = () => rows.filter((row) => view({ ...configured[row.id], ...draft[row.id] }).useDefault === true).map((row) => row.id);
880
1043
  const save = (key, path, label, value, onRefused) => {
@@ -1048,9 +1211,16 @@ function renderPage(ctx) {
1048
1211
  event.preventDefault?.();
1049
1212
  apply2(parts);
1050
1213
  };
1051
- const field = (label, control, hint, hintError) => h(
1214
+ const field = (label, control, hint, hintError, fullWidth) => h(
1052
1215
  "label",
1053
- { 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
+ },
1054
1224
  h("span", { className: import_client.default.label, style: S.label }, label),
1055
1225
  control,
1056
1226
  hint === void 0 ? null : h(
@@ -1058,8 +1228,8 @@ function renderPage(ctx) {
1058
1228
  {
1059
1229
  className: import_client.default.hint,
1060
1230
  style: {
1061
- gridColumn: "2",
1062
- fontSize: "11px",
1231
+ ...S.hint,
1232
+ marginTop: "2px",
1063
1233
  // A warning that a value can never work is an answer, not
1064
1234
  // decoration — same rule as the failed self-check text.
1065
1235
  ...hintError === true ? { color: "var(--dsw-alias-state-error-primary, #f85149)" } : {}
@@ -1095,77 +1265,83 @@ function renderPage(ctx) {
1095
1265
  )
1096
1266
  ),
1097
1267
  h("p", { className: import_client.default.defaultLead, style: S.defaultLead }, t.defaultLead),
1098
- field(
1099
- t.protocol,
1100
- h(
1101
- "select",
1102
- {
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", {
1103
1289
  className: import_client.default.input,
1104
1290
  style: S.input,
1105
- value: defaultView.protocol,
1106
- onChange: (event) => bindDefault("protocol")(event.target.value)
1107
- },
1108
- ...PROTOCOLS.map(
1109
- (protocol) => h("option", { key: protocol, value: protocol }, protocol.toUpperCase())
1110
- )
1111
- )
1112
- ),
1113
- field(
1114
- t.host,
1115
- h("input", {
1116
- className: import_client.default.input,
1117
- style: S.input,
1118
- placeholder: "127.0.0.1",
1119
- value: defaultView.host,
1120
- onChange: (event) => bindDefault("host")(event.target.value),
1121
- // Same convenience as the cards: a full URL fills the whole block.
1122
- onPaste: onProxyUrlPaste(applyDefaultUrl)
1123
- }),
1124
- defaultHostWhy ?? t.hostHint,
1125
- defaultHostWhy !== void 0
1126
- ),
1127
- field(
1128
- t.port,
1129
- h("input", {
1130
- className: import_client.default.input,
1131
- style: S.input,
1132
- type: "number",
1133
- min: 1,
1134
- max: 65535,
1135
- placeholder: "7890",
1136
- value: defaultView.port === 0 ? "" : String(defaultView.port),
1137
- onChange: (event) => {
1138
- const parsed = Number.parseInt(event.target.value, 10);
1139
- bindDefault("port")(Number.isNaN(parsed) ? 0 : parsed);
1140
- }
1141
- })
1142
- ),
1143
- field(
1144
- t.username,
1145
- h("input", {
1146
- className: import_client.default.input,
1147
- style: S.input,
1148
- autoComplete: "off",
1149
- value: defaultView.username,
1150
- onChange: (event) => bindDefault("username")(event.target.value)
1151
- })
1152
- ),
1153
- field(
1154
- t.password,
1155
- h(
1156
- "div",
1157
- { style: S.passwordControl },
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,
1158
1318
  h("input", {
1159
1319
  className: import_client.default.input,
1160
- style: { ...S.input, paddingRight: "36px" },
1161
- type: defaultPasswordVisible === true ? "text" : "password",
1320
+ style: S.input,
1162
1321
  autoComplete: "off",
1163
- value: defaultView.password,
1164
- onChange: (event) => bindDefault("password")(event.target.value)
1165
- }),
1166
- passwordToggle(defaultPasswordVisible === true, toggleDefaultPassword)
1322
+ value: defaultView.username,
1323
+ onChange: (event) => bindDefault("username")(event.target.value)
1324
+ })
1167
1325
  ),
1168
- t.passwordHintProxy
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
1344
+ )
1169
1345
  ),
1170
1346
  // Bulk opt-in, in ONE write: a per-card walk would rebuild the table N times.
1171
1347
  // Disabled while the default configuration is incomplete, because pointing every card at an
@@ -1213,7 +1389,7 @@ function renderPage(ctx) {
1213
1389
  value: globalTestUrl,
1214
1390
  onChange: (event) => bindGlobal("testUrl")(event.target.value)
1215
1391
  }),
1216
- 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(
1217
1393
  "span",
1218
1394
  {
1219
1395
  className: import_client.default.hint,
@@ -1251,21 +1427,87 @@ function renderPage(ctx) {
1251
1427
  ),
1252
1428
  h(
1253
1429
  "p",
1254
- { style: S.hint },
1430
+ { className: import_client.default.hint, style: { ...S.hint, margin: "2px 0 0" } },
1255
1431
  globalLocalDirect ? t.localDirectOnHint : t.localDirectOffHint
1256
1432
  )
1257
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
+ };
1258
1443
  const query = filter.trim().toLowerCase();
1259
- 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
+ );
1260
1477
  const filterRow = rows.length === 0 ? null : field(
1261
1478
  t.filterLabel,
1262
- h("input", {
1263
- className: import_client.default.input,
1264
- style: S.input,
1265
- placeholder: t.filterPlaceholder,
1266
- value: filter,
1267
- onChange: (event) => setFilter(event.target.value)
1268
- })
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
+ )
1269
1511
  );
1270
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;
1271
1513
  const catalogueNotice = state.error !== void 0 && rows.length > 0 ? h(
@@ -1384,11 +1626,13 @@ function renderPage(ctx) {
1384
1626
  }),
1385
1627
  passwordToggle(passwordVisible[row.id] === true, () => togglePassword(row.id))
1386
1628
  ),
1387
- route.protocol === "socks5" ? t.passwordHint : t.passwordHintProxy
1629
+ route.protocol === "socks5" ? t.passwordHint : t.passwordHintProxy,
1630
+ false,
1631
+ true
1388
1632
  )
1389
1633
  ];
1390
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 };
1391
- return h(
1635
+ const cardSection = h(
1392
1636
  "section",
1393
1637
  {
1394
1638
  className: rejected ? import_client.default.cardRejected : route.enabled ? import_client.default.cardActive : import_client.default.card,
@@ -1454,7 +1698,7 @@ function renderPage(ctx) {
1454
1698
  { key: `${row.id}:inherit`, style: S.inheritNote },
1455
1699
  `${t.inheritingDefault}${defaultOrigin}`
1456
1700
  ) : null,
1457
- ...useDefault ? [] : endpointFields,
1701
+ ...useDefault ? [] : [h("div", { className: import_client.default.formGrid, style: S.formGrid, key: `${row.id}:form-grid` }, ...endpointFields)],
1458
1702
  // The self-check is offered whenever there is something to check: an
1459
1703
  // enabled route (the Host's table carries it, and a rejected one still
1460
1704
  // reports WHY), or a disabled one whose endpoint is already complete —
@@ -1495,7 +1739,12 @@ function renderPage(ctx) {
1495
1739
  { key: "status" },
1496
1740
  `HTTP ${result.status}`
1497
1741
  ) : null,
1498
- 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
+ ),
1499
1748
  result.direct === true ? h("span", { key: "direct" }, t.directLabel) : null,
1500
1749
  result.cached === true ? h("span", { key: "cached" }, t.cachedLabel) : null,
1501
1750
  result.target === void 0 ? null : h(
@@ -1506,13 +1755,22 @@ function renderPage(ctx) {
1506
1755
  flexBasis: "100%",
1507
1756
  fontSize: "11px",
1508
1757
  color: "var(--dsw-alias-label-secondary)",
1509
- wordBreak: "break-all"
1758
+ wordBreak: "break-all",
1759
+ display: "inline-flex",
1760
+ alignItems: "center",
1761
+ gap: "3px"
1510
1762
  }
1511
1763
  },
1512
- `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`
1764
+ `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`,
1765
+ copyButton(`${row.id}:target`, result.target, t.copy)
1513
1766
  )
1514
1767
  ] : [
1515
- 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
+ ),
1516
1774
  typeof result.status === "number" ? h("span", { key: "status" }, `HTTP ${result.status}`) : null,
1517
1775
  result.target === void 0 ? null : h(
1518
1776
  "span",
@@ -1522,10 +1780,14 @@ function renderPage(ctx) {
1522
1780
  flexBasis: "100%",
1523
1781
  fontSize: "11px",
1524
1782
  color: "var(--dsw-alias-label-secondary)",
1525
- wordBreak: "break-all"
1783
+ wordBreak: "break-all",
1784
+ display: "inline-flex",
1785
+ alignItems: "center",
1786
+ gap: "3px"
1526
1787
  }
1527
1788
  },
1528
- `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`
1789
+ `${t.testTargetLabel}${result.target}${targetSourceSuffix(result, t)}`,
1790
+ copyButton(`${row.id}:target`, result.target, t.copy)
1529
1791
  )
1530
1792
  ]
1531
1793
  ) : null,
@@ -1588,6 +1850,58 @@ function renderPage(ctx) {
1588
1850
  )
1589
1851
  )
1590
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;
1591
1905
  });
1592
1906
  return h(
1593
1907
  "div",
@@ -1628,6 +1942,7 @@ function renderPage(ctx) {
1628
1942
  defaultBlock,
1629
1943
  globalBlock,
1630
1944
  catalogueNotice,
1945
+ chipRow,
1631
1946
  filterRow,
1632
1947
  ...body,
1633
1948
  h(
@@ -1679,6 +1994,10 @@ var EN = {
1679
1994
  defaultMissing: "The default proxy configuration is incomplete, so this provider is not proxied: ",
1680
1995
  filterLabel: "Filter",
1681
1996
  filterPlaceholder: "Filter by name or id\u2026",
1997
+ filterStatusAll: "All",
1998
+ filterStatusProxied: "Proxied",
1999
+ filterStatusDirect: "Direct",
2000
+ filterStatusRejected: "Rejected",
1682
2001
  noMatch: "No provider matches the filter.",
1683
2002
  setAsDefault: "Set as the default proxy",
1684
2003
  applyDefaultAll: "Use the default proxy for all providers",
@@ -1690,7 +2009,7 @@ var EN = {
1690
2009
  showPassword: "Show",
1691
2010
  hidePassword: "Hide",
1692
2011
  confirmPromoteToDefault: "The default proxy is already in use by one or more providers. Replace it with this card's endpoint?",
1693
- 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.",
2012
+ hostHint: "IP or domain only; paste full URL supported",
1694
2013
  passwordHint: "Sent to the SOCKS5 proxy for authentication; stored with this setting.",
1695
2014
  passwordHintProxy: "Sent to the proxy as an authentication password; stored with this setting.",
1696
2015
  hitCountPrefix: "",
@@ -1703,6 +2022,12 @@ var EN = {
1703
2022
  minutesAgo: " min ago",
1704
2023
  hoursAgo: " h ago",
1705
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",
1706
2031
  testButton: "Test",
1707
2032
  testRunning: "Testing\u2026",
1708
2033
  testDisabledNote: "Not enabled \u2014 test only: ",
@@ -1744,7 +2069,9 @@ var EN = {
1744
2069
  catalogueMismatch: "Saved but not listed by the Host catalogue: ",
1745
2070
  catalogueMismatchSuffix: ". Their proxy settings are kept.",
1746
2071
  saveFailedPrefix: "Save failed: ",
1747
- close: "Close"
2072
+ close: "Close",
2073
+ copy: "Copy",
2074
+ copied: "Copied"
1748
2075
  };
1749
2076
  var ZH = {
1750
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",
@@ -1760,6 +2087,10 @@ var ZH = {
1760
2087
  defaultMissing: "\u9876\u5C42\u9ED8\u8BA4\u4EE3\u7406\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF0C\u8BE5 provider \u4E0D\u4F1A\u8D70\u4EE3\u7406\uFF1A",
1761
2088
  filterLabel: "\u8FC7\u6EE4",
1762
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",
1763
2094
  noMatch: "\u6CA1\u6709\u5339\u914D\u7684 provider\u3002",
1764
2095
  setAsDefault: "\u8BBE\u4E3A\u9ED8\u8BA4\u4EE3\u7406",
1765
2096
  applyDefaultAll: "\u6240\u6709 provider \u4F7F\u7528\u9ED8\u8BA4\u4EE3\u7406",
@@ -1771,7 +2102,7 @@ var ZH = {
1771
2102
  showPassword: "\u663E\u793A",
1772
2103
  hidePassword: "\u9690\u85CF",
1773
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",
1774
- 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",
2105
+ hostHint: "\u4EC5\u9650 IP \u6216\u57DF\u540D\uFF0C\u652F\u6301\u7C98\u8D34\u5B8C\u6574\u4EE3\u7406 URL",
1775
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",
1776
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",
1777
2108
  hitCountPrefix: "\u5DF2\u4EE3\u7406 ",
@@ -1784,6 +2115,12 @@ var ZH = {
1784
2115
  minutesAgo: " \u5206\u949F\u524D",
1785
2116
  hoursAgo: " \u5C0F\u65F6\u524D",
1786
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",
1787
2124
  testButton: "\u6D4B\u8BD5",
1788
2125
  testRunning: "\u6D4B\u8BD5\u4E2D\u2026",
1789
2126
  testDisabledNote: "\u672A\u542F\u7528\uFF0C\u4EC5\u6D4B\u8BD5\uFF1A",
@@ -1825,7 +2162,9 @@ var ZH = {
1825
2162
  catalogueMismatch: "\u5DF2\u4FDD\u5B58\u4F46\u672A\u51FA\u73B0\u5728 Host \u76EE\u5F55\u4E2D\u7684 provider\uFF1A",
1826
2163
  catalogueMismatchSuffix: "\u3002\u4EE3\u7406\u914D\u7F6E\u4ECD\u4F1A\u4FDD\u7559\u3002",
1827
2164
  saveFailedPrefix: "\u4FDD\u5B58\u5931\u8D25\uFF1A",
1828
- close: "\u5173\u95ED"
2165
+ close: "\u5173\u95ED",
2166
+ copy: "\u590D\u5236",
2167
+ copied: "\u5DF2\u590D\u5236"
1829
2168
  };
1830
2169
  function stringsFor(ctx) {
1831
2170
  const language = localeOf(ctx);