dsh-remote-mobile 1.2.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -47,10 +47,10 @@ __export(index_exports, {
47
47
  inject: () => inject
48
48
  });
49
49
  module.exports = __toCommonJS(index_exports);
50
- var import_react9 = __toESM(require("react"), 1);
50
+ var import_react10 = __toESM(require("react"), 1);
51
51
 
52
52
  // src/client/components/MainSection.jsx
53
- var import_react8 = __toESM(require("react"), 1);
53
+ var import_react9 = __toESM(require("react"), 1);
54
54
 
55
55
  // src/client/api.js
56
56
  function fetchStatus() {
@@ -132,6 +132,46 @@ function clearIpStatsApi() {
132
132
  return res.json();
133
133
  });
134
134
  }
135
+ function fetchStyles() {
136
+ return fetch("/api/remote-mobile/styles?_t=" + Date.now()).then(function(res) {
137
+ return res.json();
138
+ });
139
+ }
140
+ function saveStyleSnippet(payload) {
141
+ return fetch("/api/remote-mobile/styles", {
142
+ method: "POST",
143
+ headers: { "Content-Type": "application/json" },
144
+ body: JSON.stringify(payload)
145
+ }).then(function(res) {
146
+ return res.json();
147
+ });
148
+ }
149
+ function toggleStyleApi(id, scope, enabled) {
150
+ return fetch("/api/remote-mobile/styles/toggle", {
151
+ method: "POST",
152
+ headers: { "Content-Type": "application/json" },
153
+ body: JSON.stringify({ id, scope, enabled })
154
+ }).then(function(res) {
155
+ return res.json();
156
+ });
157
+ }
158
+ function deleteStyleSnippetApi(id) {
159
+ return fetch("/api/remote-mobile/styles/delete", {
160
+ method: "POST",
161
+ headers: { "Content-Type": "application/json" },
162
+ body: JSON.stringify({ id })
163
+ }).then(function(res) {
164
+ return res.json();
165
+ });
166
+ }
167
+ function resetStylesApi() {
168
+ return fetch("/api/remote-mobile/styles/reset", {
169
+ method: "POST",
170
+ headers: { "Content-Type": "application/json" }
171
+ }).then(function(res) {
172
+ return res.json();
173
+ });
174
+ }
135
175
 
136
176
  // src/client/qrcode.js
