dsh-remote-plugin 0.4.8 → 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 +6 -4
- package/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +293 -1
- 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 +313 -18
- 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
|
@@ -50,7 +50,7 @@ const state = {
|
|
|
50
50
|
history: emptyHistory(),
|
|
51
51
|
errCount: 0,
|
|
52
52
|
refreshTimer: null,
|
|
53
|
-
fs: { path: null, initial: null, loaded: false }
|
|
53
|
+
fs: { path: null, initial: null, loaded: false, upload: null }
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const $ = (id) => document.getElementById(id)
|
|
@@ -1219,45 +1219,271 @@ 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
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
+
}
|
|
1317
|
+
|
|
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')
|
|
1239
|
-
showFsProgress(0, 0, file.size)
|
|
1240
1332
|
xhr.upload.onprogress = (e) => {
|
|
1241
|
-
if (e.lengthComputable)
|
|
1333
|
+
if (e.lengthComputable) {
|
|
1334
|
+
const loaded = up.offset + Math.min(e.loaded, e.total)
|
|
1335
|
+
showFsProgress(Math.round(loaded / Math.max(1, up.size) * 100), loaded, up.size)
|
|
1336
|
+
}
|
|
1242
1337
|
}
|
|
1243
1338
|
xhr.onload = () => {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1339
|
+
if (up.xhr === xhr) up.xhr = null
|
|
1340
|
+
let json = {}
|
|
1341
|
+
try { json = JSON.parse(xhr.responseText || '{}') } catch {}
|
|
1342
|
+
resolve({ status: xhr.status, json })
|
|
1343
|
+
}
|
|
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
|
+
}
|
|
1352
|
+
xhr.send(blob)
|
|
1353
|
+
})
|
|
1354
|
+
|
|
1355
|
+
const probe = async () => {
|
|
1356
|
+
const res = await fetch(fsApiUrl('/upload-probe', { path: up.path, name: up.name, session: up.session }), { headers: fsHeaders() })
|
|
1357
|
+
if (res.status === 401) { fsAuthError(401); return null }
|
|
1358
|
+
const json = await res.json().catch(() => ({}))
|
|
1359
|
+
if (json.ok) up.offset = json.partialSize || 0
|
|
1360
|
+
return json
|
|
1361
|
+
}
|
|
1362
|
+
|
|
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
|
|
1373
|
+
}
|
|
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
|
|
1385
|
+
}
|
|
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
|
|
1399
|
+
}
|
|
1400
|
+
hideFsProgress()
|
|
1401
|
+
setFsButtons(false)
|
|
1402
|
+
state.fs.upload = null
|
|
1403
|
+
toast(`已上传 ${up.name}(SHA-256 已校验${wasResumed ? ' · 断点续传' : ''})`, 'ok')
|
|
1247
1404
|
loadFs()
|
|
1248
1405
|
return
|
|
1249
1406
|
}
|
|
1250
|
-
if (
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
+
}
|
|
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
|
+
}
|
|
1449
|
+
hideFsProgress()
|
|
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
|
|
1255
1468
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
|
1482
|
+
}
|
|
1483
|
+
hideFsProgress()
|
|
1484
|
+
setFsButtons(false)
|
|
1485
|
+
toast(`上传中断:${e.message}(重选同一文件可断点续传)`, 'err')
|
|
1259
1486
|
}
|
|
1260
|
-
doUpload(false)
|
|
1261
1487
|
}
|
|
1262
1488
|
|
|
1263
1489
|
function fsUp() {
|
|
@@ -1458,6 +1684,71 @@ function autosize(el) {
|
|
|
1458
1684
|
}
|
|
1459
1685
|
|
|
1460
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
|
+
|
|
1461
1752
|
function initToken() {
|
|
1462
1753
|
const urlToken = new URLSearchParams(location.search).get('token')
|
|
1463
1754
|
if (urlToken) {
|
|
@@ -1514,6 +1805,7 @@ function bindUi() {
|
|
|
1514
1805
|
$('goal-close').addEventListener('click', () => $('modal-goal').classList.add('hidden'))
|
|
1515
1806
|
$('goal-edit').addEventListener('click', submitGoalEdit)
|
|
1516
1807
|
// 设置
|
|
1808
|
+
$('btn-scan-pair').addEventListener('click', scanPair)
|
|
1517
1809
|
$('btn-change-token').addEventListener('click', () => {
|
|
1518
1810
|
const t = prompt('输入访问令牌(网关启动时打印的 token):', state.token)
|
|
1519
1811
|
if (t && t.trim()) { state.token = t.trim(); LS.set('token', t.trim()); $('token-desc').textContent = '已保存'; toast('已保存,正在重连', 'ok'); openStreams(); refreshAll() }
|
|
@@ -1561,6 +1853,8 @@ function bindUi() {
|
|
|
1561
1853
|
if (f) uploadFsFile(f)
|
|
1562
1854
|
e.target.value = '' // 允许连续选同一个文件
|
|
1563
1855
|
})
|
|
1856
|
+
$('fs-pause-btn').addEventListener('click', pauseFsUpload)
|
|
1857
|
+
$('fs-cancel-btn').addEventListener('click', cancelFsUpload)
|
|
1564
1858
|
bindFsPullRefresh()
|
|
1565
1859
|
|
|
1566
1860
|
bindRail()
|
|
@@ -1603,6 +1897,7 @@ async function boot() {
|
|
|
1603
1897
|
initToken()
|
|
1604
1898
|
bindUi()
|
|
1605
1899
|
bindNativeBack()
|
|
1900
|
+
bindNativeLinks()
|
|
1606
1901
|
applyNativeInsets()
|
|
1607
1902
|
updateConn()
|
|
1608
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>
|