[fix] show remote control peers in admin details

This commit is contained in:
dijunkun committed 2026-06-02 11:07:51 +08:00
1 parent 48918df647
commit 37d29b2359
5 files changed
+87 -3

No files matched your search

+16 -3
View File
@@ -180,6 +180,7 @@ const char kAdminHtml[] = R"HTML(<!doctype html>
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px 16px; }
.detail-grid span { color: #667085; display: block; font-size: 12px; }
.detail-grid strong { display: block; font-size: 14px; margin-top: 2px; }
.peer-list { word-break: break-all; }
.sort-order { min-width: 62px; }
.pager { display: flex; gap: 8px; align-items: center; justify-content: flex-end; margin-top: 12px; color: #667085; font-size: 13px; flex-wrap: wrap; }
.status { color: #067647; font-weight: 600; }
@@ -417,14 +418,20 @@ const char kAdminHtml[] = R"HTML(<!doctype html>
}
function sessionSummary(device) {
const controlling = Number(device.active_control_count) || 0;
const controlled = Number(device.active_controlled_count) || 0;
const targets = Array.isArray(device.active_control_targets) ? device.active_control_targets : [];
const controlledBy = Array.isArray(device.active_controlled_by) ? device.active_controlled_by : [];
const controlling = Number(device.active_control_count) || targets.length;
const controlled = Number(device.active_controlled_count) || controlledBy.length;
const parts = [];
if (controlling > 0) parts.push(`controlling ${controlling}`);
if (controlled > 0) parts.push(`controlled ${controlled}`);
if (controlled > 0) parts.push(`controlled by ${controlled}`);
return parts.join(', ') || '-';
}
function peerList(value) {
return Array.isArray(value) && value.length ? value.join(', ') : '-';
}
function appendDetailItem(parent, label, value, className, dataset) {
const item = document.createElement('div');
appendText(item, 'span', label);
@@ -498,6 +505,8 @@ const char kAdminHtml[] = R"HTML(<!doctype html>
appendDetailItem(details, 'Online since', formatTime(device.online_since));
appendDetailItem(details, 'Last online', formatTime(device.online ? 0 : device.updated_at));
appendDetailItem(details, 'Active session', sessionSummary(device));
appendDetailItem(details, 'Controlling', peerList(device.active_control_targets), 'peer-list');
appendDetailItem(details, 'Controlled by', peerList(device.active_controlled_by), 'peer-list');
detailsCell.appendChild(details);
detailsRow.appendChild(detailsCell);
body.appendChild(detailsRow);
@@ -1006,6 +1015,10 @@ AdminHttpResponse AdminController::HandleOverview(
{"active_control_count", active_control_count},
{"active_controlled_count",
active_controlled_count},
{"active_control_targets",
device.active_control_targets},
{"active_controlled_by",
device.active_controlled_by},
{"active_session_count",
active_control_count +
active_controlled_count}});
@@ -51,6 +51,27 @@ std::string NormalizeRemoteDeviceId(const std::string& device_id) {
return device_id.rfind("C-", 0) == 0 ? device_id.substr(2) : device_id;
}
std::vector<std::string> SplitCommaSeparatedIds(const std::string& value) {
std::vector<std::string> result;
size_t start = 0;
while (start <= value.size()) {
size_t end = value.find(',', start);
std::string item =
value.substr(start, end == std::string::npos ? std::string::npos
: end - start);
if (!item.empty()) {
result.push_back(item);
}
if (end == std::string::npos) {
break;
}
start = end + 1;
}
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
return result;
}
std::string NormalizedRemoteDeviceExpr(const std::string& column) {
return "CASE WHEN " + column + " LIKE 'C-%' THEN substr(" + column +
", 3) ELSE " + column + " END";
@@ -1271,6 +1292,22 @@ std::vector<OnlineDeviceInfo> DeviceDBManager::ListDevicePresence(
" = device_presence.device_id), 0) "
"AS active_controlled_count, "
"COALESCE(("
"SELECT GROUP_CONCAT(DISTINCT " +
NormalizedRemoteDeviceExpr("host_id") +
") FROM remote_control_sessions "
"WHERE " +
NormalizedRemoteDeviceExpr("guest_id") +
" = device_presence.device_id), '') "
"AS active_control_targets, "
"COALESCE(("
"SELECT GROUP_CONCAT(DISTINCT " +
NormalizedRemoteDeviceExpr("guest_id") +
") FROM remote_control_sessions "
"WHERE " +
NormalizedRemoteDeviceExpr("host_id") +
" = device_presence.device_id), '') "
"AS active_controlled_by, "
"COALESCE(("
"SELECT COUNT(*) FROM remote_control_sessions "
"WHERE " +
NormalizedRemoteDeviceExpr("guest_id") +
@@ -1314,6 +1351,8 @@ std::vector<OnlineDeviceInfo> DeviceDBManager::ListDevicePresence(
info.total_controlled_seconds = sqlite3_column_int64(stmt, 7);
info.active_control_count = sqlite3_column_int64(stmt, 8);
info.active_controlled_count = sqlite3_column_int64(stmt, 9);
info.active_control_targets = SplitCommaSeparatedIds(ColumnText(stmt, 10));
info.active_controlled_by = SplitCommaSeparatedIds(ColumnText(stmt, 11));
result.push_back(info);
}
sqlite3_finalize(stmt);
@@ -32,6 +32,8 @@ struct OnlineDeviceInfo {
int64_t total_controlled_seconds = 0;
int64_t active_control_count = 0;
int64_t active_controlled_count = 0;
std::vector<std::string> active_control_targets;
std::vector<std::string> active_controlled_by;
};
struct OnlineDurationStats {
+16
View File
@@ -1,10 +1,12 @@
#include "admin_controller.h"
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "admin_auth.h"
#include "device_db_manager.h"
@@ -19,6 +21,10 @@ int main() {
++failures;
}
};
auto contains_id = [](const std::vector<std::string>& ids,
const std::string& id) {
return std::find(ids.begin(), ids.end(), id) != ids.end();
};
expect(AdminController::IsAdminRoute("/admin"), "/admin is admin route");
expect(AdminController::IsAdminRoute("/api/admin/overview"),
@@ -195,9 +201,19 @@ int main() {
"overview applies device sort order");
expect(active_body["devices"][0]["active_controlled_count"] == 2,
"overview reports active controlled count");
auto first_controlled_by =
active_body["devices"][0]["active_controlled_by"]
.get<std::vector<std::string>>();
expect(contains_id(first_controlled_by, "device-admin-offline") &&
contains_id(first_controlled_by, "device-admin-control"),
"overview reports active controlled-by peer ids");
expect(active_body["devices"][1]["id"] == "device-admin-control" &&
active_body["devices"][1]["active_control_count"] == 1,
"overview maps clone guest control count to base device");
auto clone_targets = active_body["devices"][1]["active_control_targets"]
.get<std::vector<std::string>>();
expect(contains_id(clone_targets, "device-admin-1"),
"overview maps clone guest control target to base device");
expect(active_body["devices"][2]["active_control_count"] == 1,
"overview reports active control count");
+14
View File
@@ -1,10 +1,12 @@
#include "presence_manager.h"
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include "device_db_manager.h"
@@ -19,6 +21,10 @@ int main() {
++failures;
}
};
auto contains_id = [](const std::vector<std::string>& ids,
const std::string& id) {
return std::find(ids.begin(), ids.end(), id) != ids.end();
};
expect(presence.GetOnlineDeviceCount() == 0,
"initial online device count is zero");
@@ -150,9 +156,13 @@ int main() {
expect(active_devices[0].device_id == "device-1" &&
active_devices[0].active_control_count == 1,
"presence list reports active control count");
expect(contains_id(active_devices[0].active_control_targets, "device-2"),
"presence list reports active control target id");
expect(active_devices[1].device_id == "device-2" &&
active_devices[1].active_controlled_count == 1,
"presence list reports active controlled count");
expect(contains_id(active_devices[1].active_controlled_by, "device-1"),
"presence list reports active controlled-by peer id");
db.SetDeviceOnline("device-4", true);
expect(db.StartRemoteControlSession("tx-clone", "device-2", "C-device-4"),
"clone remote control session starts");
@@ -161,6 +171,10 @@ int main() {
expect(clone_control.size() == 1 &&
clone_control[0].active_control_count == 1,
"presence list maps clone guest control count to base device");
expect(!clone_control.empty() &&
contains_id(clone_control[0].active_control_targets,
"device-2"),
"presence list maps clone guest control target to base device");
std::this_thread::sleep_for(std::chrono::seconds(1));
expect(db.EndRemoteControlSession("tx-clone", "device-2", "C-device-4"),
"clone remote control session ends");