129 lines
4.6 KiB
HTML
129 lines
4.6 KiB
HTML
{{define "config_versions"}}
|
|
{{template "device_header" .}}
|
|
|
|
<div class="card">
|
|
<div class="section-title">
|
|
<div>
|
|
<h2>配置历史</h2>
|
|
<div class="muted small">每次下发配置时自动保存的快照,可对比任意两个版本。</div>
|
|
</div>
|
|
<div class="actions">
|
|
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}">← 返回设备</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{if .ConfigVersions}}
|
|
<div style="margin-bottom:16px">
|
|
<span style="font-size:12px;color:var(--muted);margin-right:8px">对比:</span>
|
|
<select id="version-a" style="width:200px;font-size:12px">
|
|
<option value="">选择版本 A</option>
|
|
{{range .ConfigVersions}}
|
|
<option value="{{.ID}}">{{.CreatedAt}} · {{shortID .ConfigID}}</option>
|
|
{{end}}
|
|
</select>
|
|
<span style="margin:0 8px;color:var(--muted)">vs</span>
|
|
<select id="version-b" style="width:200px;font-size:12px">
|
|
<option value="">选择版本 B</option>
|
|
{{range .ConfigVersions}}
|
|
<option value="{{.ID}}">{{.CreatedAt}} · {{shortID .ConfigID}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
|
|
<div id="diff-result" style="display:none">
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
|
<div>
|
|
<div style="font-size:11px;color:var(--muted);margin-bottom:4px" id="label-a"></div>
|
|
<pre id="diff-a" style="height:400px;overflow:auto;font-size:11px;line-height:1.5;margin:0"></pre>
|
|
</div>
|
|
<div>
|
|
<div style="font-size:11px;color:var(--muted);margin-bottom:4px" id="label-b"></div>
|
|
<pre id="diff-b" style="height:400px;overflow:auto;font-size:11px;line-height:1.5;margin:0"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-wrap" style="margin-top:16px">
|
|
<table>
|
|
<thead><tr><th>时间</th><th>配置 ID</th><th>大小</th></tr></thead>
|
|
<tbody>
|
|
{{range .ConfigVersions}}
|
|
<tr>
|
|
<td class="mono small">{{.CreatedAt}}</td>
|
|
<td class="mono small">{{shortID .ConfigID}}</td>
|
|
<td class="small">{{len .ConfigJSON}} 字符</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{else}}
|
|
<div class="empty-state">
|
|
<div class="empty-title">暂无配置历史</div>
|
|
<div class="muted">通过管控台或部署向导下发配置后,快照将自动保存在这里。</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<script>
|
|
(function(){
|
|
var selA = document.getElementById("version-a");
|
|
var selB = document.getElementById("version-b");
|
|
var diffResult = document.getElementById("diff-result");
|
|
if (!selA || !selB) return;
|
|
|
|
function formatJSON(s) {
|
|
try {
|
|
return JSON.stringify(JSON.parse(s), null, 2);
|
|
} catch(e) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
function compare() {
|
|
var a = selA.value, b = selB.value;
|
|
if (!a || !b) { diffResult.style.display = "none"; return; }
|
|
var labelA = selA.options[selA.selectedIndex].text;
|
|
var labelB = selB.options[selB.selectedIndex].text;
|
|
document.getElementById("label-a").textContent = labelA;
|
|
document.getElementById("label-b").textContent = labelB;
|
|
|
|
Promise.all([
|
|
fetch("/ui/devices/{{.Device.DeviceID}}/config-versions/"+a).then(function(r){return r.json()}),
|
|
fetch("/ui/devices/{{.Device.DeviceID}}/config-versions/"+b).then(function(r){return r.json()})
|
|
]).then(function(results){
|
|
var linesA = formatJSON(results[0].config_json).split("\n");
|
|
var linesB = formatJSON(results[1].config_json).split("\n");
|
|
var maxLen = Math.max(linesA.length, linesB.length);
|
|
var outA = [], outB = [];
|
|
for (var i = 0; i < maxLen; i++) {
|
|
var la = linesA[i] || "", lb = linesB[i] || "";
|
|
if (la === lb) {
|
|
outA.push(la);
|
|
outB.push(lb);
|
|
} else {
|
|
outA.push('\x1b[41m' + la + '\x1b[0m');
|
|
outB.push('\x1b[42m' + lb + '\x1b[0m');
|
|
}
|
|
}
|
|
var preA = document.getElementById("diff-a");
|
|
var preB = document.getElementById("diff-b");
|
|
function render(lines) {
|
|
return lines.map(function(l){
|
|
if (l.indexOf('\x1b[41m') >= 0) return '<span style="background:rgba(220,70,70,.25);display:block">' + l.replace(/\x1b\[41m/g,"").replace(/\x1b\[0m/g,"") + '</span>';
|
|
if (l.indexOf('\x1b[42m') >= 0) return '<span style="background:rgba(70,180,70,.25);display:block">' + l.replace(/\x1b\[42m/g,"").replace(/\x1b\[0m/g,"") + '</span>';
|
|
return '<span style="display:block">' + l + '</span>';
|
|
}).join("");
|
|
}
|
|
preA.innerHTML = render(outA);
|
|
preB.innerHTML = render(outB);
|
|
diffResult.style.display = "";
|
|
});
|
|
}
|
|
|
|
selA.addEventListener("change", compare);
|
|
selB.addEventListener("change", compare);
|
|
})();
|
|
</script>
|
|
{{end}}
|