New Database

This commit is contained in:
喆OvO
2026-08-11 22:07:55 +08:00
parent cbbb2e6d34
commit ceb4404c6a
28 changed files with 1825 additions and 768 deletions
+111 -35
View File
@@ -1060,10 +1060,115 @@
var tab = $('users-tab');
if (tab) tab.style.display = '';
loadUsers();
// 数据库管理仅 Root (id 0) 可见
if (status.user.id === 0) {
var dbSec = $('db-admin-section');
if (dbSec) dbSec.style.display = '';
loadDbStatus();
}
}
});
}
// ─── Database administration (Root only) ───
function loadDbStatus() {
var el = $('db-status');
if (!el) return;
api('GET', '/api/admin/db/status', null, function (d) {
if (d && d.database) {
el.textContent = 'PostgreSQL · schema v' + d.db_version + ' · 表 ' + (d.tables ? d.tables.length : '?') + ' 张';
el.style.color = '#2e7d32';
} else {
el.textContent = (d && d.error) || '获取状态失败';
el.style.color = '#E05555';
}
});
}
function dbMsg(text, isErr) {
var el = $('db-admin-msg');
if (!el) return;
el.textContent = text;
el.style.color = isErr ? '#E05555' : '#2e7d32';
}
function initDbAdmin() {
var exportBtn = $('db-export-btn');
if (exportBtn) {
exportBtn.addEventListener('click', function () {
exportBtn.disabled = true;
dbMsg('正在导出...', false);
_reauthThen(function () {
// 用 fetch 携带 Authorization 下载备份文件
var token = Auth.getAccess && Auth.getAccess();
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/admin/db/export');
if (token) xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.responseType = 'blob';
xhr.onload = function () {
exportBtn.disabled = false;
if (xhr.status === 200) {
var blob = xhr.response;
var disp = xhr.getResponseHeader('Content-Disposition') || '';
var m = disp.match(/filename="?([^";]+)"?/);
var name = m ? m[1] : 'iot-dboard-backup.json';
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = name;
document.body.appendChild(a);
a.click();
setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 1000);
dbMsg('导出成功,文件已开始下载', false);
} else if (xhr.status === 403) {
dbMsg('需要重新验证身份,请重试', true);
} else {
dbMsg('导出失败 (HTTP ' + xhr.status + ')', true);
}
};
xhr.onerror = function () { exportBtn.disabled = false; dbMsg('导出失败:网络错误', true); };
xhr.send();
}, function () {
exportBtn.disabled = false;
dbMsg('身份验证未通过,导出已取消', true);
});
});
}
var form = $('db-import-form');
if (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
var fileInput = $('db-import-file');
var file = fileInput && fileInput.files && fileInput.files[0];
if (!file) { dbMsg('请先选择文件', true); return; }
var btn = $('db-import-btn');
btn.disabled = true;
dbMsg('正在导入(大文件可能需要一些时间)...', false);
_reauthThen(function () {
var fd = new FormData();
fd.append('file', file);
api('POST', '/api/admin/db/import', fd, function (res) {
btn.disabled = false;
if (res && res.ok) {
dbMsg(res.message || '导入成功', false);
// 导入会重建全部数据并清空会话(含当前设备),提示后跳转登录页
setTimeout(function () {
try { localStorage.removeItem('dashboard_access_token'); } catch (e) {}
location.href = '/login?view=control&next=%2Fcontrol';
}, 1800);
} else {
dbMsg((res && res.error) || '导入失败', true);
}
});
}, function () {
btn.disabled = false;
dbMsg('身份验证未通过,导入已取消', true);
});
});
}
}
initDbAdmin();
// ─── User Management (admin only) ─────────
var _totpTimer = null;
@@ -1137,30 +1242,13 @@
function loadUsers() {
if (!$('users-tab') || $('users-tab').style.display === 'none') return;
api('GET', '/api/admin/users', null, function (users) {
var pending = users.filter(function (u) { return u.status === 'pending'; });
var active = users.filter(function (u) { return u.status === 'active'; });
// Pending requests
var pendHtml = '';
if (pending.length > 0) {
pendHtml = '<h4 style="margin:0 0 8px;font-size:14px;">⏳ 待确认申请(用户自助填写信息)</h4>';
pending.forEach(function (u) {
pendHtml += '<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 10px;border:1px solid var(--border);border-radius:8px;margin-bottom:6px;background:var(--warn-bg);">' +
'<div><strong>' + _esc(u.remark) + '</strong> <span style="font-size:12px;color:var(--text-dim);">' + _esc(u.created_at || '') + '</span></div>' +
'<div style="display:flex;gap:6px;">' +
'<button class="small-btn" data-action="confirm-request" data-id="' + u.id + '" style="color:#2e7d32;">确认并生成临时账户</button>' +
'<button class="small-btn" data-action="delete-request" data-id="' + u.id + '" style="color:#E05555;">拒绝</button>' +
'</div></div>';
});
}
$('pending-requests').innerHTML = pendHtml;
// Active users
// 统一渲染全部用户(含历史 pending 申请用户,标记后可删除)
var html = '<h4 style="margin:0 0 8px;font-size:14px;">👥 用户列表</h4>';
if (active.length === 0) html += '<div style="color:var(--text-dim);font-size:13px;">暂无用户</div>';
active.forEach(function (u) {
if (users.length === 0) html += '<div style="color:var(--text-dim);font-size:13px;">暂无用户</div>';
users.forEach(function (u) {
var badges = [];
if (u.role === 'admin') badges.push('<span class="badge badge-on">管理员</span>');
if (u.status === 'pending') badges.push('<span class="badge badge-off">待确认申请</span>');
if (u.is_temp) badges.push('<span class="badge badge-off">临时账户</span>');
if (u.has_mfa) badges.push('<span class="badge badge-on">已配置 MFA</span>');
html += '<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border:1px solid var(--border);border-radius:8px;margin-bottom:6px;flex-wrap:wrap;gap:6px;">' +
@@ -1183,23 +1271,11 @@
}
function bindUserActions() {
document.querySelectorAll('#pending-requests [data-action], #user-list [data-action]').forEach(function (btn) {
document.querySelectorAll('#user-list [data-action]').forEach(function (btn) {
btn.addEventListener('click', function () {
var action = btn.dataset.action;
var id = parseInt(btn.dataset.id, 10);
if (action === 'confirm-request') {
_reauthThen(function () {
api('POST', '/api/admin/users/confirm', { request_id: id }, function (res) {
if (res.ok) { toast('已生成临时账户'); loadUsers(); } else { toast(res.error || '操作失败'); }
});
});
} else if (action === 'delete-request') {
_reauthThen(function () {
api('DELETE', '/api/admin/users/' + id, null, function (res) {
if (res.ok) { toast('已拒绝该申请'); loadUsers(); } else { toast(res.error || '操作失败'); }
});
});
} else if (action === 'edit-remark') {
if (action === 'edit-remark') {
_reauthThen(function () {
showPrompt('编辑用户备注', '新的备注', '', function (newRemark) {
if (newRemark === null || !newRemark.trim()) return;