mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(dashboard): restarts from 30 seconds; no "undefined" in the restart banner
Restarts can be scheduled in seconds (30 s to 24 h; minutes still accepted), with 30 second and 1 minute choices for quick restarts. The restart banner only names who scheduled it when that is known: staff who manage restarts read it from /api/server/restart, everyone else gets when and why from the status, which leaves the name out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -463,7 +463,7 @@ namespace {
|
||||
if (when == "start") part.status = "Restarts when the event ends";
|
||||
return true;
|
||||
}
|
||||
const auto error = LiveWorld::ScheduleRestart(part.config.value("minutes", 15), Text(part.config, "reason"), run.actor, false);
|
||||
const auto error = LiveWorld::ScheduleRestart(part.config.value("minutes", int64_t{ 15 }) * 60, Text(part.config, "reason"), run.actor, false);
|
||||
part.status = error ? "Not scheduled: " + *error : "Restart scheduled when it " + std::string(when == "start" ? "started" : "ended");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace {
|
||||
constexpr const char* RESTART_STATE = "scheduled_restart";
|
||||
// Warnings before a scheduled restart, in seconds; only those shorter than the notice given are sent
|
||||
constexpr int64_t WARNINGS[] = { 3600, 1800, 900, 600, 300, 120, 60, 30, 10 };
|
||||
// How soon a scheduled restart may be (short ones are for quick restarts while nobody is on), and how late
|
||||
constexpr int64_t MIN_RESTART_SECONDS = 30;
|
||||
constexpr int64_t MAX_RESTART_SECONDS = 24 * 3600;
|
||||
|
||||
struct WorldPlayers {
|
||||
PlayerPositions positions;
|
||||
@@ -149,17 +152,17 @@ namespace {
|
||||
}
|
||||
|
||||
namespace LiveWorld {
|
||||
std::optional<std::string> ScheduleRestart(int64_t minutes, const std::string& reason, const HTTPContext& actor, bool replace) {
|
||||
if (minutes < 1 || minutes > 1440) return "Pick between 1 minute and 24 hours";
|
||||
std::optional<std::string> ScheduleRestart(int64_t seconds, const std::string& reason, const HTTPContext& actor, bool replace) {
|
||||
if (seconds < MIN_RESTART_SECONDS || seconds > MAX_RESTART_SECONDS) return "Pick between 30 seconds and 24 hours";
|
||||
if (reason.size() > 300) return "The reason can be at most 300 characters";
|
||||
if (!g_RestartLoaded) LoadRestart();
|
||||
if (g_Restart && !replace) return "A restart is already scheduled (by " + g_Restart->by + ")";
|
||||
|
||||
g_Restart = Restart{ static_cast<int64_t>(std::time(nullptr)) + minutes * 60, reason, actor.authenticatedUser, 0 };
|
||||
g_Restart = Restart{ static_cast<int64_t>(std::time(nullptr)) + seconds, reason, actor.authenticatedUser, 0 };
|
||||
SaveRestart();
|
||||
g_NextPush = {}; // send the first warning right away
|
||||
Audit(actor, "schedule_restart", "In " + Duration(minutes * 60) + (reason.empty() ? "" : ": " + reason));
|
||||
Alerts::Emit("server", "Restart scheduled", "The server restarts in " + Duration(minutes * 60) + ".",
|
||||
Audit(actor, "schedule_restart", "In " + Duration(seconds) + (reason.empty() ? "" : ": " + reason));
|
||||
Alerts::Emit("server", "Restart scheduled", "The server restarts in " + Duration(seconds) + ".",
|
||||
{ { "By", actor.authenticatedUser }, { "Reason", reason } }, "/");
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -292,13 +295,13 @@ namespace LiveWorld {
|
||||
JsonReply(reply, eHTTPStatusCode::OK, RestartStatus(true));
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::POST, "/api/server/restart", Perm("server_restart"), "Schedule a restart with in-game warnings. Body: {minutes (1-1440), reason}. Needs a process supervisor to start the server again",
|
||||
Route(eHTTPMethod::POST, "/api/server/restart", Perm("server_restart"), "Schedule a restart with in-game warnings. Body: {seconds (30-86400) or minutes (1-1440), reason}. Needs a process supervisor to start the server again",
|
||||
[](HTTPReply& reply, const HTTPContext& context) {
|
||||
const auto body = ParseBody(context);
|
||||
if (!body) return JsonError(reply, eHTTPStatusCode::BAD_REQUEST, "Invalid JSON");
|
||||
const auto minutes = body->value("minutes", 0);
|
||||
const int64_t seconds = body->contains("seconds") ? body->value("seconds", int64_t{ 0 }) : body->value("minutes", int64_t{ 0 }) * 60;
|
||||
const std::string reason = body->value("reason", "");
|
||||
if (const auto error = LiveWorld::ScheduleRestart(minutes, reason, context, true)) return JsonError(reply, eHTTPStatusCode::BAD_REQUEST, *error);
|
||||
if (const auto error = LiveWorld::ScheduleRestart(seconds, reason, context, true)) return JsonError(reply, eHTTPStatusCode::BAD_REQUEST, *error);
|
||||
JsonSuccess(reply, { {"message", "Restart scheduled"}, {"restart", RestartStatus(true)} });
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace LiveWorld {
|
||||
* Schedule a restart in `minutes` (1 to 1440) with in-game warnings, audited as `actor`. One already scheduled is
|
||||
* replaced when `replace`, or kept with an error. The error, if it wasn't scheduled
|
||||
*/
|
||||
std::optional<std::string> ScheduleRestart(int64_t minutes, const std::string& reason, const HTTPContext& actor, bool replace);
|
||||
std::optional<std::string> ScheduleRestart(int64_t seconds, const std::string& reason, const HTTPContext& actor, bool replace);
|
||||
|
||||
// Scheduled restart for the status sent to every dashboard ({} when none)
|
||||
// The scheduled restart ({} when none); the staff member's name only with withStaffName
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
function tick() {
|
||||
var left = Math.max(0, Math.round(restart.at - Date.now() / 1000));
|
||||
var text = left >= 60 ? Math.floor(left / 60) + ' min ' + (left % 60) + ' s' : left + ' s';
|
||||
banner.textContent = 'Server restart in ' + text + (restart.reason ? ': ' + restart.reason : '') + ' (scheduled by ' + restart.by + ')';
|
||||
banner.textContent = 'Server restart in ' + text + (restart.reason ? ': ' + restart.reason : '') + (restart.by ? ' (scheduled by ' + restart.by + ')' : '');
|
||||
}
|
||||
tick();
|
||||
restartTimer = setInterval(tick, 1000);
|
||||
@@ -304,6 +304,8 @@
|
||||
if (initial) initial.textContent = (document.body.dataset.username || '?').charAt(0).toUpperCase();
|
||||
connect();
|
||||
if (DASH.can('moderate_names')) api.get('/api/moderation/counts').then(updateBadges).catch(function () {});
|
||||
api.get('/api/status').then(function (s) { showRestart(s.restart); }).catch(function () {});
|
||||
// Staff who manage restarts also see who scheduled it; everyone else gets when and why from the status
|
||||
if (DASH.can('server_restart')) api.get('/api/server/restart').then(function (r) { showRestart(r && r.at ? r : null); }).catch(function () {});
|
||||
else api.get('/api/status').then(function (s) { showRestart(s.restart); }).catch(function () {});
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -77,9 +77,9 @@
|
||||
The server shuts down cleanly; your process supervisor (systemd, Docker restart policy...) starts it again.</p>
|
||||
<div class="input-group input-group-sm mb-2">
|
||||
<span class="input-group-text">In</span>
|
||||
<select class="form-select" id="restartMinutes">
|
||||
<option value="5">5 minutes</option><option value="10">10 minutes</option><option value="15" selected>15 minutes</option>
|
||||
<option value="30">30 minutes</option><option value="60">1 hour</option><option value="120">2 hours</option>
|
||||
<select class="form-select" id="restartSeconds">
|
||||
<option value="30">30 seconds</option><option value="60">1 minute</option><option value="300">5 minutes</option><option value="600">10 minutes</option><option value="900" selected>15 minutes</option>
|
||||
<option value="1800">30 minutes</option><option value="3600">1 hour</option><option value="7200">2 hours</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2"><input class="form-control form-control-sm" id="restartReason" maxlength="300" placeholder="Reason shown to players (optional)"></div>
|
||||
@@ -112,7 +112,7 @@ $(function () {
|
||||
};
|
||||
schedule.addEventListener('click', function () {
|
||||
if (!confirm('Schedule a restart? Everyone online will be warned and then disconnected.')) return;
|
||||
api.action('/api/server/restart', { minutes: parseInt(document.getElementById('restartMinutes').value, 10), reason: document.getElementById('restartReason').value.trim() }, 'Restart scheduled')
|
||||
api.action('/api/server/restart', { seconds: parseInt(document.getElementById('restartSeconds').value, 10), reason: document.getElementById('restartReason').value.trim() }, 'Restart scheduled')
|
||||
.then(function (r) { showRestart(r.restart); }).catch(function () {});
|
||||
});
|
||||
document.getElementById('restartCancel').addEventListener('click', function () {
|
||||
|
||||
Reference in New Issue
Block a user