Files
personal-progress-monitor/app/static/index.html
MobKBK 02b81f0113 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>
2026-08-24 12:11:03 +08:00

73 lines
4.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>进度台</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div id="app" class="app-shell" aria-busy="true">
<header class="topbar">
<button class="brand-button" id="homeButton" type="button" title="返回总览">
<span class="brand-mark" aria-hidden="true">P</span>
<span><strong>进度台</strong><small id="logicalDate"></small></span>
</button>
<div class="top-actions">
<button class="button primary" id="addTaskButton" type="button">+ 新建任务</button>
<button class="icon-button" id="adminButton" type="button" title="数据管理" aria-label="数据管理">⚙</button>
</div>
</header>
<div class="workspace">
<aside id="sidebar" class="sidebar"></aside>
<main id="main" class="main-content"></main>
</div>
</div>
<dialog id="taskDialog" class="dialog task-dialog">
<form id="taskForm">
<div class="dialog-heading">
<div><p class="eyebrow">任务定义</p><h2 id="taskDialogTitle">新建任务</h2></div>
<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"></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>
<label class="wide">说明<textarea name="description" maxlength="2000" rows="3"></textarea></label>
</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" 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="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>
<div id="loginOverlay" class="login-overlay" hidden>
<form id="loginForm" class="login-panel">
<span class="brand-mark large" aria-hidden="true">P</span>
<p class="eyebrow">PERSONAL PROGRESS</p>
<h1>登录进度台</h1>
<label>访问密码<input name="password" type="password" autocomplete="current-password" required autofocus></label>
<p id="loginError" class="form-error" role="alert"></p>
<button class="button primary wide-button" type="submit">登录</button>
</form>
</div>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="/static/app.js" defer></script>
</body>
</html>