diff --git a/dDashboardServer/routes/ServerRoutes.cpp b/dDashboardServer/routes/ServerRoutes.cpp index f466cd4f3..3fb974130 100644 --- a/dDashboardServer/routes/ServerRoutes.cpp +++ b/dDashboardServer/routes/ServerRoutes.cpp @@ -81,6 +81,12 @@ namespace { pid = ServerState::g_UgcPid; } out["pid"] = pid; + // Up since its process started when this machine runs it (the dashboard may have started after it) + if (pid && out.value("online", false)) { + for (const auto& process : ServerRoutes::Processes()) { + if (process.pid == pid && process.startedAt) out["since"] = process.startedAt; + } + } // Its traffic report (every few seconds, via master) carries its workers, totals and storage const auto traffic = Traffic::Server("ugc"); out["gauges"] = traffic.value("gauges", nlohmann::json::object()); diff --git a/dDashboardServer/static/js/common.js b/dDashboardServer/static/js/common.js index fc1010be2..506088330 100644 --- a/dDashboardServer/static/js/common.js +++ b/dDashboardServer/static/js/common.js @@ -123,6 +123,21 @@ // Formatting helpers used by table renderers window.fmt = { unix: function (ts) { return ts ? new Date(ts * 1000).toLocaleString() : '-'; }, + // 1536 -> "1.5 KB" + bytes: function (n) { + n = Number(n) || 0; + var units = ['B', 'KB', 'MB', 'GB', 'TB'], i = 0; + while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; } + return (i === 0 ? n : n.toFixed(n < 10 ? 1 : 0)) + ' ' + units[i]; + }, + // Seconds -> "3d 4h", "2h 5m", "7m", "12s" + duration: function (s) { + s = Math.max(0, Math.floor(Number(s) || 0)); + var d = Math.floor(s / 86400), h = Math.floor(s % 86400 / 3600), m = Math.floor(s % 3600 / 60); + if (d) return d + 'd ' + h + 'h'; + if (h) return h + 'h ' + m + 'm'; + return m ? m + 'm' : s + 's'; + }, date: function (s) { if (!s) return '-'; var d = new Date(String(s).replace(' ', 'T') + (String(s).indexOf('Z') === -1 && String(s).indexOf('T') === -1 ? 'Z' : '')); diff --git a/dDashboardServer/templates/health.jinja2 b/dDashboardServer/templates/health.jinja2 index 56bee6bfb..5e723b6ac 100644 --- a/dDashboardServer/templates/health.jinja2 +++ b/dDashboardServer/templates/health.jinja2 @@ -31,7 +31,12 @@
Players online (highest)
Worlds running
Memory (all servers)
-
Auth and chat servers
+
Auth, chat and UGC servers
+ + +
+
Servers
Memory and CPU from this machine (Linux); CPU is of one core since the last refresh
+

Loading…

