Files
rond-with-you/internal/web/templates/admin/settings.html
T
tamakyi d4e4257a23 启动模板自检 + 站长导航放行 + 统计脚本关口收紧
排查:/admin/settings 渲染到一半就断
- 现象:页面在「统计脚本」那张卡片处截断,没有保存按钮、没有 </form></html>,而 HTTP 仍是 200。
- 根因:跑的是旧二进制(改动前编译),磁盘上的模板却是新的。debug=true 下 pageSet()
  每次渲染都从磁盘重解析模板,于是新 settings.html 引用了旧 setting.Settings 里没有的
  TrackEnabled —— 执行期报错,而 render 只把错误写进日志,客户端只看到半张页面。
- 判据:strings <二进制> | grep -c cleanTrackScript(旧 0 / 新 2)。别用 strings | grep
  TrackScript —— config 里本来就有这个名字,旧二进制也命中,是个假信号。

启动自检(防复发)
- template_smoke.go:pageProbe 给 24 个页面各配一份最小可用数据,New() 里全部渲染一遍,
  任一页失败就拒绝启动。pageProbe 与 pageFiles 双向校验,新加页面必须补探针,
  漏配按失败处理(不是省事)。
- smokeOne 用 recover 兜住方法里的 panic,报「哪一页坏了」而不是崩栈。
- 实测:往 settings.html 插一个不存在的字段 → 启动即报
  「模板自检未通过…admin/settings: can't evaluate field …」,退出码 1。
- 门禁 template_smoke_test.go:一条跑全量自检,一条变异验证(探针换成字段更少的类型
  必须报错且点名该页)。

站长导航被页面开关误伤
- guardPage 只拦访客(站长始终能访问全部公开页),但导航写的是 {{if .Settings.HasPage "x"}},
  没有 IsAdmin 分支 ⇒ 页面开关一关,站长自己也只剩「主页」一个入口 —— 能访问却点不到。
  改成 {{if or .IsAdmin (.Settings.HasPage "x")}}(map/places/timeline/stats/report 五处)。
- 门禁 nav_visibility_test.go:三向不变量(站长仍有全部入口 / 访客一个都没有 /
  全开放时访客也看得到),做过变异验证。
- 访客侧「只有主页」是按后台配置应有的状态,没有动数据。

统计脚本关口收紧(cleanTrackScript)
- src= 只在 <script …> 开标签内部找。原来对整段做 strings.Index,脚本体里一句
  `img.src=u` 就被命中、整段被判危险而清空 —— 页面照样提示「设置已保存」,站长看不出没挂上。
- 开闭标签从「只拒 shut > open」改成严格配平:末尾未闭合的 <script> 会把页面收尾整段吞进脚本。
- 不再静默:签名改成 (script, warn),说明码经 ?warn= 回显成人话;判为危险时整段不采纳、
  库里原脚本与开关都不动(把站长手上好用的脚本一并清掉更糟)。
- 门禁 TestCleanTrackScript 重写:逐条边界 + 定义性不变量(采纳了非空结果时,
  里面开闭标签数必定相等)+ TestTrackWarnMessage(未识别码不得回显)。变异验证做过。
- 文案订正:settings.html 的「conf/app.ini 的 track_script 会作为这里的默认值」有歧义 ——
  它不回填文本框,而是「这个框留空时、注入那一刻才回落到 ini」。

其余
- bin/ 目录删除(15 个 exe / 412MB),二进制只留仓库根 rond.exe;
  技能 rond-web-verify 与 memory details 十八里 bin/rond.exe 的写法同步改掉。
- 验收:./scripts/verify.sh 全绿且 0 skip(PASS=346 FAIL=0 SKIP=0)。
2026-10-09 11:06:41 +08:00

