dsh-remote-plugin 0.5.3 → 0.5.4
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/apk/dsh-remote.apk +0 -0
- package/package.json +1 -1
- package/public/admin.html +80 -30
- package/public/admin.js +49 -1
- package/public/app.js +68 -1
- package/public/index.html +28 -2
- package/public/styles.css +542 -173
- package/public/theme.js +57 -0
- package/public/update.json +3 -3
- package/public/version.json +1 -1
package/apk/dsh-remote.apk
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-plugin",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "DSH Remote 官方 bundle 插件:DSH 左侧原生边栏入口 + 右侧抽屉内嵌管理控制台;内置网关随 DSH 自动启停(systemd 独立单元),抽屉直显令牌与设备监控;网关提供 /fs/* 文件传输端点(列表/断点下载/上传),配合 Android App 远程操控会话/审批/提问/goal 与文件互传(多服务器测速切换、聊天记录离线缓存)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
package/public/admin.html
CHANGED
|
@@ -3,68 +3,99 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
|
-
<meta name="theme-color" content="#
|
|
6
|
+
<meta name="theme-color" content="#05348B">
|
|
7
7
|
<title>DSH Remote · 管理</title>
|
|
8
|
+
<script>/* 首帧前应用皮肤 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
|
|
8
9
|
<link rel="stylesheet" href="../styles.css">
|
|
9
10
|
<style>
|
|
10
|
-
.admin-wrap { max-width:
|
|
11
|
+
.admin-wrap { max-width: 1180px; margin: 0 auto; padding: calc(env(safe-area-inset-top, 0px) + 14px) 14px 40px; }
|
|
11
12
|
.admin-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 14px; }
|
|
12
13
|
.admin-title h1 { font-size: 20px; margin: 0; white-space: nowrap; }
|
|
13
|
-
.login-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; display: flex; gap: 8px; }
|
|
14
|
-
.login-card input { flex: 1; background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; font: inherit; outline: none; }
|
|
15
|
-
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
|
16
|
-
.stat-card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; }
|
|
17
|
-
.stat-card .v { font-size: 20px; font-weight: 700;
|
|
18
|
-
.stat-card .k { font-size: 11px; color: var(--muted); margin-top: 2px; }
|
|
19
|
-
.stat-card.ok .v { color: var(--
|
|
20
|
-
.table-wrap { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); overflow-x: auto; }
|
|
21
|
-
table { width: 100%; border-collapse: collapse; font-size: 13px; min-width:
|
|
22
|
-
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
23
|
-
th { font-size: 11px; color: var(--muted); letter-spacing: .5px; white-space: nowrap; }
|
|
14
|
+
.login-card { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: var(--dsr-radius); padding: 16px; display: flex; gap: 8px; }
|
|
15
|
+
.login-card input { flex: 1; background: var(--dsr-bg); color: var(--dsr-text); border: 1px solid var(--dsr-line); border-radius: 10px; padding: 10px 12px; font: inherit; outline: none; }
|
|
16
|
+
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 8px; margin-bottom: 14px; }
|
|
17
|
+
.stat-card { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: var(--dsr-radius); padding: 12px 14px; }
|
|
18
|
+
.stat-card .v { font-size: 20px; font-weight: 700; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
19
|
+
.stat-card .k { font-size: 11px; color: var(--dsr-muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
20
|
+
.stat-card.ok .v { color: var(--dsr-success); } .stat-card.warn .v { color: var(--dsr-warning); }
|
|
21
|
+
.table-wrap { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: var(--dsr-radius); overflow-x: auto; }
|
|
22
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 0; }
|
|
23
|
+
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--dsr-line); vertical-align: top; }
|
|
24
|
+
th { font-size: 11px; color: var(--dsr-muted); letter-spacing: .5px; white-space: nowrap; }
|
|
24
25
|
tr:last-child td { border-bottom: none; }
|
|
25
|
-
.badge { display: inline-block; font-size: 11px; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); white-space: nowrap; }
|
|
26
|
-
.badge.app { color: var(--
|
|
27
|
-
.badge.admin { color: var(--
|
|
28
|
-
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--line); margin-right: 5px; }
|
|
29
|
-
.dot.on { background: var(--
|
|
30
|
-
.dot.off { background: var(--
|
|
26
|
+
.badge { display: inline-block; font-size: 11px; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--dsr-line); color: var(--dsr-muted); white-space: nowrap; }
|
|
27
|
+
.badge.app { color: var(--dsr-accent-strong); border-color: var(--dsr-accent-line); background: var(--dsr-accent-soft); }
|
|
28
|
+
.badge.admin { color: var(--dsr-accent-2); border-color: var(--dsr-accent-2-line); background: var(--dsr-accent-2-soft); }
|
|
29
|
+
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--dsr-line); margin-right: 5px; }
|
|
30
|
+
.dot.on { background: var(--dsr-success); box-shadow: 0 0 6px var(--dsr-success-line); }
|
|
31
|
+
.dot.off { background: var(--dsr-warning); }
|
|
31
32
|
.mono { font-family: ui-monospace, monospace; font-size: 12px; word-break: break-all; }
|
|
32
33
|
.nowrap { white-space: nowrap; }
|
|
33
|
-
.ua { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
|
|
34
|
+
.ua { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsr-muted); }
|
|
35
|
+
/* 宽屏: 固定列宽让表格铺满容器, 名称列吸收剩余宽度, 消除临界横向滚动条 */
|
|
36
|
+
@media (min-width: 1000px) {
|
|
37
|
+
table { table-layout: fixed; }
|
|
38
|
+
th:nth-child(1) { width: 80px; }
|
|
39
|
+
th:nth-child(2) { width: 180px; }
|
|
40
|
+
th:nth-child(3) { width: 88px; }
|
|
41
|
+
th:nth-child(4) { width: 120px; }
|
|
42
|
+
th:nth-child(5) { width: 84px; }
|
|
43
|
+
th:nth-child(6) { width: 56px; }
|
|
44
|
+
th:nth-child(7) { width: 110px; }
|
|
45
|
+
th:nth-child(8) { width: 280px; }
|
|
46
|
+
th:nth-child(9) { width: 72px; }
|
|
47
|
+
td:nth-child(2) { white-space: nowrap; }
|
|
48
|
+
td:nth-child(2) b { display: inline-block; max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
|
|
49
|
+
tbody tr:hover { background: var(--dsr-accent-soft); }
|
|
50
|
+
}
|
|
34
51
|
.token-row { display: flex; flex-direction: column; align-items: stretch; gap: 8px; margin: 10px 0 14px; }
|
|
35
|
-
.token-row code { flex: none; background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; font-size: 12px; white-space: nowrap; overflow-x: auto; }
|
|
52
|
+
.token-row code { flex: none; width: 100%; background: var(--dsr-bg); border: 1px solid var(--dsr-line); border-radius: 8px; padding: 8px 10px; font-size: 12px; white-space: nowrap; overflow-x: auto; }
|
|
36
53
|
.token-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
54
|
+
.token-actions .mini-btn { flex: 1 1 auto; text-align: center; padding: 6px 8px; font-size: 12.5px; }
|
|
37
55
|
.conn-badge { white-space: nowrap; }
|
|
38
|
-
|
|
56
|
+
#btn-close-drawer { white-space: nowrap; }
|
|
57
|
+
@media (max-width: 720px) {
|
|
39
58
|
.admin-title h1 { font-size: 16px; }
|
|
40
59
|
.admin-title .right { gap: 6px; }
|
|
41
|
-
.gh-btn
|
|
60
|
+
.gh-btn { display: none; }
|
|
61
|
+
#btn-close-drawer span { display: none; }
|
|
62
|
+
#btn-close-drawer { padding: 5px 9px; }
|
|
63
|
+
#btn-console span { display: none; }
|
|
64
|
+
#btn-console { padding: 5px 9px; }
|
|
65
|
+
.conn-badge { padding: 3px 7px; }
|
|
42
66
|
}
|
|
43
|
-
@media (max-width:
|
|
67
|
+
@media (max-width: 760px) {
|
|
44
68
|
.table-wrap { overflow: visible; background: transparent; border: none; }
|
|
45
69
|
.table-wrap table { min-width: 0; }
|
|
46
70
|
.table-wrap thead { display: none; }
|
|
47
71
|
.table-wrap tbody { display: flex; flex-direction: column; gap: 8px; }
|
|
48
|
-
.table-wrap tr { display: block; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px; }
|
|
72
|
+
.table-wrap tr { display: block; background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 12px; padding: 10px 12px; }
|
|
49
73
|
.table-wrap tr:last-child td { border-bottom: none; }
|
|
50
|
-
.table-wrap td { display: flex; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px dashed var(--
|
|
74
|
+
.table-wrap td { display: flex; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px dashed var(--dsr-divider); }
|
|
51
75
|
.table-wrap td:last-child { border-bottom: none; }
|
|
52
|
-
.table-wrap td[data-label]::before { content: attr(data-label); min-width: 64px; font-size: 11px; color: var(--muted); flex: none; }
|
|
76
|
+
.table-wrap td[data-label]::before { content: attr(data-label); min-width: 64px; font-size: 11px; color: var(--dsr-muted); flex: none; }
|
|
53
77
|
.table-wrap td.ua { display: none; }
|
|
54
78
|
.table-wrap td.act { justify-content: flex-end; }
|
|
55
79
|
.table-wrap td .mini-btn { margin-left: auto; }
|
|
56
80
|
}
|
|
57
|
-
.pair-box { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; margin-bottom: 14px; text-align: center; }
|
|
81
|
+
.pair-box { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: var(--dsr-radius); padding: 14px; margin-bottom: 14px; text-align: center; }
|
|
58
82
|
.pair-box .pair-title { font-weight: 600; margin-bottom: 8px; }
|
|
59
83
|
.pair-qr { display: inline-block; background: #fff; border-radius: 8px; padding: 6px; line-height: 0; }
|
|
60
84
|
.pair-qr svg { display: block; width: 180px; height: 180px; }
|
|
61
85
|
.pair-box .muted { margin-top: 8px; font-size: 12px; }
|
|
62
|
-
.empty { text-align: center; color: var(--muted); padding: 30px 0; }
|
|
86
|
+
.empty { text-align: center; color: var(--dsr-muted); padding: 30px 0; }
|
|
63
87
|
.empty-actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 12px; }
|
|
64
88
|
a.mini-btn { text-decoration: none; display: inline-flex; align-items: center; }
|
|
65
|
-
.gh-btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; color: var(--text); }
|
|
89
|
+
.gh-btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; color: var(--dsr-text); }
|
|
66
90
|
.gh-btn svg { width: 16px; height: 16px; fill: currentColor; }
|
|
67
91
|
.admin-title .right { display: flex; align-items: center; gap: 10px; }
|
|
92
|
+
/* 放在 .gh-btn 基础规则之后, 窄屏才能覆盖 display */
|
|
93
|
+
@media (max-width: 720px) {
|
|
94
|
+
a.gh-btn { display: none; }
|
|
95
|
+
}
|
|
96
|
+
@media (max-width: 400px) {
|
|
97
|
+
.admin-title h1 span { display: none; }
|
|
98
|
+
}
|
|
68
99
|
</style>
|
|
69
100
|
</head>
|
|
70
101
|
<body>
|
|
@@ -80,6 +111,9 @@
|
|
|
80
111
|
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8a8.01 8.01 0 0 0 5.47 7.59c.4.08.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
|
|
81
112
|
<span data-i18n="repo">仓库</span>
|
|
82
113
|
</a>
|
|
114
|
+
<button id="btn-theme" class="mini-btn" title="">
|
|
115
|
+
<span id="theme-swatch" class="theme-swatch-dot"></span><span id="theme-label" class="t-label">默认深空</span>
|
|
116
|
+
</button>
|
|
83
117
|
<button id="btn-lang" class="mini-btn" title="Language / 语言">EN</button>
|
|
84
118
|
<button id="conn-badge" class="conn-badge off" title="网关面板"><span data-i18n="unauth">未认证</span></button>
|
|
85
119
|
</div>
|
|
@@ -125,6 +159,17 @@
|
|
|
125
159
|
</div>
|
|
126
160
|
</div>
|
|
127
161
|
|
|
162
|
+
<!-- 皮肤选择模态 -->
|
|
163
|
+
<div id="modal-theme" class="modal hidden">
|
|
164
|
+
<div class="modal-card">
|
|
165
|
+
<div class="modal-title" data-i18n="theme.panelTitle">选择配色</div>
|
|
166
|
+
<div id="theme-options" class="theme-panel-list"></div>
|
|
167
|
+
<div class="modal-actions">
|
|
168
|
+
<button id="theme-close" class="btn subtle" data-i18n="theme.close">关闭</button>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
128
173
|
<div id="toast" class="toast hidden"></div>
|
|
129
174
|
<script>
|
|
130
175
|
window.ADMIN_STR = {
|
|
@@ -133,6 +178,8 @@
|
|
|
133
178
|
'consoleTitle': '返回控制台',
|
|
134
179
|
'collapse': '收起面板',
|
|
135
180
|
'admin': '管理',
|
|
181
|
+
'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔',
|
|
182
|
+
'theme.panelTitle': '选择配色', 'theme.close': '关闭',
|
|
136
183
|
'repo': '仓库',
|
|
137
184
|
'unauth': '未认证',
|
|
138
185
|
'tokenPlaceholder': '输入网关访问令牌',
|
|
@@ -191,6 +238,8 @@
|
|
|
191
238
|
'consoleTitle': 'Back to console',
|
|
192
239
|
'collapse': 'Collapse panel',
|
|
193
240
|
'admin': 'Admin',
|
|
241
|
+
'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower',
|
|
242
|
+
'theme.panelTitle': 'Choose theme', 'theme.close': 'Close',
|
|
194
243
|
'repo': 'Repo',
|
|
195
244
|
'unauth': 'Not connected',
|
|
196
245
|
'tokenPlaceholder': 'Gateway access token',
|
|
@@ -248,6 +297,7 @@
|
|
|
248
297
|
</script>
|
|
249
298
|
<script src="../qrcode.min.js"></script>
|
|
250
299
|
<script src="../i18n.js"></script>
|
|
300
|
+
<script src="../theme.js"></script>
|
|
251
301
|
<script src="../admin.js"></script>
|
|
252
302
|
</body>
|
|
253
303
|
</html>
|
package/public/admin.js
CHANGED
|
@@ -92,7 +92,7 @@ function render(st) {
|
|
|
92
92
|
const upOk = st.upstream.reachable
|
|
93
93
|
const hostIPs = (st.lanIPs || []).join(t('stat.ipSep')) || '127.0.0.1'
|
|
94
94
|
const latestHtml = st.latest?.newer
|
|
95
|
-
? `<div class="v">${t('stat.updateAvailable', { version: st.latest.version })}</div><div class="k">${t('stat.currentV', { version: st.version })} · <a href="${st.latest.url || '#'}" target="_blank" rel="noopener" style="color:var(--
|
|
95
|
+
? `<div class="v">${t('stat.updateAvailable', { version: st.latest.version })}</div><div class="k">${t('stat.currentV', { version: st.version })} · <a href="${st.latest.url || '#'}" target="_blank" rel="noopener" style="color:var(--dsr-accent-strong)">${t('stat.download')}</a></div>`
|
|
96
96
|
: `<div class="v">v${st.version}</div><div class="k">${isPlugin ? t('stat.embedded') : st.latest?.error ? t('stat.updateCheck', { error: st.latest.error }) : st.latest?.version ? t('stat.latest') : t('stat.notChecked')}</div>`
|
|
97
97
|
$('stats').innerHTML = `
|
|
98
98
|
<div class="stat-card"><div class="v">v${st.version}</div><div class="k">${t(isPlugin ? 'stat.pluginVersion' : 'stat.gatewayVersion')}</div></div>
|
|
@@ -332,13 +332,60 @@ function renderLangBtn() {
|
|
|
332
332
|
if (btn) btn.textContent = I18N.lang === 'zh' ? 'EN' : '中文'
|
|
333
333
|
document.title = t('login.title')
|
|
334
334
|
}
|
|
335
|
+
|
|
336
|
+
const THEME_META = [
|
|
337
|
+
{ id: 'default', sw: ['#0B0E1A', '#151B33', '#5B8CFF'] },
|
|
338
|
+
{ id: 'dark', sw: ['#05348B', '#0D438F', '#F9A647'] },
|
|
339
|
+
{ id: 'light', sw: ['#EFEEEC', '#FAF8F5', '#E6BC7B'] },
|
|
340
|
+
{ id: 'neutral', sw: ['#DDD4B8', '#585818', '#832D15'] }
|
|
341
|
+
]
|
|
342
|
+
|
|
343
|
+
function renderThemeBtn() {
|
|
344
|
+
const cur = window.DSHTheme.get()
|
|
345
|
+
const meta = THEME_META.find(m => m.id === cur)
|
|
346
|
+
const label = $('theme-label')
|
|
347
|
+
const swatch = $('theme-swatch')
|
|
348
|
+
if (label) label.textContent = t('theme.' + cur)
|
|
349
|
+
if (swatch && meta) swatch.style.background = meta.sw[0]
|
|
350
|
+
const btn = $('btn-theme')
|
|
351
|
+
if (btn) btn.title = t('theme.' + cur)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function renderThemeOptions() {
|
|
355
|
+
const box = $('theme-options')
|
|
356
|
+
if (!box) return
|
|
357
|
+
const cur = window.DSHTheme.get()
|
|
358
|
+
box.innerHTML = THEME_META.map(m => `
|
|
359
|
+
<button class="theme-option ${m.id === cur ? 'current' : ''}" data-theme="${m.id}" title="${t('theme.' + m.id)}">
|
|
360
|
+
<span class="theme-swatches">${m.sw.map(c => `<i style="background:${c}"></i>`).join('')}</span>
|
|
361
|
+
<span class="theme-name">${t('theme.' + m.id)}</span>
|
|
362
|
+
<span class="theme-check">${m.id === cur ? '✓' : ''}</span>
|
|
363
|
+
</button>`).join('')
|
|
364
|
+
box.querySelectorAll('.theme-option').forEach(btn =>
|
|
365
|
+
btn.addEventListener('click', () => {
|
|
366
|
+
window.DSHTheme.set(btn.dataset.theme)
|
|
367
|
+
renderThemeBtn()
|
|
368
|
+
renderThemeOptions()
|
|
369
|
+
$('modal-theme').classList.add('hidden')
|
|
370
|
+
}))
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function openThemePanel() {
|
|
374
|
+
renderThemeOptions()
|
|
375
|
+
$('modal-theme').classList.remove('hidden')
|
|
376
|
+
}
|
|
377
|
+
|
|
335
378
|
$('btn-lang').addEventListener('click', () => {
|
|
336
379
|
I18N.setLang(I18N.lang === 'zh' ? 'en' : 'zh')
|
|
337
380
|
renderLangBtn()
|
|
381
|
+
renderThemeBtn()
|
|
338
382
|
if (lastState) render(lastState)
|
|
339
383
|
else if (!token && !pluginMode) $('conn-badge').textContent = t('unauth')
|
|
340
384
|
})
|
|
341
385
|
|
|
386
|
+
$('btn-theme').addEventListener('click', openThemePanel)
|
|
387
|
+
$('theme-close').addEventListener('click', () => $('modal-theme').classList.add('hidden'))
|
|
388
|
+
|
|
342
389
|
function start(showLogin) {
|
|
343
390
|
if (!showLogin) {
|
|
344
391
|
$('login-view').classList.add('hidden')
|
|
@@ -363,3 +410,4 @@ if (pluginMode) {
|
|
|
363
410
|
$('login-view').classList.remove('hidden')
|
|
364
411
|
}
|
|
365
412
|
renderLangBtn()
|
|
413
|
+
renderThemeBtn()
|
package/public/app.js
CHANGED
|
@@ -1713,6 +1713,24 @@ function cmpVersion(a, b) {
|
|
|
1713
1713
|
return 0
|
|
1714
1714
|
}
|
|
1715
1715
|
|
|
1716
|
+
function resetUpdateExpand() {
|
|
1717
|
+
const desc = $('update-desc')
|
|
1718
|
+
if (desc) desc.classList.remove('expanded')
|
|
1719
|
+
const btn = $('btn-update-expand')
|
|
1720
|
+
if (btn) btn.classList.add('hidden')
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
function renderUpdateExpandBtn() {
|
|
1724
|
+
const btn = $('btn-update-expand')
|
|
1725
|
+
if (!btn || btn.classList.contains('hidden')) return
|
|
1726
|
+
btn.textContent = t($('update-desc').classList.contains('expanded') ? 'update.collapse' : 'update.expand')
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
function toggleUpdateExpand() {
|
|
1730
|
+
$('update-desc').classList.toggle('expanded')
|
|
1731
|
+
renderUpdateExpandBtn()
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1716
1734
|
async function loadLocalVersion() {
|
|
1717
1735
|
try {
|
|
1718
1736
|
const res = await fetch('version.json?t=' + Date.now())
|
|
@@ -1726,6 +1744,7 @@ async function checkUpdate(silent) {
|
|
|
1726
1744
|
if (!base) {
|
|
1727
1745
|
if (!silent) toast(t('update.needServer'), 'err')
|
|
1728
1746
|
$('update-desc').textContent = state.localVersion ? `${t('update.currentV', { version: state.localVersion })} · ${t('update.needServer')}` : t('update.needServer')
|
|
1747
|
+
resetUpdateExpand()
|
|
1729
1748
|
return
|
|
1730
1749
|
}
|
|
1731
1750
|
if (!silent) toast(t('update.checking'))
|
|
@@ -1735,7 +1754,11 @@ async function checkUpdate(silent) {
|
|
|
1735
1754
|
const info = await res.json()
|
|
1736
1755
|
if (info.version && cmpVersion(info.version, state.localVersion) > 0) {
|
|
1737
1756
|
state.updateInfo = info
|
|
1738
|
-
|
|
1757
|
+
const hasNotes = !!(info.notes && String(info.notes).trim())
|
|
1758
|
+
$('update-desc').textContent = t('update.found', { version: info.version, notes: hasNotes ? ':' + info.notes : '' })
|
|
1759
|
+
$('update-desc').classList.remove('expanded')
|
|
1760
|
+
$('btn-update-expand').classList.toggle('hidden', !hasNotes)
|
|
1761
|
+
renderUpdateExpandBtn()
|
|
1739
1762
|
$('btn-download-update').classList.remove('hidden')
|
|
1740
1763
|
if (!silent) toast(t('update.found', { version: info.version }), 'ok')
|
|
1741
1764
|
else notify(t('update.foundTitle'), t('update.foundBody', { version: info.version }))
|
|
@@ -1743,10 +1766,12 @@ async function checkUpdate(silent) {
|
|
|
1743
1766
|
state.updateInfo = null
|
|
1744
1767
|
$('update-desc').textContent = state.localVersion ? t('update.latestV', { version: state.localVersion }) : t('update.latestRemote', { version: info.version || '?' })
|
|
1745
1768
|
$('btn-download-update').classList.add('hidden')
|
|
1769
|
+
resetUpdateExpand()
|
|
1746
1770
|
if (!silent) toast(t('update.latestToast'), 'ok')
|
|
1747
1771
|
}
|
|
1748
1772
|
} catch (e) {
|
|
1749
1773
|
$('update-desc').textContent = t('update.checkFailedDesc', { msg: e.message || t('fs.networkError') })
|
|
1774
|
+
resetUpdateExpand()
|
|
1750
1775
|
if (!silent) toast(t('update.checkFailed', { msg: e.message }), 'err')
|
|
1751
1776
|
}
|
|
1752
1777
|
}
|
|
@@ -1960,11 +1985,50 @@ function renderLangBtn() {
|
|
|
1960
1985
|
if (btn) btn.textContent = I18N.lang === 'zh' ? 'EN' : '中文'
|
|
1961
1986
|
}
|
|
1962
1987
|
|
|
1988
|
+
const THEME_META = [
|
|
1989
|
+
{ id: 'default', sw: ['#0B0E1A', '#151B33', '#5B8CFF'] },
|
|
1990
|
+
{ id: 'dark', sw: ['#05348B', '#0D438F', '#F9A647'] },
|
|
1991
|
+
{ id: 'light', sw: ['#EFEEEC', '#FAF8F5', '#E6BC7B'] },
|
|
1992
|
+
{ id: 'neutral', sw: ['#DDD4B8', '#585818', '#832D15'] }
|
|
1993
|
+
]
|
|
1994
|
+
|
|
1995
|
+
function renderThemeBtn() {
|
|
1996
|
+
const btn = $('btn-theme')
|
|
1997
|
+
if (btn) btn.textContent = t('theme.' + window.DSHTheme.get())
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
function renderThemeOptions() {
|
|
2001
|
+
const box = $('theme-options')
|
|
2002
|
+
if (!box) return
|
|
2003
|
+
const cur = window.DSHTheme.get()
|
|
2004
|
+
box.innerHTML = THEME_META.map(m => `
|
|
2005
|
+
<button class="theme-option ${m.id === cur ? 'current' : ''}" data-theme="${m.id}" title="${t('theme.' + m.id)}">
|
|
2006
|
+
<span class="theme-swatches">${m.sw.map(c => `<i style="background:${c}"></i>`).join('')}</span>
|
|
2007
|
+
<span class="theme-name">${t('theme.' + m.id)}</span>
|
|
2008
|
+
<span class="theme-check">${m.id === cur ? '✓' : ''}</span>
|
|
2009
|
+
</button>`).join('')
|
|
2010
|
+
box.querySelectorAll('.theme-option').forEach(btn =>
|
|
2011
|
+
btn.addEventListener('click', () => {
|
|
2012
|
+
window.DSHTheme.set(btn.dataset.theme)
|
|
2013
|
+
renderThemeBtn()
|
|
2014
|
+
renderThemeOptions()
|
|
2015
|
+
$('modal-theme').classList.add('hidden')
|
|
2016
|
+
}))
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
function openThemePanel() {
|
|
2020
|
+
renderThemeOptions()
|
|
2021
|
+
$('modal-theme').classList.remove('hidden')
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1963
2024
|
function bindUi() {
|
|
1964
2025
|
renderLangBtn()
|
|
2026
|
+
renderThemeBtn()
|
|
1965
2027
|
$('btn-lang').addEventListener('click', () => {
|
|
1966
2028
|
I18N.setLang(I18N.lang === 'zh' ? 'en' : 'zh')
|
|
1967
2029
|
renderLangBtn()
|
|
2030
|
+
renderThemeBtn()
|
|
2031
|
+
renderUpdateExpandBtn()
|
|
1968
2032
|
renderServers()
|
|
1969
2033
|
renderSessions()
|
|
1970
2034
|
renderPending(); renderQueue(); renderJobs()
|
|
@@ -1975,6 +2039,8 @@ function bindUi() {
|
|
|
1975
2039
|
if (state.hostInfo) $('host-desc').textContent = t('settings.hostDesc', { version: state.hostInfo.version, cwd: state.hostInfo.cwd, n: state.hostInfo.attachedSessions })
|
|
1976
2040
|
$('token-desc').textContent = state.token ? t('token.savedLocal') : t('token.notSet')
|
|
1977
2041
|
})
|
|
2042
|
+
$('btn-theme').addEventListener('click', openThemePanel)
|
|
2043
|
+
$('theme-close').addEventListener('click', () => $('modal-theme').classList.add('hidden'))
|
|
1978
2044
|
renderServers()
|
|
1979
2045
|
// 底部导航
|
|
1980
2046
|
document.querySelectorAll('.nav-btn').forEach(b =>
|
|
@@ -2041,6 +2107,7 @@ function bindUi() {
|
|
|
2041
2107
|
})
|
|
2042
2108
|
$('btn-check-update').addEventListener('click', () => checkUpdate(false))
|
|
2043
2109
|
$('btn-download-update').addEventListener('click', downloadUpdate)
|
|
2110
|
+
$('btn-update-expand').addEventListener('click', toggleUpdateExpand)
|
|
2044
2111
|
$('btn-reset').addEventListener('click', () => {
|
|
2045
2112
|
if (!confirm(t('settings.confirmReset'))) return
|
|
2046
2113
|
LS.del('token'); LS.del('notify'); LS.del('server')
|
package/public/index.html
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
|
|
6
|
-
<meta name="theme-color" content="#
|
|
6
|
+
<meta name="theme-color" content="#05348B">
|
|
7
7
|
<meta name="mobile-web-app-capable" content="yes">
|
|
8
8
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
9
9
|
<title>DSH Remote</title>
|
|
10
|
+
<script>/* 首帧前应用皮肤, 避免闪白/闪黑 */ (function(){try{var t=localStorage.getItem('dshTheme');if(t!=='default'&&t!=='dark'&&t!=='light'&&t!=='neutral'){t=window.matchMedia('(prefers-color-scheme: light)').matches?'light':'default'}document.documentElement.setAttribute('data-theme',t)}catch(e){}})()</script>
|
|
10
11
|
<link rel="manifest" href="manifest.webmanifest">
|
|
11
12
|
<link rel="icon" href="icon.svg" type="image/svg+xml">
|
|
12
13
|
<link rel="stylesheet" href="styles.css">
|
|
@@ -155,6 +156,10 @@
|
|
|
155
156
|
<div><div class="setting-name" data-i18n="settings.langTitle">语言 / Language</div><div class="setting-desc" data-i18n="settings.langDesc">界面语言,跟随系统或手动切换</div></div>
|
|
156
157
|
<button id="btn-lang" class="mini-btn">EN</button>
|
|
157
158
|
</div>
|
|
159
|
+
<div class="setting-row">
|
|
160
|
+
<div><div class="setting-name" data-i18n="settings.themeTitle">皮肤</div><div class="setting-desc" data-i18n="settings.themeDesc">默认深空 / 落日 / 易北爱乐厅 / 草原孤塔</div></div>
|
|
161
|
+
<button id="btn-theme" class="mini-btn" style="min-width:104px">默认深空</button>
|
|
162
|
+
</div>
|
|
158
163
|
<div class="setting-row">
|
|
159
164
|
<div><div class="setting-name" data-i18n="settings.tokenTitle">访问令牌</div><div class="setting-desc" id="token-desc" data-i18n="token.notSet">未设置</div></div>
|
|
160
165
|
<button id="btn-change-token" class="mini-btn" data-i18n="settings.change">更换</button>
|
|
@@ -165,7 +170,8 @@
|
|
|
165
170
|
</div>
|
|
166
171
|
<div class="setting-row">
|
|
167
172
|
<div><div class="setting-name" data-i18n="settings.updateTitle">检查更新</div><div class="setting-desc" id="update-desc" data-i18n="settings.updateLoading">加载中…</div>
|
|
168
|
-
<button id="btn-download-update" class="mini-btn hidden" style="margin-top:6px" data-i18n="settings.downloadUpdate">下载并安装更新</button
|
|
173
|
+
<button id="btn-download-update" class="mini-btn hidden" style="margin-top:6px" data-i18n="settings.downloadUpdate">下载并安装更新</button>
|
|
174
|
+
<button id="btn-update-expand" class="mini-btn hidden" style="margin-top:6px" data-i18n="update.expand">展开</button></div>
|
|
169
175
|
<button id="btn-check-update" class="mini-btn" data-i18n="settings.check">检查</button>
|
|
170
176
|
</div>
|
|
171
177
|
</div>
|
|
@@ -233,6 +239,17 @@
|
|
|
233
239
|
</div>
|
|
234
240
|
</div>
|
|
235
241
|
|
|
242
|
+
<!-- 皮肤选择模态 -->
|
|
243
|
+
<div id="modal-theme" class="modal hidden">
|
|
244
|
+
<div class="modal-card">
|
|
245
|
+
<div class="modal-title" data-i18n="theme.panelTitle">选择配色</div>
|
|
246
|
+
<div id="theme-options" class="theme-panel-list"></div>
|
|
247
|
+
<div class="modal-actions">
|
|
248
|
+
<button id="theme-close" class="btn subtle" data-i18n="theme.close">关闭</button>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
236
253
|
<div id="toast" class="toast hidden"></div>
|
|
237
254
|
|
|
238
255
|
<script>
|
|
@@ -324,6 +341,7 @@
|
|
|
324
341
|
'update.checkFailedDesc': '检查失败:{msg}', 'update.checkFailed': '检查更新失败:{msg}',
|
|
325
342
|
'update.downloadStarted': '开始下载,完成后会弹出安装页', 'update.downloadFailed': '无法启动下载:{msg}',
|
|
326
343
|
'update.installUnsupported': '当前版本不支持 App 内安装,已转浏览器下载',
|
|
344
|
+
'update.expand': '展开', 'update.collapse': '收起',
|
|
327
345
|
'scan.imageLoadFailed': '图片加载失败', 'scan.decodeUnsupported': '当前设备不支持图片解码',
|
|
328
346
|
'scan.browserHint': '浏览器请打开主机管理页,用手机相机扫码', 'scan.unsupported': '当前 App 版本不支持拍照扫码,请先更新 App',
|
|
329
347
|
'scan.permissionDenied': '未授予相机权限',
|
|
@@ -336,6 +354,9 @@
|
|
|
336
354
|
'settings.notifyTitle': '通知', 'settings.notifyDesc': '收到审批/提问时推送',
|
|
337
355
|
'settings.toolsTitle': '显示工具调用', 'settings.toolsDesc': '关闭后隐藏 🔧/📦 工具调用与结果',
|
|
338
356
|
'settings.langTitle': '语言 / Language', 'settings.langDesc': '界面语言,跟随系统或手动切换',
|
|
357
|
+
'settings.themeTitle': '皮肤', 'settings.themeDesc': '默认深空 / 落日 / 易北爱乐厅 / 草原孤塔',
|
|
358
|
+
'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔',
|
|
359
|
+
'theme.panelTitle': '选择配色', 'theme.close': '关闭',
|
|
339
360
|
'settings.tokenTitle': '访问令牌', 'settings.change': '更换',
|
|
340
361
|
'settings.hostTitle': 'DSH 状态', 'settings.hostProbing': '探测中…', 'settings.probe': '探测', 'settings.probeFailed': '探测失败',
|
|
341
362
|
'settings.hostDesc': 'DSH {version} · {cwd} · 附加会话 {n}',
|
|
@@ -437,6 +458,7 @@
|
|
|
437
458
|
'update.checkFailedDesc': 'Check failed: {msg}', 'update.checkFailed': 'Update check failed: {msg}',
|
|
438
459
|
'update.downloadStarted': 'Downloading, the install page will open when ready', 'update.downloadFailed': 'Could not start download: {msg}',
|
|
439
460
|
'update.installUnsupported': 'This version cannot install in-app, opening browser download',
|
|
461
|
+
'update.expand': 'Expand', 'update.collapse': 'Collapse',
|
|
440
462
|
'scan.imageLoadFailed': 'Image failed to load', 'scan.decodeUnsupported': 'This device cannot decode images',
|
|
441
463
|
'scan.browserHint': 'In a browser, open the host admin page and scan with your phone camera', 'scan.unsupported': 'This app version cannot scan, please update first',
|
|
442
464
|
'scan.permissionDenied': 'Camera permission not granted',
|
|
@@ -449,6 +471,9 @@
|
|
|
449
471
|
'settings.notifyTitle': 'Notifications', 'settings.notifyDesc': 'Push on approvals / questions',
|
|
450
472
|
'settings.toolsTitle': 'Show tool calls', 'settings.toolsDesc': 'Hide 🔧/📦 tool calls and results when off',
|
|
451
473
|
'settings.langTitle': 'Language / 语言', 'settings.langDesc': 'UI language, follows system or switch manually',
|
|
474
|
+
'settings.themeTitle': 'Theme', 'settings.themeDesc': 'Default deep space / Sunset / Elbphilharmonie / Prairie Tower',
|
|
475
|
+
'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower',
|
|
476
|
+
'theme.panelTitle': 'Choose theme', 'theme.close': 'Close',
|
|
452
477
|
'settings.tokenTitle': 'Access token', 'settings.change': 'Change',
|
|
453
478
|
'settings.hostTitle': 'DSH status', 'settings.hostProbing': 'Probing…', 'settings.probe': 'Probe', 'settings.probeFailed': 'Probe failed',
|
|
454
479
|
'settings.hostDesc': 'DSH {version} · {cwd} · {n} attached sessions',
|
|
@@ -468,6 +493,7 @@
|
|
|
468
493
|
<script src="sha256.js"></script>
|
|
469
494
|
<script src="jsqr.min.js"></script>
|
|
470
495
|
<script src="i18n.js"></script>
|
|
496
|
+
<script src="theme.js"></script>
|
|
471
497
|
<script src="app.js"></script>
|
|
472
498
|
</body>
|
|
473
499
|
</html>
|