${escapeHtml(value([device.manufacturer, device.model].filter(Boolean).join(' ') || device.imei || '未知设备'))}
-
-
+
`
}).join('')
- $$('.instance-card', root).forEach(card => card.addEventListener('click', (event) => {
- if (event.target.closest('button')) return
- selectInstance(card.dataset.id)
- }))
- $$('[data-edit-id]', root).forEach(btn => btn.addEventListener('click', () => openInstanceDialog(btn.dataset.editId)))
- $$('[data-delete-id]', root).forEach(btn => btn.addEventListener('click', () => deleteInstance(btn.dataset.deleteId)))
+ $$('[data-open-detail]', root).forEach(card => {
+ const open = () => openDeviceDetail(card.dataset.openDetail)
+ card.addEventListener('click', open)
+ card.addEventListener('keydown', (event) => {
+ if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); open() }
+ })
+ })
+}
+
+function renderInstances() { renderHomeCards() }
+
+function openDeviceDetail(id) {
+ const item = instances.find(x => x.id === id)
+ if (!item) return
+ activeId = id
+ localStorage.setItem('multi-simadmin-active', id)
+ $('loginBtn').disabled = false
+ $('openExternal').href = item.url
+ $('openExternal').classList.remove('disabled')
+ $('emptyState').hidden = true
+ $('frame').hidden = false
+ $('frame').src = item.url
+ switchView('detail')
+ renderHomeCards()
+ renderOverview()
+ renderDeviceDetails()
+ renderDetailActions()
+}
+
+function setActiveHome() {
+ activeId = null
+ localStorage.removeItem('multi-simadmin-active')
+ $('loginBtn').disabled = true
+ $('openExternal').href = '#'
+ $('openExternal').classList.add('disabled')
+ $('emptyState').hidden = false
+ $('frame').hidden = true
+ $('frame').removeAttribute('src')
+ switchView('home')
+ renderHomeCards()
}
function renderHero() {
const { item, s, device, sim, network, system, data, ota } = getMonitorData()
if (!item) {
- $('activeKicker').textContent = 'DEVICE MONITOR'
- $('activeName').textContent = '选择一个设备查看实时状态'
- $('activeMeta').textContent = '主页面展示各设备的状态、温度、流量、SIM、信号、系统资源与短信统计。'
+ $('activeKicker').textContent = 'DEVICE DETAIL'
+ $('activeName').textContent = '选择一个 SimAdmin 设备'
+ $('activeMeta').textContent = '从首页卡片进入详情页后,可查看状态并使用功能交互。'
$('signalPanel').innerHTML = [
['状态', '等待选择'], ['设备', '-'], ['温度', '-'], ['流量速率', '-'],
].map(([k, v]) => `
${k}${v}
`).join('')
return
}
$('activeKicker').textContent = `${item.id} · ${statusText(s)}`
- $('activeName').textContent = `${item.name || item.id} 设备监控`
+ $('activeName').textContent = `${item.name || item.id} 详情页`
$('activeMeta').textContent = `${device.manufacturer || ''} ${device.model || ''}`.trim() || item.url
$('signalPanel').innerHTML = [
['在线状态', statusText(s)],
@@ -264,18 +291,8 @@ function renderHero() {
}
function renderOverview() {
- const { item, s, device, sim, network, data, sms, system } = getMonitorData()
- if (!item) {
- $('overview').innerHTML = [
- metricCard('设备', `${instances.length} 台`),
- metricCard('在线', [...statuses.values()].filter(x => statusKind(x) === 'online').length),
- metricCard('温度', '选择设备后显示'),
- metricCard('流量', '选择设备后显示'),
- ].join('')
- renderHero()
- renderDeviceDetails()
- return
- }
+ const { item, s, sim, network, data, sms, system } = getMonitorData()
+ if (!item) return
const dataState = firstValue(data.active, data.connected, data.enabled)
$('overview').innerHTML = [
metricCard('状态', s ? (s.reachable ? (s.authenticated === false ? '在线/未登录' : '在线') : '离线') : '未知', pillClass(s)),
@@ -288,16 +305,12 @@ function renderOverview() {
metricCard('短信', sms.total != null ? `总 ${sms.total} / 收 ${value(sms.incoming)} / 发 ${value(sms.outgoing)}` : '-'),
].join('')
renderHero()
- renderDeviceDetails()
}
function renderDeviceDetails() {
const { item, device, sim, network, data, sms, ota, system, s } = getMonitorData()
- if (!item) {
- $('featureGrid').innerHTML = `
设备监控大屏
选择左侧任一设备后,这里会按分类展示真实设备信息、温度、流量、系统资源、SIM 与短信状态。
`
- return
- }
- $('featureGrid').innerHTML = [
+ if (!item) return
+ $('detailDeviceGrid').innerHTML = [
detailCard('设备信息', [
['名称', item.name || item.id], ['地址', item.url], ['型号', device.model], ['厂商', device.manufacturer], ['IMEI', device.imei], ['电源', boolText(device.powered)], ['在线', boolText(device.online)], ['固件/版本', ota.currentVersion || device.firmware || device.revision], ['Commit', ota.currentCommit],
], 'device'),
@@ -316,7 +329,26 @@ function renderDeviceDetails() {
].join('')
}
-function renderFeatures() { renderDeviceDetails() }
+function renderDetailActions() {
+ const disabled = activeId ? '' : ' disabled'
+ const primary = [
+ ['设备状态', '/api/device'], ['SIM 信息', '/api/sim'], ['蜂窝网络', '/api/network'], ['系统资源', '/api/stats'],
+ ['短信统计', '/api/sms/stats'], ['通话记录', '/api/calls'], ['OTA 状态', '/api/ota/status'], ['数据连接', '/api/data'],
+ ]
+ $('detailActions').innerHTML = `
+
+ ${primary.map(([label, ep]) => `
`).join('')}
+ `
+ $$('[data-detail-ep]', $('detailActions')).forEach(btn => btn.addEventListener('click', () => {
+ switchView('api')
+ $('endpointInput').value = btn.dataset.detailEp
+ $('methodSelect').value = inferMethod(btn.dataset.detailEp)
+ runEndpoint()
+ }))
+ $$('[data-view]', $('detailActions')).forEach(btn => btn.addEventListener('click', () => switchView(btn.dataset.view)))
+}
+
+function renderFeatures() { renderDeviceDetails(); renderDetailActions() }
function renderEndpointList() {
const groups = catalog.length ? catalog : modules.map(m => ({ id: m.id, name: m.title, endpoints: m.endpoints }))
@@ -332,26 +364,13 @@ function renderEndpointList() {
}
function switchView(view) {
- $$('.phone-app[data-view]').forEach(btn => btn.classList.toggle('active', btn.dataset.view === view))
+ if (!['home', 'detail', 'rawpage', 'api'].includes(view)) view = activeId ? 'detail' : 'home'
+ $$('[data-view]').forEach(btn => btn.classList.toggle('active', btn.dataset.view === view))
$$('.view').forEach(panel => panel.classList.toggle('active', panel.id === `${view}View`))
localStorage.setItem('multi-simadmin-view', view)
}
-function selectInstance(id) {
- const item = instances.find(x => x.id === id)
- if (!item) return
- activeId = id
- localStorage.setItem('multi-simadmin-active', id)
- $('loginBtn').disabled = false
- $('openExternal').href = item.url
- $('openExternal').classList.remove('disabled')
- $('emptyState').hidden = true
- $('frame').hidden = false
- $('frame').src = item.url
- renderInstances()
- renderOverview()
- renderFeatures()
-}
+function selectInstance(id) { openDeviceDetail(id) }
function openInstanceDialog(id = '') {
const item = id ? instances.find(x => x.id === id) : null
@@ -420,10 +439,8 @@ async function loadConfig({ preserveSelection = activeId } = {}) {
const saved = preserveSelection || localStorage.getItem('multi-simadmin-active')
activeId = saved && instances.some(x => x.id === saved) ? saved : null
renderEndpointList()
- renderInstances()
- renderOverview()
- renderFeatures()
- if (activeId) selectInstance(activeId)
+ renderHomeCards()
+ if (activeId) openDeviceDetail(activeId)
}
async function refreshStatus({ silent = false } = {}) {
@@ -434,9 +451,12 @@ async function refreshStatus({ silent = false } = {}) {
if (!res.ok) throw new Error(`状态读取失败:HTTP ${res.status}`)
const data = await res.json()
statuses = new Map((data.instances || []).map(item => [item.id, item]))
- renderInstances()
- renderOverview()
- renderFeatures()
+ renderHomeCards()
+ if (activeId) {
+ renderOverview()
+ renderDeviceDetails()
+ renderDetailActions()
+ }
if (!silent) showToast(`已刷新 ${statuses.size} 个实例`)
} catch (error) {
showToast(error.message || '刷新失败', 4200)
@@ -469,7 +489,7 @@ function parseBody() {
async function runEndpoint() {
if (!activeId) {
- showToast('请先选择左侧设备')
+ showToast('请先在首页选择设备卡片')
return
}
const endpoint = $('endpointInput').value.trim()
@@ -505,16 +525,17 @@ function bindEvents() {
$('addInstanceBtn').addEventListener('click', () => openInstanceDialog())
$('loginBtn').addEventListener('click', loginActive)
$('runEndpoint').addEventListener('click', runEndpoint)
- $('instanceSearch').addEventListener('input', renderInstances)
+ $('instanceSearch').addEventListener('input', renderHomeCards)
+ $('backToFleetBtn').addEventListener('click', setActiveHome)
$('densityBtn').addEventListener('click', () => {
document.body.classList.toggle('compact')
localStorage.setItem('multi-simadmin-density', document.body.classList.contains('compact') ? 'compact' : 'normal')
})
- $$('.phone-app[data-view]').forEach(btn => btn.addEventListener('click', () => switchView(btn.dataset.view)))
+ $$('[data-view]').forEach(btn => btn.addEventListener('click', () => switchView(btn.dataset.view)))
$$('#statusFilters .tab-leaf').forEach(btn => btn.addEventListener('click', () => {
activeFilter = btn.dataset.filter
$$('#statusFilters .tab-leaf').forEach(x => x.classList.toggle('active', x === btn))
- renderInstances()
+ renderHomeCards()
}))
$('loginForm').addEventListener('submit', async (event) => {
event.preventDefault()
@@ -543,7 +564,7 @@ function bindEvents() {
function restorePrefs() {
if (localStorage.getItem('multi-simadmin-density') === 'compact') document.body.classList.add('compact')
- switchView(localStorage.getItem('multi-simadmin-view') || 'dashboard')
+ switchView(localStorage.getItem('multi-simadmin-view') || 'home')
}
try {
diff --git a/public/index.html b/public/index.html
index 62edc1c..7869044 100644
--- a/public/index.html
+++ b/public/index.html
@@ -17,86 +17,95 @@
●