137
177
  var generateQrSvg = (function() {
@@ -739,7 +779,7 @@ var translations = {
739
779
  storageCardDesc: "\u6240\u6709\u654F\u611F\u51ED\u8BC1\u5747\u5728\u672C\u5730\u7ECF\u8FC7\u52A0\u76D0\u54C8\u5E0C\u6216\u975E\u5BF9\u79F0\u52A0\u5BC6\u4FDD\u62A4\uFF0C\u4E0D\u4F9D\u8D56\u4EFB\u4F55\u7B2C\u4E09\u65B9\u4E91\u7AEF\u9274\u6743\u4E2D\u5FC3\u3002",
740
780
  storageDeviceFile: "\u5DF2\u6388\u6743\u8BBE\u5907\u4E0E\u4F1A\u8BDD\u7F13\u5B58\u6587\u4EF6\uFF1A",
741
781
  storageRsaFile: "\u670D\u52A1\u7AEF RSA \u79C1\u94A5\u6587\u4EF6 (0o600 \u6743\u9650\u4FDD\u62A4)\uFF1A",
742
- storageConfigFile: "\u4E3B\u914D\u7F6E\u6587\u4EF6 (\u547D\u540D\u7A7A\u95F4: dsh-remote-mobile)\uFF1A",
782
+ storageStylesFile: "\u79FB\u52A8\u7AEF\u6837\u5F0F\u7247\u6BB5\u6587\u4EF6 (\u53EF\u72EC\u7ACB\u542F\u505C\u7684 CSS \u5C0F\u63D2\u4EF6)\uFF1A",
743
783
  // 卡片 7: 全局高级配置
744
784
  configCardTitle: "\u5168\u5C40\u9AD8\u7EA7\u5B89\u5168\u53C2\u6570\u914D\u7F6E",
745
785
  configCardDesc: "\u8C03\u6574\u5355 IP \u8BBF\u95EE\u9891\u7387\u9650\u5236\u3001\u8F93\u9519\u5C01\u7981\u9608\u503C\u4E0E\u9501\u5B9A\u6301\u7EED\u65F6\u95F4\u3002\u70B9\u51FB\u4FDD\u5B58\u5C06\u7ACB\u5373\u751F\u6548\u5E76\u81EA\u52A8\u540C\u6B65\u5199\u56DE ~/.dsh/settings.yaml \u914D\u7F6E\u6587\u4EF6\u3002",
@@ -758,6 +798,49 @@ var translations = {
758
798
  saveConfigSuccessToast: "\u9AD8\u7EA7\u5B89\u5168\u53C2\u6570\u5DF2\u6210\u529F\u4FDD\u5B58\u5E76\u540C\u6B65\u5230 settings.yaml\uFF01",
759
799
  restoreDefaultsSuccessToast: "\u5DF2\u6210\u529F\u6062\u590D\u9ED8\u8BA4\u5B89\u5168\u53C2\u6570\u5E76\u7ACB\u5373\u751F\u6548\uFF01",
760
800
  saveConfigFailTip: "\u53C2\u6570\u5FC5\u987B\u4E3A\u5927\u4E8E 0 \u7684\u6709\u6548\u6574\u6570",
801
+ // 卡片 8: 移动端样式片段(样式小插件)
802
+ tabAccess: "\u63A5\u5165",
803
+ tabDevices: "\u8BBE\u5907\u4E0E\u5B89\u5168",
804
+ tabStyle: "\u6837\u5F0F\u8986\u5199",
805
+ tabStorage: "\u672C\u5730\u6570\u636E",
806
+ stylesCardTitle: "\u79FB\u52A8\u7AEF\u6837\u5F0F\u7247\u6BB5\uFF08\u6837\u5F0F\u5C0F\u63D2\u4EF6\uFF09",
807
+ stylesCardDesc: "\u4EE5\u53EF\u72EC\u7ACB\u542F\u505C\u7684 CSS \u7247\u6BB5\u65B9\u5F0F\u9002\u914D DSH Web \u754C\u9762\u3002\u5185\u7F6E\u9884\u8BBE\u6309\u754C\u9762\u533A\u57DF\u5206\u4E3A\u4FA7\u8FB9\u680F\u3001\u8BBE\u7F6E\u9762\u677F\u4E0E\u6B63\u6587\u4E09\u6BB5\uFF0C\u6BCF\u6BB5\u5747\u53EF\u5206\u522B\u8BBE\u7F6E PC / \u79FB\u52A8\u7AEF\u662F\u5426\u542F\u7528\uFF1B\u4F60\u4E5F\u53EF\u4EE5\u7C98\u8D34\u81EA\u5DF1\u7684 CSS \u5C0F\u63D2\u4EF6\uFF0C\u4FDD\u5B58\u540E\u4E0B\u4E00\u6B21\u9875\u9762\u52A0\u8F7D\u7ACB\u5373\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\u3002",
808
+ stylesPresetsTitle: "\u{1F9E9} \u5185\u7F6E\u9884\u8BBE\u7247\u6BB5",
809
+ stylesCustomTitle: "\u270D\uFE0F \u81EA\u5B9A\u4E49\u7247\u6BB5",
810
+ stylesPcShort: "PC",
811
+ stylesMobileShort: "\u79FB\u52A8\u7AEF",
812
+ stylesBothDevices: "PC + \u79FB\u52A8\u7AEF",
813
+ stylesMobileOnlyBadge: "\u4EC5\u79FB\u52A8\u7AEF",
814
+ stylesPcOnlyBadge: "\u4EC5 PC",
815
+ stylesDisabled: "\u5DF2\u505C\u7528",
816
+ stylesViewCss: "\u67E5\u770B CSS",
817
+ stylesHideCss: "\u6536\u8D77 CSS",
818
+ stylesCopyCss: "\u{1F4CB} \u590D\u5236\u6837\u5F0F",
819
+ stylesCopyCssDone: "\u6837\u5F0F\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\uFF01",
820
+ stylesNamePlaceholder: "\u7247\u6BB5\u540D\u79F0\uFF08\u5FC5\u586B\uFF09",
821
+ stylesCssPlaceholder: "\u7C98\u8D34\u6216\u7F16\u5199 CSS \u4EE3\u7801\u7247\u6BB5\u2026\uFF08\u79FB\u52A8\u7AEF\u53EF\u7528 html[data-dsh-mobile] \u4F5C\u4E3A\u4F5C\u7528\u57DF\u9009\u62E9\u5668\uFF09",
822
+ stylesAddNew: "\uFF0B \u65B0\u589E\u81EA\u5B9A\u4E49\u7247\u6BB5",
823
+ stylesSave: "\u{1F4BE} \u4FDD\u5B58\u7247\u6BB5",
824
+ stylesCancelEdit: "\u53D6\u6D88",
825
+ stylesEditBtn: "\u7F16\u8F91",
826
+ stylesDeleteBtn: "\u5220\u9664",
827
+ stylesResetAll: "\u{1F504} \u6062\u590D\u9ED8\u8BA4\u542F\u505C",
828
+ stylesEmptyCustom: "\u6682\u65E0\u81EA\u5B9A\u4E49\u7247\u6BB5\uFF0C\u70B9\u51FB\u300C\u65B0\u589E\u81EA\u5B9A\u4E49\u7247\u6BB5\u300D\u5F00\u59CB\u3002",
829
+ stylesLoadFail: "\u52A0\u8F7D\u6837\u5F0F\u7247\u6BB5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
830
+ stylesSaveSuccess: "\u6837\u5F0F\u7247\u6BB5\u5DF2\u4FDD\u5B58\uFF0C\u5237\u65B0\u9875\u9762\u5373\u53EF\u770B\u5230\u6548\u679C",
831
+ stylesDeleteConfirm: "\u786E\u5B9A\u8981\u5220\u9664\u8BE5\u81EA\u5B9A\u4E49\u6837\u5F0F\u7247\u6BB5\u5417\uFF1F",
832
+ stylesResetConfirm: "\u786E\u5B9A\u8981\u6062\u590D\u6240\u6709\u7247\u6BB5\u7684\u9ED8\u8BA4\u542F\u505C\u72B6\u6001\u5417\uFF1F\uFF08\u5185\u7F6E\u9884\u8BBE\uFF1APC \u5173\u95ED\u3001\u79FB\u52A8\u7AEF\u542F\u7528\uFF1B\u81EA\u5B9A\u4E49\u7247\u6BB5\uFF1APC \u5173\u95ED\u3001\u79FB\u52A8\u7AEF\u542F\u7528\uFF09",
833
+ stylesFieldsRequired: "\u8BF7\u586B\u5199\u7247\u6BB5\u540D\u79F0\u4E0E CSS \u5185\u5BB9",
834
+ stylesHint: "\u{1F4A1} \u63D0\u793A\uFF1A\u6BCF\u6BB5\u9884\u8BBE\u4E0E\u81EA\u5B9A\u4E49\u7247\u6BB5\u90FD\u652F\u6301 PC / \u79FB\u52A8\u7AEF\u5206\u522B\u542F\u7528\u3002\u79FB\u52A8\u7AEF\u8BF7\u6C42\u4F1A\u7ED9 <html> \u6253\u4E0A data-dsh-mobile \u6807\u8BB0\uFF1B\u5185\u7F6E\u9884\u8BBE\u9762\u5411\u79FB\u52A8\u7AEF\u4F18\u5316\uFF08\u9ED8\u8BA4 PC \u5173\u95ED\uFF09\uFF0C\u5728 PC \u4E0A\u542F\u7528\u65F6\u8BF7\u786E\u4FDD\u7247\u6BB5\u4E0D\u4F9D\u8D56 data-dsh-mobile \u4F5C\u7528\u57DF\u3002",
835
+ // 插件共存警示横幅
836
+ conflictBannerTitle: "\u68C0\u6D4B\u5230\u5176\u4ED6\u8FDC\u7A0B\u63A5\u5165\u63D2\u4EF6",
837
+ conflictUnknownPlugin: "\u53E6\u4E00\u6B3E\u8FDC\u7A0B\u63A5\u5165\u7C7B\u63D2\u4EF6",
838
+ conflictBannerBody: "\u68C0\u6D4B\u5230 {plugin} \u5DF2\u5360\u7528\u914D\u5BF9\u5171\u4EAB\u670D\u52A1\uFF08remoteWebUiPairing\uFF09\uFF0C\u672C\u63D2\u4EF6\u5DF2\u81EA\u52A8\u8BA9\u51FA\u4EE5\u4FDD\u8BC1\u6B63\u5E38\u542F\u52A8\u3002\u4E24\u5957\u8FDC\u7A0B\u63A5\u5165\u529F\u80FD\u5E76\u5B58\u53EF\u80FD\u51FA\u73B0\u91CD\u590D\u5165\u53E3\u4E0E\u72B6\u6001\u4E0D\u4E00\u81F4\uFF0C\u5EFA\u8BAE\u53EA\u4FDD\u7559\u5176\u4E00\u3002\u53EF\u590D\u5236\u4E0B\u65B9\u8BCA\u65AD\u4FE1\u606F\uFF0C\u7C98\u8D34\u7ED9 AI \u52A9\u624B\u6309\u6B65\u9AA4\u5904\u7406\u3002",
839
+ conflictCopyBtn: "\u{1F4CB} \u590D\u5236\u8BCA\u65AD\u4E0E\u4FEE\u590D\u4FE1\u606F",
840
+ conflictCopiedTip: "\u8BCA\u65AD\u4FE1\u606F\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u7ED9 AI \u52A9\u624B\u534F\u52A9\u5904\u7406",
841
+ conflictReportText: "[DSH \u63D2\u4EF6\u51B2\u7A81\u8BCA\u65AD\u62A5\u544A]\n\u73B0\u8C61\uFF1Adsh-remote-mobile \u8BBE\u7F6E\u9762\u677F\u63D0\u793A\u300C\u68C0\u6D4B\u5230\u5176\u4ED6\u8FDC\u7A0B\u63A5\u5165\u63D2\u4EF6\u300D\u3002\n\u539F\u56E0\uFF1A\u53E6\u4E00\u6B3E\u8FDC\u7A0B/Web \u63A5\u5165\u7C7B\u63D2\u4EF6\u5360\u7528\u4E86 Cordis \u5171\u4EAB\u670D\u52A1 remoteWebUiPairing\uFF0Cdsh-remote-mobile \u4E3A\u4FDD\u8BC1\u6B63\u5E38\u542F\u52A8\u5DF2\u81EA\u52A8\u8BA9\u51FA\u8BE5\u670D\u52A1\uFF0C\u5F53\u524D\u4E24\u5957\u8FDC\u7A0B\u63A5\u5165\u529F\u80FD\u5E76\u5B58\u3002\n\u5360\u7528\u65B9\u63D2\u4EF6\u5305\u540D\uFF1A{plugin}\n\u5360\u7528\u65B9 loader entry id\uFF1A{entryId}\n\u5904\u7406\u65B9\u6848\uFF08\u4E8C\u9009\u4E00\uFF09\uFF1A\n\u65B9\u6848 A\uFF08\u4FDD\u7559 dsh-remote-mobile\uFF0C\u7981\u7528\u5360\u7528\u65B9\uFF09\uFF1A\n1. \u6253\u5F00\u914D\u7F6E\u6587\u4EF6 ~/.dsh/profiles/web/cordis.patch.yml\n2. \u5728\u6587\u4EF6\u672B\u5C3E\u8FFD\u52A0\u4EE5\u4E0B\u4E24\u884C\uFF08\u6CE8\u610F YAML \u7F29\u8FDB\uFF0C\u7B2C\u4E8C\u884C\u524D\u6709\u4E24\u4E2A\u7A7A\u683C\uFF09\uFF1A\n- id: {entryId}\n disabled: true\n3. \u7ED3\u675F\u5F53\u524D\u6B63\u5728\u8FD0\u884C\u7684 dsh web \u8FDB\u7A0B\uFF0C\u91CD\u65B0\u6267\u884C dsh web --no-open\n\u65B9\u6848 B\uFF08\u4FDD\u7559\u5360\u7528\u65B9\uFF0C\u5378\u8F7D dsh-remote-mobile\uFF09\uFF1A\n\u6267\u884C\u547D\u4EE4 dsh plugin --profile web remove dsh-remote-mobile \u540E\u91CD\u542F dsh web",
842
+ conflictReportNoEntryIdText: "[DSH \u63D2\u4EF6\u51B2\u7A81\u8BCA\u65AD\u62A5\u544A]\n\u73B0\u8C61\uFF1Adsh-remote-mobile \u8BBE\u7F6E\u9762\u677F\u63D0\u793A\u300C\u68C0\u6D4B\u5230\u5176\u4ED6\u8FDC\u7A0B\u63A5\u5165\u63D2\u4EF6\u300D\u3002\n\u539F\u56E0\uFF1A\u53E6\u4E00\u6B3E\u8FDC\u7A0B/Web \u63A5\u5165\u7C7B\u63D2\u4EF6\u5360\u7528\u4E86 Cordis \u5171\u4EAB\u670D\u52A1 remoteWebUiPairing\uFF0Cdsh-remote-mobile \u4E3A\u4FDD\u8BC1\u6B63\u5E38\u542F\u52A8\u5DF2\u81EA\u52A8\u8BA9\u51FA\u8BE5\u670D\u52A1\uFF0C\u5F53\u524D\u4E24\u5957\u8FDC\u7A0B\u63A5\u5165\u529F\u80FD\u5E76\u5B58\u3002\n\u5360\u7528\u65B9\u63D2\u4EF6\u5305\u540D\uFF1A{plugin}\n\u5360\u7528\u65B9 loader entry id\uFF1A\u672A\u80FD\u81EA\u52A8\u8BC6\u522B\n\u5904\u7406\u65B9\u6848\uFF08\u4E8C\u9009\u4E00\uFF09\uFF1A\n\u65B9\u6848 A\uFF08\u4FDD\u7559 dsh-remote-mobile\uFF0C\u7981\u7528\u5360\u7528\u65B9\uFF09\uFF1A\n1. \u6267\u884C dsh --dump-config \u67E5\u770B\u5168\u90E8 loader entry\uFF0C\u627E\u5230\u5360\u7528\u65B9\u63D2\u4EF6\u5BF9\u5E94\u7684\u6761\u76EE id\n2. \u6253\u5F00\u914D\u7F6E\u6587\u4EF6 ~/.dsh/profiles/web/cordis.patch.yml\uFF0C\u5728\u6587\u4EF6\u672B\u5C3E\u8FFD\u52A0\uFF08\u6CE8\u610F YAML \u7F29\u8FDB\uFF09\uFF1A\n- id: <\u5360\u7528\u65B9\u6761\u76EEid>\n disabled: true\n3. \u7ED3\u675F\u5F53\u524D\u6B63\u5728\u8FD0\u884C\u7684 dsh web \u8FDB\u7A0B\uFF0C\u91CD\u65B0\u6267\u884C dsh web --no-open\n\u65B9\u6848 B\uFF08\u4FDD\u7559\u5360\u7528\u65B9\uFF0C\u5378\u8F7D dsh-remote-mobile\uFF09\uFF1A\n\u6267\u884C\u547D\u4EE4 dsh plugin --profile web remove dsh-remote-mobile \u540E\u91CD\u542F dsh web",
843
+ conflictBannerDismiss: "\u77E5\u9053\u4E86",
761
844
  // Toast 提示
762
845
  toastDeviceConnected: "\u{1F389} \u65B0\u8BBE\u5907\u63A5\u5165\u6210\u529F\uFF1A{name} ({ip})",
763
846
  toastDeviceOnline: "\u{1F4F6} \u8BBE\u5907\u91CD\u65B0\u4E0A\u7EBF\uFF1A{name} ({ip})",
@@ -850,7 +933,7 @@ var translations = {
850
933
  storageCardDesc: "All sensitive credentials are protected locally via slow salt hashing and asymmetric RSA cryptography, without cloud dependencies.",
851
934
  storageDeviceFile: "Authorized devices & session cache:",
852
935
  storageRsaFile: "Server RSA private key (0o600 protected):",
853
- storageConfigFile: "Main configuration (namespace: dsh-remote-mobile):",
936
+ storageStylesFile: "Mobile style snippets file (toggleable CSS mini-plugins):",
854
937
  // Card 7: Advanced Security Config
855
938
  configCardTitle: "Global Security Policy Configuration",
856
939
  configCardDesc: "Configure per-IP rate limits, brute-force failure thresholds, and lockout durations. Changes take effect immediately and sync to ~/.dsh/settings.yaml.",
@@ -869,6 +952,49 @@ var translations = {
869
952
  saveConfigSuccessToast: "Security policy successfully saved and synced to settings.yaml!",
870
953
  restoreDefaultsSuccessToast: "Default security policies restored and applied immediately!",
871
954
  saveConfigFailTip: "Parameters must be valid integers greater than 0",
955
+ // Card 8: Mobile Style Snippets (style mini-plugin)
956
+ tabAccess: "Access",
957
+ tabDevices: "Devices & Security",
958
+ tabStyle: "Style Overrides",
959
+ tabStorage: "Local Data",
960
+ stylesCardTitle: "Mobile Style Snippets",
961
+ stylesCardDesc: "Adapt the DSH web UI via toggleable CSS snippets. Built-in presets are split by UI area: sidebar, settings panel and conversation. Each can be enabled per device (PC / Mobile). You can also add your own CSS mini-plugins; changes take effect on the next page load without restart.",
962
+ stylesPresetsTitle: "\u{1F9E9} Built-in Presets",
963
+ stylesCustomTitle: "\u270D\uFE0F Custom Snippets",
964
+ stylesPcShort: "PC",
965
+ stylesMobileShort: "Mobile",
966
+ stylesBothDevices: "PC + Mobile",
967
+ stylesMobileOnlyBadge: "Mobile only",
968
+ stylesPcOnlyBadge: "PC only",
969
+ stylesDisabled: "Disabled",
970
+ stylesViewCss: "View CSS",
971
+ stylesHideCss: "Hide CSS",
972
+ stylesCopyCss: "\u{1F4CB} Copy CSS",
973
+ stylesCopyCssDone: "CSS copied to clipboard!",
974
+ stylesNamePlaceholder: "Snippet name (required)",
975
+ stylesCssPlaceholder: "Paste or write CSS here\u2026 (html[data-dsh-mobile] can be used as a mobile scope selector)",
976
+ stylesAddNew: "\uFF0B Add Custom Snippet",
977
+ stylesSave: "\u{1F4BE} Save Snippet",
978
+ stylesCancelEdit: "Cancel",
979
+ stylesEditBtn: "Edit",
980
+ stylesDeleteBtn: "Delete",
981
+ stylesResetAll: "\u{1F504} Reset Defaults",
982
+ stylesEmptyCustom: 'No custom snippets yet. Click "Add Custom Snippet" to start.',
983
+ stylesLoadFail: "Failed to load style snippets, please try again later",
984
+ stylesSaveSuccess: "Style snippet saved. Refresh to see the effect.",
985
+ stylesDeleteConfirm: "Are you sure you want to delete this custom style snippet?",
986
+ stylesResetConfirm: "Reset all snippets to default states? (Built-ins: PC off / Mobile on. Custom: PC off / Mobile on.)",
987
+ stylesFieldsRequired: "Please fill in both the snippet name and CSS content",
988
+ stylesHint: "\u{1F4A1} Tip: every preset and custom snippet has separate PC / Mobile toggles. Mobile requests add a data-dsh-mobile attribute to <html>. Built-ins target mobile (PC off by default); when enabling them on PC, make sure they do not depend on the data-dsh-mobile scope.",
989
+ // Plugin coexistence banner
990
+ conflictBannerTitle: "Another remote-access plugin detected",
991
+ conflictUnknownPlugin: "another remote-access plugin",
992
+ conflictBannerBody: "{plugin} has already registered the shared pairing service (remoteWebUiPairing); this plugin has automatically yielded it to keep startup safe. Running two remote-access features side by side may cause duplicated entries and inconsistent state \u2014 keeping only one is recommended. Copy the diagnosis below and paste it to an AI assistant for step-by-step help.",
993
+ conflictCopyBtn: "\u{1F4CB} Copy diagnosis & fix",
994
+ conflictCopiedTip: "Diagnosis copied! Paste it to your AI assistant",
995
+ conflictReportText: '[DSH plugin conflict diagnosis]\nSymptom: the dsh-remote-mobile settings panel shows "Another remote-access plugin detected".\nCause: another remote/Web access plugin registered the shared Cordis service remoteWebUiPairing first; dsh-remote-mobile yielded it to keep startup safe, so both remote features now run in parallel.\nConflicting plugin package: {plugin}\nConflicting loader entry id: {entryId}\nFix (choose ONE):\nOption A (keep dsh-remote-mobile, disable the other):\n1. Open ~/.dsh/profiles/web/cordis.patch.yml\n2. Append these two lines at the end (mind YAML indentation, two spaces before disabled):\n- id: {entryId}\n disabled: true\n3. Stop the running dsh web process, then run dsh web --no-open again\nOption B (keep the other, remove dsh-remote-mobile):\nRun dsh plugin --profile web remove dsh-remote-mobile and restart dsh web',
996
+ conflictReportNoEntryIdText: '[DSH plugin conflict diagnosis]\nSymptom: the dsh-remote-mobile settings panel shows "Another remote-access plugin detected".\nCause: another remote/Web access plugin registered the shared Cordis service remoteWebUiPairing first; dsh-remote-mobile yielded it to keep startup safe, so both remote features now run in parallel.\nConflicting plugin package: {plugin}\nConflicting loader entry id: could not be detected automatically\nFix (choose ONE):\nOption A (keep dsh-remote-mobile, disable the other):\n1. Run dsh --dump-config to list all loader entries and find the entry id of the conflicting plugin\n2. Open ~/.dsh/profiles/web/cordis.patch.yml and append at the end (mind YAML indentation):\n- id: <the conflicting entry id>\n disabled: true\n3. Stop the running dsh web process, then run dsh web --no-open again\nOption B (keep the other, remove dsh-remote-mobile):\nRun dsh plugin --profile web remove dsh-remote-mobile and restart dsh web',
997
+ conflictBannerDismiss: "Got it",
872
998
  // Toast
873
999
  toastDeviceConnected: "\u{1F389} New device connected: {name} ({ip})",
874
1000
  toastDeviceOnline: "\u{1F4F6} Device reconnected: {name} ({ip})",
@@ -2430,8 +2556,42 @@ function SecurityCard({ status, unlockIp, clearIpStats, lang }) {
2430
2556
  // src/client/components/StorageCard.jsx
2431
2557
  var import_react6 = __toESM(require("react"), 1);
2432
2558
  var import_jsx_runtime6 = require("react/jsx-runtime");
2433
- function StorageCard({ status, lang }) {
2434
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2559
+ function StorageCard({ status, lang, copyText }) {
2560
+ const handleCopy = (path, tip) => {
2561
+ if (!path) return;
2562
+ if (typeof copyText === "function") {
2563
+ copyText(path, tip);
2564
+ return;
2565
+ }
2566
+ if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
2567
+ navigator.clipboard.writeText(path).catch(() => {
2568
+ });
2569
+ }
2570
+ };
2571
+ const devicesPath = status.persistPath || "";
2572
+ const rsaPath = status.rsaKeyPath || "";
2573
+ const stylesPath = status.styleSnippetsPath || "";
2574
+ const items = [
2575
+ {
2576
+ icon: "\u{1F4C1}",
2577
+ title: t("storageDeviceFile", lang),
2578
+ path: devicesPath,
2579
+ tip: lang === "en" ? "Devices storage path copied!" : "\u5DF2\u6210\u529F\u590D\u5236\u8BBE\u5907\u5B58\u50A8\u6587\u4EF6\u8DEF\u5F84\uFF01"
2580
+ },
2581
+ {
2582
+ icon: "\u{1F511}",
2583
+ title: t("storageRsaFile", lang),
2584
+ path: rsaPath,
2585
+ tip: lang === "en" ? "RSA key path copied!" : "\u5DF2\u6210\u529F\u590D\u5236 RSA \u5BC6\u94A5\u6587\u4EF6\u8DEF\u5F84\uFF01"
2586
+ },
2587
+ {
2588
+ icon: "\u{1F3A8}",
2589
+ title: t("storageStylesFile", lang),
2590
+ path: stylesPath,
2591
+ tip: lang === "en" ? "Style snippets path copied!" : "\u5DF2\u6210\u529F\u590D\u5236\u6837\u5F0F\u7247\u6BB5\u6587\u4EF6\u8DEF\u5F84\uFF01"
2592
+ }
2593
+ ];
2594
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2435
2595
  "div",
2436
2596
  {
2437
2597
  style: {
@@ -2443,138 +2603,69 @@ function StorageCard({ status, lang }) {
2443
2603
  flexDirection: "column",
2444
2604
  gap: "10px"
2445
2605
  },
2446
- children: [
2447
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2448
- "div",
2449
- {
2450
- style: {
2451
- display: "flex",
2452
- alignItems: "center",
2453
- justifyContent: "space-between",
2454
- gap: "12px"
2455
- },
2456
- children: [
2457
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", minWidth: 0 }, children: [
2458
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "18px" }, children: "\u{1F4C1}" }),
2459
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 }, children: [
2460
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2461
- "div",
2462
- {
2463
- style: {
2464
- fontSize: "12px",
2465
- fontWeight: "600",
2466
- color: "var(--dsw-alias-label-primary, inherit)"
2467
- },
2468
- children: t("storageDeviceFile", lang)
2469
- }
2470
- ),
2471
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2472
- "code",
2473
- {
2474
- style: {
2475
- fontSize: "11px",
2476
- color: "var(--dsw-alias-brand-primary, #3b82f6)",
2477
- fontFamily: "monospace",
2478
- wordBreak: "break-all"
2479
- },
2480
- children: status.persistPath || "~/.dsh/remote-mobile/devices.json"
2481
- }
2482
- )
2483
- ] })
2484
- ] }),
2485
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2486
- "button",
2487
- {
2488
- type: "button",
2489
- onClick: () => {
2490
- const p = status.persistPath || "~/.dsh/remote-mobile/devices.json";
2491
- navigator.clipboard.writeText(p).then(() => {
2492
- alert((lang === "en" ? "Copied path:\n" : "\u5B58\u50A8\u6587\u4EF6\u8DEF\u5F84\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\uFF01\n") + p);
2493
- });
2494
- },
2495
- style: {
2496
- padding: "4px 10px",
2497
- background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.1))",
2498
- border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.2))",
2499
- color: "var(--dsw-alias-label-secondary, inherit)",
2500
- borderRadius: "6px",
2501
- fontSize: "11px",
2502
- cursor: "pointer",
2503
- whiteSpace: "nowrap"
2504
- },
2505
- children: t("copyUrlBtn", lang)
2506
- }
2507
- )
2508
- ]
2509
- }
2510
- ),
2511
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2512
- "div",
2513
- {
2514
- style: {
2515
- display: "flex",
2516
- alignItems: "center",
2517
- justifyContent: "space-between",
2518
- gap: "12px",
2519
- paddingTop: "8px",
2520
- borderTop: "1px dashed var(--dsw-alias-border-l2, rgba(128,128,128,0.15))"
2521
- },
2522
- children: [
2523
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", minWidth: 0 }, children: [
2524
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "18px" }, children: "\u{1F511}" }),
2525
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 }, children: [
2526
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2527
- "div",
2528
- {
2529
- style: {
2530
- fontSize: "12px",
2531
- fontWeight: "600",
2532
- color: "var(--dsw-alias-label-primary, inherit)"
2533
- },
2534
- children: t("storageRsaFile", lang)
2535
- }
2536
- ),
2537
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2538
- "code",
2539
- {
2540
- style: {
2541
- fontSize: "11px",
2542
- color: "var(--dsw-alias-brand-primary, #3b82f6)",
2543
- fontFamily: "monospace",
2544
- wordBreak: "break-all"
2545
- },
2546
- children: status.rsaKeyPath || "~/.dsh/remote-mobile/rsa-keys.json"
2547
- }
2548
- )
2549
- ] })
2550
- ] }),
2551
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2552
- "button",
2553
- {
2554
- type: "button",
2555
- onClick: () => {
2556
- const p = status.rsaKeyPath || "~/.dsh/remote-mobile/rsa-keys.json";
2557
- navigator.clipboard.writeText(p).then(() => {
2558
- alert((lang === "en" ? "Copied RSA path:\n" : "RSA \u5BC6\u94A5\u6587\u4EF6\u8DEF\u5F84\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\uFF01\n") + p);
2559
- });
2560
- },
2561
- style: {
2562
- padding: "4px 10px",
2563
- background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.1))",
2564
- border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.2))",
2565
- color: "var(--dsw-alias-label-secondary, inherit)",
2566
- borderRadius: "6px",
2567
- fontSize: "11px",
2568
- cursor: "pointer",
2569
- whiteSpace: "nowrap"
2570
- },
2571
- children: t("copyUrlBtn", lang)
2572
- }
2573
- )
2574
- ]
2575
- }
2576
- )
2577
- ]
2606
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2607
+ "div",
2608
+ {
2609
+ style: {
2610
+ display: "flex",
2611
+ alignItems: "center",
2612
+ justifyContent: "space-between",
2613
+ gap: "12px",
2614
+ paddingTop: index > 0 ? "8px" : "0",
2615
+ borderTop: index > 0 ? "1px dashed var(--dsw-alias-border-l2, rgba(128,128,128,0.15))" : "none"
2616
+ },
2617
+ children: [
2618
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", minWidth: 0, flex: 1 }, children: [
2619
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "18px", flexShrink: 0 }, children: item.icon }),
2620
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 }, children: [
2621
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2622
+ "div",
2623
+ {
2624
+ style: {
2625
+ fontSize: "12px",
2626
+ fontWeight: "600",
2627
+ color: "var(--dsw-alias-label-primary, inherit)"
2628
+ },
2629
+ children: item.title
2630
+ }
2631
+ ),
2632
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2633
+ "code",
2634
+ {
2635
+ style: {
2636
+ fontSize: "11px",
2637
+ color: "var(--dsw-alias-brand-primary, #3b82f6)",
2638
+ fontFamily: "monospace",
2639
+ wordBreak: "break-all"
2640
+ },
2641
+ children: item.path || "..."
2642
+ }
2643
+ )
2644
+ ] })
2645
+ ] }),
2646
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2647
+ "button",
2648
+ {
2649
+ type: "button",
2650
+ onClick: () => handleCopy(item.path, item.tip),
2651
+ style: {
2652
+ padding: "4px 10px",
2653
+ background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.1))",
2654
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.2))",
2655
+ color: "var(--dsw-alias-label-secondary, inherit)",
2656
+ borderRadius: "6px",
2657
+ fontSize: "11px",
2658
+ cursor: "pointer",
2659
+ whiteSpace: "nowrap",
2660
+ flexShrink: 0
2661
+ },
2662
+ children: t("copyUrlBtn", lang)
2663
+ }
2664
+ )
2665
+ ]
2666
+ },
2667
+ index
2668
+ ))
2578
2669
  }
