feat: harden auth for public deploy + gantt, duration units, docker packaging

Security (fail-secure for production):
- Refuse to start without PPM_PASSWORD/PPM_PASSWORD_HASH unless
  PPM_ALLOW_NO_AUTH=1 (local dev only)
- Add login rate limiting (5 failures / 60s per client), enforced inside
  SessionManager.login so it cannot be bypassed at the endpoint layer
- compose defaults PPM_COOKIE_SECURE=true for HTTPS behind reverse proxy
- Add .dockerignore to keep runtime data/out of the image

Features:
- Task timeline gantt on home page (week/month zoom, horizontal scroll,
  infinite-arrow for open-ended dates, CSS Grid layout)
- Duration metrics gain a second/minute/hour unit for input & display
  (canonical storage stays integer seconds)
- Per-metric progress bars on home page (no more merged average)
- Backfill historical records via date picker in the today-record card
- Admin panel: stats overview, JSON import/restore, full task list mgmt

Fixes:
- Nullable task startDate; summarize handles missing start date
- Task dialog cancel/close no longer submits the form
- Import validates whole backup first, rejects path traversal, rebuilds
  archives, and replaces stale files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MobKBK
2026-08-24 12:11:03 +08:00
parent bcc2abe623
commit 02b81f0113
16 changed files with 705 additions and 45 deletions

View File

@@ -26,14 +26,14 @@
</div>
<dialog id="taskDialog" class="dialog task-dialog">
<form id="taskForm" method="dialog">
<form id="taskForm">
<div class="dialog-heading">
<div><p class="eyebrow">任务定义</p><h2 id="taskDialogTitle">新建任务</h2></div>
<button class="icon-button" value="cancel" formmethod="dialog" title="关闭" aria-label="关闭">×</button>
<button class="icon-button" id="closeTaskButton" type="button" title="关闭" aria-label="关闭">×</button>
</div>
<div class="form-grid">
<label class="wide">任务名称<input name="name" required maxlength="120"></label>
<label>开始日期<input name="startDate" type="date" required></label>
<label>开始日期<input name="startDate" type="date"></label>
<label>结束日期<input name="endDate" type="date"></label>
<label>优先级<select name="priority"><option value="1">1 · 紧急</option><option value="2">2 · 高</option><option value="3" selected>3 · 正常</option><option value="4">4 · 低</option><option value="5">5 · 挂起候选</option></select></label>
<label>状态<select name="state"><option value="active">进行中</option><option value="paused">已挂起</option></select></label>
@@ -41,13 +41,16 @@
</div>
<div class="metric-heading"><div><p class="eyebrow">衡量标准</p><h3>多个指标可同时记录</h3></div><button class="button subtle" id="addMetricButton" type="button">+ 添加指标</button></div>
<div id="metricEditor" class="metric-editor"></div>
<div class="dialog-actions"><button class="button" value="cancel" formmethod="dialog">取消</button><button class="button primary" id="saveTaskButton" type="submit">保存任务</button></div>
<div class="dialog-actions"><button class="button" id="cancelTaskButton" type="button">取消</button><button class="button primary" id="saveTaskButton" type="submit">保存任务</button></div>
</form>
</dialog>
<dialog id="adminDialog" class="dialog admin-dialog">
<div class="dialog-heading"><div><p class="eyebrow">后台</p><h2>数据管理</h2></div><button class="icon-button" id="closeAdminButton" title="关闭" aria-label="关闭">×</button></div>
<div class="admin-actions"><button class="button" id="exportButton">导出 JSON</button><button class="button" id="archiveButton">执行补归档</button><button class="button" id="logoutButton">退出登录</button></div>
<div class="admin-actions"><button class="button" id="exportButton">导出 JSON</button><button class="button" id="importButton">导入还原</button><input type="file" id="importFile" accept="application/json,.json" hidden><button class="button" id="archiveButton">执行补归档</button><button class="button" id="logoutButton">退出登录</button></div>
<div class="admin-stats" id="adminStats"></div>
<h3>全部任务</h3>
<div id="taskAdminList" class="trash-list"></div>
<h3>回收站</h3>
<div id="trashList" class="trash-list"></div>
</dialog>