dsh-crossterm 0.7.1 → 0.7.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/CHANGELOG.md +24 -0
- package/README.md +9 -8
- package/lib/bus.js +5 -1
- package/lib/cloud/home.js +50 -3
- package/lib/cloud/presence.js +71 -0
- package/lib/index.js +42 -15
- package/lib/web.js +27 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# dsh-crossterm 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.7.2 — 2026-10-19
|
|
4
|
+
|
|
5
|
+
### 停用云端老文件夹:设备心跳搬进账号共享目录
|
|
6
|
+
|
|
7
|
+
- 背景(User said, paraphrased):查清云盘老目录 `/dsh云盘/crossterm/bus` 里没有必须迁移的数据后,停用老文件夹。
|
|
8
|
+
- 老目录盘点:17 个目录 / 412 个文件 / 679,619 B,其中 200 个 `.md` 是历史邮件、200 个 `.tmp` 是原子写残留、
|
|
9
|
+
12 个 `json` 是状态镜像;本机 `F:\dsh\001X\crossterm-data\mail` 有 253 个(342,402 B)、`inbox` 306 个(537,410 B),
|
|
10
|
+
云端那份只是子集。唯一可能陌生的是发往本机的 2 封 `pending`,内容是 10-19 00:52 的跨终端验收测试信。
|
|
11
|
+
- 新文件 `lib/cloud/presence.js`:`DEVICE_FILE='_device.json'`、`makePresence()`、`writePresence({pan,layout,info})`
|
|
12
|
+
(写到 `shared/<deviceId>/_device.json`)、`listPresences({pan,layout,onlineMs=3分钟})`(遍历共享目录、按 `at` 倒序、标 `online`)。
|
|
13
|
+
- `lib/cloud/home.js`:新增 `deviceInfo` 参数(实时快照:版本/会话数/链/pid)、`presenceNow()`、`devices()`;
|
|
14
|
+
`status().presence = { at, error, intervalMs }`;`start()` 起 `presenceIntervalMs`(默认 60 s)定时器,启动 5 s 先报一次。
|
|
15
|
+
- `lib/index.js`:`enableCloud()` 里只有 `cfg.busSyncEnabled === true` 才创建老 `createCloud` 镜像;云端目录(home)
|
|
16
|
+
一律启用。`term_cloud` 的 `once` 在镜像停用后明确回「总线镜像已停用」,status 兜底对象带 `design:'cloud-home'` 与 `home`。
|
|
17
|
+
- `lib/bus.js` DEFAULTS:新增 `busSyncEnabled: false`(老镜像默认关闭,需要时在 config.json 里开回来)与 `presenceIntervalMs: 60000`。
|
|
18
|
+
- `lib/web.js`:`/crossterm/api/devices` 合并两份来源 —— 老目录 `listCloudDevices(cloudRoot)`(给 `<0.7.2` 的设备)
|
|
19
|
+
+ 新布局 `listPresences()`(`source: 'layout'`,同 nodeId 时新布局优先);`status.cloud` 增 `design`,
|
|
20
|
+
非镜像模式下 `root` 显示账号目录、`intervalMs` 显示心跳间隔。
|
|
21
|
+
- 探针 `probe-presence.mjs`:16 项全 PASS(心跳字段/落点/两台设备在线判定/排序/跳过无心跳目录/无布局返回 no-layout/
|
|
22
|
+
上传失败抛错/home 集成:`presenceNow`、`status.presence`、`devices`、`start` 后间隔)。
|
|
23
|
+
- 兼容:还跑 `<0.7.2` 的设备仍然读老目录里的邮件与 presence,所以老目录**只停写、不删除**;
|
|
24
|
+
等所有设备升到 0.7.2 之后再整体删掉。
|
|
25
|
+
- 要不要跟着动手:**要** —— 别的机器升级到 0.7.2 后云端设备列表才来自新布局;老目录可在全部升级后删除。
|
|
26
|
+
|
|
3
27
|
## 0.7.1 — 2026-10-19
|
|
4
28
|
|
|
5
29
|
### 「更新」按钮在别的机器上装不上:三级兜底 + 全步骤回执
|
package/README.md
CHANGED
|
@@ -28,14 +28,15 @@ A 终端看不到 B 终端里有哪个会话、在干什么,也没法把一条
|
|
|
28
28
|
> `locatePkgJson()` 对子路径行返回 undefined),而插件页顶部标题与这一行共用同一份 `locale/zh.json` 的 meta,
|
|
29
29
|
> 所以它单独做总开关,标题带「(总开关)」;卡片头用 `shortTitle` 显示短名「跨端互通」。
|
|
30
30
|
|
|
31
|
-
**本版更新(0.7.
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
31
|
+
**本版更新(0.7.2)**
|
|
32
|
+
|
|
33
|
+
- **云端老文件夹停用**:不再把本机总线镜像到 `/dsh云盘/crossterm/bus`(老目录里 12 个 json 是状态镜像、
|
|
34
|
+
200 个 md 是本机邮件的子集、200 个 tmp 是原子写残留,没有必须迁移的数据)。
|
|
35
|
+
- **设备心跳搬进账号共享目录**:`<云端根>/<账号>/shared/<设备>/_device.json`,每 60 s 写一次;
|
|
36
|
+
「云端设备」列表改为新布局优先,老目录只作 `<0.7.2` 设备的兼容来源。
|
|
37
|
+
- 老镜像需要时可在 `config.json` 里把 `busSyncEnabled` 设 true 开回来;`term_cloud` 的 `once` 在镜像停用时给出说明。
|
|
38
|
+
- 老目录**只停写、不删除**:还跑 `<0.7.2` 的设备仍从那里读邮件与 presence,等全部升级后再整体删掉。
|
|
39
|
+
- 要不要跟着动手:**要** —— 别的机器升到 0.7.2 后,云端设备列表才会来自新布局。
|
|
39
40
|
|
|
40
41
|
## 一、安装与卸载
|
|
41
42
|
|
package/lib/bus.js
CHANGED
|
@@ -22,7 +22,7 @@ import os from 'node:os'
|
|
|
22
22
|
import path from 'node:path'
|
|
23
23
|
import crypto from 'node:crypto'
|
|
24
24
|
|
|
25
|
-
export const VERSION = '0.7.
|
|
25
|
+
export const VERSION = '0.7.2'
|
|
26
26
|
|
|
27
27
|
/** DSH 的家目录:与宿主其它状态(sessions / profiles)同一个根,多终端天然共享 */
|
|
28
28
|
export function dshHome() {
|
|
@@ -61,6 +61,9 @@ const DEFAULTS = {
|
|
|
61
61
|
inboxRoot: '',
|
|
62
62
|
// 云总线(百度网盘)同步:默认关闭,开启后插件自己按 cloudIntervalMs 往返镜像。
|
|
63
63
|
cloudEnabled: false,
|
|
64
|
+
// 0.7.2 起老的总线镜像(cloudRoot 那一套)默认不开,云端只用「账号/设备/共享收件箱」目录。
|
|
65
|
+
// 还跑 <0.7.2 的设备仍会把状态写进 cloudRoot,本机用只读方式兼容它。
|
|
66
|
+
busSyncEnabled: false,
|
|
64
67
|
cloudRoot: '/dsh云盘/crossterm/bus',
|
|
65
68
|
cloudIntervalMs: 4000,
|
|
66
69
|
// 云端目录(0.3.39 起):<cloudHome>/<账号>/<设备>/ + <账号>/shared/<设备>/inbox
|
|
@@ -68,6 +71,7 @@ const DEFAULTS = {
|
|
|
68
71
|
cloudAccount: '',
|
|
69
72
|
relayIntervalMs: 20000,
|
|
70
73
|
backupIntervalMs: 300000,
|
|
74
|
+
presenceIntervalMs: 60000,
|
|
71
75
|
backupSessionsPerRun: 20,
|
|
72
76
|
backupAttachmentsPerRun: 20,
|
|
73
77
|
attachmentBackupEnabled: true,
|
package/lib/cloud/home.js
CHANGED
|
@@ -4,19 +4,21 @@ import fs from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { ensureLayout, readLayoutCache, DEFAULT_HOME } from './layout.js'
|
|
6
6
|
import { createRelay } from './relay.js'
|
|
7
|
+
import { writePresence, listPresences } from './presence.js'
|
|
7
8
|
import { backupSessions, backupAttachments, backupSummary } from './session-backup.js'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @param {object} opts
|
|
11
12
|
* @param {object} opts.cfg 插件配置(readConfig 结果)
|
|
12
13
|
* @param {object} opts.device { nodeId, name, version, platform, sessions }
|
|
14
|
+
* @param {Function} [opts.deviceInfo] 返回当前设备快照(版本/会话数/链),心跳用;不传就用 device
|
|
13
15
|
* @param {object} opts.pan 云盘客户端
|
|
14
16
|
* @param {string} opts.busDir 总线根(<DSH_HOME>/crossterm)
|
|
15
17
|
* @param {Function} opts.deliver async ({ to, envelope, files }) => { ok, sessionId, reason }
|
|
16
18
|
* @param {Function} [opts.names] 返回 { <会话短号>: { name } },给 _session.json 补显示名
|
|
17
19
|
* @param {Function} [opts.log]
|
|
18
20
|
*/
|
|
19
|
-
export function createCloudHome({ cfg, device, pan, busDir, deliver, names, log = () => {} }) {
|
|
21
|
+
export function createCloudHome({ cfg, device, deviceInfo, pan, busDir, deliver, names, log = () => {} }) {
|
|
20
22
|
const home = path.dirname(busDir)
|
|
21
23
|
const root = path.join(busDir, 'cloud')
|
|
22
24
|
fs.mkdirSync(root, { recursive: true })
|
|
@@ -33,6 +35,9 @@ export function createCloudHome({ cfg, device, pan, busDir, deliver, names, log
|
|
|
33
35
|
let backupBusy = null
|
|
34
36
|
let relayTimer = 0
|
|
35
37
|
let backupTimer = 0
|
|
38
|
+
let presenceTimer = 0
|
|
39
|
+
let presenceAt = 0
|
|
40
|
+
let presenceError = ''
|
|
36
41
|
|
|
37
42
|
const relay = createRelay({
|
|
38
43
|
pan,
|
|
@@ -114,6 +119,44 @@ export function createCloudHome({ cfg, device, pan, busDir, deliver, names, log
|
|
|
114
119
|
return relay.sendEnvelope(opts)
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
/** 把本机状态写进共享目录(0.7.2:取代云端老目录的 presence 镜像) */
|
|
123
|
+
async function presenceNow() {
|
|
124
|
+
try {
|
|
125
|
+
const layout = await getLayout(false)
|
|
126
|
+
if (!layout || !layout.paths) return { ok: false, reason: 'no-layout' }
|
|
127
|
+
const extra = typeof deviceInfo === 'function' ? deviceInfo() || {} : {}
|
|
128
|
+
const ret = await writePresence({
|
|
129
|
+
pan,
|
|
130
|
+
layout,
|
|
131
|
+
info: {
|
|
132
|
+
nodeId: device.nodeId,
|
|
133
|
+
name: extra.name || device.name,
|
|
134
|
+
hostname: extra.hostname || device.hostname || '',
|
|
135
|
+
version: extra.version || device.version,
|
|
136
|
+
platform: extra.platform || device.platform,
|
|
137
|
+
pid: extra.pid || device.pid || 0,
|
|
138
|
+
sessions: extra.sessions ?? device.sessions ?? 0,
|
|
139
|
+
chains: extra.chains || [],
|
|
140
|
+
startedAt: extra.startedAt || 0,
|
|
141
|
+
},
|
|
142
|
+
log: (line) => log(line),
|
|
143
|
+
})
|
|
144
|
+
presenceAt = Date.now()
|
|
145
|
+
presenceError = ret.ok ? '' : ret.reason || 'unknown'
|
|
146
|
+
return ret
|
|
147
|
+
} catch (e) {
|
|
148
|
+
presenceError = (e && e.message) || String(e)
|
|
149
|
+
return { ok: false, reason: presenceError }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 读云端所有设备心跳(新布局;不含老目录) */
|
|
154
|
+
async function devices() {
|
|
155
|
+
const layout = await getLayout(false)
|
|
156
|
+
if (!layout || !layout.paths) return { ok: false, devices: [] }
|
|
157
|
+
return listPresences({ pan, layout, log: (line) => log(line) })
|
|
158
|
+
}
|
|
159
|
+
|
|
117
160
|
function status() {
|
|
118
161
|
const layout = readLayoutCache(cacheFile)
|
|
119
162
|
return {
|
|
@@ -132,6 +175,7 @@ export function createCloudHome({ cfg, device, pan, busDir, deliver, names, log
|
|
|
132
175
|
: null,
|
|
133
176
|
relay: { ...relay.stats(), intervalMs: relayTimer },
|
|
134
177
|
backup: { ...backupSummary(backupState), intervalMs: backupTimer },
|
|
178
|
+
presence: { at: presenceAt, error: presenceError, intervalMs: presenceTimer },
|
|
135
179
|
roots: { sessions: sessionsRoot, attachments: attachmentsRoot },
|
|
136
180
|
}
|
|
137
181
|
}
|
|
@@ -141,14 +185,17 @@ export function createCloudHome({ cfg, device, pan, busDir, deliver, names, log
|
|
|
141
185
|
started = true
|
|
142
186
|
relayTimer = Number(cfg.relayIntervalMs || 20000)
|
|
143
187
|
backupTimer = Number(cfg.backupIntervalMs || 300000)
|
|
188
|
+
presenceTimer = Number(cfg.presenceIntervalMs || 60000)
|
|
144
189
|
void getLayout(false).catch(() => {})
|
|
145
190
|
timers.push(setInterval(() => { void relayNow().catch(() => {}) }, relayTimer))
|
|
146
191
|
timers.push(setInterval(() => { void backupNow().catch(() => {}) }, backupTimer))
|
|
192
|
+
timers.push(setInterval(() => { void presenceNow().catch(() => {}) }, presenceTimer))
|
|
147
193
|
for (const t of timers) if (t && typeof t.unref === 'function') t.unref()
|
|
148
194
|
// 启动 8 秒后先跑一轮收信、30 秒后跑第一轮备份(别和宿主启动抢资源)
|
|
149
195
|
setTimeout(() => { void relayNow().catch(() => {}) }, 8000)
|
|
150
196
|
setTimeout(() => { void backupNow().catch(() => {}) }, 30000)
|
|
151
|
-
|
|
197
|
+
setTimeout(() => { void presenceNow().catch(() => {}) }, 5000)
|
|
198
|
+
log(`云端目录就绪:relay ${relayTimer}ms / backup ${backupTimer}ms / presence ${presenceTimer}ms(根 ${cfg.cloudHome || DEFAULT_HOME})`)
|
|
152
199
|
}
|
|
153
200
|
|
|
154
201
|
function stop() {
|
|
@@ -157,5 +204,5 @@ export function createCloudHome({ cfg, device, pan, busDir, deliver, names, log
|
|
|
157
204
|
timers = []
|
|
158
205
|
}
|
|
159
206
|
|
|
160
|
-
return { start, stop, status, getLayout, backupNow, relayNow, send, cacheFile, relayDir }
|
|
207
|
+
return { start, stop, status, getLayout, backupNow, relayNow, send, presenceNow, devices, cacheFile, relayDir }
|
|
161
208
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// 新布局下的设备心跳:<accountDir>/shared/<deviceId>/_device.json
|
|
2
|
+
//
|
|
3
|
+
// 为什么要它:0.7.2 起不再把本地总线镜像到云端老目录(/dsh云盘/crossterm/bus),
|
|
4
|
+
// 但「云端设备」列表得继续有数据 —— 于是每台设备把自己的状态写进账号共享目录。
|
|
5
|
+
// 老目录仍在读(给还跑旧版本的设备留兼容),只是不再写。
|
|
6
|
+
|
|
7
|
+
export const DEVICE_FILE = '_device.json'
|
|
8
|
+
|
|
9
|
+
/** 组装一份心跳内容(全字段可 JSON 序列化) */
|
|
10
|
+
export function makePresence({
|
|
11
|
+
nodeId = '',
|
|
12
|
+
name = '',
|
|
13
|
+
hostname = '',
|
|
14
|
+
version = '',
|
|
15
|
+
platform = '',
|
|
16
|
+
pid = 0,
|
|
17
|
+
sessions = 0,
|
|
18
|
+
chains = [],
|
|
19
|
+
startedAt = 0,
|
|
20
|
+
at = Date.now(),
|
|
21
|
+
} = {}) {
|
|
22
|
+
return {
|
|
23
|
+
nodeId,
|
|
24
|
+
name,
|
|
25
|
+
hostname,
|
|
26
|
+
version,
|
|
27
|
+
platform,
|
|
28
|
+
pid,
|
|
29
|
+
sessions,
|
|
30
|
+
chains: Array.isArray(chains) ? chains : [],
|
|
31
|
+
startedAt,
|
|
32
|
+
at,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 把心跳写进共享目录(覆盖自己那份) */
|
|
37
|
+
export async function writePresence({ pan, layout, info, log = () => {} }) {
|
|
38
|
+
const paths = layout && layout.paths
|
|
39
|
+
if (!paths || !paths.deviceRoot) return { ok: false, reason: 'no-layout' }
|
|
40
|
+
const remote = `${paths.deviceRoot}/${DEVICE_FILE}`
|
|
41
|
+
const payload = makePresence(info)
|
|
42
|
+
const buf = Buffer.from(JSON.stringify(payload, null, 2) + '\n', 'utf8')
|
|
43
|
+
await pan.putBuffer(buf, remote)
|
|
44
|
+
log(`presence: 已上报 ${payload.name || payload.nodeId} v${payload.version} → ${remote}`)
|
|
45
|
+
return { ok: true, remote, payload }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 列出所有设备的共享目录,逐个取 _device.json */
|
|
49
|
+
export async function listPresences({ pan, layout, onlineMs = 3 * 60 * 1000, now = Date.now(), log = () => {} }) {
|
|
50
|
+
const paths = layout && layout.paths
|
|
51
|
+
if (!paths || !paths.sharedDir) return { ok: false, devices: [], files: 0, reason: 'no-layout' }
|
|
52
|
+
const list = await pan.lsAll(paths.sharedDir)
|
|
53
|
+
const entries = (list && list.list) || []
|
|
54
|
+
const devices = []
|
|
55
|
+
let files = 0
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (entry.isdir !== 1) continue
|
|
58
|
+
const remote = `${paths.sharedDir}/${entry.server_filename}/${DEVICE_FILE}`
|
|
59
|
+
try {
|
|
60
|
+
const got = await pan.getBuffer(remote)
|
|
61
|
+
const info = JSON.parse(got.buffer.toString('utf8'))
|
|
62
|
+
devices.push({ ...info, online: now - Number(info.at || 0) < onlineMs })
|
|
63
|
+
files += 1
|
|
64
|
+
} catch {
|
|
65
|
+
// 目录里还没心跳(或刚建好),跳过
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
devices.sort((a, b) => Number(b.at || 0) - Number(a.at || 0))
|
|
69
|
+
log(`presence: 共享目录里读到 ${devices.length} 台设备(文件 ${files})`)
|
|
70
|
+
return { ok: true, devices, files, tmpFiles: 0 }
|
|
71
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1205,27 +1205,30 @@ class CrossTerm {
|
|
|
1205
1205
|
}
|
|
1206
1206
|
}
|
|
1207
1207
|
|
|
1208
|
-
/**
|
|
1208
|
+
/** 启动云端:0.7.2 起默认只跑「云端目录」(账号/设备/共享收件箱/中转站/备份),
|
|
1209
|
+
* 老的总线镜像(cloudRoot=/dsh云盘/crossterm/bus)需要 busSyncEnabled=true 才跑 */
|
|
1209
1210
|
enableCloud() {
|
|
1210
|
-
if (this.cloud) return this.cloud
|
|
1211
1211
|
const pan = createPan({
|
|
1212
1212
|
appFile: this.cfg.baiduAppFile,
|
|
1213
1213
|
authFile: this.cfg.baiduAuthFile,
|
|
1214
1214
|
log: (m) => void this.note('cloud: ' + m),
|
|
1215
1215
|
})
|
|
1216
|
-
this.cloud
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1216
|
+
if (this.cfg.busSyncEnabled && !this.cloud) {
|
|
1217
|
+
this.cloud = createCloud({
|
|
1218
|
+
busDir: this.bus.root,
|
|
1219
|
+
mailDir: this.cfg.mailDir,
|
|
1220
|
+
inboxDir: this.cfg.inboxRoot,
|
|
1221
|
+
cloudRoot: this.cfg.cloudRoot,
|
|
1222
|
+
stateFile: this.cfg.cloudStateFile,
|
|
1223
|
+
intervalMs: this.cfg.cloudIntervalMs,
|
|
1224
|
+
pan,
|
|
1225
|
+
log: (m) => void this.note('cloud: ' + m),
|
|
1226
|
+
})
|
|
1227
|
+
this.cloud.start()
|
|
1228
|
+
void this.note(`总线镜像启动 → ${this.cfg.cloudRoot}(${this.cfg.cloudIntervalMs}ms)`)
|
|
1229
|
+
}
|
|
1228
1230
|
this.enableCloudHome(pan)
|
|
1231
|
+
this.pan = pan
|
|
1229
1232
|
return this.cloud
|
|
1230
1233
|
}
|
|
1231
1234
|
|
|
@@ -1246,6 +1249,17 @@ class CrossTerm {
|
|
|
1246
1249
|
busDir: this.bus.root,
|
|
1247
1250
|
deliver: (opts) => this.relayDeliver(opts),
|
|
1248
1251
|
names: () => this.sessionNames(),
|
|
1252
|
+
deviceInfo: () => ({
|
|
1253
|
+
nodeId: this.nodeId,
|
|
1254
|
+
name: this.cfg.name,
|
|
1255
|
+
hostname: os.hostname(),
|
|
1256
|
+
version: VERSION,
|
|
1257
|
+
platform: process.platform,
|
|
1258
|
+
pid: process.pid,
|
|
1259
|
+
sessions: this.sessions().length,
|
|
1260
|
+
chains: [...new Set([...this.links.values()].map((l) => l.code))],
|
|
1261
|
+
startedAt: this.startedAt || 0,
|
|
1262
|
+
}),
|
|
1249
1263
|
log: (m) => void this.note('cloud: ' + m),
|
|
1250
1264
|
})
|
|
1251
1265
|
this.home.start()
|
|
@@ -1803,10 +1817,23 @@ function buildTools(term, agent, part = 'all') {
|
|
|
1803
1817
|
}
|
|
1804
1818
|
else if (action === 'once') {
|
|
1805
1819
|
const c = term.cloud || term.enableCloud()
|
|
1820
|
+
if (!c) return { ok: false, detail: '总线镜像已停用(0.7.2 起默认只跑云端目录:账号/设备/共享收件箱),改用 action="home" 或 "relay"/"backup"' }
|
|
1806
1821
|
const result = await c.once()
|
|
1807
1822
|
return { ok: true, status: c.status(), result }
|
|
1808
1823
|
}
|
|
1809
|
-
const status = term.cloud
|
|
1824
|
+
const status = term.cloud
|
|
1825
|
+
? term.cloud.status()
|
|
1826
|
+
: {
|
|
1827
|
+
running: Boolean(term.home),
|
|
1828
|
+
design: term.home ? 'cloud-home' : 'off',
|
|
1829
|
+
intervalMs: term.cfg.cloudIntervalMs,
|
|
1830
|
+
cloudRoot: term.home ? term.cfg.cloudHome || 'dsh' : '',
|
|
1831
|
+
cycles: 0,
|
|
1832
|
+
up: 0,
|
|
1833
|
+
down: 0,
|
|
1834
|
+
merged: 0,
|
|
1835
|
+
home: term.home ? term.home.status() : null,
|
|
1836
|
+
}
|
|
1810
1837
|
return { ok: true, status }
|
|
1811
1838
|
} catch (e) {
|
|
1812
1839
|
return { ok: false, detail: (e && e.message) || String(e) }
|
package/lib/web.js
CHANGED
|
@@ -8,6 +8,7 @@ import { spawn } from 'node:child_process'
|
|
|
8
8
|
import { VERSION, readConfig, writeConfig } from './bus.js'
|
|
9
9
|
import { createPan } from './cloud/pan.js'
|
|
10
10
|
import { listCloudDevices, localDevice } from './cloud/devices.js'
|
|
11
|
+
import { listPresences } from './cloud/presence.js'
|
|
11
12
|
import { runSetup } from './cloud/setup.js'
|
|
12
13
|
|
|
13
14
|
const PKG = 'dsh-crossterm'
|
|
@@ -344,7 +345,10 @@ function statusOf(term) {
|
|
|
344
345
|
/* ignore */
|
|
345
346
|
}
|
|
346
347
|
const cloud = term.cloud
|
|
348
|
+
const home = term.home
|
|
349
|
+
const homeStatus = home ? home.status() : null
|
|
347
350
|
const provider = cloudProvider(cfg)
|
|
351
|
+
const busMirror = cfg.busSyncEnabled === true
|
|
348
352
|
return {
|
|
349
353
|
ok: true,
|
|
350
354
|
version: VERSION,
|
|
@@ -358,14 +362,19 @@ function statusOf(term) {
|
|
|
358
362
|
// 没配云盘时不要把默认路径当成「已连接的云端」显示出来
|
|
359
363
|
configured: provider.configured,
|
|
360
364
|
enabled: cfg.cloudEnabled === true && provider.configured,
|
|
361
|
-
running: provider.configured && Boolean(cloud && cloud.running),
|
|
362
|
-
root: provider.configured
|
|
363
|
-
|
|
365
|
+
running: provider.configured && Boolean((cloud && cloud.running) || home),
|
|
366
|
+
root: provider.configured
|
|
367
|
+
? busMirror
|
|
368
|
+
? cfg.cloudRoot || ''
|
|
369
|
+
: (homeStatus && homeStatus.layout && homeStatus.layout.paths && homeStatus.layout.paths.accountDir) || cfg.cloudRoot || ''
|
|
370
|
+
: '',
|
|
371
|
+
intervalMs: busMirror ? cfg.cloudIntervalMs || 0 : (cfg.presenceIntervalMs || 60000),
|
|
372
|
+
design: busMirror ? 'bus+home' : 'home',
|
|
364
373
|
provider,
|
|
365
374
|
},
|
|
366
375
|
chains,
|
|
367
|
-
// 云端目录(0.3.39
|
|
368
|
-
home:
|
|
376
|
+
// 云端目录(0.3.39):账号/角色/设备目录/中转站/会话备份/设备心跳
|
|
377
|
+
home: homeStatus,
|
|
369
378
|
}
|
|
370
379
|
}
|
|
371
380
|
|
|
@@ -482,6 +491,19 @@ export function registerWeb(ctx, term, log) {
|
|
|
482
491
|
log: (m) => log('devices: ' + m),
|
|
483
492
|
})
|
|
484
493
|
const result = await listCloudDevices(pan, cfg.cloudRoot)
|
|
494
|
+
let fresh = { devices: [], files: 0 }
|
|
495
|
+
try {
|
|
496
|
+
const layout = term.home ? await term.home.getLayout(false) : null
|
|
497
|
+
if (layout) fresh = await listPresences({ pan, layout, log: (m) => log('devices: ' + m) })
|
|
498
|
+
} catch (error) {
|
|
499
|
+
log('devices: 新布局读取失败 ' + String((error && error.message) || error))
|
|
500
|
+
}
|
|
501
|
+
const byId = new Map()
|
|
502
|
+
for (const d of result.devices || []) byId.set(d.nodeId, d)
|
|
503
|
+
for (const d of fresh.devices || []) byId.set(d.nodeId, { ...byId.get(d.nodeId), ...d, source: 'layout' })
|
|
504
|
+
result.devices = [...byId.values()].sort((a, b) => Number(b.lastSeen ? Date.parse(b.lastSeen) : b.at || 0) - Number(a.lastSeen ? Date.parse(a.lastSeen) : a.at || 0))
|
|
505
|
+
delete result.tmpFiles
|
|
506
|
+
result.files = (result.files || 0) + (fresh.files || 0)
|
|
485
507
|
const me = await localDevice(path.join(cfg.busDir || '', 'presence'), info.nodeId || '')
|
|
486
508
|
if (me && !result.devices.some((d) => d.nodeId === me.nodeId)) result.devices.unshift(me)
|
|
487
509
|
json(res, 200, { ...result, configured: true, me: info.nodeId || '' })
|