[verified] refactor: harden operations and redesign device console

This commit is contained in:
chick
2026-07-15 16:33:16 +08:00
parent e04a16e817
commit 4977afdc20
24 changed files with 769 additions and 779 deletions
+48 -153
View File
@@ -1,168 +1,63 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Multi SimAdmin Island</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/styles.css" />
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>设备首页 · Multi SimAdmin Island</title><link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="island-bg" aria-hidden="true">
<span class="cloud cloud-a"></span>
<span class="cloud cloud-b"></span>
<span class="leaf leaf-a"></span>
<span class="leaf leaf-b"></span>
</div>
<div id="appShell" class="sim-shell">
<header class="sim-topbar island-card pattern-app-teal">
<div>
<p class="eyebrow">MULTI SIMADMIN</p>
<h1>SimAdmin 设备卡片墙</h1>
<p class="topbar-copy">首页直接展示每个 SimAdmin 设备卡片;点击卡片进入设备详情页,再进行状态查看和功能交互。</p>
</div>
<div class="hero-actions">
<button id="homeBtn" class="animal-btn default-btn" type="button">设备首页</button>
<button id="refreshBtn" class="animal-btn primary-btn" type="button">刷新状态</button>
<button id="addInstanceBtn" class="animal-btn primary-btn" type="button"> 添加设备</button>
<button id="densityBtn" class="animal-btn default-btn" type="button">切换密度</button>
</div>
<div class="island-bg" aria-hidden="true"></div>
<div id="appShell" class="app-shell">
<aside class="desktop-sidebar" aria-label="主导航">
<a class="brand" href="#" data-view="home"><span>🏝️</span><b>SIM 岛</b></a>
<nav class="side-nav">
<button type="button" data-view="home" aria-current="page"><span>设备首页</span></button>
<button type="button" data-view="detail"><span>设备详情</span></button>
<button type="button" data-view="rawpage"><span>原始页面</span></button>
<button type="button" data-view="api"><span>API 工作台</span></button>
</nav>
<div class="sidebar-foot"><span>本地配置</span><code id="configPath">config.json</code></div>
</aside>
<div class="app-content">
<header class="sim-topbar">
<div><p class="eyebrow">MULTI SIMADMIN</p><h1 id="pageHeading">设备首页</h1><p id="pageSubtitle">集中巡检你的 SimAdmin 设备</p></div>
<div class="hero-actions"><button id="refreshBtn" class="animal-btn default-btn" type="button">刷新状态</button><button id="addInstanceBtn" class="animal-btn primary-btn" type="button"> 添加设备</button></div>
</header>
<div id="persistentError" class="persistent-message error" role="alert" hidden><span id="persistentErrorText"></span><button id="dismissError" type="button">关闭</button></div>
<div id="staleBanner" class="persistent-message warn" role="status" hidden></div>
<main class="sim-main">
<section id="homeView" class="view active home-view" aria-label="SimAdmin 卡片首页">
<section class="home-toolbar island-card pattern-default">
<div>
<div class="section-ribbon color-app-green">设备卡片墙</div>
<p class="board-caption">每张卡片对应一个 SimAdmin;卡片上直接显示在线状态、SIM、信号、温度、流量和短信摘要。</p>
</div>
<div class="home-tools">
<section class="wallet-row" id="fleetStats"></section>
<label class="search-pill" aria-label="搜索设备">
<span></span>
<input id="instanceSearch" type="search" placeholder="搜索设备、标签、地址、ICCID…" autocomplete="off" />
</label>
<nav class="island-tabs" id="statusFilters" aria-label="状态筛选">
<button class="tab-leaf active" data-filter="all">全部</button>
<button class="tab-leaf" data-filter="online">在线</button>
<button class="tab-leaf" data-filter="auth">需登录</button>
<button class="tab-leaf" data-filter="offline">离线</button>
<section id="homeView" class="view active home-view" aria-labelledby="pageHeading">
<div id="initialLoading" class="state-card" role="status">正在读取本地配置与设备状态…</div>
<div id="fatalState" class="state-card error" role="alert" hidden><h2>应用无法初始化</h2><p id="fatalText"></p><button id="retryInit" class="animal-btn primary-btn" type="button">重试</button></div>
<section id="homeContent" hidden>
<div class="fleet-overview island-card"><div><h2>设备卡片墙</h2><p>状态、资产与连接信息一览</p></div><div id="fleetStats" class="wallet-row"></div></div>
<div class="home-toolbar island-card">
<label for="instanceSearch">搜索设备</label><input id="instanceSearch" type="search" placeholder="名称、ID、标签、地址、ICCID…" autocomplete="off">
<label for="sortSelect">排序</label><select id="sortSelect"><option value="name">名称</option><option value="status">状态</option><option value="latency">延迟</option></select>
<button id="densityBtn" class="animal-btn default-btn" type="button" aria-pressed="false">紧凑密度</button>
<nav class="status-filters" id="statusFilters" aria-label="设备状态筛选">
<button type="button" data-filter="all" aria-pressed="true">全部</button><button type="button" data-filter="online" aria-pressed="false">在线</button><button type="button" data-filter="auth" aria-pressed="false">需登录</button><button type="button" data-filter="offline" aria-pressed="false">离线</button><button type="button" data-filter="unknown" aria-pressed="false">未知</button>
</nav>
</div>
</section>
<section id="homeDeviceGrid" class="home-device-grid" aria-label="设备卡片列表"></section>
</section>
<section id="detailView" class="view detail-view" aria-label="设备详情页">
<header class="workspace-hero island-card pattern-app-teal detail-hero">
<div>
<p id="activeKicker" class="eyebrow">DEVICE DETAIL</p>
<h1 id="activeName">选择一个 SimAdmin 设备</h1>
<p id="activeMeta">从首页设备卡片进入详情页后,可查看完整状态并使用功能入口。</p>
</div>
<div class="hero-actions">
<button id="backToFleetBtn" type="button" class="animal-btn default-btn">← 返回卡片墙</button>
<button id="loginBtn" type="button" class="animal-btn default-btn" disabled>登录/刷新会话</button>
<a id="openExternal" href="#" target="_blank" rel="noreferrer" class="animal-btn default-btn disabled">原站打开</a>
</div>
</header>
<section class="detail-layout">
<section class="signal-board island-card pattern-app-yellow monitor-summary-card">
<div class="section-ribbon color-app-yellow">实时状态</div>
<div id="signalPanel" class="signal-grid"></div>
</section>
<section id="overview" class="nook-metrics monitor-kpis" aria-label="关键状态指标"></section>
<section class="module-board island-card pattern-default device-detail-board">
<div class="board-head">
<div class="section-ribbon color-app-green">设备详情</div>
<p class="board-caption">设备、SIM、蜂窝网络、系统资源、温度、流量、短信与版本。</p>
</div>
<div id="detailDeviceGrid" class="device-detail-grid"></div>
</section>
<section class="island-card pattern-purple detail-actions-card">
<div class="section-ribbon color-purple">功能交互</div>
<div id="detailActions" class="detail-actions"></div>
</section>
<section id="homeDeviceGrid" class="home-device-grid" aria-label="设备卡片列表"></section>
</section>
</section>
<section id="rawpageView" class="view rawpage-view">
<div class="frame-toolbar island-card pattern-app-blue">
<button type="button" class="animal-btn default-btn" data-view="detail">← 返回详情</button>
<span id="frameHint">原始 SimAdmin 页面会在下方显示;若目标站禁止 iframe,请使用“原站打开”。</span>
</div>
<section id="emptyState" class="empty-state island-card pattern-default">
<div class="section-ribbon color-app-pink">等待选择</div>
<h3>还没有选择设备</h3>
<p>先在首页点击一个设备卡片,再进入原始页面。</p>
</section>
<iframe id="frame" class="frame" title="SimAdmin instance" hidden></iframe>
<section id="detailView" class="view detail-view" aria-label="设备详情">
<header class="workspace-hero island-card"><div><p id="activeKicker" class="eyebrow">DEVICE DETAIL</p><h2 id="activeName">设备详情</h2><p id="activeMeta"></p></div><div class="hero-actions"><button id="backToFleetBtn" class="animal-btn default-btn" type="button">返回首页</button><button id="editActiveBtn" class="animal-btn default-btn" type="button">编辑设备</button><button id="loginBtn" class="animal-btn default-btn" type="button">登录/刷新会话</button><a id="openExternal" class="animal-btn default-btn" href="#" target="_blank" rel="noopener noreferrer">原站打开</a></div></header>
<section class="detail-layout"><section class="signal-board island-card"><h3>实时状态</h3><div id="signalPanel" class="signal-grid"></div></section><section id="overview" class="nook-metrics" aria-label="关键指标"></section><section class="island-card detail-board"><h3>设备详情</h3><div id="detailDeviceGrid" class="device-detail-grid"></div></section><section class="island-card detail-actions-card"><h3>功能入口</h3><div id="detailActions" class="detail-actions"></div></section></section>
</section>
<section id="apiView" class="view api-layout">
<aside class="api-catalog island-card pattern-purple">
<div class="section-ribbon color-purple">调试接口</div>
<div id="endpointList" class="endpoint-list"></div>
</aside>
<section class="api-console island-card pattern-default">
<div class="console-toolbar">
<select id="methodSelect" aria-label="请求方法">
<option>GET</option>
<option>POST</option>
<option>DELETE</option>
</select>
<input id="endpointInput" value="/api/device" spellcheck="false" aria-label="接口路径" />
<button id="runEndpoint" class="animal-btn primary-btn" type="button">发送</button>
</div>
<textarea id="requestBody" class="request-body" placeholder='POST/DELETE 请求体 JSON,例如 {"enabled":true}'></textarea>
<pre id="rawOutput" class="raw-output">从设备详情页进入后,可对当前设备调用代理接口。</pre>
</section>
<section id="rawpageView" class="view rawpage-view" aria-label="原始管理页"><div class="frame-toolbar island-card"><button type="button" class="animal-btn default-btn" data-view="detail">返回详情</button><span id="frameHint">若目标站禁止 iframe,请使用“原站打开”。</span></div><iframe id="frame" class="frame" title="SimAdmin 原始管理页面" hidden></iframe></section>
<section id="apiView" class="view api-layout" aria-label="API 工作台">
<aside class="api-catalog island-card"><h2>接口目录</h2><label for="endpointSearch">搜索接口</label><input id="endpointSearch" type="search" placeholder="搜索 endpoint"><div id="endpointList" class="endpoint-list"></div></aside>
<section class="api-console island-card"><div class="console-toolbar"><label for="methodSelect">方法</label><select id="methodSelect"><option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option></select><label for="endpointInput">路径</label><input id="endpointInput" value="/api/device" spellcheck="false"><button id="runEndpoint" class="animal-btn primary-btn" type="button">发送请求</button></div><div id="writeWarning" class="danger-warning" role="alert" hidden>写操作可能改变设备状态。发送前将创建一次性确认令牌。</div><label for="requestBody">JSON 请求体(GET 不发送请求体)</label><textarea id="requestBody" class="request-body" placeholder='{"enabled":true}'></textarea><div class="output-tools"><button id="outputPretty" type="button" aria-pressed="true">Pretty</button><button id="outputRaw" type="button" aria-pressed="false">Raw</button><button id="copyOutput" type="button">复制结果</button></div><pre id="rawOutput" class="raw-output">选择接口不会自动发送。确认设备和参数后点击“发送请求”。</pre></section>
</section>
</main>
<footer class="config-note sim-config-note">
<span>本地配置</span>
<code id="configPath">config.json</code>
</footer>
</div>
<dialog id="loginDialog">
<form method="dialog" id="loginForm" class="login-card island-card pattern-default">
<div class="section-ribbon color-app-teal">会话通行证</div>
<h3>登录 SimAdmin</h3>
<p>输入该实例管理员密码;只发送到本地聚合服务,不写入浏览器存储。</p>
<input id="passwordInput" type="password" placeholder="管理员密码" autocomplete="current-password" />
<menu>
<button value="cancel" class="animal-btn default-btn">取消</button>
<button id="submitLogin" value="default" class="animal-btn primary-btn">登录</button>
</menu>
</form>
</dialog>
<dialog id="instanceDialog">
<form method="dialog" id="instanceForm" class="instance-form island-card pattern-default">
<div id="instanceFormRibbon" class="section-ribbon color-app-green">添加设备地址</div>
<p class="form-help">配置会立即写入本地 config.json,并更新左侧设备列表。</p>
<input type="hidden" id="instanceOriginalId" />
<label>设备 ID <input id="instanceIdInput" required pattern="[a-zA-Z0-9_.-]+" placeholder="home-cpe" /></label>
<label>显示名称 <input id="instanceNameInput" required placeholder="客厅 CPE" /></label>
<label>设备地址 <input id="instanceUrlInput" required type="url" placeholder="http://192.168.1.1" /></label>
<label>描述 <textarea id="instanceDescInput" placeholder="位置、用途或备注"></textarea></label>
<label>标签 <input id="instanceTagsInput" placeholder="home, 5g, backup" /></label>
<label>管理员密码 <input id="instancePasswordInput" type="password" placeholder="留空表示无密码或清除已保存密码" autocomplete="new-password" /></label>
<menu class="form-menu">
<button value="cancel" class="animal-btn default-btn">取消</button>
<button id="deleteInstanceBtn" type="button" class="animal-btn danger-btn" hidden>删除</button>
<button id="saveInstanceBtn" value="default" class="animal-btn primary-btn">保存</button>
</menu>
</form>
</dialog>
<div id="toast" class="toast" hidden></div>
<script src="/app.js" type="module"></script>
</body>
</html>
<nav class="mobile-nav" aria-label="移动端主导航"><button type="button" data-view="home" aria-current="page"><span>首页</span></button><button type="button" data-view="detail"><span>详情</span></button><button type="button" data-view="rawpage"><span>原页</span></button><button type="button" data-view="api"><span>API</span></button></nav>
</div>
<dialog id="loginDialog" aria-labelledby="loginTitle"><form method="dialog" id="loginForm" class="dialog-card"><h2 id="loginTitle">登录 SimAdmin</h2><p>密码仅发送至本地聚合服务。</p><label for="passwordInput">管理员密码</label><input id="passwordInput" type="password" autocomplete="current-password"><p id="loginError" class="form-error" role="alert" hidden></p><menu><button type="button" data-dialog-cancel="loginDialog" class="animal-btn default-btn">取消</button><button id="submitLogin" value="default" class="animal-btn primary-btn">登录</button></menu></form></dialog>
<dialog id="instanceDialog" aria-labelledby="instanceDialogTitle"><form method="dialog" id="instanceForm" class="dialog-card"><h2 id="instanceDialogTitle">添加设备</h2><input type="hidden" id="instanceOriginalId"><label for="instanceIdInput">设备 ID</label><input id="instanceIdInput" required pattern="[a-zA-Z0-9_.-]+"><label for="instanceNameInput">显示名称</label><input id="instanceNameInput" required><label for="instanceUrlInput">设备地址</label><input id="instanceUrlInput" required type="url"><label for="instanceDescInput">描述</label><textarea id="instanceDescInput"></textarea><label for="instanceTagsInput">标签(逗号分隔)</label><input id="instanceTagsInput"><fieldset id="passwordActions"><legend>保存密码</legend><label><input id="passwordPreserve" type="radio" name="passwordAction" value="preserve" checked> 保留现有密码</label><label><input id="passwordSet" type="radio" name="passwordAction" value="set"> 设置新密码</label><label><input id="passwordClear" type="radio" name="passwordAction" value="clear"> 清除密码</label></fieldset><label id="newPasswordLabel" for="instancePasswordInput" hidden>新密码<input id="instancePasswordInput" type="password" autocomplete="new-password"></label><label id="clearPasswordLabel" class="confirm-check" hidden><input id="clearPasswordConfirm" type="checkbox"> 我确认清除已保存密码</label><p id="instanceFormError" class="form-error" role="alert" hidden></p><menu><button type="button" data-dialog-cancel="instanceDialog" class="animal-btn default-btn">取消</button><button id="deleteInstanceBtn" type="button" class="animal-btn danger-btn" hidden>删除设备</button><button id="saveInstanceBtn" value="default" class="animal-btn primary-btn">保存</button></menu></form></dialog>
<dialog id="deleteDialog" aria-labelledby="deleteDialogTitle"><form method="dialog" class="dialog-card"><h2 id="deleteDialogTitle">确认删除设备</h2><div id="deleteDetails" class="delete-details"></div><p>此操作将立即写入本地配置,且不可撤销。</p><p id="deleteError" class="form-error" role="alert" hidden></p><menu><button type="button" data-dialog-cancel="deleteDialog" class="animal-btn default-btn">取消</button><button id="confirmDeleteBtn" value="default" class="animal-btn danger-primary">确认删除</button></menu></form></dialog>
<dialog id="apiDeleteDialog" aria-labelledby="apiDeleteTitle"><form method="dialog" class="dialog-card"><h2 id="apiDeleteTitle">确认发送写操作</h2><p id="apiDeleteDetails"></p><pre id="apiDeleteBody" class="raw-output"></pre><p>该请求将改变设备状态,请核对设备、方法、路径和请求体。</p><menu><button type="button" data-dialog-cancel="apiDeleteDialog" class="animal-btn default-btn">取消</button><button id="confirmApiDelete" value="default" class="animal-btn danger-primary">确认并发送</button></menu></form></dialog>
<div id="toast" class="toast" aria-live="polite" role="status" hidden></div>
<script src="/app.js" type="module"></script>
</body></html>