server status fix

This commit is contained in:
Frank Harris 2026-06-17 08:28:31 -05:00
parent bb02be7daa
commit bd3875743e
8 changed files with 555 additions and 306 deletions

View file

@ -66,6 +66,14 @@ function gsp_project_request_url(): string {
return 'https://runlevelsystems.com/start-project.php';
}
function gsp_discord_invite_url(): string {
return 'https://discord.gg/qt9Hnkj6cv';
}
function gsp_server_status_url(): string {
return 'server_status.php';
}
function gsp_panel_footer_copyright(): string {
return "\u{00A9} 2026 " . gsp_company_name();
}

View file

@ -63,30 +63,29 @@
color: #d2def4;
}
.dashboard-service-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 16px;
margin-top: 20px;
.dashboard-widget-copy {
position: relative;
padding-right: 56px;
}
.dashboard-promo-card,
.dashboard-secondary-card {
padding: 18px 20px;
background: linear-gradient(180deg, rgba(12, 22, 40, 0.94) 0%, rgba(8, 16, 29, 0.96) 100%);
border: 1px solid rgba(77, 160, 255, 0.2);
border-radius: 8px;
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.24);
}
.dashboard-promo-card {
border-left: 3px solid #48c4f5;
}
.dashboard-promo-card p,
.dashboard-secondary-card p {
.dashboard-widget-copy p {
margin: 0 0 12px;
color: #d2def4;
}
.dashboard-widget-icon {
position: absolute;
top: 4px;
right: 0;
width: 40px;
height: 40px;
opacity: 0.9;
}
.dashboard-link-group {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 14px;
}
.dashboard-card-note {
@ -121,6 +120,11 @@
color: #f0f5ff;
}
.dashboard-support-link-secondary {
background: rgba(72, 196, 245, 0.08);
border-color: rgba(72, 196, 245, 0.24);
}
.dashboard-support-link:hover,
.dashboard-support-link:focus {
color: #ffffff;
@ -128,16 +132,32 @@
box-shadow: 0 0 0 3px rgba(90, 199, 247, 0.12);
}
@media (max-width: 991px) {
.dashboard-service-grid {
grid-template-columns: 1fr;
}
.dashboard-status-panel {
margin-top: 20px;
}
.dashboard-status-content {
padding: 18px 20px;
text-align: center;
color: #d2def4;
}
.dashboard-status-content p {
margin: 0 0 14px;
}
.dashboard-status-button {
margin-bottom: 10px;
}
.dashboard-status-note {
display: block;
color: #9fb5d8;
}
@media (max-width: 430px) {
.dashboard-promo-card,
.dashboard-secondary-card {
padding: 16px;
.dashboard-widget-copy {
padding-right: 0;
}
.dashboard-cta-button,
@ -145,4 +165,10 @@
width: 100%;
text-align: center;
}
.dashboard-widget-icon {
position: static;
display: block;
margin: 0 0 12px auto;
}
}

View file

@ -32,6 +32,8 @@ function exec_ogp_module()
global $db, $settings, $loggedInUserInfo;
$projectRequestUrl = htmlspecialchars(gsp_project_request_url(), ENT_QUOTES, 'UTF-8');
$discordInviteUrl = htmlspecialchars(gsp_discord_invite_url(), ENT_QUOTES, 'UTF-8');
$serverStatusUrl = htmlspecialchars(gsp_server_status_url(), ENT_QUOTES, 'UTF-8');
$isAdmin = $db->isAdmin($_SESSION['user_id']);
$user_id = $_SESSION['user_id'];
@ -85,16 +87,27 @@ function exec_ogp_module()
$content[3] = 'View all your notifications. ';
$href[3] = 'home.php?m=circular&p=show_circular&list=true';
// Support Resources quick link
$title[4] = 'Support Resources';
$content[4] ='Need routine help? Use the support section for tickets, service problems, documentation, and troubleshooting with existing features.';
$href[4] = 'home.php?m=tickets';
// Custom Server Code
$title[4] = 'Custom Server Code';
$content[4] = '<div class="dashboard-widget-copy">'
. '<p>Need something beyond the standard server options? Our developers can customize, repair, automate, or extend your game server with mods, scripts, integrations, migrations, and server-specific tools.</p>'
. '<p class="dashboard-card-note">Tell us what you want to build or improve, and we will review the request with you.</p>'
. '<a class="dashboard-cta-button" href="' . $projectRequestUrl . '" target="_blank" rel="noopener noreferrer">Request Custom Development</a>'
. '</div>';
$href[4] = null;
// Support
$title[5] = (isset($settings['support_widget_title']) && $settings['support_widget_title'] != "") ?
$settings['support_widget_title'] : get_lang('support');
$content[5] = '<img src="themes/' . $settings['theme'] . '/images/icons/support.png" style="width:48px;float:right;margin:0 0 0 8px" /> Submit a SUPPORT TICKET or use our Discord Chat at the bottom right. Click this box to JOIN our Discord';
$href[5] = 'https://discord.gg/cWHAbav';
$title[5] = 'Support';
$content[5] = '<div class="dashboard-widget-copy">'
. '<img src="themes/' . $settings['theme'] . '/images/icons/support.png" class="dashboard-widget-icon" alt="" />'
. '<p>Need help with an existing service? Submit a support ticket or join our Discord support server.</p>'
. '<p class="dashboard-card-note">Support is for service problems, broken functionality, routine troubleshooting, and help using the features already included with your server.</p>'
. '<div class="dashboard-link-group">'
. '<a class="dashboard-support-link" href="home.php?m=tickets">Open Support Tickets</a>'
. '<a class="dashboard-support-link dashboard-support-link-secondary" href="' . $discordInviteUrl . '" target="_blank" rel="noopener noreferrer">Join Discord Support</a>'
. '</div>'
. '</div>';
$href[5] = null;
@ -145,30 +158,14 @@ function exec_ogp_module()
echo $html.'</div>';
}
echo "<div class='dashboard-service-grid'>
<div class='dashboard-promo-card'>
<h4>CUSTOM SERVER DEVELOPMENT</h4>
<p>Need something beyond the standard server options? Our developers can customize, repair, automate, or extend your game server with mods, scripts, integrations, migrations, and server-specific tools.</p>
<p class='dashboard-card-note'>Tell us what you want to build or improve, and we will review the request with you.</p>
<a class='dashboard-cta-button' href='{$projectRequestUrl}'>Request Custom Development</a>
</div>
<div class='dashboard-secondary-card'>
<h4>Support and Troubleshooting</h4>
<p>Use support when something is broken, you need routine troubleshooting, or you need help with the features already included with your service.</p>
<a class='dashboard-support-link' href='home.php?m=tickets'>Open Support Resources</a>
</div>
</div>";
// Server Status Link - Available to all users
echo "<div style='margin-top:20px;'>
<div class='bloc rounded' >
echo "<div class='dashboard-status-panel'>
<div class='bloc rounded'>
<h4>Server Status</h4>
<div style='text-align: center; padding: 20px;'>
<p>View the status of all game servers</p>
<a href='server_status.php' target='_blank' style='background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; display: inline-block; margin: 10px;'>
🖥️ View Server Status
</a>
<br><small style='color: #666;'>Opens in a new window</small>
<div class='dashboard-status-content'>
<p>Check the current state of configured remote servers without delaying the main dashboard.</p>
<a class='dashboard-cta-button dashboard-status-button' href='{$serverStatusUrl}' target='_blank' rel='noopener noreferrer'>View Server Status</a>
<small class='dashboard-status-note'>Opens in a new tab and runs remote checks only when requested.</small>
</div>
</div>
</div>";

View file

@ -13,7 +13,7 @@
<link>https://worlddomination.software/projects/gsp</link>
<description>Quick answers for GameServer Panel (GSP) operators</description>
<dc:language>en</dc:language>
<generator>World Domination Software FAQ Generator</generator>
<generator>Runlevel Systems FAQ Generator</generator>
<pubDate>Tue, 11 Feb 2025 12:00:00 GMT</pubDate>
<image>

View file

@ -43,7 +43,7 @@
<category>General</category>
<content:encoded>
(1) submit a ticket on our Panel menu
(2) join our &lt;a href=https://discord.gg/cWHAbav target=_blank &gt; Discord Server&lt;/a&gt;
(2) join our &lt;a href=https://discord.gg/qt9Hnkj6cv target=_blank rel=&quot;noopener noreferrer&quot;&gt; Discord Server&lt;/a&gt;
(3) Look at the LOWER RIGHT of every page: Click the Discord icon

View file

@ -22,269 +22,444 @@
*
*/
// Standalone Server Status Page - Available to all users
require_once("includes/functions.php");
require_once("includes/helpers.php");
require_once("includes/html_functions.php");
require_once "includes/functions.php";
require_once "includes/helpers.php";
require_once "includes/html_functions.php";
startSession();
// Report all PHP errors
error_reporting(E_ERROR);
// Path definitions
define("IMAGES", "images/");
define("INCLUDES", "includes/");
define("MODULES", "modules/");
define("CONFIG_FILE","includes/config.inc.php");
define("CONFIG_FILE", "includes/config.inc.php");
require_once CONFIG_FILE;
require_once('includes/lib_remote.php');
require_once "includes/lib_remote.php";
// Connect to the database server and select database.
$db = createDatabaseConnection($db_type, $db_host, $db_user, $db_pass, $db_name, $table_prefix, isset($db_port) ? $db_port : NULL);
$db = createDatabaseConnection($db_type, $db_host, $db_user, $db_pass, $db_name, $table_prefix, isset($db_port) ? $db_port : null);
// Load languages.
include_once("includes/lang.php");
include_once "includes/lang.php";
if (!$db instanceof OGPDatabase) {
ogpLang();
die(get_lang('no_db_connection'));
}
// Check if user is logged in
if (!isset($_SESSION['users_login'])) {
header('Location: index.php');
exit();
}
// Get user info
$loggedInUserInfo = $db->getUserById($_SESSION['user_id']);
// Get settings
$settings = $db->getSettings();
@$GLOBALS['panel_language'] = $settings['panel_language'];
ogpLang();
function ping_host($host, $timeout = 5) {
if (function_exists('exec')) {
function panel_ping_host($host, $timeout = 3) {
if (!function_exists('exec')) {
return false;
}
$output = array();
$result = 0;
// Use ping command based on OS
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
exec("ping -n 1 -w " . ($timeout * 1000) . " " . escapeshellarg($host), $output, $result);
exec("ping -n 1 -w " . ((int)$timeout * 1000) . " " . escapeshellarg($host), $output, $result);
} else {
exec("ping -c 1 -W " . $timeout . " " . escapeshellarg($host), $output, $result);
exec("ping -c 1 -W " . (int)$timeout . " " . escapeshellarg($host), $output, $result);
}
if ($result !== 0) {
return false;
}
if ($result === 0) {
// Extract ping time from output
foreach ((array)$output as $line) {
if (preg_match('/time[<=]([0-9.]+)\s*ms/i', $line, $matches)) {
return floatval($matches[1]);
if (preg_match('/time[=<]?\s*([0-9.]+)\s*ms/i', $line, $matches)) {
return (float)$matches[1];
}
}
return 0; // Host is up but couldn't extract time
}
}
return false; // Host is down or ping unavailable
return 0.0;
}
function get_hostname($ip) {
$hostname = gethostbyaddr($ip);
return ($hostname && $hostname !== $ip) ? $hostname : false;
function agent_socket_reachable($host, $port, $timeout = 2.0) {
$errno = 0;
$errstr = '';
$socket = @fsockopen($host, (int)$port, $errno, $errstr, $timeout);
if ($socket === false) {
return false;
}
fclose($socket);
return true;
}
// Get all remote servers
function status_badge($label, $class) {
return '<span class="status-badge ' . htmlspecialchars($class, ENT_QUOTES, 'UTF-8') . '">' .
htmlspecialchars($label, ENT_QUOTES, 'UTF-8') .
'</span>';
}
$checkedAt = gmdate('Y-m-d H:i:s') . ' UTC';
$servers = $db->getRemoteServers();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Status - OGP</title>
<title>Server Status | <?php echo htmlspecialchars(gsp_company_name(), ENT_QUOTES, 'UTF-8'); ?></title>
<style>
:root {
color-scheme: dark;
--bg: #07111f;
--panel: rgba(11, 20, 36, 0.94);
--panel-strong: rgba(15, 26, 46, 0.98);
--border: rgba(77, 160, 255, 0.18);
--accent: #48c4f5;
--accent-strong: #2f9fde;
--text: #e6f0ff;
--muted: #9fb5d8;
--success: #42d392;
--warning: #f2c94c;
--danger: #f46d75;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 24px 16px 32px;
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
background: radial-gradient(circle at top, rgba(35, 74, 129, 0.28), transparent 36%), var(--bg);
color: var(--text);
}
.container {
max-width: 1200px;
a {
color: inherit;
}
.status-shell {
max-width: 1180px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.status-card {
background: linear-gradient(180deg, var(--panel) 0%, var(--panel-strong) 100%);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: 0 18px 36px rgba(0, 0, 0, 0.26);
padding: 24px;
}
.status-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
margin-bottom: 18px;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #4CAF50;
padding-bottom: 10px;
margin: 0 0 8px;
font-size: 28px;
line-height: 1.1;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.status-table {
.status-subtitle,
.status-note,
.status-updated,
.status-empty {
color: var(--muted);
}
.status-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
justify-content: flex-end;
}
.status-button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 42px;
padding: 10px 16px;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
border: 1px solid rgba(107, 214, 255, 0.55);
background: linear-gradient(180deg, #5ac7f7 0%, #2f9fde 100%);
color: #04111f;
}
.status-button-secondary {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.12);
color: var(--text);
}
.status-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin: 0 0 20px;
}
.status-meta-item {
padding: 14px 16px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.status-meta-item strong {
display: block;
margin-bottom: 6px;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text);
}
.status-table-wrap {
overflow-x: auto;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.02);
}
table {
width: 100%;
min-width: 760px;
border-collapse: collapse;
margin-top: 20px;
}
.status-table th {
background-color: #4CAF50;
color: white;
padding: 12px;
th,
td {
padding: 14px 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
text-align: left;
border: 1px solid #ddd;
vertical-align: top;
}
.status-table td {
padding: 12px;
border: 1px solid #ddd;
text-align: center;
th {
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
background: rgba(255, 255, 255, 0.03);
}
.status-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.status-table tr:hover {
background-color: #f5f5f5;
}
.status-up {
color: #4CAF50;
font-weight: bold;
font-size: 18px;
}
.status-down {
color: #f44336;
font-weight: bold;
font-size: 18px;
}
.ping-good {
color: #4CAF50;
font-weight: bold;
}
.ping-medium {
color: #ff9800;
font-weight: bold;
}
.ping-bad {
color: #f44336;
font-weight: bold;
}
.refresh-btn {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-bottom: 20px;
font-size: 16px;
}
.refresh-btn:hover {
background-color: #45a049;
}
.last-updated {
text-align: center;
color: #666;
margin-top: 20px;
font-style: italic;
tbody tr:hover {
background: rgba(72, 196, 245, 0.05);
}
.server-name {
font-weight: bold;
text-align: left;
font-weight: 700;
color: var(--text);
}
.server-detail {
display: block;
margin-top: 4px;
color: var(--muted);
font-size: 12px;
}
.status-badge {
display: inline-flex;
align-items: center;
padding: 6px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.status-online {
background: rgba(66, 211, 146, 0.14);
color: var(--success);
}
.status-warning {
background: rgba(242, 201, 76, 0.14);
color: var(--warning);
}
.status-offline {
background: rgba(244, 109, 117, 0.14);
color: var(--danger);
}
.latency-good {
color: var(--success);
font-weight: 700;
}
.latency-medium {
color: var(--warning);
font-weight: 700;
}
.latency-bad {
color: var(--danger);
font-weight: 700;
}
.latency-muted {
color: var(--muted);
}
.status-footer {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 12px;
margin-top: 18px;
font-size: 13px;
color: var(--muted);
}
@media (max-width: 640px) {
body {
padding: 16px 12px 24px;
}
.status-card {
padding: 18px;
}
h1 {
font-size: 22px;
}
.status-actions {
width: 100%;
justify-content: stretch;
}
.status-button {
width: 100%;
}
.no-servers {
text-align: center;
color: #666;
padding: 40px;
font-size: 18px;
}
</style>
</head>
<body>
<div class="container">
<h1>🖥️ Server Status Dashboard</h1>
<div class="status-shell">
<div class="status-card">
<div class="status-header">
<div>
<h1>Server Status</h1>
<p class="status-subtitle">Configured remote servers are checked in real time when this page loads. The main dashboard stays fast because these checks do not run until you open this page.</p>
</div>
<div class="status-actions">
<a class="status-button" href="<?php echo htmlspecialchars(gsp_server_status_url(), ENT_QUOTES, 'UTF-8'); ?>">Refresh Status</a>
<a class="status-button status-button-secondary" href="home.php?m=dashboard&p=dashboard">Back to Dashboard</a>
</div>
</div>
<button class="refresh-btn" onclick="window.location.reload();">🔄 Refresh Status</button>
<div class="status-meta">
<div class="status-meta-item">
<strong>Latency label</strong>
<span>Panel-to-server latency. This is a server-side connectivity check from the Panel host, not latency from the customer browser.</span>
</div>
<div class="status-meta-item">
<strong>Status source</strong>
<span>Remote servers from the active Panel configuration with live agent status checks.</span>
</div>
<div class="status-meta-item">
<strong>Checked</strong>
<span><?php echo htmlspecialchars($checkedAt, ENT_QUOTES, 'UTF-8'); ?></span>
</div>
</div>
<?php if (empty($servers)): ?>
<div class="no-servers">
No servers configured in the system.
</div>
<p class="status-empty">No remote servers are currently configured.</p>
<?php else: ?>
<table class="status-table">
<div class="status-table-wrap">
<table>
<thead>
<tr>
<th>Server Name</th>
<th>Location/IP</th>
<th>Hostname</th>
<th>Location / IP</th>
<th>Status</th>
<th>Ping (ms)</th>
<th>Agent Status</th>
<th>Panel-to-server latency</th>
<th>Last Checked</th>
</tr>
</thead>
<tbody>
<?php foreach ((array)$servers as $server):
$server_ip = gethostbyname($server['agent_ip']);
$hostname = get_hostname($server_ip);
<?php foreach ((array)$servers as $server): ?>
<?php
$agentIp = isset($server['agent_ip']) ? $server['agent_ip'] : '';
$resolvedIp = $agentIp !== '' ? gethostbyname($agentIp) : '';
$displayIp = $resolvedIp !== '' ? $resolvedIp : $agentIp;
$agentPort = isset($server['agent_port']) ? (int)$server['agent_port'] : 0;
$agentReachable = ($agentIp !== '' && $agentPort > 0) ? agent_socket_reachable($agentIp, $agentPort, 2.0) : false;
$remote = new OGPRemoteLibrary($agentIp, $agentPort, $server['encryption_key'], $server['timeout']);
$agentStatus = $remote->status_chk();
$latency = $displayIp !== '' ? panel_ping_host($displayIp, 3) : false;
// Check server status
$remote = new OGPRemoteLibrary($server['agent_ip'], $server['agent_port'],
$server['encryption_key'], $server['timeout']);
$status = $remote->status_chk();
$is_online = ($status === 1);
// Get ping time
$ping_time = ping_host($server_ip, 3);
// Determine ping color class
$ping_class = '';
if ($ping_time !== false) {
if ($ping_time <= 50) {
$ping_class = 'ping-good';
} elseif ($ping_time <= 150) {
$ping_class = 'ping-medium';
if ($agentStatus === 1) {
$overallStatus = status_badge('Online', 'status-online');
$agentBadge = status_badge('Available', 'status-online');
} elseif ($agentReachable) {
$overallStatus = status_badge('Unknown', 'status-warning');
$agentBadge = status_badge('Timed out', 'status-warning');
} else {
$ping_class = 'ping-bad';
$overallStatus = status_badge('Offline', 'status-offline');
$agentBadge = status_badge('Unavailable', 'status-offline');
}
$latencyClass = 'latency-muted';
if ($latency !== false) {
if ($latency <= 50) {
$latencyClass = 'latency-good';
} elseif ($latency <= 150) {
$latencyClass = 'latency-medium';
} else {
$latencyClass = 'latency-bad';
}
}
?>
<tr>
<td class="server-name"><?php echo htmlspecialchars($server['remote_server_name']); ?></td>
<td><?php echo htmlspecialchars($server['agent_ip']); ?>
<?php if ($server_ip !== $server['agent_ip']): ?>
<br><small>(<?php echo htmlspecialchars($server_ip); ?>)</small>
<?php endif; ?>
</td>
<td><?php echo $hostname ? htmlspecialchars($hostname) : '<em>N/A</em>'; ?></td>
<td>
<?php if ($is_online): ?>
<span class="status-up">🟢 UP</span>
<?php else: ?>
<span class="status-down">🔴 DOWN</span>
<?php endif; ?>
<span class="server-name"><?php echo htmlspecialchars($server['remote_server_name'], ENT_QUOTES, 'UTF-8'); ?></span>
<span class="server-detail">Agent port <?php echo htmlspecialchars((string)$agentPort, ENT_QUOTES, 'UTF-8'); ?></span>
</td>
<td>
<?php if ($ping_time !== false): ?>
<span class="<?php echo $ping_class; ?>">
<?php echo number_format($ping_time, 1); ?> ms
</span>
<?php else: ?>
<span style="color: #999;">N/A</span>
<?php echo htmlspecialchars($agentIp, ENT_QUOTES, 'UTF-8'); ?>
<?php if ($displayIp !== '' && $displayIp !== $agentIp): ?>
<span class="server-detail">Resolved IP: <?php echo htmlspecialchars($displayIp, ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
</td>
<td><?php echo $overallStatus; ?></td>
<td><?php echo $agentBadge; ?></td>
<td>
<?php if ($latency !== false): ?>
<span class="<?php echo htmlspecialchars($latencyClass, ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars(number_format($latency, 1), ENT_QUOTES, 'UTF-8'); ?> ms</span>
<?php else: ?>
<span class="latency-muted">Unavailable</span>
<?php endif; ?>
</td>
<td><?php echo htmlspecialchars($checkedAt, ENT_QUOTES, 'UTF-8'); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<div class="last-updated">
Last updated: <?php echo date('Y-m-d H:i:s'); ?>
<div class="status-footer">
<span class="status-note">One unavailable remote can still report cleanly without changing the dashboard page flow.</span>
<span><?php echo htmlspecialchars(gsp_panel_footer_copyright(), ENT_QUOTES, 'UTF-8'); ?></span>
</div>
<div style="text-align: center; margin-top: 30px;">
<a href="home.php" style="color: #4CAF50; text-decoration: none; font-weight: bold;"> Back to Dashboard</a>
</div>
</div>
</body>

View file

@ -9,6 +9,7 @@ Main landing dashboard with widgets and quick server overview.
## Current Status
- Functional
- Uses the existing collapsible widget layout for customer-facing quick actions
## Dependencies
@ -39,12 +40,12 @@ Main landing dashboard with widgets and quick server overview.
## Known Issues
- some default widgets are legacy
- some default widget IDs are legacy and limit how far the collapsible layout can be expanded without a schema change
## Missing Functionality
- richer status and alert surfaces
- clear separation between support requests and custom project work
- deeper async server-status embedding without adding remote checks to normal dashboard loads
## Suggested Future Improvements
@ -59,6 +60,8 @@ Main landing dashboard with widgets and quick server overview.
- Dashboard render file: `Panel/modules/dashboard/dashboard.php`
- Dashboard styles: `Panel/modules/dashboard/dashboard.css`
- Shared Panel project URL helper: `Panel/includes/functions.php`
- Shared Discord invite helper: `Panel/includes/functions.php`
- Shared server-status route helper: `Panel/includes/functions.php`
Current project request URL:
@ -66,5 +69,17 @@ Current project request URL:
Dashboard behavior:
- retained collapsible sections:
- `Account Overview`
- `Custom Server Code`
- `Support`
- removed duplicate standalone sections:
- `Custom Server Development`
- `Support and Troubleshooting`
- support remains the path for routine troubleshooting, service issues, and existing features
- the custom-development CTA is separate and points users at Runlevel Systems for project work such as custom scripts, mods, integrations, automation, migrations, dashboards, and advanced server tooling
- the custom-development CTA points users at Runlevel Systems for project work such as custom scripts, mods, integrations, automation, migrations, dashboards, and advanced server tooling
- the Discord support invite should use:
- `https://discord.gg/qt9Hnkj6cv`
- `Custom Server Code` should open the Runlevel project request in a new tab
- `Support` should keep ticketing and Discord support separate from paid project work
- `Server Status` stays available from the dashboard, but remote checks are intentionally deferred to the separate status page so normal dashboard loads do not block on agent/network timeouts

View file

@ -8,8 +8,8 @@ Admin status page for server/node state.
## Current Status
- Experimental
- Alpha
- Functional standalone page
- Kept separate from the dashboard request path on purpose
## Dependencies
@ -22,11 +22,13 @@ Admin status page for server/node state.
## Agent Interaction
- may read status summaries
- reads configured remote servers from the active Panel database
- performs live agent availability checks when the status page is opened
## User Workflow
- not a primary customer workflow
- open `Panel/server_status.php` from the dashboard when status information is needed
- refresh manually when a new check is required
## Admin Workflow
@ -38,17 +40,43 @@ Admin status page for server/node state.
## Known Issues
- alpha-grade module
- live remote checks may wait on agent/network timeouts, so the page must not be loaded as part of the normal dashboard request
## Missing Functionality
- stable dashboard integration
- async dashboard embedding if a future implementation can safely avoid blocking dashboard render
## Current Architecture
- Render file: `Panel/server_status.php`
- Dashboard entry point: `Panel/modules/dashboard/dashboard.php`
- Data source: `$db->getRemoteServers()` plus `OGPRemoteLibrary::status_chk()`
- Dashboard architecture decision: keep status checks on the separate page so the main dashboard remains responsive even when one or more remote servers are slow or unavailable
## Display Rules
- Removed the old `Hostname` column
- Keep:
- `Server Name`
- `Location / IP`
- `Status`
- `Agent Status`
- `Panel-to-server latency`
- `Last Checked`
- Latency wording must remain truthful:
- current label: `Panel-to-server latency`
- this is a server-side connectivity check from the Panel host
- it is not customer-browser latency and must not be presented as the player's ping
## Theme Notes
- The status page should visually match the active dark Panel theme
- Use responsive table wrapping for mobile widths instead of forcing a wide desktop table into the viewport
## Suggested Future Improvements
- replace with a proper node health/status dashboard
- optional browser-side latency testing only if each location has a safe public health endpoint and the implementation can remain honest about what is being measured
## Recommendation
- Rewrite / Deprecate
- Keep / Improve