文件
workbuddy-portal/workbuddy_portal/web/templates/profile.html
T
wangchuanli 3751dffef9 feat: 新增备份恢复与公网加固
- 新增备份管理页与 API:在线快照、自动周期备份、按份数清理、下载、一键恢复(恢复前自动兜底)
- 新增 /profile/export,普通用户可导出本人全部数据(不含 Cookie 明文)
- 修复 X-Forwarded-For 可伪造导致三道 IP 防线失效,统一走 client_ip() 取客户端地址
- 取消 admin123 硬编码默认口令,留空则生成随机初始口令并仅打印一次
- .dockerignore 排除 backups/ 并加构建期断言,防止密钥随镜像分发
- 新增会话版本号,改密/停用/删除及恢复备份后其他会话立即失效
- 新增容器资源上限、采集跨度硬顶 31 天、重操作最小间隔与并发 409
- 新增访问日志、HSTS 条件下发、口令黑名单、验证码抗模板匹配、instance.json 0600
- 版本号升至 1.4.0,同步更新 README、SECURITY、.env.example 与 compose 配置
2026-09-18 08:46:34 +08:00

126 行
6.0 KiB
HTML
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
{% extends "base.html" %}
{% block title %}个人中心 · {{ project_title }}{% endblock %}
{% block body %}
<div class="pagehead">
<div>
<h1>个人中心</h1>
<p class="lead">账号 <b>{{ me.username }}</b> · 注册于 {{ (me.created_at or '')[:16] }} ·
最近登录 {{ (me.last_login_at or '未登录')[:19] }}</p>
</div>
<div class="actions">
<a class="btn" href="{{ url_for('views.config_page') }}">管理我的凭证</a>
<a class="btn ghost" href="{{ url_for('views.profile_export') }}"
title="导出你的全部用量明细、采集历史、操作审计与有效配置(不含 Cookie 明文)">导出我的全部数据</a>
</div>
</div>
<div class="kpis">
<div class="kpi" style="--c:var(--cyan)">
<span>我的记录</span><b>{{ '{:,}'.format(my.n or 0) }}</b>
<i>{{ my.d0 or '—' }} ~ {{ my.d1 or '—' }}</i>
</div>
<div class="kpi" style="--c:var(--violet)">
<span>我的积分</span><b>{{ '%.2f'|format(my.c or 0) }}</b>
<i>仅统计归属本账号的数据</i>
</div>
<div class="kpi" style="--c:var(--blue)">
<span>采集次数</span><b>{{ '{:,}'.format(runs) }}</b>
<i>{% if sch.last %}最近 {{ sch.last.started_at[5:16] if sch.last.started_at else '—' }}{% else %}尚无采集{% endif %}</i>
</div>
<div class="kpi" style="--c:{{ 'var(--green)' if cred.set and not cred.broken else 'var(--red)' }}">
<span>我的 Cookie</span>
<b>{% if cred.broken %}无法解密{% elif cred.set %}已配置{% else %}未配置{% endif %}</b>
<i>{% if cred.set and not cred.broken %}{{ cred.chars }} 字符,结尾 …{{ cred.tail }}
{%- elif cred.broken %}实例密钥被更换,请重新粘贴
{%- else %}采集需要本人凭证{% endif %}</i>
</div>
</div>
<div class="grid2">
<section class="card">
<h2>修改资料</h2>
<form id="formProfile">
<label class="row"><span>用户名</span>
<input value="{{ me.username }}" disabled spellcheck="false">
<em class="unit">登录名不可改</em></label>
<label class="row"><span>显示名</span>
<input name="display_name" maxlength="64" value="{{ me.display_name or '' }}" spellcheck="false"></label>
<label class="row"><span>邮箱</span>
<input name="email" type="email" maxlength="128" value="{{ me.email or '' }}" spellcheck="false"></label>
<button class="btn primary" type="submit">保存资料</button>
</form>
</section>
<section class="card">
<h2>修改登录密码</h2>
<form id="formPwd">
<label class="col">原密码<input name="old" type="password" autocomplete="current-password"></label>
<label class="col">新密码<input name="new" type="password" autocomplete="new-password"></label>
<label class="col">确认新密码<input name="new2" type="password" autocomplete="new-password"></label>
<button class="btn primary" type="submit">修改密码</button>
<p class="hint">至少 {{ pwd_min }} 位,且需包含大写字母、小写字母、数字、符号中的至少两类,
且不能是常见弱口令。
<b>修改成功后其他设备上的登录会立刻失效</b>(本机这次会话保留,不必重新登录)——
「怀疑被盗所以改密码」才能真正生效。</p>
</form>
</section>
</div>
<section class="card">
<div class="cardhead">
<h2>导出我的全部数据</h2>
<span class="tag mute">数据可携带</span>
</div>
<p class="hint" style="margin-top:0">
打包下载<b>属于你账号</b>的所有内容:用量明细、采集历史、与你相关的操作审计、
以及账号信息与对你有效的配置。
<br><b>不含</b> Cookie 明文(只写「配没配、多少字符、什么时候更新的」)、
也不含其他任何账号的数据与实例级运行日志。
</p>
<div class="btnrow">
<a class="btn primary" href="{{ url_for('views.profile_export') }}">导出我的全部数据(zip)</a>
<a class="btn ghost" href="{{ url_for('views.records') }}">只导出用量明细(CSV)</a>
</div>
<p class="hint">导出动作按账号有最小间隔(10 秒)保护,避免脚本反复触发整表扫描。</p>
</section>
<section class="card">
<div class="cardhead">
<h2>我的采集凭证</h2>
<span class="tag {{ 'ok' if cred.set and not cred.broken else ('bad' if not cred.set else 'warn') }}">
{{ '正常' if cred.set and not cred.broken else ('未配置' if not cred.set else '需要重配') }}</span>
</div>
<table class="kv">
<tr><th>状态</th><td>
{% if cred.broken %}<span class="tag bad">已保存但无法解密</span>,请到「配置管理」重新粘贴
{% elif cred.set %}<span class="tag ok">已保存(密文入库)</span>
{% else %}<span class="tag bad">未配置</span>{% endif %}
</td></tr>
<tr><th>字符数 / 尾部</th><td class="mono">{{ cred.chars or 0 }} / {{ ('…' + cred.tail) if cred.tail else '—' }}</td></tr>
<tr><th>最后更新</th><td class="mono">{{ cred.at or '—' }}</td></tr>
<tr><th>调度</th><td>
{% if sch.enabled %}{{ sch.times | join(' · ') or '未设置时刻' }}{% else %}<span class="tag bad">已停用</span>{% endif %}
{% if sch.next_run %}· 下次 <span class="mono">{{ sch.next_run }}</span>{% endif %}
</td></tr>
</table>
<p class="hint">
凭证以密文形式存在数据库里(主密钥在 <code>data/instance.json</code>),
页面与接口**任何时候都不回传明文**,只显示长度与尾部 4 位。要更换请到
<a href="{{ url_for('views.config_page') }}">配置管理</a>粘贴新的 Cookie 与 User-Agent
(两者必须取自同一次浏览器请求)。
</p>
</section>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<script>
WBU.bindForm('#formProfile', '/api/profile');
WBU.bindForm('#formPwd', '/api/password', {
validate: function (d) { return d.new === d.new2 ? null : '两次输入的新密码不一致'; }
});
</script>
{% endblock %}