2579
2670
  );
2580
2671
  }
@@ -2592,7 +2683,8 @@ function ConfigCard({
2592
2683
  saveAdvancedConfig,
2593
2684
  resetAdvancedConfigDefaults,
2594
2685
  isSaving,
2595
- lang
2686
+ lang,
2687
+ copyText
2596
2688
  }) {
2597
2689
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2598
2690
  "div",
@@ -2636,11 +2728,12 @@ function ConfigCard({
2636
2728
  {
2637
2729
  type: "button",
2638
2730
  onClick: () => {
2639
- navigator.clipboard.writeText("~/.dsh/settings.yaml").then(() => {
2640
- alert(
2641
- lang === "en" ? "Copied settings path:\n~/.dsh/settings.yaml (namespace: dsh-remote-mobile)" : "\u5DF2\u590D\u5236\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF1A\n~/.dsh/settings.yaml (\u547D\u540D\u7A7A\u95F4: dsh-remote-mobile)"
2731
+ if (typeof copyText === "function") {
2732
+ copyText(
2733
+ "~/.dsh/settings.yaml",
2734
+ lang === "en" ? "Settings path copied (~/.dsh/settings.yaml)" : "\u5DF2\u590D\u5236\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08~/.dsh/settings.yaml\uFF09"
2642
2735
  );
2643
- });
2736
+ }
2644
2737
  },
2645
2738
  style: {
2646
2739
  padding: "4px 10px",
@@ -2890,15 +2983,495 @@ function ConfigCard({
2890
2983
  );
2891
2984
  }
2892
2985
 
2986
+ // src/client/components/StylesCard.jsx
2987
+ var import_react8 = __toESM(require("react"), 1);
2988
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2989
+ function StylesCard({ lang, showToast }) {
2990
+ const [snippets, setSnippets] = (0, import_react8.useState)([]);
2991
+ const [loading, setLoading] = (0, import_react8.useState)(true);
2992
+ const [editingId, setEditingId] = (0, import_react8.useState)(null);
2993
+ const [showForm, setShowForm] = (0, import_react8.useState)(false);
2994
+ const [name, setName] = (0, import_react8.useState)("");
2995
+ const [css, setCss] = (0, import_react8.useState)("");
2996
+ const [pcEnabled, setPcEnabled] = (0, import_react8.useState)(false);
2997
+ const [mobileEnabled, setMobileEnabled] = (0, import_react8.useState)(true);
2998
+ const [expanded, setExpanded] = (0, import_react8.useState)({});
2999
+ const [feedback, setFeedback] = (0, import_react8.useState)("");
3000
+ const showFeedback = (0, import_react8.useCallback)((msg, type = "success") => {
3001
+ if (typeof showToast === "function") {
3002
+ showToast(msg, type);
3003
+ } else {
3004
+ setFeedback(msg);
3005
+ setTimeout(() => setFeedback(""), 3500);
3006
+ }
3007
+ }, [showToast]);
3008
+ const load = (0, import_react8.useCallback)(() => {
3009
+ fetchStyles().then((data) => {
3010
+ setSnippets(data.snippets || []);
3011
+ setLoading(false);
3012
+ }).catch(() => {
3013
+ setLoading(false);
3014
+ showFeedback(t("stylesLoadFail", lang), "danger");
3015
+ });
3016
+ }, [lang, showFeedback]);
3017
+ (0, import_react8.useEffect)(() => {
3018
+ load();
3019
+ }, [load]);
3020
+ const toggle = (id, scope, enabled) => {
3021
+ toggleStyleApi(id, scope, enabled).then((data) => {
3022
+ if (data.success) {
3023
+ load();
3024
+ const scopeLabel = scope === "pc" ? lang === "en" ? "PC" : "PC \u7AEF" : lang === "en" ? "Mobile" : "\u79FB\u52A8\u7AEF";
3025
+ showFeedback(
3026
+ enabled ? lang === "en" ? scopeLabel + " enabled!" : scopeLabel + " \u5DF2\u542F\u7528\uFF01" : lang === "en" ? scopeLabel + " disabled!" : scopeLabel + " \u5DF2\u505C\u7528\uFF01",
3027
+ "success"
3028
+ );
3029
+ }
3030
+ });
3031
+ };
3032
+ const startEdit = (s) => {
3033
+ setEditingId(s.id);
3034
+ setShowForm(true);
3035
+ setName(s.name);
3036
+ setCss(s.css);
3037
+ setPcEnabled(s.pcEnabled !== false);
3038
+ setMobileEnabled(s.mobileEnabled !== false);
3039
+ setFeedback("");
3040
+ };
3041
+ const cancelEdit = () => {
3042
+ setEditingId(null);
3043
+ setShowForm(false);
3044
+ setName("");
3045
+ setCss("");
3046
+ setPcEnabled(false);
3047
+ setMobileEnabled(true);
3048
+ setFeedback("");
3049
+ };
3050
+ const save = () => {
3051
+ if (!name.trim() || !css.trim()) {
3052
+ alert(t("stylesFieldsRequired", lang));
3053
+ return;
3054
+ }
3055
+ saveStyleSnippet({
3056
+ id: editingId || void 0,
3057
+ name: name.trim(),
3058
+ css,
3059
+ pcEnabled,
3060
+ mobileEnabled
3061
+ }).then((data) => {
3062
+ if (data.success) {
3063
+ load();
3064
+ cancelEdit();
3065
+ showFeedback(t("stylesSaveSuccess", lang));
3066
+ } else {
3067
+ alert((lang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (data.reason || ""));
3068
+ }
3069
+ }).catch(() => {
3070
+ alert(lang === "en" ? "Network request failed, please try again." : "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
3071
+ });
3072
+ };
3073
+ const remove = (s) => {
3074
+ if (s.builtin) return;
3075
+ if (confirm(t("stylesDeleteConfirm", lang))) {
3076
+ deleteStyleSnippetApi(s.id).then((data) => {
3077
+ if (data.success) {
3078
+ if (editingId === s.id) cancelEdit();
3079
+ load();
3080
+ showFeedback(lang === "en" ? "Style snippet deleted!" : "\u81EA\u5B9A\u4E49\u6837\u5F0F\u7247\u6BB5\u5DF2\u5220\u9664\uFF01");
3081
+ }
3082
+ });
3083
+ }
3084
+ };
3085
+ const resetAll = () => {
3086
+ if (!confirm(t("stylesResetConfirm", lang))) return;
3087
+ resetStylesApi().then((data) => {
3088
+ if (data.success) {
3089
+ load();
3090
+ showFeedback(lang === "en" ? "Style snippets reset to defaults!" : "\u5DF2\u6062\u590D\u6240\u6709\u7247\u6BB5\u7684\u9ED8\u8BA4\u542F\u505C\u72B6\u6001\uFF01");
3091
+ }
3092
+ });
3093
+ };
3094
+ const toggleExpand = (id) => {
3095
+ setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
3096
+ };
3097
+ const copyCss = (cssText) => {
3098
+ navigator.clipboard.writeText(cssText).then(() => showFeedback(t("stylesCopyCssDone", lang))).catch(() => {
3099
+ const ta = document.createElement("textarea");
3100
+ ta.value = cssText;
3101
+ document.body.appendChild(ta);
3102
+ ta.select();
3103
+ try {
3104
+ document.execCommand("copy");
3105
+ showFeedback(t("stylesCopyCssDone", lang));
3106
+ } catch {
3107
+ showFeedback(lang === "en" ? "Copy failed, please copy manually." : "\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u3002", "danger");
3108
+ }
3109
+ document.body.removeChild(ta);
3110
+ });
3111
+ };
3112
+ const scopeBadge = (s) => {
3113
+ if (s.pcEnabled && s.mobileEnabled) return t("stylesBothDevices", lang);
3114
+ if (s.mobileEnabled) return "\u{1F4F1} " + t("stylesMobileOnlyBadge", lang);
3115
+ if (s.pcEnabled) return "\u{1F5A5}\uFE0F " + t("stylesPcOnlyBadge", lang);
3116
+ return t("stylesDisabled", lang);
3117
+ };
3118
+ const rowStyle = {
3119
+ display: "flex",
3120
+ flexDirection: "column",
3121
+ gap: "8px",
3122
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))",
3123
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.12))",
3124
+ borderRadius: "8px",
3125
+ padding: "10px 12px"
3126
+ };
3127
+ const miniBtnStyle = {
3128
+ padding: "4px 10px",
3129
+ background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.1))",
3130
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.2))",
3131
+ color: "var(--dsw-alias-label-secondary, inherit)",
3132
+ borderRadius: "6px",
3133
+ fontSize: "11px",
3134
+ cursor: "pointer",
3135
+ whiteSpace: "nowrap"
3136
+ };
3137
+ const primaryBtnStyle = {
3138
+ ...miniBtnStyle,
3139
+ background: "rgba(59,130,246,0.15)",
3140
+ color: "var(--dsw-alias-brand-primary, #3b82f6)",
3141
+ border: "1px solid rgba(59,130,246,0.3)"
3142
+ };
3143
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3144
+ "div",
3145
+ {
3146
+ style: {
3147
+ background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.06))",
3148
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3149
+ borderRadius: "12px",
3150
+ padding: "18px 20px",
3151
+ display: "flex",
3152
+ flexDirection: "column",
3153
+ gap: "14px"
3154
+ },
3155
+ children: [
3156
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "10px", flexWrap: "wrap" }, children: [
3157
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3158
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "16px" }, children: "\u{1F3A8}" }),
3159
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "15px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: t("stylesCardTitle", lang) })
3160
+ ] }),
3161
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: resetAll, style: miniBtnStyle, children: t("stylesResetAll", lang) })
3162
+ ] }),
3163
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.5" }, children: t("stylesCardDesc", lang) }),
3164
+ feedback && typeof showToast !== "function" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3165
+ "div",
3166
+ {
3167
+ style: {
3168
+ fontSize: "12px",
3169
+ padding: "8px 12px",
3170
+ borderRadius: "8px",
3171
+ background: "rgba(16,185,129,0.12)",
3172
+ color: "var(--dsw-alias-success, #10b981)",
3173
+ border: "1px solid rgba(16,185,129,0.25)"
3174
+ },
3175
+ children: feedback
3176
+ }
3177
+ ) : null,
3178
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)" }, children: lang === "en" ? "Loading style snippets..." : "\u6B63\u5728\u52A0\u8F7D\u6837\u5F0F\u7247\u6BB5..." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3179
+ snippets.some((s) => s.builtin) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3180
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "13px", fontWeight: "600", color: "var(--dsw-alias-label-secondary, inherit)" }, children: t("stylesPresetsTitle", lang) }),
3181
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: snippets.filter((s) => s.builtin).map((s) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
3182
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: "8px", flexWrap: "wrap" }, children: [
3183
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: 1, minWidth: "180px" }, children: [
3184
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3185
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3186
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3187
+ "span",
3188
+ {
3189
+ style: {
3190
+ fontSize: "10px",
3191
+ padding: "1px 6px",
3192
+ borderRadius: "8px",
3193
+ background: "rgba(139,92,246,0.15)",
3194
+ color: "#8b5cf6",
3195
+ fontWeight: "600"
3196
+ },
3197
+ children: "Preset"
3198
+ }
3199
+ ),
3200
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3201
+ "span",
3202
+ {
3203
+ style: {
3204
+ fontSize: "10px",
3205
+ padding: "1px 6px",
3206
+ borderRadius: "8px",
3207
+ background: "rgba(59,130,246,0.12)",
3208
+ color: "#3b82f6",
3209
+ fontWeight: "600"
3210
+ },
3211
+ children: scopeBadge(s)
3212
+ }
3213
+ )
3214
+ ] }),
3215
+ s.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", marginTop: "3px" }, children: s.description }) : null
3216
+ ] }),
3217
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0, flexWrap: "wrap" }, children: [
3218
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3219
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3220
+ "input",
3221
+ {
3222
+ type: "checkbox",
3223
+ checked: s.pcEnabled,
3224
+ onChange: (e) => toggle(s.id, "pc", e.target.checked),
3225
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3226
+ }
3227
+ ),
3228
+ "\u{1F5A5}\uFE0F ",
3229
+ t("stylesPcShort", lang)
3230
+ ] }),
3231
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3232
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3233
+ "input",
3234
+ {
3235
+ type: "checkbox",
3236
+ checked: s.mobileEnabled,
3237
+ onChange: (e) => toggle(s.id, "mobile", e.target.checked),
3238
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3239
+ }
3240
+ ),
3241
+ "\u{1F4F1} ",
3242
+ t("stylesMobileShort", lang)
3243
+ ] }),
3244
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => toggleExpand(s.id), style: miniBtnStyle, children: expanded[s.id] ? t("stylesHideCss", lang) : t("stylesViewCss", lang) }),
3245
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => copyCss(s.css), style: miniBtnStyle, children: t("stylesCopyCss", lang) })
3246
+ ] })
3247
+ ] }),
3248
+ expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3249
+ "pre",
3250
+ {
3251
+ style: {
3252
+ maxHeight: "180px",
3253
+ overflow: "auto",
3254
+ fontSize: "11px",
3255
+ lineHeight: "1.5",
3256
+ margin: 0,
3257
+ padding: "8px 10px",
3258
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.25))",
3259
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3260
+ borderRadius: "6px",
3261
+ color: "var(--dsw-alias-label-secondary, inherit)",
3262
+ whiteSpace: "pre-wrap",
3263
+ wordBreak: "break-word",
3264
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)"
3265
+ },
3266
+ children: s.css
3267
+ }
3268
+ ) : null
3269
+ ] }, s.id)) })
3270
+ ] }) : null,
3271
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "13px", fontWeight: "600", color: "var(--dsw-alias-label-secondary, inherit)", marginTop: "4px" }, children: t("stylesCustomTitle", lang) }),
3272
+ !showForm ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3273
+ "button",
3274
+ {
3275
+ type: "button",
3276
+ onClick: () => setShowForm(true),
3277
+ style: {
3278
+ width: "100%",
3279
+ padding: "12px",
3280
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))",
3281
+ border: "1px dashed var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3282
+ borderRadius: "8px",
3283
+ color: "var(--dsw-alias-brand-primary, #3b82f6)",
3284
+ fontSize: "12px",
3285
+ fontWeight: "600",
3286
+ cursor: "pointer"
3287
+ },
3288
+ children: t("stylesAddNew", lang)
3289
+ }
3290
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3291
+ "div",
3292
+ {
3293
+ style: {
3294
+ display: "flex",
3295
+ flexDirection: "column",
3296
+ gap: "8px",
3297
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))",
3298
+ border: "1px dashed var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3299
+ borderRadius: "8px",
3300
+ padding: "12px"
3301
+ },
3302
+ children: [
3303
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3304
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3305
+ "input",
3306
+ {
3307
+ type: "text",
3308
+ value: name,
3309
+ onChange: (e) => setName(e.target.value),
3310
+ placeholder: t("stylesNamePlaceholder", lang),
3311
+ style: {
3312
+ flex: 1,
3313
+ minWidth: "160px",
3314
+ padding: "8px 10px",
3315
+ fontSize: "12px",
3316
+ borderRadius: "6px",
3317
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3318
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.2))",
3319
+ color: "var(--dsw-alias-label-primary, inherit)",
3320
+ outline: "none"
3321
+ }
3322
+ }
3323
+ ),
3324
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3325
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3326
+ "input",
3327
+ {
3328
+ type: "checkbox",
3329
+ checked: pcEnabled,
3330
+ onChange: (e) => setPcEnabled(e.target.checked),
3331
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3332
+ }
3333
+ ),
3334
+ "\u{1F5A5}\uFE0F ",
3335
+ t("stylesPcShort", lang)
3336
+ ] }),
3337
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3338
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3339
+ "input",
3340
+ {
3341
+ type: "checkbox",
3342
+ checked: mobileEnabled,
3343
+ onChange: (e) => setMobileEnabled(e.target.checked),
3344
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3345
+ }
3346
+ ),
3347
+ "\u{1F4F1} ",
3348
+ t("stylesMobileShort", lang)
3349
+ ] })
3350
+ ] }),
3351
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3352
+ "textarea",
3353
+ {
3354
+ value: css,
3355
+ onChange: (e) => setCss(e.target.value),
3356
+ placeholder: t("stylesCssPlaceholder", lang),
3357
+ rows: 5,
3358
+ style: {
3359
+ width: "100%",
3360
+ boxSizing: "border-box",
3361
+ padding: "8px 10px",
3362
+ fontSize: "12px",
3363
+ lineHeight: "1.5",
3364
+ borderRadius: "6px",
3365
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3366
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.2))",
3367
+ color: "var(--dsw-alias-label-primary, inherit)",
3368
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)",
3369
+ outline: "none",
3370
+ resize: "vertical"
3371
+ }
3372
+ }
3373
+ ),
3374
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3375
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: save, style: primaryBtnStyle, children: t("stylesSave", lang) }),
3376
+ editingId !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null,
3377
+ showForm && !editingId ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null
3378
+ ] })
3379
+ ]
3380
+ }
3381
+ ),
3382
+ snippets.filter((s) => !s.builtin).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)" }, children: t("stylesEmptyCustom", lang) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: snippets.filter((s) => !s.builtin).map((s) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
3383
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: "8px", flexWrap: "wrap" }, children: [
3384
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: 1, minWidth: "180px" }, children: [
3385
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3386
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3387
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3388
+ "span",
3389
+ {
3390
+ style: {
3391
+ fontSize: "10px",
3392
+ padding: "1px 6px",
3393
+ borderRadius: "8px",
3394
+ background: "rgba(59,130,246,0.12)",
3395
+ color: "#3b82f6",
3396
+ fontWeight: "600"
3397
+ },
3398
+ children: scopeBadge(s)
3399
+ }
3400
+ )
3401
+ ] }),
3402
+ s.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", marginTop: "3px" }, children: s.description }) : null
3403
+ ] }),
3404
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0, flexWrap: "wrap" }, children: [
3405
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3406
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3407
+ "input",
3408
+ {
3409
+ type: "checkbox",
3410
+ checked: s.pcEnabled,
3411
+ onChange: (e) => toggle(s.id, "pc", e.target.checked),
3412
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3413
+ }
3414
+ ),
3415
+ "\u{1F5A5}\uFE0F ",
3416
+ t("stylesPcShort", lang)
3417
+ ] }),
3418
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3419
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3420
+ "input",
3421
+ {
3422
+ type: "checkbox",
3423
+ checked: s.mobileEnabled,
3424
+ onChange: (e) => toggle(s.id, "mobile", e.target.checked),
3425
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3426
+ }
3427
+ ),
3428
+ "\u{1F4F1} ",
3429
+ t("stylesMobileShort", lang)
3430
+ ] }),
3431
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => startEdit(s), style: miniBtnStyle, children: t("stylesEditBtn", lang) }),
3432
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => remove(s), style: { ...miniBtnStyle, color: "#ef4444" }, children: t("stylesDeleteBtn", lang) }),
3433
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => toggleExpand(s.id), style: miniBtnStyle, children: expanded[s.id] ? t("stylesHideCss", lang) : t("stylesViewCss", lang) }),
3434
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => copyCss(s.css), style: miniBtnStyle, children: t("stylesCopyCss", lang) })
3435
+ ] })
3436
+ ] }),
3437
+ expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3438
+ "pre",
3439
+ {
3440
+ style: {
3441
+ maxHeight: "180px",
3442
+ overflow: "auto",
3443
+ fontSize: "11px",
3444
+ lineHeight: "1.5",
3445
+ margin: 0,
3446
+ padding: "8px 10px",
3447
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.25))",
3448
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3449
+ borderRadius: "6px",
3450
+ color: "var(--dsw-alias-label-secondary, inherit)",
3451
+ whiteSpace: "pre-wrap",
3452
+ wordBreak: "break-word",
3453
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)"
3454
+ },
3455
+ children: s.css
3456
+ }
3457
+ ) : null
3458
+ ] }, s.id)) }),
3459
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.6" }, children: t("stylesHint", lang) })
3460
+ ] })
3461
+ ]
3462
+ }
3463
+ );
3464
+ }
3465
+
2893
3466
  // src/client/version.js
