fix: 第二轮修复 — 数据库启动检查、会话持久化、URL路由、设备排序等

1. DevTools 启动前检查数据库状态,失败时自动尝试启动
2. ai-core 添加数据库断线重连机制 (30秒间隔)
3. Dashboard 添加数据库状态卡片 (启动/停止/重启)
4. Gateway 会话空闲超时管理 (30分钟标记空闲)
5. 会话/消息 PostgreSQL 持久化 (SessionStore + REST API)
6. 前端服务端会话持久化 + URL hash 路由 + 侧边栏管理
7. 管理员回到主对话按钮
8. IoT 设备卡片固定排序
9. 更新相关文档
This commit is contained in:
2026-05-17 17:18:02 +08:00
parent 745b1c6aad
commit e7b7eff0d8
21 changed files with 1735 additions and 284 deletions
+54 -12
View File
@@ -744,19 +744,22 @@ async function renderDashboard() {
<div class="cards-grid cards-4" id="dashboard-svc-cards"></div>
</div>
<!-- 数据库连接状态 -->
<div class="card">
<!-- 数据库状态卡片 -->
<div class="card" id="db-card">
<div class="card-header">
<span class="card-title">🗄️ 数据库连接</span>
${data.database?.checked ? `
<span class="badge ${data.database.postgresAlive ? 'badge-running' : 'badge-error'}">
PostgreSQL ${data.database.postgresAlive ? '通联' : '断开'}
</span>
<span class="badge ${data.database.tunnelRunning ? 'badge-running' : 'badge-stopped'}" style="margin-left:6px">
隧道 ${data.database.tunnelRunning ? '运行中' : '未运行'}
</span>
` : '<span class="badge badge-stopped">待检查</span>'}
<a href="#" onclick="switchPanel('database');return false" style="font-size:11px;color:var(--accent);text-decoration:none">🔍 详情 →</a>
<span class="card-title">🗄️ 数据库</span>
<span class="badge badge-stopped" id="db-status-badge">检查中...</span>
</div>
<div class="metrics">
<div class="metric"><div class="value" id="db-type-display">PostgreSQL</div><div class="label">类型</div></div>
<div class="metric"><div class="value" id="db-port-display">5432</div><div class="label">端口</div></div>
<div class="metric"><div class="value" id="db-uptime-display">—</div><div class="label">状态</div></div>
</div>
<div class="btn-group" style="margin-top:10px">
<button class="btn btn-xs btn-green" onclick="controlDB('start')">▶ 启动</button>
<button class="btn btn-xs btn-red" onclick="controlDB('stop')">⏹ 停止</button>
<button class="btn btn-xs" onclick="controlDB('restart')">🔄 重启</button>
<a href="#" onclick="switchPanel('database');return false" style="font-size:10px;color:var(--accent);text-decoration:none;margin-left:auto;align-self:center">🔍 详情 →</a>
</div>
</div>
@@ -789,6 +792,9 @@ async function renderDashboard() {
// 渲染服务卡片
renderDashboardSvcCards(svcs);
// 渲染数据库卡片
renderDBCard();
// 渲染性能快照
const perfContainer = document.getElementById('dashboard-perf');
const perf = data.performance?.perService || {};
@@ -1701,6 +1707,42 @@ async function tunnelAction(action) {
}
}
// ========== 数据库卡片控制 ==========
async function renderDBCard() {
const data = await api('/api/db/status');
const badge = document.getElementById('db-status-badge');
const typeDisplay = document.getElementById('db-type-display');
const portDisplay = document.getElementById('db-port-display');
const uptimeDisplay = document.getElementById('db-uptime-display');
if (data.error) {
if (badge) { badge.textContent = '错误'; badge.className = 'badge badge-error'; }
if (uptimeDisplay) uptimeDisplay.textContent = '错误';
return;
}
const online = data.online;
if (badge) {
badge.textContent = online ? '🟢 在线' : '🔴 离线';
badge.className = 'badge ' + (online ? 'badge-running' : 'badge-error');
}
if (typeDisplay) typeDisplay.textContent = 'PostgreSQL';
if (portDisplay) portDisplay.textContent = data.port || 5432;
if (uptimeDisplay) uptimeDisplay.textContent = online ? '已连接' : '未连接';
}
async function controlDB(action) {
showToast('正在' + action + '数据库...', 'info');
const data = await api('/api/db/' + action, { method: 'POST' });
if (data.error) {
showToast('操作失败: ' + data.error, 'error');
} else {
showToast('数据库 ' + action + ' 完成', 'success');
// 等待2秒后刷新状态
setTimeout(renderDBCard, 2000);
}
}
</script>
<script src="iot-panel.js"></script>
<script>
+10
View File
@@ -52,6 +52,16 @@ async function renderIoTPanel() {
}
var devices = result.devices;
// 固定设备排列顺序: 先按类型,同类型再按 device_id
var typeOrder = { 'sensor': 1, 'ac': 2, 'light': 3, 'curtain': 4, 'lock': 5, 'camera': 6, 'speaker': 7, 'thermostat': 8 };
devices.sort(function(a, b) {
var oa = typeOrder[a.type] || 99;
var ob = typeOrder[b.type] || 99;
if (oa !== ob) return oa - ob;
return (a.id || a.entity_id || '').localeCompare(b.id || b.entity_id || '');
});
var badge = document.getElementById('iot-badge');
if (badge) {
badge.textContent = devices.length;