dsh-remote-mobile 1.2.0 → 1.4.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
@@ -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,47 @@ 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
+ stylesNamePlaceholder: "\u7247\u6BB5\u540D\u79F0\uFF08\u5FC5\u586B\uFF09",
819
+ 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",
820
+ stylesAddNew: "\uFF0B \u65B0\u589E\u81EA\u5B9A\u4E49\u7247\u6BB5",
821
+ stylesSave: "\u{1F4BE} \u4FDD\u5B58\u7247\u6BB5",
822
+ stylesCancelEdit: "\u53D6\u6D88",
823
+ stylesEditBtn: "\u7F16\u8F91",
824
+ stylesDeleteBtn: "\u5220\u9664",
825
+ stylesResetAll: "\u{1F504} \u6062\u590D\u9ED8\u8BA4\u542F\u505C",
826
+ stylesEmptyCustom: "\u6682\u65E0\u81EA\u5B9A\u4E49\u7247\u6BB5\uFF0C\u70B9\u51FB\u300C\u65B0\u589E\u81EA\u5B9A\u4E49\u7247\u6BB5\u300D\u5F00\u59CB\u3002",
827
+ stylesLoadFail: "\u52A0\u8F7D\u6837\u5F0F\u7247\u6BB5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",
828
+ stylesSaveSuccess: "\u6837\u5F0F\u7247\u6BB5\u5DF2\u4FDD\u5B58\uFF0C\u5237\u65B0\u9875\u9762\u5373\u53EF\u770B\u5230\u6548\u679C",
829
+ stylesDeleteConfirm: "\u786E\u5B9A\u8981\u5220\u9664\u8BE5\u81EA\u5B9A\u4E49\u6837\u5F0F\u7247\u6BB5\u5417\uFF1F",
830
+ 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",
831
+ stylesFieldsRequired: "\u8BF7\u586B\u5199\u7247\u6BB5\u540D\u79F0\u4E0E CSS \u5185\u5BB9",
832
+ 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",
833
+ // 插件共存警示横幅
834
+ conflictBannerTitle: "\u68C0\u6D4B\u5230\u5176\u4ED6\u8FDC\u7A0B\u63A5\u5165\u63D2\u4EF6",
835
+ conflictUnknownPlugin: "\u53E6\u4E00\u6B3E\u8FDC\u7A0B\u63A5\u5165\u7C7B\u63D2\u4EF6",
836
+ 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",
837
+ conflictCopyBtn: "\u{1F4CB} \u590D\u5236\u8BCA\u65AD\u4E0E\u4FEE\u590D\u4FE1\u606F",
838
+ conflictCopiedTip: "\u8BCA\u65AD\u4FE1\u606F\u5DF2\u590D\u5236\uFF0C\u53EF\u76F4\u63A5\u7C98\u8D34\u7ED9 AI \u52A9\u624B\u534F\u52A9\u5904\u7406",
839
+ 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",
840
+ 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",
841
+ conflictBannerDismiss: "\u77E5\u9053\u4E86",
761
842
  // Toast 提示
762
843
  toastDeviceConnected: "\u{1F389} \u65B0\u8BBE\u5907\u63A5\u5165\u6210\u529F\uFF1A{name} ({ip})",
763
844
  toastDeviceOnline: "\u{1F4F6} \u8BBE\u5907\u91CD\u65B0\u4E0A\u7EBF\uFF1A{name} ({ip})",