2894
- var PLUGIN_VERSION = "v1.2.1";
2895
- var BUILD_TIME = "2026-08-23 00:31:17";
3467
+ var PLUGIN_VERSION = "v1.4.1";
3468
+ var BUILD_TIME = "2026-08-26 23:00:00";
2896
3469
 
2897
3470
  // src/client/components/MainSection.jsx
2898
- var import_jsx_runtime8 = require("react/jsx-runtime");
3471
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2899
3472
  function TailscaleMobileSection(props) {
2900
3473
  const ctx = props && props.ctx || typeof window !== "undefined" && window.__DSH_CLIENT_CTX__ || null;
2901
- const [status, setStatus] = (0, import_react8.useState)({
3474
+ const [status, setStatus] = (0, import_react9.useState)({
2902
3475
  tailscaleIp: "",
2903
3476
  lanIp: "",
2904
3477
  code: "",
@@ -2915,24 +3488,50 @@ function TailscaleMobileSection(props) {
2915
3488
  copied: false,
2916
3489
  loading: true,
2917
3490
  persistPath: "",
2918
- locale: "zh"
3491
+ rsaKeyPath: "",
3492
+ styleSnippetsPath: "",
3493
+ locale: "zh",
3494
+ pairingBridgeMode: "pending",
3495
+ pairingBridgeConflict: "",
3496
+ pairingBridgeConflictId: ""
2919
3497
  });
2920
- const [secretInput, setSecretInput] = (0, import_react8.useState)("");
2921
- const [showSecret, setShowSecret] = (0, import_react8.useState)(false);
2922
- const [showCode, setShowCode] = (0, import_react8.useState)(false);
2923
- const [showQr, setShowQr] = (0, import_react8.useState)(false);
2924
- const [selectedTab, setSelectedTab] = (0, import_react8.useState)("lan");
2925
- const [timeLeft, setTimeLeft] = (0, import_react8.useState)(300);
2926
- const [toast, setToast] = (0, import_react8.useState)(null);
2927
- const [maxVisitsInput, setMaxVisitsInput] = (0, import_react8.useState)("60");
2928
- const [maxFailedInput, setMaxFailedInput] = (0, import_react8.useState)("5");
2929
- const [lockDurationMinsInput, setLockDurationMinsInput] = (0, import_react8.useState)("15");
2930
- const [isSaving, setIsSaving] = (0, import_react8.useState)(false);
2931
- const [showVersionTip, setShowVersionTip] = (0, import_react8.useState)(false);
2932
- const [currentLang, setCurrentLang] = (0, import_react8.useState)(resolveLocale(ctx, status));
2933
- const refreshStatusOnly = (0, import_react8.useCallback)(() => {
3498
+ const [secretInput, setSecretInput] = (0, import_react9.useState)("");
3499
+ const [showSecret, setShowSecret] = (0, import_react9.useState)(false);
3500
+ const [showCode, setShowCode] = (0, import_react9.useState)(false);
3501
+ const [showQr, setShowQr] = (0, import_react9.useState)(false);
3502
+ const [selectedTab, setSelectedTab] = (0, import_react9.useState)("lan");
3503
+ const [timeLeft, setTimeLeft] = (0, import_react9.useState)(300);
3504
+ const [toast, setToast] = (0, import_react9.useState)(null);
3505
+ const [maxVisitsInput, setMaxVisitsInput] = (0, import_react9.useState)("60");
3506
+ const [maxFailedInput, setMaxFailedInput] = (0, import_react9.useState)("5");
3507
+ const [lockDurationMinsInput, setLockDurationMinsInput] = (0, import_react9.useState)("15");
3508
+ const [isSaving, setIsSaving] = (0, import_react9.useState)(false);
3509
+ const [showVersionTip, setShowVersionTip] = (0, import_react9.useState)(false);
3510
+ const [currentLang, setCurrentLang] = (0, import_react9.useState)(resolveLocale(ctx, status));
3511
+ const [conflictBannerDismissed, setConflictBannerDismissed] = (0, import_react9.useState)(false);
3512
+ const qrTabTouchedRef = (0, import_react9.useRef)(false);
3513
+ const updateQrTab = (0, import_react9.useCallback)((tab) => {
3514
+ qrTabTouchedRef.current = true;
3515
+ setSelectedTab(tab);
3516
+ }, []);
3517
+ const [activeTab, setActiveTab] = (0, import_react9.useState)(() => {
3518
+ try {
3519
+ const saved = localStorage.getItem("dsh-rm-settings-tab");
3520
+ if (saved === "access" || saved === "devices" || saved === "style" || saved === "storage") return saved;
3521
+ } catch (e) {
3522
+ }
3523
+ return "access";
3524
+ });
3525
+ const switchTab = (tab) => {
3526
+ setActiveTab(tab);
3527
+ try {
3528
+ localStorage.setItem("dsh-rm-settings-tab", tab);
3529
+ } catch (e) {
3530
+ }
3531
+ };
3532
+ const refreshStatusOnly = (0, import_react9.useCallback)(() => {
2934
3533
  fetchStatus().then((data) => {
2935
- if (data.tailscaleIp) {
3534
+ if (data.tailscaleIp && !qrTabTouchedRef.current) {
2936
3535
  setSelectedTab("tailscale");
2937
3536
  }
2938
3537
  if (data.maxVisitsPerMinute) setMaxVisitsInput(String(data.maxVisitsPerMinute));
@@ -2957,13 +3556,17 @@ function TailscaleMobileSection(props) {
2957
3556
  ipSecurityStats: data.ipSecurityStats || [],
2958
3557
  persistPath: data.persistPath || "",
2959
3558
  rsaKeyPath: data.rsaKeyPath || "",
3559
+ styleSnippetsPath: data.styleSnippetsPath || "",
2960
3560
  locale: data.locale || "zh",
3561
+ pairingBridgeMode: data.pairingBridgeMode || "pending",
3562
+ pairingBridgeConflict: data.pairingBridgeConflict || "",
3563
+ pairingBridgeConflictId: data.pairingBridgeConflictId || "",
2961
3564
  loading: false
2962
3565
  }));
2963
3566
  }).catch(() => {
2964
3567
  });
2965
3568
  }, [ctx]);
2966
- const refreshStatusAndCode = (0, import_react8.useCallback)(() => {
3569
+ const refreshStatusAndCode = (0, import_react9.useCallback)(() => {
2967
3570
  refreshStatusOnly();
2968
3571
  generatePairCode().then((data) => {
2969
3572
  if (data.success) {
@@ -2978,7 +3581,7 @@ function TailscaleMobileSection(props) {
2978
3581
  }).catch(() => {
2979
3582
  });
2980
3583
  }, [refreshStatusOnly]);
2981
- (0, import_react8.useEffect)(() => {
3584
+ (0, import_react9.useEffect)(() => {
2982
3585
  refreshStatusAndCode();
2983
3586
  const localeUnsubs = [];
2984
3587
  try {
@@ -3015,8 +3618,7 @@ function TailscaleMobileSection(props) {
3015
3618
  name: dev.deviceName || "Device",
3016
3619
  ip: dev.ip
3017
3620
  });
3018
- setToast({ message: msg, type: "info" });
3019
- setTimeout(() => setToast(null), 4e3);
3621
+ showToast(msg, "info", 4e3);
3020
3622
  }
3021
3623
  if (detail.type === "device-revoked") {
3022
3624
  setStatus((prev) => {
@@ -3031,8 +3633,7 @@ function TailscaleMobileSection(props) {
3031
3633
  }
3032
3634
  if (detail.type === "ip-security-alert") {
3033
3635
  const alertMsg = t("toastIpLocked", currentLang, { ip: detail.ip });
3034
- setToast({ message: alertMsg, type: "danger" });
3035
- setTimeout(() => setToast(null), 5e3);
3636
+ showToast(alertMsg, "danger", 5e3);
3036
3637
  }
3037
3638
  refreshStatusOnly();
3038
3639
  };
@@ -3048,7 +3649,7 @@ function TailscaleMobileSection(props) {
3048
3649
  }
3049
3650
  };
3050
3651
  }, [refreshStatusAndCode, refreshStatusOnly, currentLang, ctx]);
3051
- (0, import_react8.useEffect)(() => {
3652
+ (0, import_react9.useEffect)(() => {
3052
3653
  if (timeLeft <= 0) return;
3053
3654
  const timer = setInterval(() => {
3054
3655
  setTimeLeft((prev) => {
@@ -3093,11 +3694,11 @@ function TailscaleMobileSection(props) {
3093
3694
  if (data.success) {
3094
3695
  setStatus((prev) => ({ ...prev, hasSecret: true }));
3095
3696
  setSecretInput("");
3096
- setToast({
3097
- message: currentLang === "en" ? "Persistent password saved!" : "\u957F\u671F\u8BBF\u95EE\u5BC6\u7801\u5DF2\u6210\u529F\u4FDD\u5B58\u5E76\u542F\u7528\uFF01",
3098
- type: "success"
3099
- });
3100
- setTimeout(() => setToast(null), 3e3);
3697
+ showToast(
3698
+ currentLang === "en" ? "Persistent password saved!" : "\u957F\u671F\u8BBF\u95EE\u5BC6\u7801\u5DF2\u6210\u529F\u4FDD\u5B58\u5E76\u542F\u7528\uFF01",
3699
+ "success",
3700
+ 3e3
3701
+ );
3101
3702
  } else {
3102
3703
  alert((currentLang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (data.reason || ""));
3103
3704
  }
@@ -3108,11 +3709,11 @@ function TailscaleMobileSection(props) {
3108
3709
  clearSecretApi().then(() => {
3109
3710
  setStatus((prev) => ({ ...prev, hasSecret: false }));
3110
3711
  setSecretInput("");
3111
- setToast({
3112
- message: currentLang === "en" ? "Password cleared!" : "\u5DF2\u6210\u529F\u6E05\u9664\u957F\u671F\u8BBF\u95EE\u5BC6\u7801\uFF01",
3113
- type: "info"
3114
- });
3115
- setTimeout(() => setToast(null), 3e3);
3712
+ showToast(
3713
+ currentLang === "en" ? "Password cleared!" : "\u5DF2\u6210\u529F\u6E05\u9664\u957F\u671F\u8BBF\u95EE\u5BC6\u7801\uFF01",
3714
+ "info",
3715
+ 3e3
3716
+ );
3116
3717
  });
3117
3718
  }
3118
3719
  };
@@ -3120,11 +3721,11 @@ function TailscaleMobileSection(props) {
3120
3721
  if (confirm(t("revokeDeviceConfirm", currentLang))) {
3121
3722
  revokeDeviceApi(token).then(() => {
3122
3723
  refreshStatusAndCode();
3123
- setToast({
3124
- message: currentLang === "en" ? "Device disconnected!" : "\u5DF2\u6210\u529F\u8E22\u51FA\u8BE5\u8BBE\u5907\uFF01",
3125
- type: "info"
3126
- });
3127
- setTimeout(() => setToast(null), 2500);
3724
+ showToast(
3725
+ currentLang === "en" ? "Device disconnected!" : "\u5DF2\u6210\u529F\u8E22\u51FA\u8BE5\u8BBE\u5907\uFF01",
3726
+ "info",
3727
+ 2500
3728
+ );
3128
3729
  });
3129
3730
  }
3130
3731
  };
@@ -3132,11 +3733,11 @@ function TailscaleMobileSection(props) {
3132
3733
  if (confirm(t("revokeAllConfirm", currentLang))) {
3133
3734
  revokeAllDevicesApi().then(() => {
3134
3735
  refreshStatusAndCode();
3135
- setToast({
3136
- message: currentLang === "en" ? "All devices disconnected!" : "\u5DF2\u6210\u529F\u6CE8\u9500\u5E76\u8E22\u4E0B\u7EBF\u6240\u6709\u8BBE\u5907\uFF01",
3137
- type: "info"
3138
- });
3139
- setTimeout(() => setToast(null), 2500);
3736
+ showToast(
3737
+ currentLang === "en" ? "All devices disconnected!" : "\u5DF2\u6210\u529F\u6CE8\u9500\u5E76\u8E22\u4E0B\u7EBF\u6240\u6709\u8BBE\u5907\uFF01",
3738
+ "info",
3739
+ 2500
3740
+ );
3140
3741
  });
3141
3742
  }
3142
3743
  };
@@ -3144,11 +3745,11 @@ function TailscaleMobileSection(props) {
3144
3745
  if (confirm(t("unlockIpConfirm", currentLang, { ip }))) {
3145
3746
  unlockIpApi(ip).then(() => {
3146
3747
  refreshStatusAndCode();
3147
- setToast({
3148
- message: currentLang === "en" ? `IP ${ip} unlocked!` : `\u5DF2\u6210\u529F\u4E3A IP ${ip} \u89E3\u9664\u9501\u5B9A\uFF01`,
3149
- type: "success"
3150
- });
3151
- setTimeout(() => setToast(null), 3e3);
3748
+ showToast(
3749
+ currentLang === "en" ? `IP ${ip} unlocked!` : `\u5DF2\u6210\u529F\u4E3A IP ${ip} \u89E3\u9664\u9501\u5B9A\uFF01`,
3750
+ "success",
3751
+ 3e3
3752
+ );
3152
3753
  }).catch(() => {
3153
3754
  });
3154
3755
  }
@@ -3157,11 +3758,11 @@ function TailscaleMobileSection(props) {
3157
3758
  if (confirm(t("clearSecurityLogConfirm", currentLang))) {
3158
3759
  clearIpStatsApi().then(() => {
3159
3760
  refreshStatusAndCode();
3160
- setToast({
3161
- message: currentLang === "en" ? "Audit logs cleared!" : "\u5DF2\u6210\u529F\u6E05\u7A7A\u6240\u6709\u5B89\u5168\u5BA1\u8BA1\u65E5\u5FD7\uFF01",
3162
- type: "success"
3163
- });
3164
- setTimeout(() => setToast(null), 2500);
3761
+ showToast(
3762
+ currentLang === "en" ? "Audit logs cleared!" : "\u5DF2\u6210\u529F\u6E05\u7A7A\u6240\u6709\u5B89\u5168\u5BA1\u8BA1\u65E5\u5FD7\uFF01",
3763
+ "success",
3764
+ 2500
3765
+ );
3165
3766
  }).catch(() => {
3166
3767
  });
3167
3768
  }
@@ -3186,8 +3787,7 @@ function TailscaleMobileSection(props) {
3186
3787
  setIsSaving(false);
3187
3788
  if (data.success) {
3188
3789
  refreshStatusAndCode();
3189
- setToast({ message: t("saveConfigSuccessToast", currentLang), type: "success" });
3190
- setTimeout(() => setToast(null), 3e3);
3790
+ showToast(t("saveConfigSuccessToast", currentLang), "success", 3e3);
3191
3791
  } else {
3192
3792
  alert((currentLang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (data.reason || ""));
3193
3793
  }
@@ -3212,8 +3812,7 @@ function TailscaleMobileSection(props) {
3212
3812
  setIsSaving(false);
3213
3813
  if (data.success) {
3214
3814
  refreshStatusAndCode();
3215
- setToast({ message: t("restoreDefaultsSuccessToast", currentLang), type: "success" });
3216
- setTimeout(() => setToast(null), 3e3);
3815
+ showToast(t("restoreDefaultsSuccessToast", currentLang), "success", 3e3);
3217
3816
  } else {
3218
3817
  alert((currentLang === "en" ? "Reset failed: " : "\u91CD\u7F6E\u5931\u8D25\uFF1A") + (data.reason || ""));
3219
3818
  }
@@ -3230,23 +3829,57 @@ function TailscaleMobileSection(props) {
3230
3829
  const effectiveQrIp = selectedTab === "tailscale" && status.tailscaleIp ? status.tailscaleIp : status.lanIp || "127.0.0.1";
3231
3830
  const port = typeof window !== "undefined" && window.location.port || "3080";
3232
3831
  const directLink = `http://${effectiveQrIp}:${port}/auth?token=${status.code || ""}`;
3233
- navigator.clipboard.writeText(directLink).then(() => {
3234
- setStatus((prev) => ({ ...prev, copied: true }));
3235
- setTimeout(() => {
3236
- setStatus((prev) => ({ ...prev, copied: false }));
3237
- }, 2e3);
3238
- });
3832
+ copyText(directLink, t("copiedTip", currentLang));
3833
+ setStatus((prev) => ({ ...prev, copied: true }));
3834
+ setTimeout(() => {
3835
+ setStatus((prev) => ({ ...prev, copied: false }));
3836
+ }, 2e3);
3239
3837
  };
3838
+ const toastTimerRef = (0, import_react9.useRef)(null);
3839
+ const showToast = (0, import_react9.useCallback)((message, type = "success", duration = 2500) => {
3840
+ if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
3841
+ setToast({ message, type });
3842
+ toastTimerRef.current = setTimeout(() => setToast(null), duration);
3843
+ }, []);
3240
3844
  const copyText = (text, successTip) => {
3241
- navigator.clipboard.writeText(text).then(() => {
3242
- setToast({
3243
- message: successTip || (currentLang === "en" ? "Copied to clipboard!" : "\u5DF2\u6210\u529F\u590D\u5236\u5230\u526A\u8D34\u677F\uFF01"),
3244
- type: "success"
3845
+ const triggerToast = () => {
3846
+ showToast(
3847
+ successTip || (currentLang === "en" ? "Copied to clipboard!" : "\u5DF2\u6210\u529F\u590D\u5236\u5230\u526A\u8D34\u677F\uFF01"),
3848
+ "success",
3849
+ 2500
3850
+ );
3851
+ };
3852
+ if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
3853
+ navigator.clipboard.writeText(text).then(triggerToast).catch(() => {
3854
+ try {
3855
+ const ta = document.createElement("textarea");
3856
+ ta.value = text;
3857
+ ta.style.position = "fixed";
3858
+ ta.style.opacity = "0";
3859
+ document.body.appendChild(ta);
3860
+ ta.select();
3861
+ document.execCommand("copy");
3862
+ document.body.removeChild(ta);
3863
+ triggerToast();
3864
+ } catch (e) {
3865
+ }
3245
3866
  });
3246
- setTimeout(() => setToast(null), 2500);
3247
- });
3867
+ } else {
3868
+ try {
3869
+ const ta = document.createElement("textarea");
3870
+ ta.value = text;
3871
+ ta.style.position = "fixed";
3872
+ ta.style.opacity = "0";
3873
+ document.body.appendChild(ta);
3874
+ ta.select();
3875
+ document.execCommand("copy");
3876
+ document.body.removeChild(ta);
3877
+ triggerToast();
3878
+ } catch (e) {
3879
+ }
3880
+ }
3248
3881
  };
3249
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3882
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3250
3883
  "div",
3251
3884
  {
3252
3885
  style: {
@@ -3254,10 +3887,41 @@ function TailscaleMobileSection(props) {
3254
3887
  color: "var(--dsw-alias-label-primary, inherit)",
3255
3888
  display: "flex",
3256
3889
  flexDirection: "column",
3257
- gap: "16px"
3890
+ gap: "16px",
3891
+ position: "relative"
3258
3892
  },
3259
3893
  children: [
3260
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3894
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3895
+ "div",
3896
+ {
3897
+ style: {
3898
+ position: "fixed",
3899
+ top: "24px",
3900
+ left: "50%",
3901
+ transform: "translateX(-50%)",
3902
+ zIndex: 100010,
3903
+ background: toast.type === "danger" ? "linear-gradient(135deg, rgba(239, 68, 68, 0.95), rgba(185, 28, 28, 0.95))" : toast.type === "success" ? "linear-gradient(135deg, rgba(16, 185, 129, 0.95), rgba(5, 150, 105, 0.95))" : "linear-gradient(135deg, rgba(59, 130, 246, 0.95), rgba(37, 99, 235, 0.95))",
3904
+ color: "#ffffff",
3905
+ borderRadius: "10px",
3906
+ padding: "10px 18px",
3907
+ fontSize: "13px",
3908
+ fontWeight: "600",
3909
+ boxShadow: "0 12px 30px rgba(0, 0, 0, 0.35)",
3910
+ backdropFilter: "blur(10px)",
3911
+ WebkitBackdropFilter: "blur(10px)",
3912
+ border: "1px solid rgba(255, 255, 255, 0.25)",
3913
+ display: "flex",
3914
+ alignItems: "center",
3915
+ gap: "8px",
3916
+ pointerEvents: "none"
3917
+ },
3918
+ children: [
3919
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: toast.type === "danger" ? "\u{1F6A8}" : toast.type === "success" ? "\u2705" : "\u2139\uFE0F" }),
3920
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: toast.message })
3921
+ ]
3922
+ }
3923
+ ) : null,
3924
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3261
3925
  "div",
3262
3926
  {
3263
3927
  style: {
@@ -3269,8 +3933,8 @@ function TailscaleMobileSection(props) {
3269
3933
  marginBottom: "4px"
3270
3934
  },
3271
3935
  children: [
3272
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { minWidth: "220px", flex: 1 }, children: [
3273
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3936
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { minWidth: "220px", flex: 1 }, children: [
3937
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3274
3938
  "div",
3275
3939
  {
3276
3940
  style: {
@@ -3281,7 +3945,7 @@ function TailscaleMobileSection(props) {
3281
3945
  margin: "0 0 6px 0"
3282
3946
  },
3283
3947
  children: [
3284
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3948
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3285
3949
  "h3",
3286
3950
  {
3287
3951
  style: {
@@ -3293,21 +3957,21 @@ function TailscaleMobileSection(props) {
3293
3957
  children: t("title", currentLang)
3294
3958
  }
3295
3959
  ),
3296
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3960
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3297
3961
  "div",
3298
3962
  {
3299
3963
  style: { position: "relative", display: "inline-flex", alignItems: "center" },
3300
3964
  onMouseEnter: () => setShowVersionTip(true),
3301
3965
  onMouseLeave: () => setShowVersionTip(false),
3302
3966
  onClick: () => {
3303
- setToast({
3304
- message: `\u{1F4E6} dsh-remote-mobile ${PLUGIN_VERSION} (${currentLang === "en" ? "Build: " : "\u6784\u5EFA\u65F6\u95F4: "}${BUILD_TIME})`,
3305
- type: "info"
3306
- });
3307
- setTimeout(() => setToast(null), 3e3);
3967
+ showToast(
3968
+ `\u{1F4E6} dsh-remote-mobile ${PLUGIN_VERSION} (${currentLang === "en" ? "Build: " : "\u6784\u5EFA\u65F6\u95F4: "}${BUILD_TIME})`,
3969
+ "info",
3970
+ 3e3
3971
+ );
3308
3972
  },
3309
3973
  children: [
3310
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3974
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3311
3975
  "span",
3312
3976
  {
3313
3977
  style: {
@@ -3324,7 +3988,7 @@ function TailscaleMobileSection(props) {
3324
3988
  children: PLUGIN_VERSION
3325
3989
  }
3326
3990
  ),
3327
- showVersionTip ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3991
+ showVersionTip ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3328
3992
  "div",
3329
3993
  {
3330
3994
  style: {
@@ -3355,7 +4019,7 @@ function TailscaleMobileSection(props) {
3355
4019
  ]
3356
4020
  }
3357
4021
  ),
3358
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4022
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3359
4023
  "div",
3360
4024
  {
3361
4025
  style: {
@@ -3366,7 +4030,7 @@ function TailscaleMobileSection(props) {
3366
4030
  }
3367
4031
  )
3368
4032
  ] }),
3369
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4033
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3370
4034
  "a",
3371
4035
  {
3372
4036
  href: "https://github.com/IceApriler/dsh-remote-mobile",
@@ -3388,88 +4052,235 @@ function TailscaleMobileSection(props) {
3388
4052
  cursor: "pointer"
3389
4053
  },
3390
4054
  children: [
3391
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { style: { width: "16px", height: "16px", fill: "currentColor" }, viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" }) }),
3392
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "dsh-remote-mobile" })
4055
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { style: { width: "16px", height: "16px", fill: "currentColor" }, viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" }) }),
4056
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "dsh-remote-mobile" })
3393
4057
  ]
3394
4058
  }
3395
4059
  )
3396
4060
  ]
3397
4061
  }
3398
4062
  ),
3399
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3400
- NetworkCard,
3401
- {
3402
- status,
3403
- toggleTailscale,
3404
- toggleLan,
3405
- copyText,
3406
- lang: currentLang
3407
- }
3408
- ),
3409
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3410
- QrPairingCard,
3411
- {
3412
- status,
3413
- showCode,
3414
- setShowCode,
3415
- timeLeft,
3416
- showQr,
3417
- setShowQr,
3418
- selectedTab,
3419
- setSelectedTab,
3420
- generateQrSvg,
3421
- refreshStatusAndCode,
3422
- copyDirectLink,
3423
- lang: currentLang
3424
- }
3425
- ),
3426
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3427
- SecretCard,
3428
- {
3429
- status,
3430
- secretInput,
3431
- setSecretInput,
3432
- showSecret,
3433
- setShowSecret,
3434
- saveSecret,
3435
- clearSecret,
3436
- lang: currentLang
3437
- }
3438
- ),
3439
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3440
- DeviceListCard,
4063
+ status.pairingBridgeConflict && !conflictBannerDismissed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4064
+ "div",
3441
4065
  {
3442
- status,
3443
- revokeDevice,
3444
- revokeAll,
3445
- lang: currentLang
4066
+ style: {
4067
+ display: "flex",
4068
+ alignItems: "flex-start",
4069
+ gap: "10px",
4070
+ padding: "12px 14px",
4071
+ borderRadius: "10px",
4072
+ border: "1px solid rgba(245, 158, 11, 0.45)",
4073
+ background: "rgba(245, 158, 11, 0.12)"
4074
+ },
4075
+ children: [
4076
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "16px", lineHeight: "20px" }, children: "\u26A0\uFE0F" }),
4077
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4078
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4079
+ "div",
4080
+ {
4081
+ style: {
4082
+ fontSize: "13px",
4083
+ fontWeight: "700",
4084
+ marginBottom: "4px",
4085
+ color: "var(--dsw-alias-label-primary, inherit)"
4086
+ },
4087
+ children: t("conflictBannerTitle", currentLang)
4088
+ }
4089
+ ),
4090
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4091
+ "div",
4092
+ {
4093
+ style: {
4094
+ fontSize: "12px",
4095
+ lineHeight: 1.6,
4096
+ whiteSpace: "pre-line",
4097
+ color: "var(--dsw-alias-label-secondary, inherit)"
4098
+ },
4099
+ children: t("conflictBannerBody", currentLang, {
4100
+ plugin: status.pairingBridgeConflict || t("conflictUnknownPlugin", currentLang)
4101
+ })
4102
+ }
4103
+ ),
4104
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4105
+ "button",
4106
+ {
4107
+ type: "button",
4108
+ onClick: () => {
4109
+ const plugin = status.pairingBridgeConflict || t("conflictUnknownPlugin", currentLang);
4110
+ const entryId = status.pairingBridgeConflictId || "";
4111
+ const report = t(
4112
+ entryId ? "conflictReportText" : "conflictReportNoEntryIdText",
4113
+ currentLang,
4114
+ { plugin, entryId }
4115
+ );
4116
+ copyText(report, t("conflictCopiedTip", currentLang));
4117
+ },
4118
+ style: {
4119
+ marginTop: "8px",
4120
+ display: "inline-flex",
4121
+ alignItems: "center",
4122
+ gap: "6px",
4123
+ padding: "5px 10px",
4124
+ fontSize: "12px",
4125
+ fontWeight: "600",
4126
+ borderRadius: "7px",
4127
+ cursor: "pointer",
4128
+ border: "1px solid rgba(245, 158, 11, 0.55)",
4129
+ background: "rgba(245, 158, 11, 0.18)",
4130
+ color: "var(--dsw-alias-label-primary, inherit)"
4131
+ },
4132
+ children: t("conflictCopyBtn", currentLang)
4133
+ }
4134
+ )
4135
+ ] }),
4136
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4137
+ "button",
4138
+ {
4139
+ type: "button",
4140
+ onClick: () => setConflictBannerDismissed(true),
4141
+ title: t("conflictBannerDismiss", currentLang),
4142
+ style: {
4143
+ flexShrink: 0,
4144
+ width: "24px",
4145
+ height: "24px",
4146
+ display: "inline-flex",
4147
+ alignItems: "center",
4148
+ justifyContent: "center",
4149
+ border: "none",
4150
+ borderRadius: "6px",
4151
+ cursor: "pointer",
4152
+ fontSize: "12px",
4153
+ background: "rgba(128, 128, 128, 0.15)",
4154
+ color: "var(--dsw-alias-label-secondary, inherit)"
4155
+ },
4156
+ children: "\u2715"
4157
+ }
4158
+ )
4159
+ ]
3446
4160
  }
3447
- ),
3448
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3449
- SecurityCard,
4161
+ ) : null,
4162
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4163
+ "div",
3450
4164
  {
3451
- status,
3452
- unlockIp,
3453
- clearIpStats,
3454
- lang: currentLang
4165
+ style: {
4166
+ display: "flex",
4167
+ gap: "24px",
4168
+ borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2))",
4169
+ paddingBottom: "0",
4170
+ marginBottom: "6px"
4171
+ },
4172
+ children: [
4173
+ { id: "access", label: t("tabAccess", currentLang) },
4174
+ { id: "devices", label: t("tabDevices", currentLang) },
4175
+ { id: "style", label: t("tabStyle", currentLang) },
4176
+ { id: "storage", label: t("tabStorage", currentLang) }
4177
+ ].map((item) => {
4178
+ const isActive = activeTab === item.id;
4179
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4180
+ "button",
4181
+ {
4182
+ type: "button",
4183
+ onClick: () => switchTab(item.id),
4184
+ style: {
4185
+ background: "transparent",
4186
+ border: "none",
4187
+ outline: "none",
4188
+ padding: "8px 2px 10px 2px",
4189
+ fontSize: "14px",
4190
+ fontWeight: isActive ? "600" : "400",
4191
+ cursor: "pointer",
4192
+ color: isActive ? "var(--dsw-alias-label-primary, #111827)" : "var(--dsw-alias-label-tertiary, #8c8c8c)",
4193
+ borderBottom: isActive ? "2px solid var(--dsw-alias-label-primary, #111827)" : "2px solid transparent",
4194
+ marginBottom: "-1px",
4195
+ transition: "all 0.2s ease"
4196
+ },
4197
+ children: item.label
4198
+ },
4199
+ item.id
4200
+ );
4201
+ })
3455
4202
  }
3456
4203
  ),
3457
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StorageCard, { status, lang: currentLang }),
3458
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3459
- ConfigCard,
3460
- {
3461
- maxVisitsInput,
3462
- setMaxVisitsInput,
3463
- maxFailedInput,
3464
- setMaxFailedInput,
3465
- lockDurationMinsInput,
3466
- setLockDurationMinsInput,
3467
- saveAdvancedConfig,
3468
- resetAdvancedConfigDefaults,
3469
- isSaving,
3470
- lang: currentLang
3471
- }
3472
- )
4204
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: activeTab === "access" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
4205
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4206
+ NetworkCard,
4207
+ {
4208
+ status,
4209
+ toggleTailscale,
4210
+ toggleLan,
4211
+ copyText,
4212
+ lang: currentLang
4213
+ }
4214
+ ),
4215
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4216
+ QrPairingCard,
4217
+ {
4218
+ status,
4219
+ showCode,
4220
+ setShowCode,
4221
+ timeLeft,
4222
+ showQr,
4223
+ setShowQr,
4224
+ selectedTab,
4225
+ setSelectedTab: updateQrTab,
4226
+ generateQrSvg,
4227
+ refreshStatusAndCode,
4228
+ copyDirectLink,
4229
+ lang: currentLang
4230
+ }
4231
+ ),
4232
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4233
+ SecretCard,
4234
+ {
4235
+ status,
4236
+ secretInput,
4237
+ setSecretInput,
4238
+ showSecret,
4239
+ setShowSecret,
4240
+ saveSecret,
4241
+ clearSecret,
4242
+ lang: currentLang
4243
+ }
4244
+ )
4245
+ ] }),
4246
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: activeTab === "devices" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
4247
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4248
+ DeviceListCard,
4249
+ {
4250
+ status,
4251
+ revokeDevice,
4252
+ revokeAll,
4253
+ lang: currentLang
4254
+ }
4255
+ ),
4256
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4257
+ SecurityCard,
4258
+ {
4259
+ status,
4260
+ unlockIp,
4261
+ clearIpStats,
4262
+ lang: currentLang
4263
+ }
4264
+ ),
4265
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4266
+ ConfigCard,
4267
+ {
4268
+ maxVisitsInput,
4269
+ setMaxVisitsInput,
4270
+ maxFailedInput,
4271
+ setMaxFailedInput,
4272
+ lockDurationMinsInput,
4273
+ setLockDurationMinsInput,
4274
+ saveAdvancedConfig,
4275
+ resetAdvancedConfigDefaults,
4276
+ isSaving,
4277
+ lang: currentLang,
4278
+ copyText
4279
+ }
4280
+ )
4281
+ ] }),
4282
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: activeTab === "style" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StylesCard, { lang: currentLang, showToast }) }),
4283
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: activeTab === "storage" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StorageCard, { status, lang: currentLang, copyText }) })
3473
4284
  ]
3474
4285
  }
3475
4286
  );