174 lines
11 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.
{{define "content"}}
<div class="wrap">
<div class="page-head">
<div>
<h1>站点设置</h1>
<div class="sub">控制主页展示的模块、隐私处理与地图底图</div>
</div>
<div class="row"><a class="btn" href="/admin">← 返回后台</a></div>
</div>
{{if .Flash}}<div class="alert ok">设置已保存</div>{{end}}
{{if .Warn}}<div class="alert err">{{.Warn}}</div>{{end}}
<form method="post" action="/admin/settings">
<div class="card">
<h2>基本信息</h2>
<div class="card-sub">展示在主页顶部与浏览器标题</div>
<div class="grid g2">
<label class="f"><span>站点标题</span><input type="text" name="site_title" value="{{.Settings.SiteTitle}}" maxlength="40"></label>
<label class="f"><span>副标题</span><input type="text" name="site_subtitle" value="{{.Settings.SiteSubtitle}}" maxlength="80" placeholder="记录我去过的每一个地方"></label>
<label class="f"><span>署名</span><input type="text" name="owner_name" value="{{.Settings.OwnerName}}" maxlength="30"></label>
<label class="f"><span>头像(单个 emoji)</span><input type="text" name="owner_avatar" value="{{.Settings.OwnerAvatar}}" maxlength="8"></label>
</div>
<label class="f"><span>主色调</span>
<div class="row">
<input type="color" name="accent_color" value="{{.Settings.AccentColor}}" style="width:56px;height:34px;padding:2px">
<span class="muted">用于按钮、图表与选中状态</span>
</div>
</label>
</div>
<div class="card">
<h2>主页展示模块</h2>
<div class="card-sub">取消勾选的模块不会出现在主页上;一个都不选时按全部展示处理</div>
<div class="grid g2">
{{range .Modules}}
<label class="check">
<input type="checkbox" name="modules" value="{{.Key}}" {{if $.Settings.Has .Key}}checked{{end}}>
<span><b style="font-weight:500">{{.Name}}</b> <span class="muted">— {{.Desc}}</span></span>
</label>
{{end}}
</div>
</div>
<div class="card">
<h2>公开页面</h2>
<div class="card-sub">控制访客能在导航里看到并访问哪些页面;你登录后始终可以访问全部页面</div>
<div class="grid g2">
{{range .Pages}}
<label class="check">
<input type="checkbox" name="pages" value="{{.Key}}" {{if $.Settings.HasPage .Key}}checked{{end}}>
<span><b style="font-weight:500">{{.Name}}</b> <span class="muted">— {{.Desc}}</span></span>
</label>
{{end}}
</div>
<div class="muted" style="font-size:12.5px;margin-top:8px">
关闭的页面在访客导航中隐藏,直接输入网址也会得到 404。主页不受此开关影响。
</div>
<label class="f" style="max-width:320px;margin-top:14px">
<span>访客默认页面</span>
<select name="visitor_home">
<option value="home" {{if eq .Settings.VisitorHome "home"}}selected{{end}}>主页(默认)</option>
{{range .Pages}}<option value="{{.Key}}" {{if eq $.Settings.VisitorHome .Key}}selected{{end}}>{{.Name}}</option>{{end}}
</select>
</label>
<div class="muted" style="font-size:12.5px;margin-top:6px">
访客访问站点根路径时直接落到这里;你登录后访问根路径仍然看主页。
选择了一个没对访客开放的页面时不会跳转(会留在主页)。
</div>
</div>
<div class="card">
<h2>访客可见区域</h2>
<div class="card-sub">
一行一个,省 / 市 / 区都认(例如「广东省」「贵阳市」「南明区」)。
留空表示不限制,访客能看到全部数据;你自己登录后始终是全量。
</div>
<label class="check"><input type="radio" name="regions_mode" value="whitelist" {{if eq .Settings.RegionsMode "whitelist"}}checked{{end}}> 白名单:访客<span class="nobr">只能看到这些区域</span>的记录</label>
<label class="check"><input type="radio" name="regions_mode" value="blacklist" {{if eq .Settings.RegionsMode "blacklist"}}checked{{end}}> 黑名单:这些区域的记录<span class="nobr">对访客隐藏</span>,其余照常开放</label>
<label class="f" style="max-width:520px;margin-top:10px">
<span>区域列表</span>
<textarea name="regions" rows="5" placeholder="广东省&#10;贵阳市&#10;南明区">{{range .Settings.Regions}}{{.}}
{{end}}</textarea>
</label>
</div>
<div class="card">
<h2>隐私处理</h2>
<div class="card-sub">「家」和「工作」通常是敏感位置,默认已隐藏</div>
<label class="check"><input type="checkbox" name="hide_home" {{if .Settings.HideHome}}checked{{end}}> 隐藏类型为「家」的记录</label>
<label class="check"><input type="checkbox" name="hide_work" {{if .Settings.HideWork}}checked{{end}}> 隐藏类型为「工作」的记录</label>
<label class="check"><input type="checkbox" name="notes_public" {{if .Settings.NotesPublic}}checked{{end}}> 地点备注对访客公开(不勾选则只有你自己能看到)</label>
<label class="check"><input type="checkbox" name="marks_public" {{if .Settings.MarksPublic}}checked{{end}}> 「推荐 / 踩雷」标记与票数对访客公开</label>
<div class="muted" style="font-size:12.5px">
隐藏后匿名访客看不到这两类记录。你登录后可在任意公开页面用筛选器上的
<span class="mono">含家/工作</span> 开关临时显示给自己看。
</div>
<label class="f" style="max-width:280px;margin-top:12px">
<span>忽略停留时间过短的记录</span>
<select name="min_dwell_min">
<option value="0" {{if eq .Settings.MinDwellMin 0}}selected{{end}}>不过滤</option>
<option value="2" {{if eq .Settings.MinDwellMin 2}}selected{{end}}>少于 2 分钟</option>
<option value="5" {{if eq .Settings.MinDwellMin 5}}selected{{end}}>少于 5 分钟</option>
<option value="10" {{if eq .Settings.MinDwellMin 10}}selected{{end}}>少于 10 分钟</option>
</select>
</label>
<div class="muted" style="font-size:12.5px">该设置作用于全站统计与地图,可有效过滤路过点。</div>
</div>
<div class="card">
<h2>地图底图</h2>
<div class="card-sub">在 <span class="mono">conf/app.ini</span> 中配置,改完重启生效</div>
<div class="kv"><span class="k">当前生效</span><span class="v">{{.MapInfo}}</span></div>
{{if .MapLayers}}
<label class="f" style="max-width:340px;margin-top:10px"><span>默认用哪张底图</span>
<select name="map_default_base">
<option value="" {{if not .Settings.MapDefaultBase}}selected{{end}}>跟随第一张({{(index .MapLayers 0).Label}})</option>
{{range .MapLayers}}<option value="{{.Key}}" {{if eq .Key $.Settings.MapDefaultBase}}selected{{end}}>{{.Label}}</option>{{end}}
</select>
</label>
<div class="card-sub" style="margin-top:0">放大后底图自带的 POI 与路名标注太密、看不清自己的点时,换成<b>纯路网</b>。
访客在地图上手动切换过的话,浏览器会记住他自己的选择。</div>
{{end}}
<div class="muted" style="font-size:12.5px;margin-top:8px">
<div><span class="mono">map_provider</span>:<span class="mono">amap</span>(默认,免密钥)/ <span class="mono">tianditu</span>(需 <span class="mono">map_key</span>)/ <span class="mono">custom</span>(自备 <span class="mono">map_tile_url</span>)</div>
<div><span class="mono">map_key</span>:填写后瓦片改由服务端代理,密钥不会出现在页面源码里。</div>
<div><span class="mono">map_crs</span>:仅 <span class="mono">custom</span> 使用,填 <span class="mono">gcj02</span> 或 <span class="mono">wgs84</span>;站点数据本身是 GCJ-02,换成 WGS-84 底图(天地图 / OSM)时会自动纠偏。</div>
</div>
</div>
<div class="card">
<h2>图片存储</h2>
<div class="card-sub">地点图片存到哪儿。图片在<b>浏览器里</b>转成 WebP 后才上传,服务器只负责收下与转存</div>
<label class="check"><input type="radio" name="media_backend" value="local" {{if ne .Settings.MediaBackend "s3"}}checked{{end}}> 服务器本地磁盘</label>
<label class="check"><input type="radio" name="media_backend" value="s3" {{if eq .Settings.MediaBackend "s3"}}checked{{end}}> 对象存储(S3 兼容,如 Cloudflare R2)</label>
<div class="kv" style="margin-top:8px"><span class="k">对象存储</span><span class="v">{{.S3Info}}</span></div>
<div class="muted" style="font-size:12.5px;margin-top:8px">
<div>凭据放在 <span class="mono">conf/app.ini</span>,不进数据库、也不会下发到前端:</div>
<div><span class="mono">s3_endpoint</span>(R2 形如 <span class="mono">https://&lt;账号&gt;.r2.cloudflarestorage.com</span>)、<span class="mono">s3_bucket</span>、<span class="mono">s3_access_key</span>、<span class="mono">s3_secret_key</span>、<span class="mono">s3_region</span>(R2 填 <span class="mono">auto</span>)。</div>
<div><span class="mono">s3_public_base</span>:桶的公开域名(自定义域或 r2.dev),拼上对象名就是 <span class="mono">&lt;img src&gt;</span>;不填的话图片存得进去但浏览器取不到。</div>
<div>改了 <span class="mono">conf/app.ini</span> 需要重启;这里的「存哪边」保存后立即生效,已经存过的老图仍按各自原来的位置取,不受影响。</div>
</div>
</div>
<div class="card">
<h2>统计脚本</h2>
<div class="card-sub">第三方访问统计 / 追踪脚本。默认关闭,打开后只注入<b>公开页面</b>,后台与登录页不挂</div>
<label class="check"><input type="checkbox" name="track_enabled" {{if .Settings.TrackEnabled}}checked{{end}}> 启用统计脚本</label>
<label class="f" style="margin-top:10px">
<span>脚本内容</span>
<textarea name="track_script" rows="4"
placeholder="https://example.com/analytics.js">{{.Settings.TrackScript}}</textarea>
</label>
<div class="muted" style="font-size:12.5px">
<div>填一个 <span class="mono">https://…</span> 地址会包成 <span class="mono">&lt;script src=…&gt;</span>;
需要初始化代码的服务(Umami、百度统计等)可以把整段 <span class="mono">&lt;script&gt;</span> 贴进来。</div>
<div>整段脚本的 <span class="mono">&lt;script&gt;</span> 与 <span class="mono">&lt;/script&gt;</span> 必须数量相同:
多一个 <span class="mono">&lt;/script&gt;</span> 会提前闭合、少一个会把页面收尾吞进脚本,两种情况都拒收;
<span class="mono">src</span> 只接受 <span class="mono">https://</span>、<span class="mono">http://</span> 或以 <span class="mono">/</span> 开头的地址,
超过 4096 字符会被截断(并给出提示)。填了但没勾开关不会生效。</div>
<div>这个框留空时回落到 <span class="mono">conf/app.ini</span> 的 <span class="mono">track_script</span>
——它不会回填到上面这个框(改 ini 要重启)。</div>
</div>
</div>
<div class="row">
<button class="btn primary" type="submit">保存设置</button>
<a class="btn" href="/admin">取消</a>
</div>
</form>
</div>
{{end}}