@@ -850,7 +931,7 @@ var translations = {
850
931
  storageCardDesc: "All sensitive credentials are protected locally via slow salt hashing and asymmetric RSA cryptography, without cloud dependencies.",
851
932
  storageDeviceFile: "Authorized devices & session cache:",
852
933
  storageRsaFile: "Server RSA private key (0o600 protected):",
853
- storageConfigFile: "Main configuration (namespace: dsh-remote-mobile):",
934
+ storageStylesFile: "Mobile style snippets file (toggleable CSS mini-plugins):",
854
935
  // Card 7: Advanced Security Config
855
936
  configCardTitle: "Global Security Policy Configuration",
856
937
  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 +950,47 @@ var translations = {
869
950
  saveConfigSuccessToast: "Security policy successfully saved and synced to settings.yaml!",
870
951
  restoreDefaultsSuccessToast: "Default security policies restored and applied immediately!",
871
952
  saveConfigFailTip: "Parameters must be valid integers greater than 0",
953
+ // Card 8: Mobile Style Snippets (style mini-plugin)
954
+ tabAccess: "Access",
955
+ tabDevices: "Devices & Security",
956
+ tabStyle: "Style Overrides",
957
+ tabStorage: "Local Data",
958
+ stylesCardTitle: "Mobile Style Snippets",
959
+ 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.",
960
+ stylesPresetsTitle: "\u{1F9E9} Built-in Presets",
961
+ stylesCustomTitle: "\u270D\uFE0F Custom Snippets",
962
+ stylesPcShort: "PC",
963
+ stylesMobileShort: "Mobile",
964
+ stylesBothDevices: "PC + Mobile",
965
+ stylesMobileOnlyBadge: "Mobile only",
966
+ stylesPcOnlyBadge: "PC only",
967
+ stylesDisabled: "Disabled",
968
+ stylesViewCss: "View CSS",
969
+ stylesHideCss: "Hide CSS",
970
+ stylesNamePlaceholder: "Snippet name (required)",
971
+ stylesCssPlaceholder: "Paste or write CSS here\u2026 (html[data-dsh-mobile] can be used as a mobile scope selector)",
972
+ stylesAddNew: "\uFF0B Add Custom Snippet",
973
+ stylesSave: "\u{1F4BE} Save Snippet",
974
+ stylesCancelEdit: "Cancel",
975
+ stylesEditBtn: "Edit",
976
+ stylesDeleteBtn: "Delete",
977
+ stylesResetAll: "\u{1F504} Reset Defaults",
978
+ stylesEmptyCustom: 'No custom snippets yet. Click "Add Custom Snippet" to start.',
979
+ stylesLoadFail: "Failed to load style snippets, please try again later",
980
+ stylesSaveSuccess: "Style snippet saved. Refresh to see the effect.",
981
+ stylesDeleteConfirm: "Are you sure you want to delete this custom style snippet?",
982
+ stylesResetConfirm: "Reset all snippets to default states? (Built-ins: PC off / Mobile on. Custom: PC off / Mobile on.)",
983
+ stylesFieldsRequired: "Please fill in both the snippet name and CSS content",
984
+ 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.",
985
+ // Plugin coexistence banner
986
+ conflictBannerTitle: "Another remote-access plugin detected",
987
+ conflictUnknownPlugin: "another remote-access plugin",
988
+ 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.",
989
+ conflictCopyBtn: "\u{1F4CB} Copy diagnosis & fix",
990
+ conflictCopiedTip: "Diagnosis copied! Paste it to your AI assistant",
991
+ 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',
992
+ 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',
993
+ conflictBannerDismiss: "Got it",
872
994
  // Toast
873
995
  toastDeviceConnected: "\u{1F389} New device connected: {name} ({ip})",
874
996
  toastDeviceOnline: "\u{1F4F6} Device reconnected: {name} ({ip})",
@@ -2430,8 +2552,42 @@ function SecurityCard({ status, unlockIp, clearIpStats, lang }) {
2430
2552
  // src/client/components/StorageCard.jsx
2431
2553
  var import_react6 = __toESM(require("react"), 1);
2432
2554
  var import_jsx_runtime6 = require("react/jsx-runtime");
2433
- function StorageCard({ status, lang }) {
2434
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2555
+ function StorageCard({ status, lang, copyText }) {
2556
+ const handleCopy = (path, tip) => {
2557
+ if (!path) return;
2558
+ if (typeof copyText === "function") {
2559
+ copyText(path, tip);
2560
+ return;
2561
+ }
2562
+ if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
2563
+ navigator.clipboard.writeText(path).catch(() => {
2564
+ });
2565
+ }
2566
+ };
2567
+ const devicesPath = status.persistPath || "";
2568
+ const rsaPath = status.rsaKeyPath || "";
2569
+ const stylesPath = status.styleSnippetsPath || "";
2570
+ const items = [
2571
+ {
2572
+ icon: "\u{1F4C1}",
2573
+ title: t("storageDeviceFile", lang),
2574
+ path: devicesPath,
2575
+ tip: lang === "en" ? "Devices storage path copied!" : "\u5DF2\u6210\u529F\u590D\u5236\u8BBE\u5907\u5B58\u50A8\u6587\u4EF6\u8DEF\u5F84\uFF01"
2576
+ },
2577
+ {
2578
+ icon: "\u{1F511}",
2579
+ title: t("storageRsaFile", lang),
2580
+ path: rsaPath,
2581
+ tip: lang === "en" ? "RSA key path copied!" : "\u5DF2\u6210\u529F\u590D\u5236 RSA \u5BC6\u94A5\u6587\u4EF6\u8DEF\u5F84\uFF01"
2582
+ },
2583
+ {
2584
+ icon: "\u{1F3A8}",
2585
+ title: t("storageStylesFile", lang),
2586
+ path: stylesPath,
2587
+ tip: lang === "en" ? "Style snippets path copied!" : "\u5DF2\u6210\u529F\u590D\u5236\u6837\u5F0F\u7247\u6BB5\u6587\u4EF6\u8DEF\u5F84\uFF01"
2588
+ }
2589
+ ];
2590
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2435
2591
  "div",
2436
2592
  {
2437
2593
  style: {
@@ -2443,138 +2599,69 @@ function StorageCard({ status, lang }) {
2443
2599
  flexDirection: "column",
2444
2600
  gap: "10px"
2445
2601
  },
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
- ]
2602
+ children: items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2603
+ "div",
2604
+ {
2605
+ style: {
2606
+ display: "flex",
2607
+ alignItems: "center",
2608
+ justifyContent: "space-between",
2609
+ gap: "12px",
2610
+ paddingTop: index > 0 ? "8px" : "0",
2611
+ borderTop: index > 0 ? "1px dashed var(--dsw-alias-border-l2, rgba(128,128,128,0.15))" : "none"
2612
+ },
2613
+ children: [
2614
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "10px", minWidth: 0, flex: 1 }, children: [
2615
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: "18px", flexShrink: 0 }, children: item.icon }),
2616
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 }, children: [
2617
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2618
+ "div",
2619
+ {
2620
+ style: {
2621
+ fontSize: "12px",
2622
+ fontWeight: "600",
2623
+ color: "var(--dsw-alias-label-primary, inherit)"
2624
+ },
2625
+ children: item.title
2626
+ }
2627
+ ),
2628
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2629
+ "code",
2630
+ {
2631
+ style: {
2632
+ fontSize: "11px",
2633
+ color: "var(--dsw-alias-brand-primary, #3b82f6)",
2634
+ fontFamily: "monospace",
2635
+ wordBreak: "break-all"
2636
+ },
2637
+ children: item.path || "..."
2638
+ }
2639
+ )
2640
+ ] })
2641
+ ] }),
2642
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2643
+ "button",
2644
+ {
2645
+ type: "button",
2646
+ onClick: () => handleCopy(item.path, item.tip),
2647
+ style: {
2648
+ padding: "4px 10px",
2649
+ background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.1))",
2650
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.2))",
2651
+ color: "var(--dsw-alias-label-secondary, inherit)",
2652
+ borderRadius: "6px",
2653
+ fontSize: "11px",
2654
+ cursor: "pointer",
2655
+ whiteSpace: "nowrap",
2656
+ flexShrink: 0
2657
+ },
2658
+ children: t("copyUrlBtn", lang)
2659
+ }
2660
+ )
2661
+ ]
2662
+ },
2663
+ index
2664
+ ))
2578
2665
  }
2579
2666
  );
2580
2667
  }
@@ -2592,7 +2679,8 @@ function ConfigCard({
2592
2679
  saveAdvancedConfig,
2593
2680
  resetAdvancedConfigDefaults,
2594
2681
  isSaving,
2595
- lang
2682
+ lang,
2683
+ copyText
2596
2684
  }) {
2597
2685
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2598
2686
  "div",
@@ -2636,11 +2724,12 @@ function ConfigCard({
2636
2724
  {
2637
2725
  type: "button",
2638
2726
  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)"
2727
+ if (typeof copyText === "function") {
2728
+ copyText(
2729
+ "~/.dsh/settings.yaml",
2730
+ lang === "en" ? "Settings path copied (~/.dsh/settings.yaml)" : "\u5DF2\u590D\u5236\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF08~/.dsh/settings.yaml\uFF09"
2642
2731
  );
2643
- });
2732
+ }
2644
2733
  },