{% if can.logs_system %} @@ -91,13 +96,18 @@ } function uptime(samples, from, to) { - var rows = [['auth', 'Auth server'], ['chat', 'Chat server']].map(function (s) { - var up = samples.filter(function (p) { return p[s[0]]; }).length; - var percent = samples.length ? (up / samples.length * 100) : 0; + var servers = [['auth', 'Auth server'], ['chat', 'Chat server']]; + // The UGC server only counts while master was set to start it + if (samples.some(function (p) { return p.ugc_enabled; })) servers.push(['ugc', 'UGC server', 'ugc_enabled']); + var rows = servers.map(function (s) { + var counted = s[2] ? samples.filter(function (p) { return p[s[2]]; }) : samples; + var up = counted.filter(function (p) { return p[s[0]]; }).length; + var percent = counted.length ? (up / counted.length * 100) : 0; var bars = samples.map(function (p) { - return ''; + var color = s[2] && !p[s[2]] ? 'secondary-bg' : p[s[0]] ? 'success' : 'danger'; + return ''; }).join(''); - return '
' + s[1] + '' + (samples.length ? percent.toFixed(1) + '% up' : 'no samples') + '
' + + return '
' + s[1] + '' + (counted.length ? percent.toFixed(1) + '% up' : 'no samples') + '
' + '
' + bars + '
'; }); document.getElementById('uptime').innerHTML = rows.join('') + '

Red means the server was not connected when the sample was taken.

'; @@ -109,7 +119,8 @@ var c = d.current; document.getElementById('healthNow').innerHTML = [ ['Players online', nf.format(c.players)], ['Worlds running', nf.format(c.worlds)], - ['Auth / chat', (c.auth ? 'up' : 'DOWN') + ' / ' + (c.chat ? 'up' : 'DOWN')], ['Memory', c.memory_kb ? size(c.memory_kb) : 'n/a'] + [c.ugc_enabled ? 'Auth / chat / UGC' : 'Auth / chat', [c.auth, c.chat].concat(c.ugc_enabled ? [c.ugc] : []).map(function (up) { return up ? 'up' : 'DOWN'; }).join(' / ')], + ['Memory', c.memory_kb ? size(c.memory_kb) : 'n/a'] ].map(function (s) { return '
' + s[0] + '
' + esc(s[1]) + '
'; }).join(''); @@ -120,6 +131,38 @@ }); } + // Every server with its process and what its last traffic report said (UGC: workers, storage and the database queue) + function loadServers() { + api.get('/api/servers').then(function (d) { + var box = document.getElementById('servers'); + if (!d.success) { box.innerHTML = '

' + esc(d.error) + '

'; return; } + var now = Date.now() / 1000; + var details = function (s) { + var g = (s.traffic && s.traffic.gauges) || {}, parts = []; + if (s.players !== undefined) parts.push(nf.format(s.players) + ' player' + (s.players === 1 ? '' : 's')); + if (s.traffic && s.traffic.connections) parts.push(nf.format(s.traffic.connections) + ' connection' + (s.traffic.connections === 1 ? '' : 's') + (s.traffic.ping_ms ? ', ' + s.traffic.ping_ms + ' ms' : '')); + if (g.workers_threads !== undefined) parts.push('workers ' + g.workers_busy + '/' + g.workers_threads + (g.workers_queued ? ', ' + g.workers_queued + ' queued' : '')); + if (s.key === 'ugc') { + var c = d.ugc.counts || { model: {}, modular: {} }; + var sum = function (state) { return (c.model[state] || 0) + (c.modular[state] || 0); }; + parts.push(nf.format(sum('pending')) + ' waiting, ' + nf.format(sum('done')) + ' made, ' + nf.format(sum('failed')) + ' failed'); + if (g.ugc_stored_bytes !== undefined) parts.push(fmt.bytes(g.ugc_stored_bytes) + (g.ugc_max_storage_bytes ? ' of ' + fmt.bytes(g.ugc_max_storage_bytes) : '') + ' stored'); + } + return esc(parts.join(' ยท ')); + }; + box.innerHTML = '' + + d.servers.map(function (s) { + var p = s.process; + var started = (p && p.started_at) || s.since; + var label = s.key === 'ugc' && DASH.can('properties_view') ? fmt.link('/ugc', s.label) : esc(s.label); + return '' + + '' + + '' + + ''; + }).join('') + '
ServerStatusUp forPIDMemoryCPUDetails
' + label + '' + (s.kind === 'OTHER' ? fmt.badge('Not listed', 'secondary') : fmt.badge(s.online ? 'Online' : 'Offline', s.online ? 'success' : 'danger')) + '' + ((s.online || s.kind === 'OTHER') && started ? esc(fmt.duration(now - started)) : '-') + '' + (p ? esc(p.pid) : '-') + '' + (p ? esc(size(p.memory_kb)) : '-') + '' + (p ? esc(p.cpu_percent.toFixed(1) + '%') : '-') + '' + details(s) + '
'; + }).catch(function () {}); + } + function loadDumps() { var box = document.getElementById('dumps'); if (!box) return; @@ -153,7 +196,9 @@ }); window.addEventListener('resize', Live.throttle(load, 500)); setInterval(load, 60000); + setInterval(loadServers, 10000); load(); + loadServers(); loadDumps(); })(); diff --git a/dDashboardServer/templates/index.jinja2 b/dDashboardServer/templates/index.jinja2 index 6093b3f59..6f5d3ef2d 100644 --- a/dDashboardServer/templates/index.jinja2 +++ b/dDashboardServer/templates/index.jinja2 @@ -33,6 +33,17 @@
{% include "server_status.jinja2" %} {% include "statistics.jinja2" %} + {% if can.health_view %} +
+
+
+
UGC Server
+ {% if can.properties_view %}Models{% endif %} +
+

Loading…

+
+
+ {% endif %}
{% include "world_instances.jinja2" %} @@ -160,9 +171,43 @@ $(function () { if (el && value !== undefined) el.textContent = value; } + // The UGC server's card (staff with health_view): its state, the database queue and what its last report said + var ugcLoadedAt = 0; + function loadUgc(force) { + var card = document.getElementById('ugcCard'); + if (!card || (!force && Date.now() - ugcLoadedAt < 15000)) return; + ugcLoadedAt = Date.now(); + api.get('/api/servers/ugc').then(function (d) { + if (!d.success) return; + card.classList.toggle('d-none', !d.enabled && !d.online); + var g = d.gauges || {}, c = d.counts || { model: {}, modular: {} }; + var sum = function (state) { return (c.model[state] || 0) + (c.modular[state] || 0); }; + var line = function (label, value, id) { + return '
' + label + '' + value + '
'; + }; + var status = fmt.badge(d.online ? 'Online' : 'Offline', d.online ? 'success' : 'danger') + + (d.online && d.since ? ' up ' + esc(fmt.duration(Date.now() / 1000 - d.since)) + '' : ''); + var storage = g.ugc_stored_bytes !== undefined ? esc(fmt.bytes(g.ugc_stored_bytes)) + (g.ugc_max_storage_bytes ? ' of ' + esc(fmt.bytes(g.ugc_max_storage_bytes)) + '' : '') : '-'; + var workers = g.workers_threads !== undefined ? esc(g.workers_busy + ' / ' + g.workers_threads) + (g.workers_queued ? ' (' + esc(g.workers_queued) + ' queued)' : '') : '-'; + document.getElementById('ugcCardBody').innerHTML = + line('Status', status) + + line('Waiting', fmt.badge(sum('pending'), sum('pending') ? 'warning' : 'secondary')) + + line('Made', fmt.badge(sum('done'), 'primary')) + + line('Failed', fmt.badge(sum('failed'), sum('failed') ? 'danger' : 'secondary')) + + line('Workers busy', workers) + + line('Storage used', storage).replace('mb-2"', 'mb-0"'); + }).catch(function () {}); + } + function update(data) { if (data.auth) setStatus('auth-status', data.auth.online); if (data.chat) setStatus('chat-status', data.chat.online); + if (data.ugc) { + setStatus('ugc-status', data.ugc.online); + var ugcRow = document.getElementById('ugc-status-row'); + if (ugcRow) ugcRow.classList.toggle('d-none', !data.ugc.enabled); + loadUgc(); + } if (data.worlds) { setText('world-count', data.stats && data.stats.worlds !== undefined ? data.stats.worlds : data.worlds.length); if (detailedWorlds) loadDetailedWorlds(); @@ -181,6 +226,7 @@ $(function () { }); api.get('/api/status').then(update).catch(function () {}); + loadUgc(true); Live.onStatus(update); }); diff --git a/dDashboardServer/templates/server_status.jinja2 b/dDashboardServer/templates/server_status.jinja2 index 805db5a08..27e488c05 100644 --- a/dDashboardServer/templates/server_status.jinja2 +++ b/dDashboardServer/templates/server_status.jinja2 @@ -18,6 +18,15 @@ Offline {% endif %}
+ {# Only while master starts it (enable_ugc_server); the page shows it when that changes #} +
+ UGC Server + {% if ugc.online %} + Online + {% else %} + Offline + {% endif %} +
Active Worlds {{ stats.worlds }}