dsh-remote-plugin 0.4.9 → 0.5.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/README.md +4 -4
- package/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +132 -7
- package/index.mjs +4 -2
- package/package.json +1 -1
- package/public/admin.html +14 -0
- package/public/admin.js +65 -1
- package/public/app.js +287 -60
- package/public/index.html +9 -0
- package/public/qrcode.min.js +2297 -0
- package/public/sha256.js +119 -0
- package/public/styles.css +2 -0
- package/public/update.json +3 -3
- package/public/version.json +1 -1
package/public/app.js
CHANGED
|
@@ -1219,20 +1219,113 @@ function showFsProgress(pct, loaded, total) {
|
|
|
1219
1219
|
$('fs-progress-text').textContent = `${pct}% · ${fmtSize(loaded)} / ${fmtSize(total)}`
|
|
1220
1220
|
}
|
|
1221
1221
|
|
|
1222
|
+
function setFsProgressText(text) {
|
|
1223
|
+
$('fs-progress-text').textContent = text
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1222
1226
|
function hideFsProgress() {
|
|
1223
1227
|
$('fs-progress').classList.add('hidden')
|
|
1224
1228
|
$('fs-progress-bar').style.width = '0%'
|
|
1225
1229
|
}
|
|
1226
1230
|
|
|
1231
|
+
function setFsButtons(show, paused = false) {
|
|
1232
|
+
const pauseBtn = $('fs-pause-btn')
|
|
1233
|
+
const cancelBtn = $('fs-cancel-btn')
|
|
1234
|
+
if (!pauseBtn || !cancelBtn) return
|
|
1235
|
+
pauseBtn.classList.toggle('hidden', !show)
|
|
1236
|
+
cancelBtn.classList.toggle('hidden', !show)
|
|
1237
|
+
if (show) pauseBtn.textContent = paused ? '继续' : '暂停'
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function pauseFsUpload() {
|
|
1241
|
+
const up = state.fs.upload
|
|
1242
|
+
if (!up) return
|
|
1243
|
+
if (!up.active) {
|
|
1244
|
+
if (up.paused) resumeFsUpload()
|
|
1245
|
+
return
|
|
1246
|
+
}
|
|
1247
|
+
up.pauseRequested = true
|
|
1248
|
+
try { up.xhr?.abort() } catch {}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function resumeFsUpload() {
|
|
1252
|
+
const up = state.fs.upload
|
|
1253
|
+
if (!up || !up.file) return
|
|
1254
|
+
up.paused = false
|
|
1255
|
+
runFsUpload(up)
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
async function cancelFsUpload() {
|
|
1259
|
+
const up = state.fs.upload
|
|
1260
|
+
if (!up) return
|
|
1261
|
+
up.cancelled = true
|
|
1262
|
+
try { up.xhr?.abort() } catch {}
|
|
1263
|
+
try {
|
|
1264
|
+
await fetch(fsApiUrl('/upload-control', { path: up.path, name: up.name, session: up.session, action: 'cancel' }), {
|
|
1265
|
+
method: 'POST', headers: fsHeaders()
|
|
1266
|
+
})
|
|
1267
|
+
} catch {}
|
|
1268
|
+
state.fs.upload = null
|
|
1269
|
+
hideFsProgress()
|
|
1270
|
+
setFsButtons(false)
|
|
1271
|
+
toast('已取消上传')
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const FS_CHUNK_SIZE = 4 * 1024 * 1024 // 4MB/块, 断线后重选同一文件自动续传
|
|
1275
|
+
|
|
1276
|
+
/** 把文件 [start,end) 逐块喂给 hasher(续传时补齐已传前缀); 期间可被暂停打断 */
|
|
1277
|
+
async function hashFsRange(file, hasher, start, end, up) {
|
|
1278
|
+
const step = FS_CHUNK_SIZE
|
|
1279
|
+
for (let off = start; off < end; off += step) {
|
|
1280
|
+
const buf = await file.slice(off, Math.min(off + step, end)).arrayBuffer()
|
|
1281
|
+
if (up?.pauseRequested) {
|
|
1282
|
+
const err = new Error('已暂停'); err.code = 'PAUSED'; throw err
|
|
1283
|
+
}
|
|
1284
|
+
hasher.update(new Uint8Array(buf))
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1227
1288
|
function uploadFsFile(file) {
|
|
1228
1289
|
if (!file) return
|
|
1229
1290
|
if (!state.token) { toast('请先到「设置」页设置令牌', 'err'); showView('view-settings'); return }
|
|
1230
1291
|
if (file.size > 2 * 1024 * 1024 * 1024) { toast('单文件超过 2GB 上限', 'err'); return }
|
|
1292
|
+
if (state.fs.upload?.active) { toast('已有上传任务,先暂停或取消', 'err'); return }
|
|
1231
1293
|
|
|
1232
|
-
const
|
|
1294
|
+
const prev = state.fs.upload
|
|
1295
|
+
if (prev && prev.path === state.fs.path && prev.name === file.name && prev.size === file.size) {
|
|
1296
|
+
prev.file = file
|
|
1297
|
+
runFsUpload(prev)
|
|
1298
|
+
return
|
|
1299
|
+
}
|
|
1300
|
+
// 换传别的文件: 顺手清掉旧任务的服务端分片, 不残留隐藏 .part
|
|
1301
|
+
if (prev) {
|
|
1302
|
+
prev.cancelled = true
|
|
1303
|
+
try { prev.xhr?.abort() } catch {}
|
|
1304
|
+
try {
|
|
1305
|
+
fetch(fsApiUrl('/upload-control', { path: prev.path, name: prev.name, session: prev.session, action: 'cancel' }), {
|
|
1306
|
+
method: 'POST', headers: fsHeaders()
|
|
1307
|
+
})
|
|
1308
|
+
} catch {}
|
|
1309
|
+
}
|
|
1310
|
+
const up = {
|
|
1311
|
+
session: uuid(), path: state.fs.path, name: file.name, size: file.size,
|
|
1312
|
+
offset: 0, file, xhr: null, active: false, paused: false, cancelled: false
|
|
1313
|
+
}
|
|
1314
|
+
state.fs.upload = up
|
|
1315
|
+
runFsUpload(up)
|
|
1316
|
+
}
|
|
1233
1317
|
|
|
1234
|
-
|
|
1318
|
+
async function runFsUpload(up) {
|
|
1319
|
+
if (!up || !up.file) return
|
|
1320
|
+
up.active = true
|
|
1321
|
+
up.cancelled = false
|
|
1322
|
+
up.paused = false
|
|
1323
|
+
up.pauseRequested = false
|
|
1324
|
+
setFsButtons(true, false)
|
|
1325
|
+
|
|
1326
|
+
const uploadChunk = (params, blob) => new Promise((resolve, reject) => {
|
|
1235
1327
|
const xhr = new XMLHttpRequest()
|
|
1328
|
+
up.xhr = xhr
|
|
1236
1329
|
xhr.open('POST', fsApiUrl('/upload', params))
|
|
1237
1330
|
xhr.setRequestHeader('authorization', 'Bearer ' + state.token)
|
|
1238
1331
|
xhr.setRequestHeader('x-dsh-remote-client', CAP?.isNativePlatform?.() ? 'app' : 'web')
|
|
@@ -1243,17 +1336,23 @@ function uploadFsFile(file) {
|
|
|
1243
1336
|
}
|
|
1244
1337
|
}
|
|
1245
1338
|
xhr.onload = () => {
|
|
1339
|
+
if (up.xhr === xhr) up.xhr = null
|
|
1246
1340
|
let json = {}
|
|
1247
1341
|
try { json = JSON.parse(xhr.responseText || '{}') } catch {}
|
|
1248
1342
|
resolve({ status: xhr.status, json })
|
|
1249
1343
|
}
|
|
1250
|
-
xhr.onerror = () => reject(new Error('网络错误'))
|
|
1251
|
-
xhr.upload.onerror = () => reject(new Error('网络中断'))
|
|
1252
|
-
xhr.onabort = () =>
|
|
1344
|
+
xhr.onerror = () => { if (up.xhr === xhr) up.xhr = null; reject(new Error('网络错误')) }
|
|
1345
|
+
xhr.upload.onerror = () => { if (up.xhr === xhr) up.xhr = null; reject(new Error('网络中断')) }
|
|
1346
|
+
xhr.onabort = () => {
|
|
1347
|
+
if (up.xhr === xhr) up.xhr = null
|
|
1348
|
+
const err = new Error(up.cancelled ? '已取消' : '已暂停')
|
|
1349
|
+
err.code = up.cancelled ? 'CANCELLED' : 'PAUSED'
|
|
1350
|
+
reject(err)
|
|
1351
|
+
}
|
|
1253
1352
|
xhr.send(blob)
|
|
1254
1353
|
})
|
|
1255
1354
|
|
|
1256
|
-
const probe = async (
|
|
1355
|
+
const probe = async () => {
|
|
1257
1356
|
const res = await fetch(fsApiUrl('/upload-probe', { path: up.path, name: up.name, session: up.session }), { headers: fsHeaders() })
|
|
1258
1357
|
if (res.status === 401) { fsAuthError(401); return null }
|
|
1259
1358
|
const json = await res.json().catch(() => ({}))
|
|
@@ -1261,71 +1360,130 @@ function uploadFsFile(file) {
|
|
|
1261
1360
|
return json
|
|
1262
1361
|
}
|
|
1263
1362
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1363
|
+
let overwrite = false
|
|
1364
|
+
let wasResumed = false
|
|
1365
|
+
let hasher = new SHA256()
|
|
1366
|
+
try {
|
|
1367
|
+
const info = await probe()
|
|
1368
|
+
if (info === null || up.cancelled) return
|
|
1369
|
+
if (info.partialSize > 0) wasResumed = true
|
|
1370
|
+
if (info.targetExists && info.targetSize === up.size && !overwrite) {
|
|
1371
|
+
if (!confirm('文件已存在(大小相同),覆盖它?')) { state.fs.upload = null; hideFsProgress(); setFsButtons(false); return }
|
|
1372
|
+
overwrite = true
|
|
1269
1373
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
if (
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1374
|
+
if (up.offset > 0) await hashFsRange(up.file, hasher, 0, up.offset, up)
|
|
1375
|
+
showFsProgress(Math.round(up.offset / Math.max(1, up.size) * 100), up.offset, up.size)
|
|
1376
|
+
|
|
1377
|
+
while (up.offset < up.size) {
|
|
1378
|
+
if (up.cancelled) return
|
|
1379
|
+
const end = Math.min(up.offset + FS_CHUNK_SIZE, up.size)
|
|
1380
|
+
const blob = up.file.slice(up.offset, end)
|
|
1381
|
+
const before = hasher.clone()
|
|
1382
|
+
const chunkBytes = new Uint8Array(await blob.arrayBuffer())
|
|
1383
|
+
if (up.pauseRequested) {
|
|
1384
|
+
const err = new Error('已暂停'); err.code = 'PAUSED'; throw err
|
|
1279
1385
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
if (r.
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
toast(`已上传 ${file.name}${wasResumed ? '(断点续传完成)' : ''}`, 'ok')
|
|
1294
|
-
loadFs()
|
|
1295
|
-
return
|
|
1296
|
-
}
|
|
1297
|
-
if (r.status === 409 && r.json.error === 'conflict') {
|
|
1298
|
-
if (!confirm('文件已存在,覆盖它?')) { state.fs.upload = null; return }
|
|
1299
|
-
overwrite = true
|
|
1300
|
-
continue // 同一 offset 带 overwrite=1 重发
|
|
1301
|
-
}
|
|
1302
|
-
if (r.status === 409 && r.json.error === 'offset-mismatch') {
|
|
1303
|
-
await probe(up)
|
|
1304
|
-
continue
|
|
1386
|
+
hasher.update(chunkBytes)
|
|
1387
|
+
const isLast = end >= up.size
|
|
1388
|
+
const params = { path: up.path, name: up.name, session: up.session, offset: String(up.offset) }
|
|
1389
|
+
if (isLast) { params.finish = '1'; params.sha256 = hasher.hex() }
|
|
1390
|
+
if (overwrite) params.overwrite = '1'
|
|
1391
|
+
const r = await uploadChunk(params, blob)
|
|
1392
|
+
if (r.status === 401) { fsAuthError(401); return }
|
|
1393
|
+
if (r.status === 200 && r.json.offset != null) { up.offset = r.json.offset; continue }
|
|
1394
|
+
if (r.status === 201) {
|
|
1395
|
+
const expected = params.sha256 || hasher.hex()
|
|
1396
|
+
if (r.json.sha256 && r.json.sha256 !== expected) {
|
|
1397
|
+
const err = new Error('文件校验不一致(SHA-256)'); err.checksum = true
|
|
1398
|
+
throw err
|
|
1305
1399
|
}
|
|
1306
|
-
throw new Error(r.json.error || ('HTTP ' + r.status))
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// 0 字节文件: 循环不执行, 发一个空 finish 块
|
|
1310
|
-
if (up.size === 0 && up.offset === 0) {
|
|
1311
|
-
const params = { path: up.path, name: up.name, session: up.session, offset: '0', finish: '1' }
|
|
1312
|
-
if (overwrite) params.overwrite = '1'
|
|
1313
|
-
const r = await uploadChunk(params, new Blob([]), up)
|
|
1314
|
-
if (r.status === 401) { fsAuthError(401); return }
|
|
1315
|
-
if (r.status !== 201) throw new Error(r.json.error || ('HTTP ' + r.status))
|
|
1316
1400
|
hideFsProgress()
|
|
1401
|
+
setFsButtons(false)
|
|
1317
1402
|
state.fs.upload = null
|
|
1318
|
-
toast(`已上传 ${
|
|
1403
|
+
toast(`已上传 ${up.name}(SHA-256 已校验${wasResumed ? ' · 断点续传' : ''})`, 'ok')
|
|
1319
1404
|
loadFs()
|
|
1320
1405
|
return
|
|
1321
1406
|
}
|
|
1407
|
+
if (r.status === 409 && r.json.error === 'conflict') {
|
|
1408
|
+
hasher = before // 这段数据没被写入, 回退哈希状态后带 overwrite=1 重发
|
|
1409
|
+
if (!confirm('文件已存在,覆盖它?')) { state.fs.upload = null; hideFsProgress(); setFsButtons(false); return }
|
|
1410
|
+
overwrite = true
|
|
1411
|
+
continue
|
|
1412
|
+
}
|
|
1413
|
+
if (r.status === 409 && r.json.error === 'offset-mismatch') {
|
|
1414
|
+
hasher = before
|
|
1415
|
+
await probe()
|
|
1416
|
+
continue
|
|
1417
|
+
}
|
|
1418
|
+
if (r.status === 422 && r.json.error === 'checksum-mismatch') {
|
|
1419
|
+
const err = new Error('文件校验不一致(SHA-256),已清除坏分片')
|
|
1420
|
+
err.checksum = true
|
|
1421
|
+
throw err
|
|
1422
|
+
}
|
|
1423
|
+
throw new Error(r.json.error || ('HTTP ' + r.status))
|
|
1424
|
+
}
|
|
1322
1425
|
|
|
1426
|
+
// offset 已到文件末尾但还没落位(0 字节文件 / 续传时最后一块已完成而 rename 被中断):
|
|
1427
|
+
// 发一个空 finish 块完成收尾, 同时带上全量 SHA-256 校验
|
|
1428
|
+
if (up.offset >= up.size) {
|
|
1429
|
+
const expected = hasher.hex()
|
|
1430
|
+
const params = { path: up.path, name: up.name, session: up.session, offset: String(up.offset), finish: '1', sha256: expected }
|
|
1431
|
+
if (overwrite) params.overwrite = '1'
|
|
1432
|
+
const r = await uploadChunk(params, new Blob([]))
|
|
1433
|
+
if (r.status === 401) { fsAuthError(401); return }
|
|
1434
|
+
if (r.status === 409 && r.json.error === 'conflict') {
|
|
1435
|
+
if (!confirm('文件已存在,覆盖它?')) { state.fs.upload = null; hideFsProgress(); setFsButtons(false); return }
|
|
1436
|
+
params.overwrite = '1'
|
|
1437
|
+
return runFsUpload(up) // 目标冲突未写入, 重新走 probe + 空 finish
|
|
1438
|
+
}
|
|
1439
|
+
if (r.status === 422 && r.json.error === 'checksum-mismatch') {
|
|
1440
|
+
const err = new Error('文件校验不一致(SHA-256),已清除坏分片')
|
|
1441
|
+
err.checksum = true
|
|
1442
|
+
throw err
|
|
1443
|
+
}
|
|
1444
|
+
if (r.status !== 201) throw new Error(r.json.error || ('HTTP ' + r.status))
|
|
1445
|
+
if (r.json.sha256 && r.json.sha256 !== expected) {
|
|
1446
|
+
const err = new Error('文件校验不一致(SHA-256)'); err.checksum = true
|
|
1447
|
+
throw err
|
|
1448
|
+
}
|
|
1323
1449
|
hideFsProgress()
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
toast(
|
|
1450
|
+
setFsButtons(false)
|
|
1451
|
+
state.fs.upload = null
|
|
1452
|
+
toast(`已上传 ${up.name}(SHA-256 已校验${wasResumed ? ' · 断点续传' : ''})`, 'ok')
|
|
1453
|
+
loadFs()
|
|
1454
|
+
return
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
hideFsProgress()
|
|
1458
|
+
setFsButtons(false)
|
|
1459
|
+
} catch (e) {
|
|
1460
|
+
up.active = false
|
|
1461
|
+
if (up.cancelled || e?.code === 'CANCELLED') return
|
|
1462
|
+
if (e?.code === 'PAUSED') {
|
|
1463
|
+
up.paused = true
|
|
1464
|
+
setFsButtons(true, true)
|
|
1465
|
+
setFsProgressText(`已暂停 · ${Math.round(up.offset / Math.max(1, up.size) * 100)}%`)
|
|
1466
|
+
toast('已暂停,点「继续」接着传', 'ok')
|
|
1467
|
+
return
|
|
1468
|
+
}
|
|
1469
|
+
if (e?.checksum) {
|
|
1470
|
+
// 坏分片保留只会反复校验失败: 服务端删掉, 下一次「继续」从 0 完整重传
|
|
1471
|
+
up.paused = true
|
|
1472
|
+
up.offset = 0
|
|
1473
|
+
try {
|
|
1474
|
+
await fetch(fsApiUrl('/upload-control', { path: up.path, name: up.name, session: up.session, action: 'cancel' }), {
|
|
1475
|
+
method: 'POST', headers: fsHeaders()
|
|
1476
|
+
})
|
|
1477
|
+
} catch {}
|
|
1478
|
+
setFsButtons(true, true)
|
|
1479
|
+
setFsProgressText('校验失败 · 点「继续」重新上传')
|
|
1480
|
+
toast(e.message, 'err')
|
|
1481
|
+
return
|
|
1327
1482
|
}
|
|
1328
|
-
|
|
1483
|
+
hideFsProgress()
|
|
1484
|
+
setFsButtons(false)
|
|
1485
|
+
toast(`上传中断:${e.message}(重选同一文件可断点续传)`, 'err')
|
|
1486
|
+
}
|
|
1329
1487
|
}
|
|
1330
1488
|
|
|
1331
1489
|
function fsUp() {
|
|
@@ -1526,6 +1684,71 @@ function autosize(el) {
|
|
|
1526
1684
|
}
|
|
1527
1685
|
|
|
1528
1686
|
/* ---------------- 初始化 ---------------- */
|
|
1687
|
+
/** 解析 dshremote://pair?token=..&server=.. 配对二维码 */
|
|
1688
|
+
function applyPairUrl(url) {
|
|
1689
|
+
try {
|
|
1690
|
+
const u = new URL(String(url).trim())
|
|
1691
|
+
if (u.protocol !== 'dshremote:' || u.hostname !== 'pair') return false
|
|
1692
|
+
const t = (u.searchParams.get('token') || '').trim()
|
|
1693
|
+
const server = (u.searchParams.get('server') || '').trim().replace(/\/+$/, '')
|
|
1694
|
+
if (!t || !/^https?:\/\//i.test(server)) return false
|
|
1695
|
+
state.token = t
|
|
1696
|
+
LS.set('token', t)
|
|
1697
|
+
state.server = server
|
|
1698
|
+
if (!state.servers.includes(server)) state.servers.unshift(server)
|
|
1699
|
+
saveServers()
|
|
1700
|
+
renderServers()
|
|
1701
|
+
$('token-desc').textContent = '已保存(扫码)'
|
|
1702
|
+
return true
|
|
1703
|
+
} catch {
|
|
1704
|
+
return false
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/** App 内扫码: 调用原生 ML Kit, 扫到 dshremote://pair 后自动配对 */
|
|
1709
|
+
async function scanPair() {
|
|
1710
|
+
if (!CAP?.isNativePlatform?.()) {
|
|
1711
|
+
toast('浏览器请打开主机管理页,用手机相机扫码', 'err')
|
|
1712
|
+
return
|
|
1713
|
+
}
|
|
1714
|
+
const scanner = CAP.Plugins?.BarcodeScanner
|
|
1715
|
+
if (!scanner?.scan) { toast('当前 App 版本不支持扫码,请先更新 App', 'err'); return }
|
|
1716
|
+
try {
|
|
1717
|
+
const supported = await scanner.isSupported?.()
|
|
1718
|
+
if (supported && supported.supported === false) { toast('设备不支持扫码', 'err'); return }
|
|
1719
|
+
const result = await scanner.scan({ formats: ['QR_CODE'], autoZoom: true })
|
|
1720
|
+
const raw = result?.barcodes?.[0]?.rawValue || result?.barcodes?.[0]?.displayValue || ''
|
|
1721
|
+
if (!raw) { toast('没有扫到内容', 'err'); return }
|
|
1722
|
+
if (applyPairUrl(raw)) {
|
|
1723
|
+
toast('配对成功,正在连接', 'ok')
|
|
1724
|
+
openStreams()
|
|
1725
|
+
refreshAll()
|
|
1726
|
+
} else {
|
|
1727
|
+
toast('这不是 DSH Remote 的配对二维码', 'err')
|
|
1728
|
+
}
|
|
1729
|
+
} catch (e) {
|
|
1730
|
+
const msg = String(e?.message || e || '')
|
|
1731
|
+
toast(msg.includes('cancel') || msg.includes('Cancel') ? '已取消扫码' : '扫码失败:' + msg, 'err')
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/** 系统相机/浏览器扫码后通过 dshremote:// 链接唤起 App: 这里接住并配对 */
|
|
1736
|
+
function bindNativeLinks() {
|
|
1737
|
+
if (!CAP?.isNativePlatform?.()) return
|
|
1738
|
+
try {
|
|
1739
|
+
CAP.Plugins?.App?.addListener?.('appUrlOpen', (data) => {
|
|
1740
|
+
if (data?.url && applyPairUrl(data.url)) {
|
|
1741
|
+
toast('已通过二维码配对', 'ok')
|
|
1742
|
+
openStreams()
|
|
1743
|
+
refreshAll()
|
|
1744
|
+
}
|
|
1745
|
+
})
|
|
1746
|
+
CAP.Plugins?.App?.getLaunchUrl?.().then((data) => {
|
|
1747
|
+
if (data?.url) applyPairUrl(data.url)
|
|
1748
|
+
}).catch(() => {})
|
|
1749
|
+
} catch {}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1529
1752
|
function initToken() {
|
|
1530
1753
|
const urlToken = new URLSearchParams(location.search).get('token')
|
|
1531
1754
|
if (urlToken) {
|
|
@@ -1582,6 +1805,7 @@ function bindUi() {
|
|
|
1582
1805
|
$('goal-close').addEventListener('click', () => $('modal-goal').classList.add('hidden'))
|
|
1583
1806
|
$('goal-edit').addEventListener('click', submitGoalEdit)
|
|
1584
1807
|
// 设置
|
|
1808
|
+
$('btn-scan-pair').addEventListener('click', scanPair)
|
|
1585
1809
|
$('btn-change-token').addEventListener('click', () => {
|
|
1586
1810
|
const t = prompt('输入访问令牌(网关启动时打印的 token):', state.token)
|
|
1587
1811
|
if (t && t.trim()) { state.token = t.trim(); LS.set('token', t.trim()); $('token-desc').textContent = '已保存'; toast('已保存,正在重连', 'ok'); openStreams(); refreshAll() }
|
|
@@ -1629,6 +1853,8 @@ function bindUi() {
|
|
|
1629
1853
|
if (f) uploadFsFile(f)
|
|
1630
1854
|
e.target.value = '' // 允许连续选同一个文件
|
|
1631
1855
|
})
|
|
1856
|
+
$('fs-pause-btn').addEventListener('click', pauseFsUpload)
|
|
1857
|
+
$('fs-cancel-btn').addEventListener('click', cancelFsUpload)
|
|
1632
1858
|
bindFsPullRefresh()
|
|
1633
1859
|
|
|
1634
1860
|
bindRail()
|
|
@@ -1671,6 +1897,7 @@ async function boot() {
|
|
|
1671
1897
|
initToken()
|
|
1672
1898
|
bindUi()
|
|
1673
1899
|
bindNativeBack()
|
|
1900
|
+
bindNativeLinks()
|
|
1674
1901
|
applyNativeInsets()
|
|
1675
1902
|
updateConn()
|
|
1676
1903
|
loadLocalVersion()
|
package/public/index.html
CHANGED
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
<div id="fs-progress" class="fs-progress hidden">
|
|
48
48
|
<div class="fs-progress-track"><div id="fs-progress-bar" class="fs-progress-bar"></div></div>
|
|
49
49
|
<div id="fs-progress-text" class="fs-progress-text muted">0%</div>
|
|
50
|
+
<div class="fs-progress-actions">
|
|
51
|
+
<button id="fs-pause-btn" class="mini-btn hidden">暂停</button>
|
|
52
|
+
<button id="fs-cancel-btn" class="mini-btn hidden">取消</button>
|
|
53
|
+
</div>
|
|
50
54
|
</div>
|
|
51
55
|
<div id="fs-pull" class="fs-pull">松开刷新</div>
|
|
52
56
|
<div id="fs-list" class="fs-list"></div>
|
|
@@ -106,6 +110,10 @@
|
|
|
106
110
|
<input id="server-input" type="url" placeholder="http://IP:8787">
|
|
107
111
|
<button id="btn-server-add" class="mini-btn">添加</button>
|
|
108
112
|
</div>
|
|
113
|
+
<div class="setting-row">
|
|
114
|
+
<div><div class="setting-name">扫码连接</div><div class="setting-desc">扫主机管理页的令牌二维码,自动填入地址与令牌</div></div>
|
|
115
|
+
<button id="btn-scan-pair" class="mini-btn">扫码</button>
|
|
116
|
+
</div>
|
|
109
117
|
<div class="setting-row">
|
|
110
118
|
<div><div class="setting-name">通知</div><div class="setting-desc">收到审批/提问时推送</div></div>
|
|
111
119
|
<label class="switch"><input type="checkbox" id="opt-notify"><span class="slider"></span></label>
|
|
@@ -194,6 +202,7 @@
|
|
|
194
202
|
|
|
195
203
|
<div id="toast" class="toast hidden"></div>
|
|
196
204
|
|
|
205
|
+
<script src="sha256.js"></script>
|
|
197
206
|
<script src="app.js"></script>
|
|
198
207
|
</body>
|
|
199
208
|
</html>
|