写文案时去核这条,先把一条写错的断言纠正了:导出条数本来就不等于站点条数。 tableWriter.finish() 是两段:先删「基线里有、PG 已没有」的行(known − seen), 再把「导入时进不了 PG 但留档还在」的行(w.skipped × raws)按原样补回——否则 「导入→导出」一趟会凭空少数据。实测 823 条到访的数据集导出包里是 824 条,差值正常。 (第一版成功路径用例断言「导出 == 站点」,红在 824 != 823 就是这个原因;已改成更锋利的 「换底座前后导出包里 ZVISIT 的 UUID 集合逐条一致」。) 同一段代码定下了换基线的语义:新底座里站点没有的行会被清掉(进 known 没进 seen)。 所以换一份更新的手机包**不会**把手机新记录带进导出包,要带得先走「分析差异」合并。 页面提示与 flash 文案都写明了这点,并新增用例 TestSwapBaselineDoesNotCarryRowsTheSiteDoesNotHave 钉住它(站内补一条到访 → 导成包 → 从站内删掉 → 换底座 → 断言导出里既没有那条的派生 UUID、UUID 集合也与换之前逐条一致)。 验收:./scripts/verify.sh 全量 PASS=319 FAIL=0 SKIP=0。
167 lines
7.1 KiB
HTML
167 lines
7.1 KiB
HTML
{{define "content"}}
|
|
<div class="wrap">
|
|
<div class="page-head">
|
|
<div>
|
|
<h1>换基线</h1>
|
|
<div class="sub">
|
|
「底座」是导出 rondbackup 时铺数据的底稿:导出只覆盖我们托管的那几张实体表,
|
|
<strong>其余内容(Core Data 元数据、索引、ZRAWVISIT 原始采样点…)全部从底座原样带出来</strong>。
|
|
所以底座必须是手机导出的包;换底座<strong>不动站点里的任何数据</strong>,只换底稿。
|
|
</div>
|
|
</div>
|
|
<div class="row"><a class="btn" href="/admin">← 返回后台</a></div>
|
|
</div>
|
|
|
|
{{if not .Dataset}}
|
|
<div class="card">
|
|
<div class="empty"><div class="big">📦</div>还没有导入任何数据集,先上传一份备份再说。</div>
|
|
</div>
|
|
{{else}}
|
|
|
|
<div class="card">
|
|
<h2>当前底座</h2>
|
|
<div class="card-sub">数据集「{{trunc .Dataset.Name 40}}」(#{{.Dataset.ID}})的导出底稿</div>
|
|
<div class="tw">
|
|
<table class="tb tb-stack">
|
|
<thead><tr><th>项目</th><th>值</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>状态</td>
|
|
<td data-label="状态">
|
|
{{if .Status.Exists}}
|
|
<span class="tag">已就绪</span>
|
|
<span class="muted">{{size .Status.Size}} · {{dtt .Status.ModTime}}</span>
|
|
{{else}}
|
|
<span class="tag">文件缺失</span>
|
|
<span class="muted">导出时会按记档自动重建;也可以点下面的「重建」。</span>
|
|
{{end}}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>来源</td>
|
|
<td data-label="来源">
|
|
{{if .Status.SHA}}
|
|
<span class="mono">{{trunc .Status.Name 40}}</span>
|
|
<span class="muted">· {{dtt .Status.At}}</span>
|
|
{{if .FromOriginal}}<span class="tag">与数据集原始包同一份</span>{{end}}
|
|
{{else}}
|
|
<span>数据集最初上传的那份包</span>
|
|
<span class="mono muted">{{trunc .OrigName 40}}</span>
|
|
{{end}}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{{if .Status.Exists}}
|
|
<div class="card-sub" style="margin-top:14px">底座里有什么(换底座前后可以对着看)</div>
|
|
{{if .CountsErr}}
|
|
<div class="alert err">{{.CountsErr}}</div>
|
|
{{else}}
|
|
<div class="tw">
|
|
<table class="tb tb-stack">
|
|
<thead><tr><th>表</th><th>内容</th><th class="num">行数</th></tr></thead>
|
|
<tbody>
|
|
{{range .Counts}}
|
|
<tr>
|
|
<td><span class="mono">{{.Table}}</span></td>
|
|
<td data-label="内容">{{.Label}}</td>
|
|
<td class="num" data-label="行数">{{num .N}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{end}}
|
|
{{else}}
|
|
<div class="row" style="margin-top:12px">
|
|
<form method="post" action="/admin/baseline/rebuild">
|
|
<button class="btn" type="submit">按记档重建底座</button>
|
|
</form>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if .PendingSHA}}
|
|
<div class="card">
|
|
<div class="alert err">
|
|
刚上传的 <span class="mono">{{trunc .PendingName 40}}</span> 与当前数据集只对得上
|
|
<strong>{{num .PendingMatched}}/{{num .PendingWith}}</strong> 条带 UUID 的到访(正常应当有九成以上)。
|
|
它可能不是同一批数据的手机包——换下去会让导出包整个错位:站点每一行都在新底座里
|
|
认不出身份,于是全部按「新行」插到最大编号之后,真机上就是「这些到访全变成新的、老记录还在」。
|
|
</div>
|
|
<div class="row">
|
|
<form method="post" action="/admin/baseline/confirm"
|
|
onsubmit="return confirm('确认这份包确实是同一批数据的手机包?换错会让导出的备份在真机上错位。')">
|
|
<input type="hidden" name="sha" value="{{.PendingSHA}}">
|
|
<input type="hidden" name="name" value="{{.PendingName}}">
|
|
<button class="btn danger" type="submit">我确认,强制替换</button>
|
|
</form>
|
|
<a class="btn" href="/admin/baseline">放弃</a>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="card">
|
|
<h2>换成另一份包当底座</h2>
|
|
<div class="card-sub">
|
|
在 rond App 里<strong>重新导出</strong>一份备份,传到这里。换完之后:
|
|
到访数、地点、备注、主题都不变;导出 rondbackup 立刻以新底座为准。
|
|
判据有两条——这份包得跟当前数据集是同一批数据(按到访 UUID 认),而且得真的能导出成功。
|
|
换之前旧底座会留档,随时可以回滚。
|
|
</div>
|
|
<div class="alert">
|
|
<strong>注意</strong>:导出的行集合以<strong>站点数据</strong>为准(另加少量「导入时进不了站点、
|
|
靠原始留档补回」的行),所以这份包里如果有站点还没导入过的到访(手机上新记录的那些),
|
|
它们<strong>不会</strong>进导出包。想让它们进包,先用后台的「分析差异」把这份备份合并进来,再来换底座。
|
|
</div>
|
|
<form method="post" action="/admin/baseline" enctype="multipart/form-data">
|
|
<div class="row" style="align-items:center;flex-wrap:wrap">
|
|
<input type="file" name="backup" accept=".rondbackup,.zip" required>
|
|
<button class="btn primary" type="submit">上传并换底座</button>
|
|
</div>
|
|
<label class="muted" style="display:block;margin-top:10px;font-size:13px">
|
|
<input type="checkbox" name="force" value="1">
|
|
即使与当前数据集对不上也强制替换(重合率偏低时会再让你确认一次)
|
|
</label>
|
|
<div class="card-sub" style="margin-top:8px">单份上限 {{size .MaxSize}}。</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>旧底座留档</h2>
|
|
<div class="card-sub">每次替换前都会把旧底座改名留一份(最近 3 份)。换错了就回滚。</div>
|
|
{{if .Status.Backups}}
|
|
<div class="tw">
|
|
<table class="tb tb-stack">
|
|
<thead><tr><th>留档</th><th>时间</th><th class="num">大小</th></tr></thead>
|
|
<tbody>
|
|
{{range .Status.Backups}}
|
|
<tr>
|
|
<td><span class="mono">{{trunc .Name 48}}</span></td>
|
|
<td class="muted" data-label="时间">{{dt .At}}</td>
|
|
<td class="num" data-label="大小">{{size .Size}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="row" style="margin-top:12px">
|
|
<form method="post" action="/admin/baseline/rollback"
|
|
onsubmit="return confirm('回滚到最近一份留档的旧底座?当前这份也会被留档,可以再滚回来。')">
|
|
<button class="btn" type="submit">回滚到最近一份</button>
|
|
</form>
|
|
</div>
|
|
<div class="card-sub" style="margin-top:8px">
|
|
回滚后底座的「来源」记档会清空(表示它来自数据集最初那份包),导出照常可用。
|
|
</div>
|
|
{{else}}
|
|
<div class="empty"><div class="big">🗂️</div>还没有留档——这个数据集还没换过底座。</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|