feat: 设备管理批量工具栏添加清理离线设备按钮(无刷新勾选+批量操作统一入口)

This commit is contained in:
tian 2026-07-24 12:09:59 +08:00
parent 5313bb5a89
commit 16f9ef9bef
3 changed files with 30 additions and 26 deletions

View File

@ -1595,21 +1595,21 @@ func (u *UI) actionDevicesCleanupOffline(w http.ResponseWriter, r *http.Request)
continue
}
dev := deviceMap[id]
if dev == nil || dev.Online {
continue // only clean offline devices
if dev != nil && dev.Online {
continue // skip online devices
}
if strings.TrimSpace(u.dbPath) != "" {
if store, err := storage.OpenSQLite(u.dbPath); err == nil {
db := store.DB()
db.Exec("DELETE FROM device_assignments WHERE device_id = ?", dev.DeviceID)
db.Exec("DELETE FROM device_config_state WHERE device_id = ?", dev.DeviceID)
db.Exec("DELETE FROM device_features WHERE device_id = ?", dev.DeviceID)
db.Exec("DELETE FROM config_versions WHERE device_id = ?", dev.DeviceID)
db.Exec("DELETE FROM devices WHERE device_id = ?", dev.DeviceID)
db.Exec("DELETE FROM device_assignments WHERE device_id = ?", id)
db.Exec("DELETE FROM device_config_state WHERE device_id = ?", id)
db.Exec("DELETE FROM device_features WHERE device_id = ?", id)
db.Exec("DELETE FROM config_versions WHERE device_id = ?", id)
db.Exec("DELETE FROM devices WHERE device_id = ?", id)
store.Close()
deleted++
}
}
deleted++
}
http.Redirect(w, r, "/devices?msg="+urlQueryEscape(fmt.Sprintf("已清理 %d 台离线设备", deleted)), http.StatusFound)
}
@ -1688,6 +1688,11 @@ func (u *UI) actionDiscoverySearch(w http.ResponseWriter, r *http.Request) {
func (u *UI) actionDevicesBatchAction(w http.ResponseWriter, r *http.Request) {
_ = r.ParseForm()
action := strings.TrimSpace(r.FormValue("action"))
// cleanup_offline handles device IDs directly (may include DB-only offline devices)
if action == "cleanup_offline" {
u.actionDevicesCleanupOffline(w, r)
return
}
deviceIDs := filterSelectedDeviceIDs(u.registry.GetDevices(), r.Form["device_id"])
if len(deviceIDs) == 0 {
u.render(w, r, "devices", u.deviceOverviewPageData(r, nil, "请先选择设备"))

View File

@ -43,15 +43,14 @@
<button type="submit" name="action" value="media_stop" class="danger">停止服务</button>
<button type="submit" name="action" value="reload" class="secondary" {{if .ReloadSummary}}onclick='return confirm("将重载当前运行配置:{{.ReloadSummary}}")'{{end}}>重载运行配置</button>
<button type="submit" name="action" value="rollback" class="secondary" {{if .RollbackSummary}}onclick='return confirm("将回滚到上一版运行配置:{{.RollbackSummary}}")'{{end}}>回滚运行配置</button>
{{$allOffline := true}}{{range .SelectedDevices}}{{if .Online}}{{$allOffline = false}}{{end}}{{end}}
{{if $allOffline}}
<button type="submit" name="action" value="cleanup_offline" class="danger" onclick="return confirm('确认清理选中的 ' + {{len .SelectedDevices}} + ' 台离线设备及其全部关联数据?此操作不可恢复。')">清理选中离线设备</button>
{{end}}
<a class="btn secondary" href="{{.BatchConfigURL}}">下发设备分配</a>
<a class="btn secondary" href="/devices">清空选择</a>
</div>
</div>
{{$allOffline := true}}{{range .SelectedDevices}}{{if .Online}}{{$allOffline = false}}{{end}}{{end}}
<form method="post" action="/devices/cleanup-offline" id="cleanup-form" style="margin:8px 0{{if not (and .SelectedDeviceIDs $allOffline)}};display:none{{end}}" onsubmit="return confirm('确认清理选中的离线设备及其全部关联数据?此操作不可恢复。')">
{{range .SelectedDevices}}<input type="hidden" name="device_id" value="{{.DeviceID}}">{{end}}
<button type="submit" class="btn danger">清理选中离线设备</button>
</form>
<div class="table-wrap">
<table id="device-list">
<thead>
@ -129,6 +128,11 @@
</table>
</div>
</form>
{{/* Cleanup bar only shown by JS when all selected are offline */}}
<div id="cleanup-bar" style="display:none">
<span class="muted small" style="margin-right:8px">已选全部离线</span>
</div>
</div>
<script>
@ -162,9 +166,10 @@
const countEl = toolbar.querySelector('.batch-toolbar-count');
if (countEl) countEl.textContent = `已选 ${checked.length} 台`;
}
// Show or hide cleanup form based on selected devices' online status
const cleanupForm = document.getElementById('cleanup-form');
if (cleanupForm) {
// Show/hide cleanup bar based on whether ALL selected devices are offline
const cleanupBar = document.getElementById('cleanup-bar');
const cleanupIds = document.getElementById('cleanup-device-ids');
if (cleanupBar && cleanupIds) {
let allOffline = checked.length > 0;
checked.forEach(cb => {
const row = cb.closest('tr');
@ -173,17 +178,11 @@
if (statusCell && !statusCell.textContent.includes('离线')) allOffline = false;
}
});
cleanupForm.style.display = allOffline ? '' : 'none';
// Update hidden inputs to match selected devices
cleanupBar.style.display = allOffline ? '' : 'none';
if (allOffline) {
cleanupForm.querySelectorAll('input[type="hidden"]').forEach(el => el.remove());
checked.forEach(cb => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'device_id';
input.value = cb.value;
cleanupForm.appendChild(input);
});
const ids = [];
checked.forEach(cb => ids.push(cb.value));
cleanupIds.value = ids.join(',');
}
}
};

Binary file not shown.