From 4c93c8fea7c2f1b2a4188fd94e32d5e55af78faa Mon Sep 17 00:00:00 2001
From: Aaron Kimbrell
Date: Wed, 30 Sep 2026 04:03:22 -0500
Subject: [PATCH] feat(dashboard): draining (old version) badge; properties
update when empty
Co-Authored-By: Claude Opus 5.5
---
dDashboardServer/DashboardServer.cpp | 1 +
dDashboardServer/routes/LiveWorld.cpp | 4 ++--
dDashboardServer/routes/ServerState.h | 5 ++++-
dDashboardServer/templates/index.jinja2 | 8 ++++++--
dDashboardServer/templates/instances.jinja2 | 7 +++++--
5 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/dDashboardServer/DashboardServer.cpp b/dDashboardServer/DashboardServer.cpp
index 3f00708ac..0710815cb 100644
--- a/dDashboardServer/DashboardServer.cpp
+++ b/dDashboardServer/DashboardServer.cpp
@@ -229,6 +229,7 @@ namespace {
info.ip = instance.ip.string;
info.port = instance.port;
info.isPrivate = instance.isPrivate != 0;
+ info.outdated = instance.outdated != 0;
info.zoneName = GameText::ZoneName(info.mapID);
AddPropertyDetails(info);
using eState = MasterPackets::ServerListResponse::eState;
diff --git a/dDashboardServer/routes/LiveWorld.cpp b/dDashboardServer/routes/LiveWorld.cpp
index 984ad7baa..4f1e550cf 100644
--- a/dDashboardServer/routes/LiveWorld.cpp
+++ b/dDashboardServer/routes/LiveWorld.cpp
@@ -253,13 +253,13 @@ namespace LiveWorld {
for (const auto& w : ServerState::g_WorldInstances) {
worlds.push_back({ {"mapID", w.mapID}, {"zoneName", GameText::ZoneName(w.mapID)}, {"instanceID", w.instanceID}, {"cloneID", w.cloneID}, {"players", w.players},
{"isPrivate", w.isPrivate}, {"ip", w.ip}, {"port", w.port}, {"propertyId", w.propertyId}, {"propertyName", w.propertyName},
- {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state} });
+ {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state}, {"outdated", w.outdated} });
}
// Launched but not connected yet, or shutting down
for (const auto& w : ServerState::g_PendingWorlds) {
worlds.push_back({ {"mapID", w.mapID}, {"zoneName", GameText::ZoneName(w.mapID)}, {"instanceID", w.instanceID}, {"cloneID", w.cloneID}, {"players", 0},
{"isPrivate", w.isPrivate}, {"ip", w.ip}, {"port", w.port}, {"propertyId", w.propertyId}, {"propertyName", w.propertyName},
- {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state} });
+ {"ownerId", w.ownerId}, {"ownerName", w.ownerName}, {"state", w.state}, {"outdated", w.outdated} });
}
JsonSuccess(reply, { {"worlds", worlds} });
});
diff --git a/dDashboardServer/routes/ServerState.h b/dDashboardServer/routes/ServerState.h
index c28258208..6acdec293 100644
--- a/dDashboardServer/routes/ServerState.h
+++ b/dDashboardServer/routes/ServerState.h
@@ -42,6 +42,8 @@ struct WorldInstanceInfo {
// "starting" (launched, not connected yet) or "stopping" (in g_PendingWorlds), or "draining" for a world that is up
// but whose players are being moved to a new instance (in g_WorldInstances); empty for one that is just up
std::string state{};
+ // On an old binary or old zone files (a live update or world reload): takes nobody new and stops once empty
+ bool outdated{false};
};
// Where a server listens and the machine it runs on, from master's server list
@@ -88,7 +90,8 @@ namespace ServerState {
{"players", world.players},
{"isPrivate", world.isPrivate},
{"zoneName", world.zoneName},
- {"state", world.state}
+ {"state", world.state},
+ {"outdated", world.outdated}
});
}
diff --git a/dDashboardServer/templates/index.jinja2 b/dDashboardServer/templates/index.jinja2
index 0161aee90..a6f443002 100644
--- a/dDashboardServer/templates/index.jinja2
+++ b/dDashboardServer/templates/index.jinja2
@@ -60,8 +60,9 @@
Moves everything onto the server binaries on disk now, without a restart: the UGC server finishes its jobs
and restarts, auth restarts (logins pause for a few seconds), chat hands its teams over and restarts, every world instance is
replaced by a new one and its players are moved there (the game's Mythran maintenance warning, then a short loading screen), and
- the dashboard restarts last. Properties are saved before their new instance loads them; races and minigames finish first.
- Master keeps running: updating master itself takes a normal restart.
+ the dashboard restarts last. Races and minigames finish first. Properties are not moved (building in progress
+ isn't saved): their players are told an update is waiting, and a property updates once everyone has left it. Instances on the old
+ build take nobody new. Master keeps running: updating master itself takes a normal restart.
';
var instances = '' + z.instances.map(function (i) {
- var badges = (i.stale ? ' ' + fmt.badge('old files', 'warning') : '') + (i.reloading ? ' ' + fmt.badge('being replaced', 'info') : '');
+ var badges = (i.stale ? ' ' + fmt.badge('old files', 'warning') : '') + (i.reloading ? ' ' + fmt.badge('being replaced', 'info') :
+ i.outdated ? ' ' + fmt.badge(i.clone ? 'updates when empty' : 'draining (old version)', 'secondary') : '');
return 'Instance ' + esc(i.instance) + (i.clone ? ' (clone ' + esc(i.clone) + ')' : '') + ', ' +
nf.format(i.players) + ' player(s)' + badges + '';
}).join('') + '
';