dsh-remote-plugin 0.6.0 → 0.6.2
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/gateway.cjs +5 -0
- package/index.mjs +68 -3
- package/package.json +1 -1
- package/public/admin.html +17 -0
- package/public/admin.js +9 -0
- package/public/app.js +343 -11
- package/public/desktop/desktop.css +62 -8
- package/public/desktop/desktop.html +204 -18
- package/public/desktop/desktop.js +529 -1
- package/public/donate.png +0 -0
- package/public/index.html +267 -85
- package/public/update.json +10 -4
- package/public/version.json +1 -1
package/apk/dsh-remote.apk
CHANGED
|
Binary file
|
package/gateway.cjs
CHANGED
|
@@ -1622,6 +1622,10 @@ function proxyApi(req, res, url) {
|
|
|
1622
1622
|
headers[k] = v
|
|
1623
1623
|
}
|
|
1624
1624
|
headers.host = UPSTREAM.host
|
|
1625
|
+
// /remote/* 由 DSH 插件端点处理;插件侧用网关自身 token 鉴权。
|
|
1626
|
+
if (url.pathname.startsWith('/remote/')) {
|
|
1627
|
+
headers.authorization = 'Bearer ' + TOKEN
|
|
1628
|
+
}
|
|
1625
1629
|
|
|
1626
1630
|
const upstreamReq = http.request({
|
|
1627
1631
|
hostname: UPSTREAM.hostname,
|
|
@@ -1673,6 +1677,7 @@ const server = http.createServer((req, res) => {
|
|
|
1673
1677
|
if (url.pathname.startsWith('/admin/api')) return serveAdminApi(req, res, url)
|
|
1674
1678
|
if (url.pathname.startsWith('/stats')) return serveStats(req, res, url)
|
|
1675
1679
|
if (url.pathname === '/api/events.poll') return serveEventPoll(req, res, url)
|
|
1680
|
+
if (url.pathname.startsWith('/remote/')) return proxyApi(req, res, url)
|
|
1676
1681
|
if (url.pathname.startsWith('/api/')) return proxyApi(req, res, url)
|
|
1677
1682
|
if (url.pathname === '/health') return serveHealth(res)
|
|
1678
1683
|
touchDevice(req)
|
package/index.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { dirname, extname, normalize, resolve } from 'node:path'
|
|
|
13
13
|
import { fileURLToPath } from 'node:url'
|
|
14
14
|
|
|
15
15
|
export const name = 'dsh-remote'
|
|
16
|
-
export const inject = ['webServer']
|
|
16
|
+
export const inject = ['webServer', 'commands', 'agents']
|
|
17
17
|
|
|
18
18
|
const MOUNT = '/remote'
|
|
19
19
|
const PUBLIC_DIR = fileURLToPath(new URL('./public/', import.meta.url))
|
|
@@ -284,6 +284,25 @@ function statsSend(session, event) {
|
|
|
284
284
|
statsQueues.set(session.id, next)
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/** 从 sessionId 解析 DSH Agent:优先取已发布 live agent,否则走 resume。返回 { agent, resolvePath } 便于定位。 */
|
|
288
|
+
async function resolveAgent(ctx, sessionId) {
|
|
289
|
+
if (!ctx.agents) return { agent: null, resolvePath: 'no-agents-service' }
|
|
290
|
+
let live
|
|
291
|
+
try {
|
|
292
|
+
live = ctx.agents.get(sessionId)
|
|
293
|
+
} catch (e) {
|
|
294
|
+
live = undefined
|
|
295
|
+
}
|
|
296
|
+
if (live) return { agent: live, resolvePath: 'live' }
|
|
297
|
+
try {
|
|
298
|
+
const handle = await ctx.agents.resume({ resumeSessionId: sessionId })
|
|
299
|
+
if (!handle || !handle.agent) return { agent: null, resolvePath: 'resume-empty-handle' }
|
|
300
|
+
return { agent: handle.agent, resolvePath: 'resume' }
|
|
301
|
+
} catch (e) {
|
|
302
|
+
return { agent: null, resolvePath: 'resume-error: ' + (e?.message || String(e)) }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
287
306
|
async function resolveFile(pathname) {
|
|
288
307
|
let abs = targetPath(pathname)
|
|
289
308
|
if (abs === null) return null
|
|
@@ -313,7 +332,7 @@ async function resolveFile(pathname) {
|
|
|
313
332
|
}
|
|
314
333
|
}
|
|
315
334
|
|
|
316
|
-
async function serveStatic(req, res) {
|
|
335
|
+
async function serveStatic(req, res, ctx) {
|
|
317
336
|
const pathname = new URL(req.url ?? '/', 'http://x').pathname
|
|
318
337
|
|
|
319
338
|
// 无尾斜杠的入口重定向到带斜杠版本:
|
|
@@ -417,6 +436,52 @@ async function serveStatic(req, res) {
|
|
|
417
436
|
return
|
|
418
437
|
}
|
|
419
438
|
|
|
439
|
+
// 斜杠命令桥接:客户端 → 网关 → 插件端点 → ctx.commands.execute
|
|
440
|
+
if (pathname === `${MOUNT}/api/command`) {
|
|
441
|
+
if (req.method !== 'POST') {
|
|
442
|
+
res.writeHead(405, { allow: 'POST' })
|
|
443
|
+
res.end()
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
const auth = req.headers.authorization || ''
|
|
447
|
+
const expected = gatewayToken()
|
|
448
|
+
if (!expected || auth !== `Bearer ${expected}`) {
|
|
449
|
+
sendJson(res, 401, { ok: false, message: 'unauthorized' })
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
let body
|
|
453
|
+
try {
|
|
454
|
+
body = JSON.parse((await readBody(req, 8192)) || '{}')
|
|
455
|
+
} catch {
|
|
456
|
+
sendJson(res, 400, { ok: false, message: 'invalid json' })
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
const { sessionId, line } = body || {}
|
|
460
|
+
if (!sessionId || typeof line !== 'string') {
|
|
461
|
+
sendJson(res, 400, { ok: false, message: 'sessionId and line required' })
|
|
462
|
+
return
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const { agent, resolvePath } = await resolveAgent(ctx, sessionId)
|
|
466
|
+
if (!agent) {
|
|
467
|
+
sendJson(res, 200, { ok: false, message: 'agent not found', debug: { resolvePath, commandNames: [] } })
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
let commandNames = []
|
|
471
|
+
try {
|
|
472
|
+
commandNames = (await ctx.commands.list(agent)).map(c => c.name)
|
|
473
|
+
} catch (e) {
|
|
474
|
+
commandNames = ['list-error: ' + (e?.message || String(e))]
|
|
475
|
+
}
|
|
476
|
+
const signal = AbortSignal.timeout(30000)
|
|
477
|
+
const result = await ctx.commands.execute(agent, line, signal)
|
|
478
|
+
sendJson(res, 200, { ok: true, executed: result !== undefined, debug: { resolvePath, commandNames } })
|
|
479
|
+
} catch (e) {
|
|
480
|
+
sendJson(res, 200, { ok: false, message: e?.message || String(e) })
|
|
481
|
+
}
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
420
485
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
421
486
|
res.writeHead(405, { allow: 'GET, HEAD' })
|
|
422
487
|
res.end()
|
|
@@ -446,7 +511,7 @@ export function apply(ctx) {
|
|
|
446
511
|
ctx.effect(() => ctx.webServer.register({
|
|
447
512
|
kind: 'prefix',
|
|
448
513
|
path: MOUNT,
|
|
449
|
-
handler: serveStatic,
|
|
514
|
+
handler: (req, res) => serveStatic(req, res, ctx),
|
|
450
515
|
}), 'dsh-remote: /remote route')
|
|
451
516
|
// 实时统计: 监听 DSH 会话事件流, 把带 usage 的 assistant/message 投递到网关聚合
|
|
452
517
|
ctx.on('session/event', (session, event) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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
|
@@ -184,6 +184,7 @@
|
|
|
184
184
|
<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>
|
|
185
185
|
<span class="gh-label" data-i18n="repo">仓库</span>
|
|
186
186
|
</a>
|
|
187
|
+
<button id="btn-donate" class="mini-btn tb-btn" data-i18n-title="donateTitle">🎁</button>
|
|
187
188
|
<div class="fb-wrap">
|
|
188
189
|
<button id="btn-feedback" class="mini-btn fb-btn tb-btn" data-i18n-title="feedbackTitle" data-i18n-aria="feedbackTitle" aria-haspopup="menu" aria-expanded="false">
|
|
189
190
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a8 8 0 0 1-8 8H5l-1.5 2L4 20a8 8 0 0 1-1-4 8 8 0 0 1 18-4Z"/></svg>
|
|
@@ -271,6 +272,20 @@
|
|
|
271
272
|
</div>
|
|
272
273
|
</div>
|
|
273
274
|
|
|
275
|
+
<!-- 赞赏支持模态 -->
|
|
276
|
+
<div id="modal-donate" class="modal hidden">
|
|
277
|
+
<div class="modal-card">
|
|
278
|
+
<div class="modal-title" data-i18n="donateTitle">赞赏支持</div>
|
|
279
|
+
<div class="modal-body donate-body">
|
|
280
|
+
<img src="donate.png" alt="" class="donate-img" style="display:block;margin:0 auto;max-width:100%;max-height:60vh;border-radius:8px">
|
|
281
|
+
<p class="donate-thanks" data-i18n="donateThanks" style="text-align:center;margin-top:12px">感谢你的支持 ☕</p>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="modal-actions">
|
|
284
|
+
<button id="donate-close" class="btn subtle" data-i18n="theme.close">关闭</button>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
274
289
|
<div id="toast" class="toast hidden"></div>
|
|
275
290
|
<script>
|
|
276
291
|
window.ADMIN_STR = {
|
|
@@ -282,6 +297,7 @@
|
|
|
282
297
|
'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔',
|
|
283
298
|
'theme.panelTitle': '选择配色', 'theme.close': '关闭',
|
|
284
299
|
'repo': '仓库',
|
|
300
|
+
'donateTitle': '赞赏支持', 'donateThanks': '感谢你的支持 ☕',
|
|
285
301
|
'feedbackTitle': '反馈渠道',
|
|
286
302
|
'feedback.githubDesc': '反馈 bug / 提建议', 'feedback.giteeDesc': '国内镜像,无需代理', 'feedback.biliDesc': 'UP 动态页交流',
|
|
287
303
|
'unauth': '未认证',
|
|
@@ -356,6 +372,7 @@
|
|
|
356
372
|
'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower',
|
|
357
373
|
'theme.panelTitle': 'Choose theme', 'theme.close': 'Close',
|
|
358
374
|
'repo': 'Repo',
|
|
375
|
+
'donateTitle': 'Support', 'donateThanks': 'Thanks for your support ☕',
|
|
359
376
|
'feedbackTitle': 'Feedback',
|
|
360
377
|
'feedback.githubDesc': 'Report bugs · suggest features', 'feedback.giteeDesc': 'Mirror in China, no proxy needed', 'feedback.biliDesc': 'Chat on the UP\'s Bilibili page',
|
|
361
378
|
'unauth': 'Not connected',
|
package/public/admin.js
CHANGED
|
@@ -482,6 +482,11 @@ function openThemePanel() {
|
|
|
482
482
|
$('modal-theme').classList.remove('hidden')
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
+
function openDonateModal() {
|
|
486
|
+
const m = $('modal-donate')
|
|
487
|
+
if (m) m.classList.remove('hidden')
|
|
488
|
+
}
|
|
489
|
+
|
|
485
490
|
$('btn-lang').addEventListener('click', () => {
|
|
486
491
|
I18N.setLang(I18N.lang === 'zh' ? 'en' : 'zh')
|
|
487
492
|
renderLangBtn()
|
|
@@ -492,6 +497,10 @@ $('btn-lang').addEventListener('click', () => {
|
|
|
492
497
|
|
|
493
498
|
$('btn-theme').addEventListener('click', openThemePanel)
|
|
494
499
|
$('theme-close').addEventListener('click', () => $('modal-theme').classList.add('hidden'))
|
|
500
|
+
// 赞赏支持
|
|
501
|
+
$('btn-donate').addEventListener('click', openDonateModal)
|
|
502
|
+
$('donate-close').addEventListener('click', () => $('modal-donate').classList.add('hidden'))
|
|
503
|
+
$('modal-donate').addEventListener('click', (e) => { if (e.target === $('modal-donate')) $('modal-donate').classList.add('hidden') })
|
|
495
504
|
// 反馈
|
|
496
505
|
$('btn-feedback').addEventListener('click', (e) => { e.stopPropagation(); toggleFeedbackMenu() })
|
|
497
506
|
$('fb-menu').addEventListener('click', (e) => {
|