2645
2734
  style: {
2646
2735
  padding: "4px 10px",
@@ -2890,15 +2979,478 @@ function ConfigCard({
2890
2979
  );
2891
2980
  }
2892
2981
 
2982
+ // src/client/components/StylesCard.jsx
2983
+ var import_react8 = __toESM(require("react"), 1);
2984
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2985
+ function StylesCard({ lang, showToast }) {
2986
+ const [snippets, setSnippets] = (0, import_react8.useState)([]);
2987
+ const [loading, setLoading] = (0, import_react8.useState)(true);
2988
+ const [editingId, setEditingId] = (0, import_react8.useState)(null);
2989
+ const [showForm, setShowForm] = (0, import_react8.useState)(false);
2990
+ const [name, setName] = (0, import_react8.useState)("");
2991
+ const [css, setCss] = (0, import_react8.useState)("");
2992
+ const [pcEnabled, setPcEnabled] = (0, import_react8.useState)(false);
2993
+ const [mobileEnabled, setMobileEnabled] = (0, import_react8.useState)(true);
2994
+ const [expanded, setExpanded] = (0, import_react8.useState)({});
2995
+ const [feedback, setFeedback] = (0, import_react8.useState)("");
2996
+ const showFeedback = (0, import_react8.useCallback)((msg, type = "success") => {
2997
+ if (typeof showToast === "function") {
2998
+ showToast(msg, type);
2999
+ } else {
3000
+ setFeedback(msg);
3001
+ setTimeout(() => setFeedback(""), 3500);
3002
+ }
3003
+ }, [showToast]);
3004
+ const load = (0, import_react8.useCallback)(() => {
3005
+ fetchStyles().then((data) => {
3006
+ setSnippets(data.snippets || []);
3007
+ setLoading(false);
3008
+ }).catch(() => {
3009
+ setLoading(false);
3010
+ showFeedback(t("stylesLoadFail", lang), "danger");
3011
+ });
3012
+ }, [lang, showFeedback]);
3013
+ (0, import_react8.useEffect)(() => {
3014
+ load();
3015
+ }, [load]);
3016
+ const toggle = (id, scope, enabled) => {
3017
+ toggleStyleApi(id, scope, enabled).then((data) => {
3018
+ if (data.success) {
3019
+ load();
3020
+ const scopeLabel = scope === "pc" ? lang === "en" ? "PC" : "PC \u7AEF" : lang === "en" ? "Mobile" : "\u79FB\u52A8\u7AEF";
3021
+ showFeedback(
3022
+ enabled ? lang === "en" ? scopeLabel + " enabled!" : scopeLabel + " \u5DF2\u542F\u7528\uFF01" : lang === "en" ? scopeLabel + " disabled!" : scopeLabel + " \u5DF2\u505C\u7528\uFF01",
3023
+ "success"
3024
+ );
3025
+ }
3026
+ });
3027
+ };
3028
+ const startEdit = (s) => {
3029
+ setEditingId(s.id);
3030
+ setShowForm(true);
3031
+ setName(s.name);
3032
+ setCss(s.css);
3033
+ setPcEnabled(s.pcEnabled !== false);
3034
+ setMobileEnabled(s.mobileEnabled !== false);
3035
+ setFeedback("");
3036
+ };
3037
+ const cancelEdit = () => {
3038
+ setEditingId(null);
3039
+ setShowForm(false);
3040
+ setName("");
3041
+ setCss("");
3042
+ setPcEnabled(false);
3043
+ setMobileEnabled(true);
3044
+ setFeedback("");
3045
+ };
3046
+ const save = () => {
3047
+ if (!name.trim() || !css.trim()) {
3048
+ alert(t("stylesFieldsRequired", lang));
3049
+ return;
3050
+ }
3051
+ saveStyleSnippet({
3052
+ id: editingId || void 0,
3053
+ name: name.trim(),
3054
+ css,
3055
+ pcEnabled,
3056
+ mobileEnabled
3057
+ }).then((data) => {
3058
+ if (data.success) {
3059
+ load();
3060
+ cancelEdit();
3061
+ showFeedback(t("stylesSaveSuccess", lang));
3062
+ } else {
3063
+ alert((lang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (data.reason || ""));
3064
+ }
3065
+ }).catch(() => {
3066
+ alert(lang === "en" ? "Network request failed, please try again." : "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
3067
+ });
3068
+ };
3069
+ const remove = (s) => {
3070
+ if (s.builtin) return;
3071
+ if (confirm(t("stylesDeleteConfirm", lang))) {
3072
+ deleteStyleSnippetApi(s.id).then((data) => {
3073
+ if (data.success) {
3074
+ if (editingId === s.id) cancelEdit();
3075
+ load();
3076
+ showFeedback(lang === "en" ? "Style snippet deleted!" : "\u81EA\u5B9A\u4E49\u6837\u5F0F\u7247\u6BB5\u5DF2\u5220\u9664\uFF01");
3077
+ }
3078
+ });
3079
+ }
3080
+ };
3081
+ const resetAll = () => {
3082
+ if (!confirm(t("stylesResetConfirm", lang))) return;
3083
+ resetStylesApi().then((data) => {
3084
+ if (data.success) {
3085
+ load();
3086
+ showFeedback(lang === "en" ? "Style snippets reset to defaults!" : "\u5DF2\u6062\u590D\u6240\u6709\u7247\u6BB5\u7684\u9ED8\u8BA4\u542F\u505C\u72B6\u6001\uFF01");
3087
+ }
3088
+ });
3089
+ };
3090
+ const toggleExpand = (id) => {
3091
+ setExpanded((prev) => ({ ...prev, [id]: !prev[id] }));
3092
+ };
3093
+ const scopeBadge = (s) => {
3094
+ if (s.pcEnabled && s.mobileEnabled) return t("stylesBothDevices", lang);
3095
+ if (s.mobileEnabled) return "\u{1F4F1} " + t("stylesMobileOnlyBadge", lang);
3096
+ if (s.pcEnabled) return "\u{1F5A5}\uFE0F " + t("stylesPcOnlyBadge", lang);
3097
+ return t("stylesDisabled", lang);
3098
+ };
3099
+ const rowStyle = {
3100
+ display: "flex",
3101
+ flexDirection: "column",
3102
+ gap: "8px",
3103
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))",
3104
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.12))",
3105
+ borderRadius: "8px",
3106
+ padding: "10px 12px"
3107
+ };
3108
+ const miniBtnStyle = {
3109
+ padding: "4px 10px",
3110
+ background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.1))",
3111
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.2))",
3112
+ color: "var(--dsw-alias-label-secondary, inherit)",
3113
+ borderRadius: "6px",
3114
+ fontSize: "11px",
3115
+ cursor: "pointer",
3116
+ whiteSpace: "nowrap"
3117
+ };
3118
+ const primaryBtnStyle = {
3119
+ ...miniBtnStyle,
3120
+ background: "rgba(59,130,246,0.15)",
3121
+ color: "var(--dsw-alias-brand-primary, #3b82f6)",
3122
+ border: "1px solid rgba(59,130,246,0.3)"
3123
+ };
3124
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3125
+ "div",
3126
+ {
3127
+ style: {
3128
+ background: "var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.06))",
3129
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3130
+ borderRadius: "12px",
3131
+ padding: "18px 20px",
3132
+ display: "flex",
3133
+ flexDirection: "column",
3134
+ gap: "14px"
3135
+ },
3136
+ children: [
3137
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "10px", flexWrap: "wrap" }, children: [
3138
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
3139
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "16px" }, children: "\u{1F3A8}" }),
3140
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "15px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: t("stylesCardTitle", lang) })
3141
+ ] }),
3142
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: resetAll, style: miniBtnStyle, children: t("stylesResetAll", lang) })
3143
+ ] }),
3144
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.5" }, children: t("stylesCardDesc", lang) }),
3145
+ feedback && typeof showToast !== "function" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3146
+ "div",
3147
+ {
3148
+ style: {
3149
+ fontSize: "12px",
3150
+ padding: "8px 12px",
3151
+ borderRadius: "8px",
3152
+ background: "rgba(16,185,129,0.12)",
3153
+ color: "var(--dsw-alias-success, #10b981)",
3154
+ border: "1px solid rgba(16,185,129,0.25)"
3155
+ },
3156
+ children: feedback
3157
+ }
3158
+ ) : null,
3159
+ 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: [
3160
+ snippets.some((s) => s.builtin) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3161
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "13px", fontWeight: "600", color: "var(--dsw-alias-label-secondary, inherit)" }, children: t("stylesPresetsTitle", lang) }),
3162
+ /* @__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: [
3163
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: "8px", flexWrap: "wrap" }, children: [
3164
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: 1, minWidth: "180px" }, children: [
3165
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3166
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3167
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3168
+ "span",
3169
+ {
3170
+ style: {
3171
+ fontSize: "10px",
3172
+ padding: "1px 6px",
3173
+ borderRadius: "8px",
3174
+ background: "rgba(139,92,246,0.15)",
3175
+ color: "#8b5cf6",
3176
+ fontWeight: "600"
3177
+ },
3178
+ children: "Preset"
3179
+ }
3180
+ ),
3181
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3182
+ "span",
3183
+ {
3184
+ style: {
3185
+ fontSize: "10px",
3186
+ padding: "1px 6px",
3187
+ borderRadius: "8px",
3188
+ background: "rgba(59,130,246,0.12)",
3189
+ color: "#3b82f6",
3190
+ fontWeight: "600"
3191
+ },
3192
+ children: scopeBadge(s)
3193
+ }
3194
+ )
3195
+ ] }),
3196
+ 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
3197
+ ] }),
3198
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0, flexWrap: "wrap" }, children: [
3199
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3200
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3201
+ "input",
3202
+ {
3203
+ type: "checkbox",
3204
+ checked: s.pcEnabled,
3205
+ onChange: (e) => toggle(s.id, "pc", e.target.checked),
3206
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3207
+ }
3208
+ ),
3209
+ "\u{1F5A5}\uFE0F ",
3210
+ t("stylesPcShort", lang)
3211
+ ] }),
3212
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3213
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3214
+ "input",
3215
+ {
3216
+ type: "checkbox",
3217
+ checked: s.mobileEnabled,
3218
+ onChange: (e) => toggle(s.id, "mobile", e.target.checked),
3219
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3220
+ }
3221
+ ),
3222
+ "\u{1F4F1} ",
3223
+ t("stylesMobileShort", lang)
3224
+ ] }),
3225
+ /* @__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) })
3226
+ ] })
3227
+ ] }),
3228
+ expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3229
+ "pre",
3230
+ {
3231
+ style: {
3232
+ maxHeight: "180px",
3233
+ overflow: "auto",
3234
+ fontSize: "11px",
3235
+ lineHeight: "1.5",
3236
+ margin: 0,
3237
+ padding: "8px 10px",
3238
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.25))",
3239
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3240
+ borderRadius: "6px",
3241
+ color: "var(--dsw-alias-label-secondary, inherit)",
3242
+ whiteSpace: "pre-wrap",
3243
+ wordBreak: "break-word",
3244
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)"
3245
+ },
3246
+ children: s.css
3247
+ }
3248
+ ) : null
3249
+ ] }, s.id)) })
3250
+ ] }) : null,
3251
+ /* @__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) }),
3252
+ !showForm ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3253
+ "button",
3254
+ {
3255
+ type: "button",
3256
+ onClick: () => setShowForm(true),
3257
+ style: {
3258
+ width: "100%",
3259
+ padding: "12px",
3260
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))",
3261
+ border: "1px dashed var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3262
+ borderRadius: "8px",
3263
+ color: "var(--dsw-alias-brand-primary, #3b82f6)",
3264
+ fontSize: "12px",
3265
+ fontWeight: "600",
3266
+ cursor: "pointer"
3267
+ },
3268
+ children: t("stylesAddNew", lang)
3269
+ }
3270
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3271
+ "div",
3272
+ {
3273
+ style: {
3274
+ display: "flex",
3275
+ flexDirection: "column",
3276
+ gap: "8px",
3277
+ background: "var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.06))",
3278
+ border: "1px dashed var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3279
+ borderRadius: "8px",
3280
+ padding: "12px"
3281
+ },
3282
+ children: [
3283
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3284
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3285
+ "input",
3286
+ {
3287
+ type: "text",
3288
+ value: name,
3289
+ onChange: (e) => setName(e.target.value),
3290
+ placeholder: t("stylesNamePlaceholder", lang),
3291
+ style: {
3292
+ flex: 1,
3293
+ minWidth: "160px",
3294
+ padding: "8px 10px",
3295
+ fontSize: "12px",
3296
+ borderRadius: "6px",
3297
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3298
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.2))",
3299
+ color: "var(--dsw-alias-label-primary, inherit)",
3300
+ outline: "none"
3301
+ }
3302
+ }
3303
+ ),
3304
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3305
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3306
+ "input",
3307
+ {
3308
+ type: "checkbox",
3309
+ checked: pcEnabled,
3310
+ onChange: (e) => setPcEnabled(e.target.checked),
3311
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3312
+ }
3313
+ ),
3314
+ "\u{1F5A5}\uFE0F ",
3315
+ t("stylesPcShort", lang)
3316
+ ] }),
3317
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer", whiteSpace: "nowrap" }, children: [
3318
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3319
+ "input",
3320
+ {
3321
+ type: "checkbox",
3322
+ checked: mobileEnabled,
3323
+ onChange: (e) => setMobileEnabled(e.target.checked),
3324
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3325
+ }
3326
+ ),
3327
+ "\u{1F4F1} ",
3328
+ t("stylesMobileShort", lang)
3329
+ ] })
3330
+ ] }),
3331
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3332
+ "textarea",
3333
+ {
3334
+ value: css,
3335
+ onChange: (e) => setCss(e.target.value),
3336
+ placeholder: t("stylesCssPlaceholder", lang),
3337
+ rows: 5,
3338
+ style: {
3339
+ width: "100%",
3340
+ boxSizing: "border-box",
3341
+ padding: "8px 10px",
3342
+ fontSize: "12px",
3343
+ lineHeight: "1.5",
3344
+ borderRadius: "6px",
3345
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
3346
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.2))",
3347
+ color: "var(--dsw-alias-label-primary, inherit)",
3348
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)",
3349
+ outline: "none",
3350
+ resize: "vertical"
3351
+ }
3352
+ }
3353
+ ),
3354
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3355
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: save, style: primaryBtnStyle, children: t("stylesSave", lang) }),
3356
+ editingId !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null,
3357
+ showForm && !editingId ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: cancelEdit, style: miniBtnStyle, children: t("stylesCancelEdit", lang) }) : null
3358
+ ] })
3359
+ ]
3360
+ }
3361
+ ),
3362
+ 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: [
3363
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: "8px", flexWrap: "wrap" }, children: [
3364
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { flex: 1, minWidth: "180px" }, children: [
3365
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }, children: [
3366
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { fontSize: "12px", fontWeight: "600", color: "var(--dsw-alias-label-primary, inherit)" }, children: s.name }),
3367
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3368
+ "span",
3369
+ {
3370
+ style: {
3371
+ fontSize: "10px",
3372
+ padding: "1px 6px",
3373
+ borderRadius: "8px",
3374
+ background: "rgba(59,130,246,0.12)",
3375
+ color: "#3b82f6",
3376
+ fontWeight: "600"
3377
+ },
3378
+ children: scopeBadge(s)
3379
+ }
3380
+ )
3381
+ ] }),
3382
+ 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
3383
+ ] }),
3384
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0, flexWrap: "wrap" }, children: [
3385
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3386
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3387
+ "input",
3388
+ {
3389
+ type: "checkbox",
3390
+ checked: s.pcEnabled,
3391
+ onChange: (e) => toggle(s.id, "pc", e.target.checked),
3392
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3393
+ }
3394
+ ),
3395
+ "\u{1F5A5}\uFE0F ",
3396
+ t("stylesPcShort", lang)
3397
+ ] }),
3398
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: "4px", fontSize: "11px", cursor: "pointer" }, children: [
3399
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3400
+ "input",
3401
+ {
3402
+ type: "checkbox",
3403
+ checked: s.mobileEnabled,
3404
+ onChange: (e) => toggle(s.id, "mobile", e.target.checked),
3405
+ style: { accentColor: "#3b82f6", width: "16px", height: "16px", cursor: "pointer" }
3406
+ }
3407
+ ),
3408
+ "\u{1F4F1} ",
3409
+ t("stylesMobileShort", lang)
3410
+ ] }),
3411
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => startEdit(s), style: miniBtnStyle, children: t("stylesEditBtn", lang) }),
3412
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: () => remove(s), style: { ...miniBtnStyle, color: "#ef4444" }, children: t("stylesDeleteBtn", lang) }),
3413
+ /* @__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) })
3414
+ ] })
3415
+ ] }),
3416
+ expanded[s.id] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3417
+ "pre",
3418
+ {
3419
+ style: {
3420
+ maxHeight: "180px",
3421
+ overflow: "auto",
3422
+ fontSize: "11px",
3423
+ lineHeight: "1.5",
3424
+ margin: 0,
3425
+ padding: "8px 10px",
3426
+ background: "var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.25))",
3427
+ border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.15))",
3428
+ borderRadius: "6px",
3429
+ color: "var(--dsw-alias-label-secondary, inherit)",
3430
+ whiteSpace: "pre-wrap",
3431
+ wordBreak: "break-word",
3432
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)"
3433
+ },
3434
+ children: s.css
3435
+ }
3436
+ ) : null
3437
+ ] }, s.id)) }),
3438
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary, #888)", lineHeight: "1.6" }, children: t("stylesHint", lang) })
3439
+ ] })
3440
+ ]
3441
+ }
3442
+ );
3443
+ }
3444
+
2893
3445
  // src/client/version.js
2894
- var PLUGIN_VERSION = "v1.2.0";
2895
- var BUILD_TIME = "2026-08-23 00:15:34";
3446
+ var PLUGIN_VERSION = "v1.4.0";
3447
+ var BUILD_TIME = "2026-08-24 23:56:32";
2896
3448
 
2897
3449
  // src/client/components/MainSection.jsx
2898
- var import_jsx_runtime8 = require("react/jsx-runtime");
3450
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2899
3451
  function TailscaleMobileSection(props) {
2900
3452
  const ctx = props && props.ctx || typeof window !== "undefined" && window.__DSH_CLIENT_CTX__ || null;
2901
- const [status, setStatus] = (0, import_react8.useState)({
3453
+ const [status, setStatus] = (0, import_react9.useState)({
2902
3454
  tailscaleIp: "",
2903
3455
  lanIp: "",
2904
3456
  code: "",
@@ -2915,24 +3467,50 @@ function TailscaleMobileSection(props) {
2915
3467
  copied: false,
2916
3468
  loading: true,
2917
3469
  persistPath: "",
2918
- locale: "zh"
3470
+ rsaKeyPath: "",
3471
+ styleSnippetsPath: "",
3472
+ locale: "zh",
3473
+ pairingBridgeMode: "pending",
3474
+ pairingBridgeConflict: "",
3475
+ pairingBridgeConflictId: ""
2919
3476
  });
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)(() => {
3477
+ const [secretInput, setSecretInput] = (0, import_react9.useState)("");
3478
+ const [showSecret, setShowSecret] = (0, import_react9.useState)(false);
3479
+ const [showCode, setShowCode] = (0, import_react9.useState)(false);
3480
+ const [showQr, setShowQr] = (0, import_react9.useState)(false);
3481
+ const [selectedTab, setSelectedTab] = (0, import_react9.useState)("lan");
3482
+ const [timeLeft, setTimeLeft] = (0, import_react9.useState)(300);
3483
+ const [toast, setToast] = (0, import_react9.useState)(null);
3484
+ const [maxVisitsInput, setMaxVisitsInput] = (0, import_react9.useState)("60");
3485
+ const [maxFailedInput, setMaxFailedInput] = (0, import_react9.useState)("5");
3486
+ const [lockDurationMinsInput, setLockDurationMinsInput] = (0, import_react9.useState)("15");
3487
+ const [isSaving, setIsSaving] = (0, import_react9.useState)(false);
3488
+ const [showVersionTip, setShowVersionTip] = (0, import_react9.useState)(false);
3489
+ const [currentLang, setCurrentLang] = (0, import_react9.useState)(resolveLocale(ctx, status));
3490
+ const [conflictBannerDismissed, setConflictBannerDismissed] = (0, import_react9.useState)(false);
3491
+ const qrTabTouchedRef = (0, import_react9.useRef)(false);
3492
+ const updateQrTab = (0, import_react9.useCallback)((tab) => {
3493
+ qrTabTouchedRef.current = true;
3494
+ setSelectedTab(tab);
3495
+ }, []);
3496
+ const [activeTab, setActiveTab] = (0, import_react9.useState)(() => {
3497
+ try {
3498
+ const saved = localStorage.getItem("dsh-rm-settings-tab");
3499
+ if (saved === "access" || saved === "devices" || saved === "style" || saved === "storage") return saved;
3500
+ } catch (e) {
3501
+ }
3502
+ return "access";
3503
+ });
3504
+ const switchTab = (tab) => {
3505
+ setActiveTab(tab);
3506
+ try {
3507
+ localStorage.setItem("dsh-rm-settings-tab", tab);
3508
+ } catch (e) {
3509
+ }
3510
+ };
3511
+ const refreshStatusOnly = (0, import_react9.useCallback)(() => {
2934
3512
  fetchStatus().then((data) => {
2935
- if (data.tailscaleIp) {
3513
+ if (data.tailscaleIp && !qrTabTouchedRef.current) {
2936
3514
  setSelectedTab("tailscale");
2937
3515
  }
2938
3516
  if (data.maxVisitsPerMinute) setMaxVisitsInput(String(data.maxVisitsPerMinute));
@@ -2957,13 +3535,17 @@ function TailscaleMobileSection(props) {
2957
3535
  ipSecurityStats: data.ipSecurityStats || [],
2958
3536
  persistPath: data.persistPath || "",
2959
3537
  rsaKeyPath: data.rsaKeyPath || "",
3538
+ styleSnippetsPath: data.styleSnippetsPath || "",
2960
3539
  locale: data.locale || "zh",
3540
+ pairingBridgeMode: data.pairingBridgeMode || "pending",
3541
+ pairingBridgeConflict: data.pairingBridgeConflict || "",
3542
+ pairingBridgeConflictId: data.pairingBridgeConflictId || "",
2961
3543
  loading: false
2962
3544
  }));
2963
3545
  }).catch(() => {
2964
3546
  });
2965
3547
  }, [ctx]);
2966
- const refreshStatusAndCode = (0, import_react8.useCallback)(() => {
3548
+ const refreshStatusAndCode = (0, import_react9.useCallback)(() => {
2967
3549
  refreshStatusOnly();
2968
3550
  generatePairCode().then((data) => {
2969
3551
  if (data.success) {
@@ -2978,7 +3560,7 @@ function TailscaleMobileSection(props) {
2978
3560
  }).catch(() => {
2979
3561
  });
2980
3562
  }, [refreshStatusOnly]);
2981
- (0, import_react8.useEffect)(() => {
3563
+ (0, import_react9.useEffect)(() => {
2982
3564
  refreshStatusAndCode();
2983
3565
  const localeUnsubs = [];
2984
3566
  try {
@@ -3015,8 +3597,7 @@ function TailscaleMobileSection(props) {
3015
3597
  name: dev.deviceName || "Device",
3016
3598
  ip: dev.ip
3017
3599
  });
3018
- setToast({ message: msg, type: "info" });
3019
- setTimeout(() => setToast(null), 4e3);
3600
+ showToast(msg, "info", 4e3);
3020
3601
  }
3021
3602
  if (detail.type === "device-revoked") {
3022
3603
  setStatus((prev) => {
@@ -3031,8 +3612,7 @@ function TailscaleMobileSection(props) {
3031
3612
  }
3032
3613
  if (detail.type === "ip-security-alert") {
3033
3614
  const alertMsg = t("toastIpLocked", currentLang, { ip: detail.ip });
3034
- setToast({ message: alertMsg, type: "danger" });
3035
- setTimeout(() => setToast(null), 5e3);
3615
+ showToast(alertMsg, "danger", 5e3);
3036
3616
  }
3037
3617
  refreshStatusOnly();
3038
3618
  };
@@ -3048,7 +3628,7 @@ function TailscaleMobileSection(props) {
3048
3628
  }
3049
3629
  };
3050
3630
  }, [refreshStatusAndCode, refreshStatusOnly, currentLang, ctx]);
3051
- (0, import_react8.useEffect)(() => {
3631
+ (0, import_react9.useEffect)(() => {
3052
3632
  if (timeLeft <= 0) return;
3053
3633
  const timer = setInterval(() => {
3054
3634
  setTimeLeft((prev) => {
@@ -3093,11 +3673,11 @@ function TailscaleMobileSection(props) {
3093
3673
  if (data.success) {
3094
3674
  setStatus((prev) => ({ ...prev, hasSecret: true }));
3095
3675
  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);
3676
+ showToast(
3677
+ currentLang === "en" ? "Persistent password saved!" : "\u957F\u671F\u8BBF\u95EE\u5BC6\u7801\u5DF2\u6210\u529F\u4FDD\u5B58\u5E76\u542F\u7528\uFF01",
3678
+ "success",
3679
+ 3e3
3680
+ );
3101
3681
  } else {
3102
3682
  alert((currentLang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (data.reason || ""));
3103
3683
  }
@@ -3108,11 +3688,11 @@ function TailscaleMobileSection(props) {
3108
3688
  clearSecretApi().then(() => {
3109
3689
  setStatus((prev) => ({ ...prev, hasSecret: false }));
3110
3690
  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);
3691
+ showToast(
3692
+ currentLang === "en" ? "Password cleared!" : "\u5DF2\u6210\u529F\u6E05\u9664\u957F\u671F\u8BBF\u95EE\u5BC6\u7801\uFF01",
3693
+ "info",
3694
+ 3e3
3695
+ );
3116
3696
  });
3117
3697
  }
3118
3698
  };
@@ -3120,11 +3700,11 @@ function TailscaleMobileSection(props) {
3120
3700
  if (confirm(t("revokeDeviceConfirm", currentLang))) {
3121
3701
  revokeDeviceApi(token).then(() => {
3122
3702
  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);
3703
+ showToast(
3704
+ currentLang === "en" ? "Device disconnected!" : "\u5DF2\u6210\u529F\u8E22\u51FA\u8BE5\u8BBE\u5907\uFF01",
3705
+ "info",
3706
+ 2500
3707
+ );
3128
3708
  });
3129
3709
  }
3130
3710
  };
@@ -3132,11 +3712,11 @@ function TailscaleMobileSection(props) {
3132
3712
  if (confirm(t("revokeAllConfirm", currentLang))) {
3133
3713
  revokeAllDevicesApi().then(() => {
3134
3714
  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);
3715
+ showToast(
3716
+ currentLang === "en" ? "All devices disconnected!" : "\u5DF2\u6210\u529F\u6CE8\u9500\u5E76\u8E22\u4E0B\u7EBF\u6240\u6709\u8BBE\u5907\uFF01",
3717
+ "info",
3718
+ 2500
3719
+ );
3140
3720
  });
3141
3721
  }
3142
3722
  };
@@ -3144,11 +3724,11 @@ function TailscaleMobileSection(props) {
3144
3724
  if (confirm(t("unlockIpConfirm", currentLang, { ip }))) {
3145
3725
  unlockIpApi(ip).then(() => {
3146
3726
  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);
3727
+ showToast(
3728
+ currentLang === "en" ? `IP ${ip} unlocked!` : `\u5DF2\u6210\u529F\u4E3A IP ${ip} \u89E3\u9664\u9501\u5B9A\uFF01`,
3729
+ "success",
3730
+ 3e3
3731
+ );
3152
3732
  }).catch(() => {
3153
3733
  });
3154
3734
  }
@@ -3157,11 +3737,11 @@ function TailscaleMobileSection(props) {
3157
3737
  if (confirm(t("clearSecurityLogConfirm", currentLang))) {
3158
3738
  clearIpStatsApi().then(() => {
3159
3739
  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);
3740
+ showToast(
3741
+ currentLang === "en" ? "Audit logs cleared!" : "\u5DF2\u6210\u529F\u6E05\u7A7A\u6240\u6709\u5B89\u5168\u5BA1\u8BA1\u65E5\u5FD7\uFF01",
3742
+ "success",
3743
+ 2500
3744
+ );
3165
3745
  }).catch(() => {
3166
3746
  });
3167
3747
  }
@@ -3186,8 +3766,7 @@ function TailscaleMobileSection(props) {
3186
3766
  setIsSaving(false);
3187
3767
  if (data.success) {
3188
3768
  refreshStatusAndCode();
3189
- setToast({ message: t("saveConfigSuccessToast", currentLang), type: "success" });
3190
- setTimeout(() => setToast(null), 3e3);
3769
+ showToast(t("saveConfigSuccessToast", currentLang), "success", 3e3);
3191
3770
  } else {
3192
3771
  alert((currentLang === "en" ? "Save failed: " : "\u4FDD\u5B58\u5931\u8D25\uFF1A") + (data.reason || ""));
3193
3772
  }
@@ -3212,8 +3791,7 @@ function TailscaleMobileSection(props) {
3212
3791
  setIsSaving(false);
3213
3792
  if (data.success) {
3214
3793
  refreshStatusAndCode();
3215
- setToast({ message: t("restoreDefaultsSuccessToast", currentLang), type: "success" });
3216
- setTimeout(() => setToast(null), 3e3);
3794
+ showToast(t("restoreDefaultsSuccessToast", currentLang), "success", 3e3);
3217
3795
  } else {
3218
3796
  alert((currentLang === "en" ? "Reset failed: " : "\u91CD\u7F6E\u5931\u8D25\uFF1A") + (data.reason || ""));
3219
3797
  }
@@ -3230,23 +3808,57 @@ function TailscaleMobileSection(props) {
3230
3808
  const effectiveQrIp = selectedTab === "tailscale" && status.tailscaleIp ? status.tailscaleIp : status.lanIp || "127.0.0.1";
3231
3809
  const port = typeof window !== "undefined" && window.location.port || "3080";
3232
3810
  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
- });
3811
+ copyText(directLink, t("copiedTip", currentLang));
3812
+ setStatus((prev) => ({ ...prev, copied: true }));
3813
+ setTimeout(() => {
3814
+ setStatus((prev) => ({ ...prev, copied: false }));
3815
+ }, 2e3);
3239
3816
  };
3817
+ const toastTimerRef = (0, import_react9.useRef)(null);
3818
+ const showToast = (0, import_react9.useCallback)((message, type = "success", duration = 2500) => {
3819
+ if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
3820
+ setToast({ message, type });
3821
+ toastTimerRef.current = setTimeout(() => setToast(null), duration);
3822
+ }, []);
3240
3823
  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"
3824
+ const triggerToast = () => {
3825
+ showToast(
3826
+ successTip || (currentLang === "en" ? "Copied to clipboard!" : "\u5DF2\u6210\u529F\u590D\u5236\u5230\u526A\u8D34\u677F\uFF01"),
3827
+ "success",
3828
+ 2500
3829
+ );
3830
+ };
3831
+ if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
3832
+ navigator.clipboard.writeText(text).then(triggerToast).catch(() => {
3833
+ try {
3834
+ const ta = document.createElement("textarea");
3835
+ ta.value = text;
3836
+ ta.style.position = "fixed";
3837
+ ta.style.opacity = "0";
3838
+ document.body.appendChild(ta);
3839
+ ta.select();
3840
+ document.execCommand("copy");
3841
+ document.body.removeChild(ta);
3842
+ triggerToast();
3843
+ } catch (e) {
3844
+ }
3245
3845
  });
3246
- setTimeout(() => setToast(null), 2500);
3247
- });
3846
+ } else {
3847
+ try {
3848
+ const ta = document.createElement("textarea");
3849
+ ta.value = text;
3850
+ ta.style.position = "fixed";
3851
+ ta.style.opacity = "0";
3852
+ document.body.appendChild(ta);
3853
+ ta.select();
3854
+ document.execCommand("copy");
3855
+ document.body.removeChild(ta);
3856
+ triggerToast();
3857
+ } catch (e) {
3858
+ }
3859
+ }
3248
3860
  };
3249
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3861
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3250
3862
  "div",
3251
3863
  {
3252
3864
  style: {
@@ -3254,10 +3866,41 @@ function TailscaleMobileSection(props) {
3254
3866
  color: "var(--dsw-alias-label-primary, inherit)",
3255
3867
  display: "flex",
3256
3868
  flexDirection: "column",
3257
- gap: "16px"
3869
+ gap: "16px",
3870
+ position: "relative"
3258
3871
  },
3259
3872
  children: [
3260
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3873
+ toast ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3874
+ "div",
3875
+ {
3876
+ style: {
3877
+ position: "fixed",
3878
+ top: "24px",
3879
+ left: "50%",
3880
+ transform: "translateX(-50%)",
3881
+ zIndex: 100010,
3882
+ 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))",
3883
+ color: "#ffffff",
3884
+ borderRadius: "10px",
3885
+ padding: "10px 18px",
3886
+ fontSize: "13px",
3887
+ fontWeight: "600",
3888
+ boxShadow: "0 12px 30px rgba(0, 0, 0, 0.35)",
3889
+ backdropFilter: "blur(10px)",
3890
+ WebkitBackdropFilter: "blur(10px)",
3891
+ border: "1px solid rgba(255, 255, 255, 0.25)",
3892
+ display: "flex",
3893
+ alignItems: "center",
3894
+ gap: "8px",
3895
+ pointerEvents: "none"
3896
+ },
3897
+ children: [
3898
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: toast.type === "danger" ? "\u{1F6A8}" : toast.type === "success" ? "\u2705" : "\u2139\uFE0F" }),
3899
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: toast.message })
3900
+ ]
3901
+ }
3902
+ ) : null,
3903
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3261
3904
  "div",
3262
3905
  {
3263
3906
  style: {
@@ -3269,8 +3912,8 @@ function TailscaleMobileSection(props) {
3269
3912
  marginBottom: "4px"
3270
3913
  },
3271
3914
  children: [
3272
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { minWidth: "220px", flex: 1 }, children: [
3273
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3915
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { minWidth: "220px", flex: 1 }, children: [
3916
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3274
3917
  "div",
3275
3918
  {
3276
3919
  style: {
@@ -3281,7 +3924,7 @@ function TailscaleMobileSection(props) {
3281
3924
  margin: "0 0 6px 0"
3282
3925
  },
3283
3926
  children: [
3284
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3927
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3285
3928
  "h3",
3286
3929
  {
3287
3930
  style: {
@@ -3293,21 +3936,21 @@ function TailscaleMobileSection(props) {
3293
3936
  children: t("title", currentLang)
3294
3937
  }
3295
3938
  ),
3296
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3939
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3297
3940
  "div",
3298
3941
  {
3299
3942
  style: { position: "relative", display: "inline-flex", alignItems: "center" },
3300
3943
  onMouseEnter: () => setShowVersionTip(true),
3301
3944
  onMouseLeave: () => setShowVersionTip(false),
3302
3945
  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);
3946
+ showToast(
3947
+ `\u{1F4E6} dsh-remote-mobile ${PLUGIN_VERSION} (${currentLang === "en" ? "Build: " : "\u6784\u5EFA\u65F6\u95F4: "}${BUILD_TIME})`,
3948
+ "info",
3949
+ 3e3
3950
+ );
3308
3951
  },
3309
3952
  children: [
3310
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3953
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3311
3954
  "span",
3312
3955
  {
3313
3956
  style: {
@@ -3324,7 +3967,7 @@ function TailscaleMobileSection(props) {
3324
3967
  children: PLUGIN_VERSION
3325
3968
  }
3326
3969
  ),
3327
- showVersionTip ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3970
+ showVersionTip ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3328
3971
  "div",
3329
3972
  {
3330
3973
  style: {
@@ -3355,7 +3998,7 @@ function TailscaleMobileSection(props) {
3355
3998
  ]
3356
3999
  }
3357
4000
  ),
3358
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4001
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3359
4002
  "div",
3360
4003
  {
3361
4004
  style: {
@@ -3366,7 +4009,7 @@ function TailscaleMobileSection(props) {
3366
4009
  }
3367
4010
  )
3368
4011
  ] }),
3369
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4012
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3370
4013
  "a",
3371
4014
  {
3372
4015
  href: "https://github.com/IceApriler/dsh-remote-mobile",
@@ -3388,88 +4031,235 @@ function TailscaleMobileSection(props) {
3388
4031
  cursor: "pointer"
3389
4032
  },
3390
4033
  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" })
4034
+ /* @__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" }) }),
4035
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "dsh-remote-mobile" })
3393
4036
  ]
3394
4037
  }
3395
4038
  )
3396
4039
  ]
3397
4040
  }
3398
4041
  ),
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,
4042
+ status.pairingBridgeConflict && !conflictBannerDismissed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4043
+ "div",
3441
4044
  {
3442
- status,
3443
- revokeDevice,
3444
- revokeAll,
3445
- lang: currentLang
4045
+ style: {
4046
+ display: "flex",
4047
+ alignItems: "flex-start",
4048
+ gap: "10px",
4049
+ padding: "12px 14px",
4050
+ borderRadius: "10px",
4051
+ border: "1px solid rgba(245, 158, 11, 0.45)",
4052
+ background: "rgba(245, 158, 11, 0.12)"
4053
+ },
4054
+ children: [
4055
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { fontSize: "16px", lineHeight: "20px" }, children: "\u26A0\uFE0F" }),
4056
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
4057
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4058
+ "div",
4059
+ {
4060
+ style: {
4061
+ fontSize: "13px",
4062
+ fontWeight: "700",
4063
+ marginBottom: "4px",
4064
+ color: "var(--dsw-alias-label-primary, inherit)"
4065
+ },
4066
+ children: t("conflictBannerTitle", currentLang)
4067
+ }
4068
+ ),
4069
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4070
+ "div",
4071
+ {
4072
+ style: {
4073
+ fontSize: "12px",
4074
+ lineHeight: 1.6,
4075
+ whiteSpace: "pre-line",
4076
+ color: "var(--dsw-alias-label-secondary, inherit)"
4077
+ },
4078
+ children: t("conflictBannerBody", currentLang, {
4079
+ plugin: status.pairingBridgeConflict || t("conflictUnknownPlugin", currentLang)
4080
+ })
4081
+ }
4082
+ ),
4083
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4084
+ "button",
4085
+ {
4086
+ type: "button",
4087
+ onClick: () => {
4088
+ const plugin = status.pairingBridgeConflict || t("conflictUnknownPlugin", currentLang);
4089
+ const entryId = status.pairingBridgeConflictId || "";
4090
+ const report = t(
4091
+ entryId ? "conflictReportText" : "conflictReportNoEntryIdText",
4092
+ currentLang,
4093
+ { plugin, entryId }
4094
+ );
4095
+ copyText(report, t("conflictCopiedTip", currentLang));
4096
+ },
4097
+ style: {
4098
+ marginTop: "8px",
4099
+ display: "inline-flex",
4100
+ alignItems: "center",
4101
+ gap: "6px",
4102
+ padding: "5px 10px",
4103
+ fontSize: "12px",
4104
+ fontWeight: "600",
4105
+ borderRadius: "7px",
4106
+ cursor: "pointer",
4107
+ border: "1px solid rgba(245, 158, 11, 0.55)",
4108
+ background: "rgba(245, 158, 11, 0.18)",
4109
+ color: "var(--dsw-alias-label-primary, inherit)"
4110
+ },
4111
+ children: t("conflictCopyBtn", currentLang)
4112
+ }
4113
+ )
4114
+ ] }),
4115
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4116
+ "button",
4117
+ {
4118
+ type: "button",
4119
+ onClick: () => setConflictBannerDismissed(true),
4120
+ title: t("conflictBannerDismiss", currentLang),
4121
+ style: {
4122
+ flexShrink: 0,
4123
+ width: "24px",
4124
+ height: "24px",
4125
+ display: "inline-flex",
4126
+ alignItems: "center",
4127
+ justifyContent: "center",
4128
+ border: "none",
4129
+ borderRadius: "6px",
4130
+ cursor: "pointer",
4131
+ fontSize: "12px",
4132
+ background: "rgba(128, 128, 128, 0.15)",
4133
+ color: "var(--dsw-alias-label-secondary, inherit)"
4134
+ },
4135
+ children: "\u2715"
4136
+ }
4137
+ )
4138
+ ]
3446
4139
  }
3447
- ),
3448
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3449
- SecurityCard,
4140
+ ) : null,
4141
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4142
+ "div",
3450
4143
  {
3451
- status,
3452
- unlockIp,
3453
- clearIpStats,
3454
- lang: currentLang
4144
+ style: {
4145
+ display: "flex",
4146
+ gap: "24px",
4147
+ borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2))",
4148
+ paddingBottom: "0",
4149
+ marginBottom: "6px"
4150
+ },
4151
+ children: [
4152
+ { id: "access", label: t("tabAccess", currentLang) },
4153
+ { id: "devices", label: t("tabDevices", currentLang) },
4154
+ { id: "style", label: t("tabStyle", currentLang) },
4155
+ { id: "storage", label: t("tabStorage", currentLang) }
4156
+ ].map((item) => {
4157
+ const isActive = activeTab === item.id;
4158
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4159
+ "button",
4160
+ {
4161
+ type: "button",
4162
+ onClick: () => switchTab(item.id),
4163
+ style: {
4164
+ background: "transparent",
4165
+ border: "none",
4166
+ outline: "none",
4167
+ padding: "8px 2px 10px 2px",
4168
+ fontSize: "14px",
4169
+ fontWeight: isActive ? "600" : "400",
4170
+ cursor: "pointer",
4171
+ color: isActive ? "var(--dsw-alias-label-primary, #111827)" : "var(--dsw-alias-label-tertiary, #8c8c8c)",
4172
+ borderBottom: isActive ? "2px solid var(--dsw-alias-label-primary, #111827)" : "2px solid transparent",
4173
+ marginBottom: "-1px",
4174
+ transition: "all 0.2s ease"
4175
+ },
4176
+ children: item.label
4177
+ },
4178
+ item.id
4179
+ );
4180
+ })
3455
4181
  }
3456
4182
  ),
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
- )
4183
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: activeTab === "access" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
4184
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4185
+ NetworkCard,
4186
+ {
4187
+ status,
4188
+ toggleTailscale,
4189
+ toggleLan,
4190
+ copyText,
4191
+ lang: currentLang
4192
+ }
4193
+ ),
4194
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4195
+ QrPairingCard,
4196
+ {
4197
+ status,
4198
+ showCode,
4199
+ setShowCode,
4200
+ timeLeft,
4201
+ showQr,
4202
+ setShowQr,
4203
+ selectedTab,
4204
+ setSelectedTab: updateQrTab,
4205
+ generateQrSvg,
4206
+ refreshStatusAndCode,
4207
+ copyDirectLink,
4208
+ lang: currentLang
4209
+ }
4210
+ ),
4211
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4212
+ SecretCard,
4213
+ {
4214
+ status,
4215
+ secretInput,
4216
+ setSecretInput,
4217
+ showSecret,
4218
+ setShowSecret,
4219
+ saveSecret,
4220
+ clearSecret,
4221
+ lang: currentLang
4222
+ }
4223
+ )
4224
+ ] }),
4225
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: activeTab === "devices" ? "flex" : "none", flexDirection: "column", gap: "16px" }, children: [
4226
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4227
+ DeviceListCard,
4228
+ {
4229
+ status,
4230
+ revokeDevice,
4231
+ revokeAll,
4232
+ lang: currentLang
4233
+ }
4234
+ ),
4235
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4236
+ SecurityCard,
4237
+ {
4238
+ status,
4239
+ unlockIp,
4240
+ clearIpStats,
4241
+ lang: currentLang
4242
+ }
4243
+ ),
4244
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4245
+ ConfigCard,
4246
+ {
4247
+ maxVisitsInput,
4248
+ setMaxVisitsInput,
4249
+ maxFailedInput,
4250
+ setMaxFailedInput,
4251
+ lockDurationMinsInput,
4252
+ setLockDurationMinsInput,
4253
+ saveAdvancedConfig,
4254
+ resetAdvancedConfigDefaults,
4255
+ isSaving,
4256
+ lang: currentLang,
4257
+ copyText
4258
+ }
4259
+ )
4260
+ ] }),
4261
+ /* @__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 }) }),
4262
+ /* @__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
4263
  ]
3474
4264
  }
3475
4265
  );