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'; 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 { function gsp_panel_footer_copyright(): string {
return "\u{00A9} 2026 " . gsp_company_name(); return "\u{00A9} 2026 " . gsp_company_name();
} }

View file

@ -63,30 +63,29 @@
color: #d2def4; color: #d2def4;
} }
.dashboard-service-grid { .dashboard-widget-copy {
display: grid; position: relative;
grid-template-columns: 2fr 1fr; padding-right: 56px;
gap: 16px;
margin-top: 20px;
} }
.dashboard-promo-card, .dashboard-widget-copy p {
.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 {
margin: 0 0 12px; 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 { .dashboard-card-note {
@ -121,6 +120,11 @@
color: #f0f5ff; 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:hover,
.dashboard-support-link:focus { .dashboard-support-link:focus {
color: #ffffff; color: #ffffff;
@ -128,16 +132,32 @@
box-shadow: 0 0 0 3px rgba(90, 199, 247, 0.12); box-shadow: 0 0 0 3px rgba(90, 199, 247, 0.12);
} }
@media (max-width: 991px) { .dashboard-status-panel {
.dashboard-service-grid { margin-top: 20px;
grid-template-columns: 1fr; }
}
.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) { @media (max-width: 430px) {
.dashboard-promo-card, .dashboard-widget-copy {
.dashboard-secondary-card { padding-right: 0;
padding: 16px;
} }
.dashboard-cta-button, .dashboard-cta-button,
@ -145,4 +165,10 @@
width: 100%; width: 100%;
text-align: center; 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; global $db, $settings, $loggedInUserInfo;
$projectRequestUrl = htmlspecialchars(gsp_project_request_url(), ENT_QUOTES, 'UTF-8'); $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']); $isAdmin = $db->isAdmin($_SESSION['user_id']);
$user_id = $_SESSION['user_id']; $user_id = $_SESSION['user_id'];
@ -85,16 +87,27 @@ function exec_ogp_module()
$content[3] = 'View all your notifications. '; $content[3] = 'View all your notifications. ';
$href[3] = 'home.php?m=circular&p=show_circular&list=true'; $href[3] = 'home.php?m=circular&p=show_circular&list=true';
// Support Resources quick link // Custom Server Code
$title[4] = 'Support Resources'; $title[4] = 'Custom Server Code';
$content[4] ='Need routine help? Use the support section for tickets, service problems, documentation, and troubleshooting with existing features.'; $content[4] = '<div class="dashboard-widget-copy">'
$href[4] = 'home.php?m=tickets'; . '<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 // Support
$title[5] = (isset($settings['support_widget_title']) && $settings['support_widget_title'] != "") ? $title[5] = 'Support';
$settings['support_widget_title'] : get_lang('support'); $content[5] = '<div class="dashboard-widget-copy">'
$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'; . '<img src="themes/' . $settings['theme'] . '/images/icons/support.png" class="dashboard-widget-icon" alt="" />'
$href[5] = 'https://discord.gg/cWHAbav'; . '<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 $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 // Server Status Link - Available to all users
echo "<div style='margin-top:20px;'> echo "<div class='dashboard-status-panel'>
<div class='bloc rounded' > <div class='bloc rounded'>
<h4>Server Status</h4> <h4>Server Status</h4>
<div style='text-align: center; padding: 20px;'> <div class='dashboard-status-content'>
<p>View the status of all game servers</p> <p>Check the current state of configured remote servers without delaying the main dashboard.</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;'> <a class='dashboard-cta-button dashboard-status-button' href='{$serverStatusUrl}' target='_blank' rel='noopener noreferrer'>View Server Status</a>
🖥️ View Server Status <small class='dashboard-status-note'>Opens in a new tab and runs remote checks only when requested.</small>
</a>
<br><small style='color: #666;'>Opens in a new window</small>
</div> </div>
</div> </div>
</div>"; </div>";

View file

@ -13,7 +13,7 @@
<link>https://worlddomination.software/projects/gsp</link> <link>https://worlddomination.software/projects/gsp</link>
<description>Quick answers for GameServer Panel (GSP) operators</description> <description>Quick answers for GameServer Panel (GSP) operators</description>
<dc:language>en</dc:language> <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> <pubDate>Tue, 11 Feb 2025 12:00:00 GMT</pubDate>
<image> <image>

View file

@ -43,7 +43,7 @@
<category>General</category> <category>General</category>
<content:encoded> <content:encoded>
(1) submit a ticket on our Panel menu (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 (3) Look at the LOWER RIGHT of every page: Click the Discord icon

View file

@ -22,270 +22,445 @@
* *
*/ */
// Standalone Server Status Page - Available to all users require_once "includes/functions.php";
require_once("includes/functions.php"); require_once "includes/helpers.php";
require_once("includes/helpers.php"); require_once "includes/html_functions.php";
require_once("includes/html_functions.php");
startSession(); startSession();
// Report all PHP errors
error_reporting(E_ERROR); error_reporting(E_ERROR);
// Path definitions define("CONFIG_FILE", "includes/config.inc.php");
define("IMAGES", "images/");
define("INCLUDES", "includes/");
define("MODULES", "modules/");
define("CONFIG_FILE","includes/config.inc.php");
require_once CONFIG_FILE; 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) { if (!$db instanceof OGPDatabase) {
ogpLang(); ogpLang();
die(get_lang('no_db_connection')); die(get_lang('no_db_connection'));
} }
// Check if user is logged in
if (!isset($_SESSION['users_login'])) { if (!isset($_SESSION['users_login'])) {
header('Location: index.php'); header('Location: index.php');
exit(); exit();
} }
// Get user info
$loggedInUserInfo = $db->getUserById($_SESSION['user_id']); $loggedInUserInfo = $db->getUserById($_SESSION['user_id']);
// Get settings
$settings = $db->getSettings(); $settings = $db->getSettings();
@$GLOBALS['panel_language'] = $settings['panel_language']; @$GLOBALS['panel_language'] = $settings['panel_language'];
ogpLang(); ogpLang();
function ping_host($host, $timeout = 5) { function panel_ping_host($host, $timeout = 3) {
if (function_exists('exec')) { if (!function_exists('exec')) {
$output = array(); return false;
$result = 0; }
// Use ping command based on OS $output = array();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $result = 0;
exec("ping -n 1 -w " . ($timeout * 1000) . " " . escapeshellarg($host), $output, $result);
} else {
exec("ping -c 1 -W " . $timeout . " " . escapeshellarg($host), $output, $result);
}
if ($result === 0) { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// Extract ping time from output exec("ping -n 1 -w " . ((int)$timeout * 1000) . " " . escapeshellarg($host), $output, $result);
foreach ((array)$output as $line) { } else {
if (preg_match('/time[<=]([0-9.]+)\s*ms/i', $line, $matches)) { exec("ping -c 1 -W " . (int)$timeout . " " . escapeshellarg($host), $output, $result);
return floatval($matches[1]); }
}
} if ($result !== 0) {
return 0; // Host is up but couldn't extract time return false;
} }
}
return false; // Host is down or ping unavailable foreach ((array)$output as $line) {
if (preg_match('/time[=<]?\s*([0-9.]+)\s*ms/i', $line, $matches)) {
return (float)$matches[1];
}
}
return 0.0;
} }
function get_hostname($ip) { function agent_socket_reachable($host, $port, $timeout = 2.0) {
$hostname = gethostbyaddr($ip); $errno = 0;
return ($hostname && $hostname !== $ip) ? $hostname : false; $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(); $servers = $db->getRemoteServers();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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> <style>
body { :root {
font-family: Arial, sans-serif; color-scheme: dark;
margin: 20px; --bg: #07111f;
background-color: #f5f5f5; --panel: rgba(11, 20, 36, 0.94);
} --panel-strong: rgba(15, 26, 46, 0.98);
.container { --border: rgba(77, 160, 255, 0.18);
max-width: 1200px; --accent: #48c4f5;
margin: 0 auto; --accent-strong: #2f9fde;
background-color: white; --text: #e6f0ff;
padding: 20px; --muted: #9fb5d8;
border-radius: 8px; --success: #42d392;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); --warning: #f2c94c;
} --danger: #f46d75;
h1 { }
color: #333;
text-align: center; * {
margin-bottom: 30px; box-sizing: border-box;
border-bottom: 2px solid #4CAF50; }
padding-bottom: 10px;
} body {
.status-table { margin: 0;
width: 100%; padding: 24px 16px 32px;
border-collapse: collapse; font-family: Arial, sans-serif;
margin-top: 20px; background: radial-gradient(circle at top, rgba(35, 74, 129, 0.28), transparent 36%), var(--bg);
} color: var(--text);
.status-table th { }
background-color: #4CAF50;
color: white; a {
padding: 12px; color: inherit;
text-align: left; }
border: 1px solid #ddd;
} .status-shell {
.status-table td { max-width: 1180px;
padding: 12px; margin: 0 auto;
border: 1px solid #ddd; }
text-align: center;
} .status-card {
.status-table tr:nth-child(even) { background: linear-gradient(180deg, var(--panel) 0%, var(--panel-strong) 100%);
background-color: #f9f9f9; border: 1px solid var(--border);
} border-radius: 10px;
.status-table tr:hover { box-shadow: 0 18px 36px rgba(0, 0, 0, 0.26);
background-color: #f5f5f5; padding: 24px;
} }
.status-up {
color: #4CAF50; .status-header {
font-weight: bold; display: flex;
font-size: 18px; flex-wrap: wrap;
} justify-content: space-between;
.status-down { align-items: flex-start;
color: #f44336; gap: 16px;
font-weight: bold; margin-bottom: 18px;
font-size: 18px; }
}
.ping-good { h1 {
color: #4CAF50; margin: 0 0 8px;
font-weight: bold; font-size: 28px;
} line-height: 1.1;
.ping-medium { text-transform: uppercase;
color: #ff9800; letter-spacing: 0.04em;
font-weight: bold; }
}
.ping-bad { .status-subtitle,
color: #f44336; .status-note,
font-weight: bold; .status-updated,
} .status-empty {
.refresh-btn { color: var(--muted);
background-color: #4CAF50; }
color: white;
padding: 10px 20px; .status-actions {
border: none; display: flex;
border-radius: 4px; flex-wrap: wrap;
cursor: pointer; gap: 10px;
margin-bottom: 20px; align-items: center;
font-size: 16px; justify-content: flex-end;
} }
.refresh-btn:hover {
background-color: #45a049; .status-button {
} display: inline-flex;
.last-updated { align-items: center;
text-align: center; justify-content: center;
color: #666; min-height: 42px;
margin-top: 20px; padding: 10px 16px;
font-style: italic; border-radius: 6px;
} text-decoration: none;
.server-name { font-weight: 600;
font-weight: bold; border: 1px solid rgba(107, 214, 255, 0.55);
text-align: left; background: linear-gradient(180deg, #5ac7f7 0%, #2f9fde 100%);
} color: #04111f;
.no-servers { }
text-align: center;
color: #666; .status-button-secondary {
padding: 40px; background: rgba(255, 255, 255, 0.05);
font-size: 18px; border-color: rgba(255, 255, 255, 0.12);
} color: var(--text);
</style> }
.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;
}
th,
td {
padding: 14px 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
text-align: left;
vertical-align: top;
}
th {
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
background: rgba(255, 255, 255, 0.03);
}
tbody tr:hover {
background: rgba(72, 196, 245, 0.05);
}
.server-name {
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%;
}
}
</style>
</head> </head>
<body> <body>
<div class="container"> <div class="status-shell">
<h1>🖥️ Server Status Dashboard</h1> <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)): ?> <?php if (empty($servers)): ?>
<div class="no-servers"> <p class="status-empty">No remote servers are currently configured.</p>
No servers configured in the system. <?php else: ?>
</div> <div class="status-table-wrap">
<?php else: ?> <table>
<table class="status-table"> <thead>
<thead> <tr>
<tr> <th>Server Name</th>
<th>Server Name</th> <th>Location / IP</th>
<th>Location/IP</th> <th>Status</th>
<th>Hostname</th> <th>Agent Status</th>
<th>Status</th> <th>Panel-to-server latency</th>
<th>Ping (ms)</th> <th>Last Checked</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ((array)$servers as $server): <?php foreach ((array)$servers as $server): ?>
$server_ip = gethostbyname($server['agent_ip']); <?php
$hostname = get_hostname($server_ip); $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 if ($agentStatus === 1) {
$remote = new OGPRemoteLibrary($server['agent_ip'], $server['agent_port'], $overallStatus = status_badge('Online', 'status-online');
$server['encryption_key'], $server['timeout']); $agentBadge = status_badge('Available', 'status-online');
$status = $remote->status_chk(); } elseif ($agentReachable) {
$is_online = ($status === 1); $overallStatus = status_badge('Unknown', 'status-warning');
$agentBadge = status_badge('Timed out', 'status-warning');
} else {
$overallStatus = status_badge('Offline', 'status-offline');
$agentBadge = status_badge('Unavailable', 'status-offline');
}
// Get ping time $latencyClass = 'latency-muted';
$ping_time = ping_host($server_ip, 3); if ($latency !== false) {
if ($latency <= 50) {
$latencyClass = 'latency-good';
} elseif ($latency <= 150) {
$latencyClass = 'latency-medium';
} else {
$latencyClass = 'latency-bad';
}
}
?>
<tr>
<td>
<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 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; ?>
// Determine ping color class <div class="status-footer">
$ping_class = ''; <span class="status-note">One unavailable remote can still report cleanly without changing the dashboard page flow.</span>
if ($ping_time !== false) { <span><?php echo htmlspecialchars(gsp_panel_footer_copyright(), ENT_QUOTES, 'UTF-8'); ?></span>
if ($ping_time <= 50) { </div>
$ping_class = 'ping-good'; </div>
} elseif ($ping_time <= 150) { </div>
$ping_class = 'ping-medium';
} else {
$ping_class = 'ping-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; ?>
</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 endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<div class="last-updated">
Last updated: <?php echo date('Y-m-d H:i:s'); ?>
</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> </body>
</html> </html>

View file

@ -9,6 +9,7 @@ Main landing dashboard with widgets and quick server overview.
## Current Status ## Current Status
- Functional - Functional
- Uses the existing collapsible widget layout for customer-facing quick actions
## Dependencies ## Dependencies
@ -39,12 +40,12 @@ Main landing dashboard with widgets and quick server overview.
## Known Issues ## 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 ## Missing Functionality
- richer status and alert surfaces - 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 ## 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 render file: `Panel/modules/dashboard/dashboard.php`
- Dashboard styles: `Panel/modules/dashboard/dashboard.css` - Dashboard styles: `Panel/modules/dashboard/dashboard.css`
- Shared Panel project URL helper: `Panel/includes/functions.php` - 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: Current project request URL:
@ -66,5 +69,17 @@ Current project request URL:
Dashboard behavior: 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 - 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 ## Current Status
- Experimental - Functional standalone page
- Alpha - Kept separate from the dashboard request path on purpose
## Dependencies ## Dependencies
@ -22,11 +22,13 @@ Admin status page for server/node state.
## Agent Interaction ## 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 ## 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 ## Admin Workflow
@ -38,17 +40,43 @@ Admin status page for server/node state.
## Known Issues ## 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 ## 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 ## 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 ## Recommendation
- Rewrite / Deprecate - Keep / Improve