feat: 初始化 kdbx-viewer 项目

实现服务端解密的 KeePass 网页查看器,包含登录门户口令与验证码、RSA+会话级 AES 加密通道、审计日志持久化、HTTPS 自动证书、Docker 部署配置及端到端测试。
这个提交包含在:
2026-08-26 10:42:48 +08:00
当前提交 02e83f5b73
共修改 20 个文件,包含 3356 行新增和 0 行删除
+132
查看文件
@@ -0,0 +1,132 @@
:root {
--bg: #f5f6f8; --card: #fff; --line: #e3e6ea; --text: #1f2329;
--muted: #8a9099; --primary: #2f6df6; --danger: #d8392b; --active: #e8f0ff;
}
* { box-sizing: border-box; }
body {
margin: 0; font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif;
background: var(--bg); color: var(--text);
}
.hidden { display: none !important; }
.card {
max-width: 360px; margin: 12vh auto; background: var(--card);
border: 1px solid var(--line); border-radius: 12px; padding: 28px;
box-shadow: 0 4px 20px rgba(0,0,0,.05);
}
.card h1 { font-size: 20px; margin: 0 0 6px; }
.muted { color: var(--muted); font-size: 13px; margin: 0 0 16px; }
input, textarea {
width: 100%; padding: 10px 12px; border: 1px solid var(--line);
border-radius: 8px; font-size: 14px; margin-bottom: 12px;
}
button {
background: var(--primary); color: #fff; border: 0; border-radius: 8px;
padding: 10px 16px; font-size: 14px; cursor: pointer;
}
button.ghost { background: transparent; color: var(--primary); border: 1px solid var(--line); }
.err { color: var(--danger); font-size: 13px; min-height: 18px; margin: 6px 0 0; }
.badge {
background: #fff3e0; color: #b26a00; font-size: 12px;
padding: 2px 8px; border-radius: 10px; margin-left: 8px;
}
.topbar {
display: flex; align-items: center; gap: 12px; padding: 12px 16px;
background: var(--card); border-bottom: 1px solid var(--line);
}
.topbar #dbName { font-size: 15px; }
.topbar #search { width: 240px; margin: 0 0 0 auto; }
.layout { display: flex; height: calc(100vh - 53px); }
#groupTree {
width: 240px; border-right: 1px solid var(--line); overflow: auto;
padding: 10px; background: var(--card);
}
.node { padding: 7px 10px; border-radius: 6px; font-size: 13px; cursor: pointer; color: var(--text); }
.node:hover { background: #f0f2f5; }
.node.active { background: var(--active); color: var(--primary); font-weight: 600; }
main { flex: 1; overflow: auto; padding: 16px; }
#entryList { list-style: none; margin: 0; padding: 0; }
.entry {
padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px;
margin-bottom: 8px; cursor: pointer; display: flex; flex-direction: column; gap: 2px;
}
.entry:hover { border-color: var(--primary); }
.entry.active { border-color: var(--primary); background: var(--active); }
.entry .t { font-weight: 600; }
.entry .u { font-size: 12px; color: var(--muted); }
.empty { color: var(--muted); padding: 12px; }
#detail { margin-top: 12px; border-top: 1px dashed var(--line); padding-top: 12px; }
#detail h2 { margin: 0 0 12px; font-size: 18px; }
.row { display: flex; align-items: flex-start; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--line); }
.row .label { width: 56px; color: var(--muted); font-size: 13px; flex: none; }
.row .value { flex: 1; word-break: break-all; font-size: 14px; white-space: pre-wrap; }
.row .copy, .row .open { flex: none; font-size: 12px; padding: 4px 10px; }
/* ===== 详情弹窗 ===== */
.modal {
position: fixed; inset: 0; background: rgba(0,0,0,.45);
display: flex; align-items: center; justify-content: center; z-index: 50;
}
.modal.hidden { display: none !important; }
.modal-card {
background: var(--card); width: min(520px, 92vw); max-height: 86vh; overflow: auto;
border-radius: 14px; padding: 22px 24px; position: relative;
box-shadow: 0 12px 40px rgba(0,0,0,.2);
}
.modal-card h2 { margin: 0 0 14px; font-size: 18px; padding-right: 28px; }
.modal-close {
position: absolute; top: 12px; right: 12px; background: transparent; color: var(--muted);
border: 0; font-size: 22px; line-height: 1; cursor: pointer; padding: 4px 8px;
}
.modal-close:hover { color: var(--text); }
/* ===== 分页 ===== */
#pager { display: flex; align-items: center; gap: 12px; padding: 12px 4px; justify-content: center; }
#pager .pinfo { font-size: 13px; color: var(--muted); }
#pager button:disabled { opacity: .4; cursor: not-allowed; }
/* ===== 验证码 ===== */
.captcha-row { display: flex; gap: 8px; align-items: center; }
.captcha-row input { flex: 1; }
.captcha-img { display: inline-flex; height: 44px; border: 1px solid var(--border); border-radius: 8px; cursor: pointer; overflow: hidden; background: #fff; }
.captcha-img svg { height: 44px; display: block; }
/* ===== 审计日志 ===== */
#auditPanel { padding: 16px 20px; border-bottom: 1px solid var(--border); background: var(--card); }
#auditPanel h3 { margin: 0 0 10px; }
.audit-list { max-height: 260px; overflow: auto; font-family: monospace; font-size: 12px; }
.audit-list .row { display: flex; gap: 10px; padding: 2px 0; border-bottom: 1px dashed var(--border); }
.audit-list .ok { color: var(--ok, #2f855a); }
.audit-list .fail { color: var(--err, #c53030); }
/* ===== 独立审计页 ===== */
.audit-page { max-width: 1200px; margin: 0 auto; padding: 20px; }
.audit-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.audit-header h1 { font-size: 20px; margin: 0; }
.audit-actions { display: flex; gap: 8px; }
.audit-filters { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 16px; }
.audit-filters input, .audit-filters select { padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; font-size: 13px; }
.audit-filters input[type="text"] { width: 160px; }
.audit-filters .chk { font-size: 13px; display: flex; align-items: center; gap: 4px; }
.audit-blocks { background: #fff4f3; border: 1px solid #f3c7c2; border-radius: 8px; padding: 10px 14px; margin-bottom: 16px; }
.audit-blocks h3 { margin: 0 0 6px; color: var(--danger); font-size: 14px; }
.audit-blocks ul { margin: 0; padding-left: 18px; font-size: 13px; }
.audit-blocks li { padding: 2px 0; }
.audit-blocks .mono, .audit-table .mono { font-family: monospace; }
.audit-table-wrap { background: var(--card); border: 1px solid var(--line); border-radius: 10px; overflow: auto; }
.audit-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.audit-table th, .audit-table td { padding: 8px 10px; border-bottom: 1px solid var(--line); text-align: left; white-space: nowrap; }
.audit-table th { background: #fafbfc; position: sticky; top: 0; font-weight: 600; }
.audit-table tbody tr.row-fail { background: #fff5f5; }
.audit-table .ua { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
.audit-table .code { font-family: monospace; }
.audit-table .code:not(:empty) { color: var(--danger); }
.tag { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 11px; line-height: 1.5; }
.tag-internal { background: #fff3cd; color: #8a6d00; border: 1px solid #ffe69c; }
.tag-external { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
.index-info { display: block; margin-top: 6px; font-size: 12px; color: var(--muted); font-family: monospace; word-break: break-all; }
.audit-filters select { padding: 6px 8px; border: 1px solid var(--line); border-radius: 6px; font-size: 13px; }
.audit-pager { display: flex; align-items: center; gap: 12px; padding: 10px 14px; font-size: 13px; flex-wrap: wrap; }
.btn { padding: 6px 14px; background: var(--primary); color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 13px; }
.btn:hover { filter: brightness(1.05); }
.btn-ghost { padding: 6px 12px; background: #fff; border: 1px solid var(--line); border-radius: 6px; cursor: pointer; font-size: 13px; }
.btn-ghost:hover { background: #f0f2f5; }