dsh-remote-plugin 0.6.7 → 0.6.8
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.en.md +31 -25
- package/README.md +30 -24
- package/apk/dsh-remote.apk +0 -0
- package/client.js +11 -9
- package/package.json +1 -1
- package/public/admin.html +103 -7
- package/public/admin.js +24 -0
- package/public/announcements.json +12 -0
- package/public/app.js +445 -32
- package/public/desktop/desktop.css +126 -19
- package/public/desktop/desktop.html +63 -14
- package/public/desktop/desktop.js +151 -21
- package/public/index.html +145 -41
- package/public/plugin.html +151 -0
- package/public/plugin.js +179 -0
- package/public/styles.css +176 -15
- package/public/theme-vars.css +9 -0
- package/public/update.json +8 -4
- package/public/version.json +1 -1
|
@@ -52,6 +52,7 @@ const state = {
|
|
|
52
52
|
sessionSort: LS.get('sessionSort', 'time') === 'workspace' ? 'workspace' : 'time',
|
|
53
53
|
byId: new Map(),
|
|
54
54
|
current: null,
|
|
55
|
+
hostInfo: null,
|
|
55
56
|
history: { seqs: new Set(), visible: [], hasMore: false, loading: false, minSeq: Infinity },
|
|
56
57
|
approvals: [],
|
|
57
58
|
questions: [],
|
|
@@ -1055,7 +1056,7 @@ function onHostFrame(full) {
|
|
|
1055
1056
|
if (['host/session-added', 'host/session-removed', 'host/workspace-changed', 'host/workspace-removed', 'host/workspace-order-changed', 'host/archived-sessions-changed'].includes(f.type)) refreshSessions()
|
|
1056
1057
|
if (f.type === 'host/session-status') {
|
|
1057
1058
|
const s = state.byId.get(f.sessionId)
|
|
1058
|
-
if (s) { s.running = f.running; if (state.current === f.sessionId) renderSessions() }
|
|
1059
|
+
if (s) { s.running = f.running; if (state.current === f.sessionId) renderSessions(); renderOverviewDesktop() }
|
|
1059
1060
|
}
|
|
1060
1061
|
}
|
|
1061
1062
|
function applyProjection(sessionId, key, value, seq) {
|
|
@@ -1100,11 +1101,12 @@ function onSessionEvent(sessionId, event) {
|
|
|
1100
1101
|
/* ---------------- 会话 ---------------- */
|
|
1101
1102
|
async function refreshSessions() {
|
|
1102
1103
|
const v = await safeRpc('session.list', {}, '')
|
|
1103
|
-
if (!v) { renderSessions(); return }
|
|
1104
|
+
if (!v) { renderSessions(); renderOverviewDesktop(); return }
|
|
1104
1105
|
state.sessions = v.items || []
|
|
1105
1106
|
state.byId = new Map(state.sessions.map(s => [s.sessionId, s]))
|
|
1106
1107
|
renderSessions()
|
|
1107
1108
|
scheduleWorkbenchRefresh()
|
|
1109
|
+
renderOverviewDesktop()
|
|
1108
1110
|
}
|
|
1109
1111
|
function sessionCwd(s) { return typeof s?.cwd === 'string' ? s.cwd.trim() : '' }
|
|
1110
1112
|
function sessionWorkspaceLabel(s) {
|
|
@@ -1135,8 +1137,12 @@ function renderSessions() {
|
|
|
1135
1137
|
const wbIds = new Set()
|
|
1136
1138
|
if (state.wb.bound && state.wb.projects) for (const w of state.wb.projects) for (const id of (w.sessionIds || [])) wbIds.add(id)
|
|
1137
1139
|
const root = state.wb.bound ? state.wb.path : ''
|
|
1138
|
-
const visible = allItems.filter(s => !(state.wb.bound && (wbIds.has(s.sessionId) || wbStrictInside(s.cwd, root))))
|
|
1139
1140
|
const archivedSet = new Set(state.archivedIds || [])
|
|
1141
|
+
const visible = allItems.filter(s => {
|
|
1142
|
+
if (!state.wb.bound) return true
|
|
1143
|
+
if (archivedSet.has(s.sessionId)) return true
|
|
1144
|
+
return !(wbIds.has(s.sessionId) || wbStrictInside(s.cwd, root))
|
|
1145
|
+
})
|
|
1140
1146
|
const archived = visible.filter(s => archivedSet.has(s.sessionId))
|
|
1141
1147
|
const main = visible.filter(s => !archivedSet.has(s.sessionId))
|
|
1142
1148
|
const showArchived = LS.get('dsShowArchivedV1', '0') === '1'
|
|
@@ -1257,7 +1263,7 @@ function eventHtml(entry) {
|
|
|
1257
1263
|
const sysText = type === 'user/message' ? systemReminderText(blocks) : ''
|
|
1258
1264
|
if (sysText) {
|
|
1259
1265
|
const shown = sysText.length > 400 ? sysText.slice(0, 400) + '…' : sysText
|
|
1260
|
-
return `<details class="event ds-tool"><summary>${esc(t('ds.eventSystemReminder'))}</summary><pre>${esc(shown)}</pre></details>`
|
|
1266
|
+
return `<details class="event ds-tool ds-event-detail"><summary>${esc(t('ds.eventSystemReminder'))}</summary><pre>${esc(shown)}</pre></details>`
|
|
1261
1267
|
}
|
|
1262
1268
|
const text = blocks.map(blockHtml).join('')
|
|
1263
1269
|
return `<div class="ds-msg ${esc(role)}"><div class="role">${esc(role === 'user' ? t('ds.role.me') : t('ds.role.dsh'))}</div>${text || '<span style="opacity:.6">…</span>'}</div>`
|
|
@@ -1457,6 +1463,7 @@ function renderNotifStack() {
|
|
|
1457
1463
|
stack.querySelectorAll('.ds-notif-card').forEach(card => card.addEventListener('keydown', (e) => {
|
|
1458
1464
|
if (e.key === 'Escape') toast(t('ds.ignored'), 'ok')
|
|
1459
1465
|
}))
|
|
1466
|
+
renderOverviewDesktop()
|
|
1460
1467
|
}
|
|
1461
1468
|
async function approveApproval(id, allow) {
|
|
1462
1469
|
const a = state.approvals.find(x => x.approvalId === id)
|
|
@@ -1594,7 +1601,7 @@ async function loadFs(dir, silent) {
|
|
|
1594
1601
|
$('fs-path').textContent = data.path
|
|
1595
1602
|
$('fs-list').innerHTML = (data.entries || []).map(e => `
|
|
1596
1603
|
<div class="ds-fs-row" data-fs-path="${esc(e.path)}" data-fs-dir="${e.type === 'dir' ? '1' : '0'}">
|
|
1597
|
-
<span>${e.type === 'dir'
|
|
1604
|
+
<span class="ds-fs-type">${desktopFsIconSvg(e.type === 'dir')}</span>
|
|
1598
1605
|
<span class="ds-fs-name">${esc(e.name)}</span>
|
|
1599
1606
|
<span class="ds-fs-size">${e.type === 'dir' ? '' : fmtSize(e.size)}</span>
|
|
1600
1607
|
</div>`).join('') || `<div class="ds-empty">${t('ds.fsEmpty')}</div>`
|
|
@@ -1607,6 +1614,12 @@ async function loadFs(dir, silent) {
|
|
|
1607
1614
|
$('fs-list').innerHTML = `<div class="ds-empty">${esc(e.message || t('ds.toastConnFailed'))}</div>`
|
|
1608
1615
|
}
|
|
1609
1616
|
}
|
|
1617
|
+
|
|
1618
|
+
function desktopFsIconSvg(isDir) {
|
|
1619
|
+
return isDir
|
|
1620
|
+
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 6.5h6l2 2H20a1 1 0 0 1 1 1v8.5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7.5a1 1 0 0 1 .5-1Z"/></svg>'
|
|
1621
|
+
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 3.5h8l4 4V20a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4.5a1 1 0 0 1 1-1Z"/><path d="M14 3.5v4h4M8 13h8M8 16h6"/></svg>'
|
|
1622
|
+
}
|
|
1610
1623
|
function fsUp() {
|
|
1611
1624
|
if (state.fs.path && state.fs.initial && state.fs.path !== state.fs.initial) {
|
|
1612
1625
|
loadFs(fsParent(state.fs.path))
|
|
@@ -1684,15 +1697,21 @@ async function refreshWorkbench({ silent = false } = {}) {
|
|
|
1684
1697
|
const listRes = await fetch(fsApiUrl('/list', { path: state.wb.path }), { headers: fsHeaders() })
|
|
1685
1698
|
if (listRes.ok) {
|
|
1686
1699
|
const listData = await listRes.json().catch(() => ({}))
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
if (
|
|
1695
|
-
|
|
1700
|
+
if (Array.isArray(listData.entries)) {
|
|
1701
|
+
const diskDirs = new Set(listData.entries.filter(e => e.type === 'dir').map(e => wbPathKey(wbJoin(state.wb.path, e.name))))
|
|
1702
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
1703
|
+
if (!diskDirs.has(wbPathKey(items[i].path))) items.splice(i, 1)
|
|
1704
|
+
}
|
|
1705
|
+
const have = new Set(items.map(w => wbPathKey(w.path)))
|
|
1706
|
+
for (const entry of listData.entries) {
|
|
1707
|
+
if (entry.type !== 'dir') continue
|
|
1708
|
+
const projectPath = wbJoin(state.wb.path, entry.name)
|
|
1709
|
+
if (have.has(wbPathKey(projectPath))) continue
|
|
1710
|
+
try {
|
|
1711
|
+
const created = await rpc('workspace.create', { path: projectPath })
|
|
1712
|
+
if (created?.workspace) { items.push(created.workspace); have.add(wbPathKey(projectPath)) }
|
|
1713
|
+
} catch {}
|
|
1714
|
+
}
|
|
1696
1715
|
}
|
|
1697
1716
|
}
|
|
1698
1717
|
} catch {}
|
|
@@ -1723,10 +1742,11 @@ function renderWorkbench() {
|
|
|
1723
1742
|
panel.classList.toggle('hidden', !state.wb.expanded)
|
|
1724
1743
|
if (!state.wb.expanded) return
|
|
1725
1744
|
const projects = state.wb.projects || []
|
|
1745
|
+
const archivedSet = new Set(state.archivedIds || [])
|
|
1726
1746
|
let html = `<div class="ds-wb-panel-title">${esc(t('wb.projects'))}</div>`
|
|
1727
1747
|
html += projects.length ? projects.map(w => {
|
|
1728
1748
|
const id = String(w.workspaceId || '')
|
|
1729
|
-
const sessions = (w.sessionIds || []).map(sid => state.byId.get(sid)).filter(Boolean).sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))
|
|
1749
|
+
const sessions = (w.sessionIds || []).map(sid => state.byId.get(sid)).filter(Boolean).filter(s => !archivedSet.has(s.sessionId)).sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))
|
|
1730
1750
|
const open = state.wb.open === id
|
|
1731
1751
|
return `<div class="ds-wb-project ${open ? 'open' : ''}">
|
|
1732
1752
|
<button type="button" class="ds-wb-project-head" data-wb-head="${esc(id)}">
|
|
@@ -1777,7 +1797,7 @@ async function wbFsLoad(dir) {
|
|
|
1777
1797
|
const dirs = (data.entries || []).filter(e => e.type === 'dir')
|
|
1778
1798
|
box.innerHTML = dirs.length ? dirs.map(e => {
|
|
1779
1799
|
const p = wbJoin(data.path, e.name)
|
|
1780
|
-
return `<div class="ds-wb-fs-row" data-wb-dir="${esc(p)}"><span
|
|
1800
|
+
return `<div class="ds-wb-fs-row" data-wb-dir="${esc(p)}"><span class="ds-fs-type">${desktopFsIconSvg(true)}</span><span class="ds-wb-fs-name">${esc(e.name)}</span><button type="button" class="ds-btn ds-wb-select" data-wb-select="${esc(p)}">${esc(t('wb.selectDir'))}</button></div>`
|
|
1781
1801
|
}).join('') : `<div class="ds-empty">${esc(t('wb.empty'))}</div>`
|
|
1782
1802
|
box.querySelectorAll('[data-wb-dir]').forEach(row => row.addEventListener('click', e => { if (!e.target.closest('[data-wb-select]')) wbFsLoad(row.dataset.wbDir) }))
|
|
1783
1803
|
box.querySelectorAll('[data-wb-select]').forEach(button => button.addEventListener('click', () => bindWorkbench(button.dataset.wbSelect)))
|
|
@@ -1899,13 +1919,98 @@ function renderStats(days) {
|
|
|
1899
1919
|
}
|
|
1900
1920
|
|
|
1901
1921
|
/* ---------------- 视图与连接状态 ---------------- */
|
|
1922
|
+
function renderOverviewDesktop() {
|
|
1923
|
+
const ring = $('ds-overview-pulse-ring')
|
|
1924
|
+
if (!ring) return
|
|
1925
|
+
const checks = {
|
|
1926
|
+
gateway: !!state.token && !!state.server,
|
|
1927
|
+
dsh: !!state.hostInfo,
|
|
1928
|
+
mux: !!state.streamsOk?.mux,
|
|
1929
|
+
host: !!state.streamsOk?.host
|
|
1930
|
+
}
|
|
1931
|
+
const online = Object.values(checks).filter(Boolean).length
|
|
1932
|
+
const status = online === 4 ? 'Nominal' : online > 0 ? 'Degraded' : 'Offline'
|
|
1933
|
+
const pulseCard = document.querySelector('.ds-overview-pulse-card')
|
|
1934
|
+
if (pulseCard) {
|
|
1935
|
+
pulseCard.classList.remove('status-nominal', 'status-degraded', 'status-offline')
|
|
1936
|
+
pulseCard.classList.add('status-' + status.toLowerCase())
|
|
1937
|
+
}
|
|
1938
|
+
ring.style.setProperty('--pulse-pct', `${online / 4 * 100}%`)
|
|
1939
|
+
$('ds-overview-health').textContent = online === 4 ? t('ds.live') : online ? `${online}/4` : t('ds.offlineCore')
|
|
1940
|
+
$('ds-overview-health-caption').textContent = online === 4 ? t('ds.allLinked') : online ? t('ds.components', { n: online }) : t('ds.offlineShort')
|
|
1941
|
+
$('ds-overview-status').textContent = t(`ds.system${status}`)
|
|
1942
|
+
$('ds-overview-status-desc').textContent = t('ds.components', { n: online })
|
|
1943
|
+
for (const [name, ok] of Object.entries(checks)) {
|
|
1944
|
+
const item = document.querySelector(`[data-ds-overview-link="${name}"]`)
|
|
1945
|
+
if (!item) continue
|
|
1946
|
+
item.classList.toggle('ok', ok)
|
|
1947
|
+
item.classList.toggle('off', !ok)
|
|
1948
|
+
const value = item.querySelector('b')
|
|
1949
|
+
if (value) value.textContent = ok ? t('ds.online') : t('ds.offlineShort')
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
const pending = [
|
|
1953
|
+
...state.approvals.map(a => ({ kind: 'approval', item: a })),
|
|
1954
|
+
...state.questions.map(q => ({ kind: 'question', item: q }))
|
|
1955
|
+
]
|
|
1956
|
+
$('ds-overview-attention-count').textContent = pending.length ? t('ds.pendingCount', { n: pending.length }) : '—'
|
|
1957
|
+
$('ds-overview-attention-list').innerHTML = pending.length ? pending.slice(0, 4).map(({ kind, item }) => {
|
|
1958
|
+
const title = titleOf(state.byId.get(item.sessionId))
|
|
1959
|
+
if (kind === 'approval') return `<div class="ds-overview-attention-item" data-ds-overview-approval="${esc(item.approvalId)}">
|
|
1960
|
+
<span class="ds-overview-mark">⌁</span><span class="ds-overview-copy"><span class="ds-overview-item-title">${esc(item.toolName || t('ds.toolDefault'))}</span><span class="ds-overview-item-desc">${esc(item.reason || t('ds.approvalReason', { reason: '' }))} · ${esc(title)}</span></span>
|
|
1961
|
+
<span class="ds-overview-actions"><button class="ds-btn allow" data-ds-overview-approve="1">${t('ds.allow')}</button><button class="ds-btn reject" data-ds-overview-approve="0">${t('ds.reject')}</button></span>
|
|
1962
|
+
</div>`
|
|
1963
|
+
return `<button type="button" class="ds-overview-attention-item question" data-ds-overview-question="${esc(item.rpcId)}">
|
|
1964
|
+
<span class="ds-overview-mark">?</span><span class="ds-overview-copy"><span class="ds-overview-item-title">${esc(item.questions?.[0]?.question || t('ds.questionNotify'))}</span><span class="ds-overview-item-desc">${esc(title)}</span></span><span class="ds-overview-arrow">›</span>
|
|
1965
|
+
</button>`
|
|
1966
|
+
}).join('') : `<div class="ds-overview-empty">${t('ds.nothingPending')}</div>`
|
|
1967
|
+
$('ds-overview-attention-list').querySelectorAll('[data-ds-overview-approve]').forEach(btn => btn.addEventListener('click', () => approveApproval(btn.closest('[data-ds-overview-approval]')?.dataset.dsOverviewApproval || '', btn.dataset.dsOverviewApprove === '1')))
|
|
1968
|
+
$('ds-overview-attention-list').querySelectorAll('[data-ds-overview-question]').forEach(btn => btn.addEventListener('click', () => openQuestionModal(state.questions.find(q => q.rpcId === btn.dataset.dsOverviewQuestion))))
|
|
1969
|
+
|
|
1970
|
+
const running = state.sessions.filter(s => s.running).length
|
|
1971
|
+
const sessions = [...state.sessions].sort((a, b) => Number(b.running) - Number(a.running) || (new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0))).slice(0, 6)
|
|
1972
|
+
const primary = $('ds-overview-primary-action')
|
|
1973
|
+
if (primary) {
|
|
1974
|
+
let action = 'new'
|
|
1975
|
+
let label = t('ds.action.newSession')
|
|
1976
|
+
let sessionId = ''
|
|
1977
|
+
if (!state.token) {
|
|
1978
|
+
action = 'settings'
|
|
1979
|
+
label = t('ds.action.connect')
|
|
1980
|
+
} else if (online > 0 && online < 4) {
|
|
1981
|
+
action = 'refresh'
|
|
1982
|
+
label = t('ds.action.refresh')
|
|
1983
|
+
} else if (pending.length) {
|
|
1984
|
+
action = 'attention'
|
|
1985
|
+
label = t('ds.action.attention')
|
|
1986
|
+
} else if (sessions.length) {
|
|
1987
|
+
action = 'session'
|
|
1988
|
+
sessionId = sessions[0].sessionId
|
|
1989
|
+
label = t('ds.action.openSession')
|
|
1990
|
+
}
|
|
1991
|
+
primary.textContent = label
|
|
1992
|
+
primary.dataset.dsOverviewAction = action
|
|
1993
|
+
primary.dataset.dsOverviewSession = sessionId
|
|
1994
|
+
}
|
|
1995
|
+
$('ds-overview-dsh-version').textContent = state.hostInfo?.version || '—'
|
|
1996
|
+
$('ds-overview-gateway-version').textContent = checks.gateway ? t('ds.online') : t('ds.offlineShort')
|
|
1997
|
+
$('ds-overview-active-sessions').textContent = String(running)
|
|
1998
|
+
$('ds-overview-connection-mode').textContent = state.token ? t(state.streamMode === 'poll' ? 'ds.poll' : 'ds.liveWs') : '—'
|
|
1999
|
+
$('ds-overview-active-count').textContent = running ? t('ds.activeCount', { n: running }) : ''
|
|
2000
|
+
$('ds-overview-session-list').innerHTML = sessions.length ? sessions.map(s => `<button type="button" class="ds-overview-session-item ${s.running ? 'running' : ''}" data-ds-overview-session="${esc(s.sessionId)}">
|
|
2001
|
+
<span class="ds-overview-mark">${s.running ? '●' : '○'}</span><span class="ds-overview-copy"><span class="ds-overview-item-title">${esc(titleOf(s))}</span><span class="ds-overview-item-desc">${s.running ? esc(t('ds.running')) + ' · ' : ''}${esc(fmtTime(s.updatedAt))}</span></span><span class="ds-overview-arrow">›</span>
|
|
2002
|
+
</button>`).join('') : `<div class="ds-overview-empty">${t('ds.noSessions')}</div>`
|
|
2003
|
+
$('ds-overview-session-list').querySelectorAll('[data-ds-overview-session]').forEach(btn => btn.addEventListener('click', () => openSession(btn.dataset.dsOverviewSession)))
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1902
2006
|
function showView(id) {
|
|
1903
2007
|
state.view = id
|
|
1904
|
-
for (const v of ['view-sessions', 'view-chat', 'view-files', 'view-settings']) $(v).classList.toggle('hidden', v !== id)
|
|
2008
|
+
for (const v of ['view-overview', 'view-sessions', 'view-chat', 'view-files', 'view-settings']) $(v).classList.toggle('hidden', v !== id)
|
|
1905
2009
|
document.querySelectorAll('.ds-nav-item').forEach(b => b.classList.toggle('active', b.dataset.view === id))
|
|
1906
|
-
const titles = { 'view-sessions': 'ds.sessions', 'view-chat': 'ds.sessions', 'view-files': 'ds.files', 'view-settings': 'ds.settings' }
|
|
2010
|
+
const titles = { 'view-overview': 'ds.overview', 'view-sessions': 'ds.sessions', 'view-chat': 'ds.sessions', 'view-files': 'ds.files', 'view-settings': 'ds.settings' }
|
|
1907
2011
|
if (id === 'view-chat') { const s = state.byId.get(state.current); $('ds-title').textContent = s ? titleOf(s) : t('ds.sessions') }
|
|
1908
2012
|
else $('ds-title').textContent = t(titles[id])
|
|
2013
|
+
if (id === 'view-overview') renderOverviewDesktop()
|
|
1909
2014
|
if (id === 'view-files' && !state.fs.loaded) loadFs(null, true)
|
|
1910
2015
|
if (id === 'view-settings') showSettingsHome()
|
|
1911
2016
|
}
|
|
@@ -1991,6 +2096,28 @@ function bindUi() {
|
|
|
1991
2096
|
list.style.display = list.style.display === 'none' ? 'flex' : 'none'
|
|
1992
2097
|
})
|
|
1993
2098
|
document.querySelectorAll('.ds-nav-item').forEach(b => b.addEventListener('click', () => showView(b.dataset.view)))
|
|
2099
|
+
$('ds-overview-refresh').addEventListener('click', async () => {
|
|
2100
|
+
toast(t('ds.loading'))
|
|
2101
|
+
if (state.token) {
|
|
2102
|
+
await refreshSessions()
|
|
2103
|
+
const host = await safeRpc('host.describe', {}, '')
|
|
2104
|
+
if (host) state.hostInfo = host
|
|
2105
|
+
}
|
|
2106
|
+
renderOverviewDesktop()
|
|
2107
|
+
})
|
|
2108
|
+
$('ds-overview-primary-action').addEventListener('click', () => {
|
|
2109
|
+
const button = $('ds-overview-primary-action')
|
|
2110
|
+
const action = button.dataset.dsOverviewAction
|
|
2111
|
+
if (action === 'session' && button.dataset.dsOverviewSession) return openSession(button.dataset.dsOverviewSession)
|
|
2112
|
+
if (action === 'new') return $('btn-new-session').click()
|
|
2113
|
+
if (action === 'settings') return showView('view-settings')
|
|
2114
|
+
if (action === 'refresh') return $('ds-overview-refresh').click()
|
|
2115
|
+
const first = document.querySelector('.ds-overview-attention-item')
|
|
2116
|
+
if (first) {
|
|
2117
|
+
first.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
2118
|
+
if (first.matches('button')) first.focus({ preventScroll: true })
|
|
2119
|
+
}
|
|
2120
|
+
})
|
|
1994
2121
|
$('session-list').addEventListener('click', (e) => {
|
|
1995
2122
|
if (e.target.closest('[data-archived-toggle]')) {
|
|
1996
2123
|
LS.set('dsShowArchivedV1', LS.get('dsShowArchivedV1', '0') === '1' ? '0' : '1')
|
|
@@ -2128,7 +2255,7 @@ function bindUi() {
|
|
|
2128
2255
|
$('btn-lang').addEventListener('click', () => {
|
|
2129
2256
|
I18N.setLang(I18N.lang === 'zh' ? 'en' : 'zh')
|
|
2130
2257
|
$('btn-lang').textContent = I18N.lang === 'zh' ? 'EN' : '中文'
|
|
2131
|
-
renderServers(); renderSessions(); updateConn(); themeApply()
|
|
2258
|
+
renderServers(); renderSessions(); renderNotifStack(); renderOverviewDesktop(); updateConn(); themeApply()
|
|
2132
2259
|
})
|
|
2133
2260
|
$('fs-up').addEventListener('click', fsUp)
|
|
2134
2261
|
$('fs-new-workspace').addEventListener('click', openWorkspaceModal)
|
|
@@ -2140,7 +2267,7 @@ function bindUi() {
|
|
|
2140
2267
|
async function start() {
|
|
2141
2268
|
loadServers()
|
|
2142
2269
|
renderServers()
|
|
2143
|
-
showView('view-
|
|
2270
|
+
showView('view-overview')
|
|
2144
2271
|
const urlToken = new URLSearchParams(location.search).get('token')
|
|
2145
2272
|
if (urlToken) { state.token = urlToken; LS.set('token', urlToken); history.replaceState(null, '', location.pathname) }
|
|
2146
2273
|
if (!state.token) {
|
|
@@ -2155,9 +2282,12 @@ async function start() {
|
|
|
2155
2282
|
if (state.token) {
|
|
2156
2283
|
if (state.servers.length) await selectFastestServer({ silent: true, reconnect: false })
|
|
2157
2284
|
openStreams()
|
|
2158
|
-
refreshSessions()
|
|
2285
|
+
await refreshSessions()
|
|
2286
|
+
const host = await safeRpc('host.describe', {}, '')
|
|
2287
|
+
if (host) state.hostInfo = host
|
|
2159
2288
|
refreshWorkbench({ silent: true })
|
|
2160
2289
|
}
|
|
2290
|
+
renderOverviewDesktop()
|
|
2161
2291
|
}
|
|
2162
2292
|
|
|
2163
2293
|
start()
|