界面(去 AI 味): - 大屏页清除 114 处生成器残留属性 data-page-node-id - 视觉系统改回工程控制台风格:去 radial/linear-gradient、去辉光、 去标题前彩色装饰条,改为中性灰阶 + 单一蓝色强调色;KPI 色条改状态点 - 精简各页说教式长提示;修掉 profile.html 泄漏到页面上的 Markdown 星号 - 删除登录页过时的「默认账号 admin / admin123」提示(1.4.0 起已无默认口令) 安全与隐私(按「将会被公网访问」收口): - 内部异常只回 8 位事件号,完整堆栈进服务端日志(web/api.py::_internal) - 导出文件名收敛:防响应头注入与路径穿越;manage.py passwd 补用户名校验 - 登录对不存在的账号也走一次哑哈希,抹平用户名枚举的时序差异 - /api/* 读接口限速 240 次 / 60 秒 / 账号(挡住循环调 /api/bundle) - 进程 umask 0077 + 目录 0700 / 文件 0600:对话正文与主密钥的落盘权限 - 表名与库文件路径只对管理员下发;大屏页所有数据插值转义 - --debug 只允许绑定回环地址;新增 Permissions-Policy 与 413 处理器 文档: - DEPLOYMENT 新增第十三节「安全与隐私基线」;迁移表补 1.4.0 → 1.5.0 行 - SECURITY 更新支持范围、新增「信息泄漏收敛」小节与上线检查项 - .codebuddy/ 加入 .gitignore(助手工作记忆不进仓库) 版本:1.4.0 → 1.5.0(无库结构变更,user_version 仍为 4) 验证:python tools/smoke.py → ok=264 fail=0;python tools/check_docs.py → 0 处问题
120 行
5.5 KiB
HTML
120 行
5.5 KiB
HTML
{% 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 }} 位,需包含大写字母、小写字母、数字、符号中的至少两类。
|
||
修改成功后其他设备上的登录会立刻失效,本机会话保留。</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">
|
||
下载本账号的用量明细、采集历史、操作审计与有效配置。
|
||
不含 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>),页面与接口都不回传明文。
|
||
更换请到<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 %}
|