fixed world site
This commit is contained in:
parent
26fd3364ac
commit
e2e56dbaf2
14 changed files with 365 additions and 56 deletions
|
|
@ -20,6 +20,7 @@ Panel/modules/website/
|
|||
checkout.php
|
||||
payment_success.php
|
||||
payment_cancel.php
|
||||
server_status.php
|
||||
docs.php
|
||||
login.php
|
||||
register.php
|
||||
|
|
@ -67,6 +68,7 @@ Panel/modules/website/
|
|||
game_servers.php
|
||||
cart.php
|
||||
documentation.php
|
||||
server_status.php
|
||||
pricing.php
|
||||
locations.php
|
||||
panel_features.php
|
||||
|
|
@ -96,10 +98,7 @@ Primary navigation:
|
|||
|
||||
- Home
|
||||
- Game Servers
|
||||
- Pricing
|
||||
- Locations
|
||||
- Panel Features
|
||||
- Documentation
|
||||
- Support
|
||||
|
||||
Account area:
|
||||
|
|
@ -117,13 +116,15 @@ Action links:
|
|||
external link that opens the operational GSP Panel at the configured Panel URL.
|
||||
|
||||
The homepage should not duplicate the header Control Panel action with extra
|
||||
promotional Control Panel blocks. Pricing and location summaries on the homepage
|
||||
must use customer-facing sales language rather than internal planning notes.
|
||||
promotional Control Panel blocks. Pricing belongs on the catalog and order pages.
|
||||
The homepage location summary must use customer-facing language and link to the
|
||||
website-branded `server_status.php` page.
|
||||
|
||||
Footer placement:
|
||||
|
||||
- one modest `Control Panel` link under account/services
|
||||
- one `Panel Features` informational link in the explore links
|
||||
- no public documentation links in shared navigation, footer, support, or server catalog actions
|
||||
|
||||
## Public copy policy
|
||||
|
||||
|
|
@ -223,7 +224,7 @@ More detail: `docs/modules/website_billing_rebuild.md`.
|
|||
|
||||
The public game-server catalog is a compact row/list view. It should show
|
||||
customer-facing service names, user-friendly platform labels, short descriptions,
|
||||
pricing, ordering, and documentation actions. Raw XML/config keys are used
|
||||
per-slot monthly pricing, and ordering actions. Raw XML/config keys are used
|
||||
internally for service lookup and documentation routing, but should not be shown
|
||||
as the public platform label.
|
||||
|
||||
|
|
@ -239,6 +240,23 @@ platform label. The staff service editor uses expandable rows so service ID,
|
|||
enabled status, display name, platform, price, slot range, image status, and
|
||||
locations are easier to scan.
|
||||
|
||||
Catalog and order pages display `price_monthly` as a per-slot monthly price
|
||||
using wording such as `$0.50 per slot / monthly`. Cart and invoice totals
|
||||
multiply that per-slot price by selected slots and billing duration.
|
||||
|
||||
Order-page location labels are resolved from the current `remote_servers` table.
|
||||
The helper prefers customer-facing fields such as `display_name`,
|
||||
`location_name`, `server_location`, `region`, `remote_server_name`, and
|
||||
`hostname`, with public IP fields used only as a fallback. Generic `Location N`
|
||||
labels should not appear when usable database names exist.
|
||||
|
||||
## Website server status
|
||||
|
||||
`server_status.php` is a Gameservers.World-branded status page. It reads active
|
||||
remote-server rows from the configured Panel database and displays
|
||||
customer-facing location, address, and availability status with a return link to
|
||||
the website. It does not send visitors to the Panel status page.
|
||||
|
||||
## Documentation source
|
||||
|
||||
Customer documentation is read from the existing billing docs directory:
|
||||
|
|
@ -246,6 +264,8 @@ Customer documentation is read from the existing billing docs directory:
|
|||
- `Panel/modules/billing/docs/`
|
||||
|
||||
This keeps the website portable without duplicating the documentation tree.
|
||||
The docs implementation may remain available internally, but documentation is
|
||||
not presented as a public top-level navigation item in the current website menu.
|
||||
|
||||
## Deployment
|
||||
|
||||
|
|
|
|||
|
|
@ -990,6 +990,46 @@ textarea {
|
|||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.status-page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.status-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.status-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 520px;
|
||||
}
|
||||
|
||||
.status-table th,
|
||||
.status-table td {
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.status-table th {
|
||||
color: #b8cae5;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-table tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.doc-view th,
|
||||
.doc-view td {
|
||||
padding: 10px 12px;
|
||||
|
|
@ -1072,7 +1112,7 @@ textarea {
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
@media (max-width: 980px) {
|
||||
.header-shell {
|
||||
grid-template-columns: auto auto;
|
||||
gap: 16px;
|
||||
|
|
@ -1197,6 +1237,41 @@ textarea {
|
|||
flex: 1 1 160px;
|
||||
}
|
||||
|
||||
.status-table {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.status-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-table,
|
||||
.status-table tbody,
|
||||
.status-table tr,
|
||||
.status-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-table tr {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.status-table td {
|
||||
border-bottom: 0;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.status-table td::before {
|
||||
content: attr(data-label) ": ";
|
||||
color: #b8cae5;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.staff-service-row summary::after {
|
||||
justify-self: start;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ function website_create_invoice_from_cart(int $userId): ?int
|
|||
'remote_server_id' => (int)$locationId,
|
||||
'slots' => $slots,
|
||||
'duration_months' => $durationMonths,
|
||||
'price' => $price * $durationMonths,
|
||||
'price' => $price * $slots * $durationMonths,
|
||||
'price_monthly' => $price,
|
||||
];
|
||||
$validItems[] = $line;
|
||||
|
|
|
|||
|
|
@ -783,17 +783,91 @@ function website_service_locations(array $service): array
|
|||
}
|
||||
|
||||
$locations = [];
|
||||
$remoteServers = website_remote_server_labels();
|
||||
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $remoteServerId) {
|
||||
$remoteServerId = trim($remoteServerId);
|
||||
if ($remoteServerId === '' || !ctype_digit($remoteServerId)) {
|
||||
continue;
|
||||
}
|
||||
$locations[$remoteServerId] = 'Location ' . $remoteServerId;
|
||||
$locations[$remoteServerId] = $remoteServers[(int)$remoteServerId] ?? 'Server Location ' . $remoteServerId;
|
||||
}
|
||||
|
||||
return $locations;
|
||||
}
|
||||
|
||||
function website_remote_server_labels(): array
|
||||
{
|
||||
static $labels = null;
|
||||
if ($labels !== null) {
|
||||
return $labels;
|
||||
}
|
||||
|
||||
$labels = [];
|
||||
$db = website_db();
|
||||
if (!$db instanceof mysqli) {
|
||||
return $labels;
|
||||
}
|
||||
|
||||
$table = website_table_prefix() . 'remote_servers';
|
||||
if (!website_table_exists($table)) {
|
||||
return $labels;
|
||||
}
|
||||
|
||||
$columns = website_table_columns($table);
|
||||
$candidateColumns = [
|
||||
'display_name',
|
||||
'location_name',
|
||||
'server_location',
|
||||
'region',
|
||||
'remote_server_name',
|
||||
'hostname',
|
||||
'agent_ip',
|
||||
'display_public_ip',
|
||||
];
|
||||
$selectParts = ['`remote_server_id`'];
|
||||
foreach ($candidateColumns as $column) {
|
||||
if (isset($columns[$column])) {
|
||||
$safeColumn = str_replace('`', '``', $column);
|
||||
$selectParts[] = "`{$safeColumn}`";
|
||||
}
|
||||
}
|
||||
|
||||
$safeTable = str_replace('`', '``', $table);
|
||||
$result = @$db->query('SELECT ' . implode(', ', $selectParts) . " FROM `{$safeTable}` ORDER BY `remote_server_id` ASC");
|
||||
if (!$result instanceof mysqli_result) {
|
||||
return $labels;
|
||||
}
|
||||
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$id = (int)($row['remote_server_id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
continue;
|
||||
}
|
||||
$parts = [];
|
||||
foreach (['display_name', 'location_name', 'server_location', 'region', 'remote_server_name', 'hostname'] as $column) {
|
||||
$value = trim((string)($row[$column] ?? ''));
|
||||
if ($value !== '' && !in_array($value, $parts, true)) {
|
||||
$parts[] = $value;
|
||||
}
|
||||
}
|
||||
if (empty($parts)) {
|
||||
foreach (['display_public_ip', 'agent_ip'] as $column) {
|
||||
$value = trim((string)($row[$column] ?? ''));
|
||||
if ($value !== '') {
|
||||
$parts[] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($parts)) {
|
||||
$labels[$id] = implode(' - ', array_slice($parts, 0, 2));
|
||||
}
|
||||
}
|
||||
$result->free();
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
function website_cart_items(): array
|
||||
{
|
||||
website_start_session();
|
||||
|
|
|
|||
|
|
@ -27,10 +27,7 @@ $currentUser = website_current_user();
|
|||
<h2>Explore</h2>
|
||||
<ul class="footer-links">
|
||||
<li><a href="<?= website_escape(website_url('serverlist.php')) ?>">Game Servers</a></li>
|
||||
<li><a href="<?= website_escape(website_url('pricing.php')) ?>">Pricing</a></li>
|
||||
<li><a href="<?= website_escape(website_url('locations.php')) ?>">Locations</a></li>
|
||||
<li><a href="<?= website_escape(website_url('panel_features.php')) ?>">Panel Features</a></li>
|
||||
<li><a href="<?= website_escape(website_url('docs.php')) ?>">Documentation</a></li>
|
||||
<li><a href="<?= website_escape(website_url('support.php')) ?>">Support</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -61,7 +58,6 @@ $currentUser = website_current_user();
|
|||
<div>
|
||||
<h2>Help and Projects</h2>
|
||||
<ul class="footer-links">
|
||||
<li><a href="<?= website_escape(website_url('docs.php')) ?>">Server Guides</a></li>
|
||||
<li><a href="<?= website_escape(website_custom_project_url()) ?>">Request Custom Work</a></li>
|
||||
<?php if ($discordUrl !== ''): ?>
|
||||
<li><a href="<?= website_escape($discordUrl) ?>" target="_blank" rel="noopener noreferrer">Discord</a></li>
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ $cartCount = website_cart_count();
|
|||
$primaryLinks = [
|
||||
['key' => 'home', 'label' => 'Home', 'href' => website_url('index.php')],
|
||||
['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.php')],
|
||||
['key' => 'pricing', 'label' => 'Pricing', 'href' => website_url('pricing.php')],
|
||||
['key' => 'locations', 'label' => 'Locations', 'href' => website_url('locations.php')],
|
||||
['key' => 'panel-features', 'label' => 'Panel Features', 'href' => website_url('panel_features.php')],
|
||||
['key' => 'docs', 'label' => 'Documentation', 'href' => website_url('docs.php')],
|
||||
['key' => 'support', 'label' => 'Support', 'href' => website_url('support.php')],
|
||||
];
|
||||
if ($currentUser) {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ if (website_request_method() === 'POST') {
|
|||
$error = 'Select a valid billing duration.';
|
||||
} else {
|
||||
$priceMonthly = (float)($service['price_monthly'] ?? 0);
|
||||
$monthlyTotal = $priceMonthly > 0 ? $priceMonthly : 0.0;
|
||||
$monthlyTotal = $priceMonthly > 0 ? $priceMonthly * (int)$slots : 0.0;
|
||||
$lineTotal = $monthlyTotal * (int)$durationMonths;
|
||||
website_cart_add([
|
||||
'service_id' => (int)$service['service_id'],
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ $fixedCapNote = trim((string)($pricing['fixed_cap_note'] ?? ''));
|
|||
$description = trim((string)($service['description'] ?? ''));
|
||||
$price = (float)($service['price_monthly'] ?? 0);
|
||||
$orderHref = website_order_url((string)$service['service_id']);
|
||||
$docSlug = trim((string)($service['cfg_game_key'] ?? ''));
|
||||
$hasDoc = $docSlug !== '' && website_doc_path($docSlug) !== null;
|
||||
$priceLabel = $price > 0 ? '$' . number_format($price, 2) . ' per slot / monthly' : 'Contact for pricing';
|
||||
?>
|
||||
<article class="catalog-row" role="listitem">
|
||||
<div class="catalog-server">
|
||||
|
|
@ -70,14 +69,9 @@ $fixedCapNote = trim((string)($pricing['fixed_cap_note'] ?? ''));
|
|||
<span class="status-badge status-badge-neutral"><?= website_escape($platformLabel) ?></span>
|
||||
</div>
|
||||
<p class="catalog-description" data-label="Description"><?= website_escape($description !== '' ? $description : 'Virtual private hosting with full configuration access, mod support, GSP panel management, and optional custom engineering help.') ?></p>
|
||||
<div class="catalog-price" data-label="Price"><?= $price > 0 ? '$' . number_format($price, 2) . ' / month' : 'Contact for pricing' ?></div>
|
||||
<div class="catalog-price" data-label="Price"><?= website_escape($priceLabel) ?></div>
|
||||
<div class="catalog-actions">
|
||||
<a class="button button-primary" href="<?= website_escape($orderHref) ?>">Order Now</a>
|
||||
<?php if ($hasDoc): ?>
|
||||
<a class="button button-secondary" href="<?= website_escape(documentation_url($docSlug)) ?>">Documentation</a>
|
||||
<?php else: ?>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('docs.php')) ?>">Documentation</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
$discordUrl = trim((string)website_config('discord_url', ''));
|
||||
$customProjectUrl = website_custom_project_url();
|
||||
$pricing = website_config('pricing', []);
|
||||
$platform = website_config('platform', []);
|
||||
$standardSlotStart = (float)($pricing['standard_slot_start'] ?? 0.50);
|
||||
$legacyMonthlyStart = (float)($pricing['legacy_monthly_start'] ?? 4.00);
|
||||
$standardMinSlots = (int)($pricing['standard_min_slots'] ?? 16);
|
||||
$pricingDisclaimer = trim((string)($pricing['disclaimer'] ?? 'Actual prices are reflected on each server order page.'));
|
||||
$customWorkNote = trim((string)($pricing['custom_work_note'] ?? 'Custom development and complex integration work are quoted separately.'));
|
||||
$fixedCapNote = trim((string)($pricing['fixed_cap_note'] ?? 'Some fixed-cap or small co-op games use different slot limits.'));
|
||||
$platformCapacityNote = trim((string)($platform['capacity_note'] ?? 'We actively monitor host capacity and avoid overcrowding servers onto overloaded machines.'));
|
||||
$popularGames = ['Arma 3', 'DayZ', 'CS 1.6', 'TF2', 'Garry\'s Mod', 'Rust', 'Valheim'];
|
||||
$locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
|
||||
|
|
@ -80,6 +73,7 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
|
|||
$serviceName = website_service_name($service);
|
||||
$platformLabel = website_service_platform($service);
|
||||
$price = (float)($service['price_monthly'] ?? 0);
|
||||
$priceLabel = $price > 0 ? '$' . number_format($price, 2) . ' per slot / monthly' : 'See order page';
|
||||
$orderUrl = website_order_url((string)$service['service_id']);
|
||||
?>
|
||||
<article class="service-card">
|
||||
|
|
@ -87,7 +81,7 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
|
|||
<header>
|
||||
<h3><?= website_escape($serviceName) ?></h3>
|
||||
<div class="service-meta">Platform: <?= website_escape($platformLabel) ?></div>
|
||||
<div class="service-price"><?= $price > 0 ? '$' . number_format($price, 2) . ' / month' : 'See order page' ?></div>
|
||||
<div class="service-price"><?= website_escape($priceLabel) ?></div>
|
||||
</header>
|
||||
<div class="card-actions">
|
||||
<a class="button button-primary" href="<?= website_escape($orderUrl) ?>">Order</a>
|
||||
|
|
@ -103,16 +97,6 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
|
|||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="summary-grid">
|
||||
<article class="summary-card">
|
||||
<div class="kicker">Pricing summary</div>
|
||||
<h3>Flexible hosting for different communities.</h3>
|
||||
<p>Choose a game, player capacity, region, and available options for the server you want to run. Standard slot-based hosting starts at $<?= website_escape(number_format($standardSlotStart, 2)) ?> per slot, with most standard plans beginning at <?= website_escape((string)$standardMinSlots) ?> slots.</p>
|
||||
<p>Selected lightweight legacy servers may start around $<?= website_escape(number_format($legacyMonthlyStart, 2)) ?> per month. <?= website_escape($fixedCapNote) ?></p>
|
||||
<p><?= website_escape($customWorkNote) ?></p>
|
||||
<div class="card-actions">
|
||||
<a class="button button-primary" href="<?= website_escape(website_url('serverlist.php')) ?>">View Game Servers</a>
|
||||
</div>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<div class="kicker">Locations</div>
|
||||
<h3>Hosting locations for your players.</h3>
|
||||
|
|
@ -123,7 +107,7 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
|
|||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('locations.php')) ?>">View Locations</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('server_status.php')) ?>">View Server Status</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ $platformLabel = website_service_platform($service);
|
|||
$description = trim((string)($service['description'] ?? ''));
|
||||
$price = (float)($service['price_monthly'] ?? 0);
|
||||
$selectedSlots = max((int)$minSlots, (int)($_POST['slots'] ?? $minSlots));
|
||||
$monthlyEstimate = $price > 0 ? $price * $selectedSlots : 0.0;
|
||||
?>
|
||||
<section class="page-heading">
|
||||
<div class="container">
|
||||
<h1>Configure <?= website_escape($serviceName) ?></h1>
|
||||
<p><?= website_escape($description !== '' ? $description : 'Configure this server package before adding it to your cart. Login is only required when you proceed to checkout.') ?></p>
|
||||
<p><?= website_escape($description !== '' ? $description : 'Choose your slot count, hosting location, and billing term before adding this server to your cart.') ?></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -45,18 +46,17 @@ $selectedSlots = max((int)$minSlots, (int)($_POST['slots'] ?? $minSlots));
|
|||
<h3>Plan</h3>
|
||||
<p><strong><?= website_escape($serviceName) ?></strong></p>
|
||||
<p>Platform: <?= website_escape($platformLabel) ?></p>
|
||||
<p><?= $price > 0 ? 'Catalog price: $' . website_escape(number_format($price, 2)) . ' / month' : 'Catalog price: contact for pricing' ?></p>
|
||||
<p><?= $price > 0 ? 'Price: $' . website_escape(number_format($price, 2)) . ' per slot / monthly' : 'Price: contact for pricing' ?></p>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<h3>Slots and location</h3>
|
||||
<p>Minimum slots: <?= website_escape((string)$minSlots) ?></p>
|
||||
<p><?= $maxSlots > 0 ? 'Maximum slots: ' . website_escape((string)$maxSlots) : 'Maximum slots depend on game and available location capacity.' ?></p>
|
||||
<p>Location availability is confirmed before payment or provisioning.</p>
|
||||
</article>
|
||||
<article class="summary-card">
|
||||
<h3>Checkout boundary</h3>
|
||||
<p>You can add this server to your cart before logging in. Payment and final provisioning must complete server-side before the Panel creates a running game server.</p>
|
||||
<p class="muted">Return to the game-server catalog or contact support if this package should be available.</p>
|
||||
<h3>Estimated monthly total</h3>
|
||||
<p><?= $monthlyEstimate > 0 ? '$' . website_escape(number_format($monthlyEstimate, 2)) . ' / monthly' : 'Contact for pricing' ?></p>
|
||||
<p class="muted">Based on <?= website_escape((string)$selectedSlots) ?> selected slots.</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ $selectedSlots = max((int)$minSlots, (int)($_POST['slots'] ?? $minSlots));
|
|||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<p class="muted">The website stores this selection in your cart and revalidates service, slots, location, and price during checkout.</p>
|
||||
<p class="muted">You can review your selection in the cart before checkout.</p>
|
||||
<div class="card-actions">
|
||||
<button class="button button-primary" type="submit">Add to Cart</button>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('serverlist.php')) ?>">Back to Catalog</a>
|
||||
|
|
|
|||
56
Panel/modules/website/pages/server_status.php
Normal file
56
Panel/modules/website/pages/server_status.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
?>
|
||||
<section class="page-heading">
|
||||
<div class="container">
|
||||
<h1>Server Status</h1>
|
||||
<p>Check current availability for configured Gameservers.World hosting locations.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="summary-card">
|
||||
<div class="status-page-header">
|
||||
<p class="muted"><strong>Checked:</strong> <?= website_escape((string)$checkedAt) ?></p>
|
||||
<div class="card-actions">
|
||||
<a class="button button-primary" href="<?= website_escape(website_url('server_status.php')) ?>">Refresh Status</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('index.php')) ?>">Back to Gameservers.World</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (empty($statusRows)): ?>
|
||||
<div class="empty-state">
|
||||
<h2>Status is unavailable</h2>
|
||||
<p>We could not load hosting location status right now. Please contact support if you need help choosing a location.</p>
|
||||
<div class="card-actions">
|
||||
<a class="button button-primary" href="<?= website_escape(website_url('support.php')) ?>">Contact Support</a>
|
||||
<a class="button button-secondary" href="<?= website_escape(website_url('serverlist.php')) ?>">Browse Game Servers</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="status-table-wrap">
|
||||
<table class="status-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Location</th>
|
||||
<th>Address</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($statusRows as $row): ?>
|
||||
<tr>
|
||||
<td data-label="Location"><?= website_escape((string)$row['name']) ?></td>
|
||||
<td data-label="Address"><?= website_escape((string)$row['address']) ?></td>
|
||||
<td data-label="Status"><span class="status-badge <?= website_escape((string)$row['status_class']) ?>"><?= website_escape((string)$row['status']) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -34,7 +34,7 @@ $loginUnavailable = $loginUnavailable ?? false;
|
|||
<h3>Modding and troubleshooting</h3>
|
||||
<p>Use the documentation set for server guides, panel workflows, Workshop help, and technical troubleshooting across current games, older multiplayer titles, and community-maintained servers.</p>
|
||||
<div class="card-actions">
|
||||
<a class="button button-primary" href="<?= website_escape(website_url('docs.php')) ?>">Open Docs</a>
|
||||
<a class="button button-primary" href="<?= website_escape(website_url('serverlist.php')) ?>">Browse Game Servers</a>
|
||||
</div>
|
||||
</article>
|
||||
<article class="support-card">
|
||||
|
|
|
|||
97
Panel/modules/website/server_status.php
Normal file
97
Panel/modules/website/server_status.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
|
||||
function website_status_socket_reachable(string $host, int $port, float $timeout = 1.5): ?bool
|
||||
{
|
||||
if ($host === '' || $port <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
|
||||
if ($socket === false) {
|
||||
return false;
|
||||
}
|
||||
fclose($socket);
|
||||
return true;
|
||||
}
|
||||
|
||||
function website_status_locations(): array
|
||||
{
|
||||
$db = website_db();
|
||||
if (!$db instanceof mysqli) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$table = website_table_prefix() . 'remote_servers';
|
||||
if (!website_table_exists($table)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$columns = website_table_columns($table);
|
||||
$select = ['`remote_server_id`'];
|
||||
foreach (['display_name', 'location_name', 'server_location', 'region', 'remote_server_name', 'hostname', 'agent_ip', 'display_public_ip', 'agent_port', 'enabled'] as $column) {
|
||||
if (isset($columns[$column])) {
|
||||
$safeColumn = str_replace('`', '``', $column);
|
||||
$select[] = "`{$safeColumn}`";
|
||||
}
|
||||
}
|
||||
|
||||
$safeTable = str_replace('`', '``', $table);
|
||||
$result = @$db->query('SELECT ' . implode(', ', $select) . " FROM `{$safeTable}` ORDER BY `remote_server_id` ASC");
|
||||
if (!$result instanceof mysqli_result) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
if (isset($row['enabled']) && (int)$row['enabled'] !== 1) {
|
||||
continue;
|
||||
}
|
||||
$nameParts = [];
|
||||
foreach (['display_name', 'location_name', 'server_location', 'region', 'remote_server_name', 'hostname'] as $column) {
|
||||
$value = trim((string)($row[$column] ?? ''));
|
||||
if ($value !== '' && !in_array($value, $nameParts, true)) {
|
||||
$nameParts[] = $value;
|
||||
}
|
||||
}
|
||||
$agentIp = trim((string)($row['agent_ip'] ?? $row['display_public_ip'] ?? ''));
|
||||
$displayIp = trim((string)($row['display_public_ip'] ?? $agentIp));
|
||||
$agentPort = (int)($row['agent_port'] ?? 0);
|
||||
$reachable = website_status_socket_reachable($agentIp, $agentPort);
|
||||
if ($reachable === true) {
|
||||
$status = 'Online';
|
||||
$statusClass = '';
|
||||
} elseif ($reachable === false) {
|
||||
$status = 'Unavailable';
|
||||
$statusClass = 'status-badge-muted';
|
||||
} else {
|
||||
$status = 'Status pending';
|
||||
$statusClass = 'status-badge-neutral';
|
||||
}
|
||||
$rows[] = [
|
||||
'name' => $nameParts !== [] ? implode(' - ', array_slice($nameParts, 0, 2)) : 'Hosting location',
|
||||
'address' => $displayIp,
|
||||
'status' => $status,
|
||||
'status_class' => $statusClass,
|
||||
];
|
||||
}
|
||||
$result->free();
|
||||
return $rows;
|
||||
}
|
||||
|
||||
website_render(
|
||||
'server_status.php',
|
||||
[
|
||||
'activePage' => 'status',
|
||||
'pageTitle' => 'Server Status - Gameservers.World',
|
||||
'metaDescription' => 'View current Gameservers.World hosting location availability.',
|
||||
'canonicalPath' => 'server_status.php',
|
||||
'checkedAt' => gmdate('Y-m-d H:i:s') . ' UTC',
|
||||
'statusRows' => website_status_locations(),
|
||||
]
|
||||
);
|
||||
|
|
@ -73,7 +73,7 @@ Paid orders appear in the website provisioning queue. The queue is the handoff p
|
|||
|
||||
The public `serverlist.php` catalog uses a compact row/list layout so many
|
||||
services can be scanned quickly. Customer-facing rows show the display name,
|
||||
derived platform label, short description, price, order action, and documentation
|
||||
derived platform label, short description, per-slot monthly price, and order
|
||||
action. Raw XML/config keys such as `*_linux64` or `*_win32` are internal
|
||||
identifiers and should not be prominent public labels.
|
||||
|
||||
|
|
@ -100,10 +100,7 @@ Primary links:
|
|||
|
||||
- Home
|
||||
- Game Servers
|
||||
- Pricing
|
||||
- Locations
|
||||
- Panel Features
|
||||
- Documentation
|
||||
- Support
|
||||
|
||||
Account area:
|
||||
|
|
@ -123,6 +120,9 @@ opens the operational Panel at the configured Panel URL.
|
|||
|
||||
The footer should contain only one modest `Control Panel` link. Public pages,
|
||||
especially the homepage, must not repeat Control Panel promotions unnecessarily.
|
||||
Pricing and locations are not top-level public navigation items. Game-specific
|
||||
pricing is shown on the catalog and order pages, and locations are summarized on
|
||||
the homepage with a link to the website-branded server status page.
|
||||
|
||||
Control Panel links point directly to the configured Panel domain. `My Servers`
|
||||
opens a website customer page that summarizes website orders and links to the
|
||||
|
|
@ -146,6 +146,22 @@ Avoid public wording such as:
|
|||
|
||||
Use direct customer-facing language instead.
|
||||
|
||||
## Pricing, Locations, and Status
|
||||
|
||||
Catalog and order pages display `price_monthly` as per-slot monthly pricing, for
|
||||
example `$0.50 per slot / monthly`. Cart and invoice calculations multiply the
|
||||
per-slot price by selected slot count and billing duration.
|
||||
|
||||
Order-page location labels are resolved from `remote_servers` using the best
|
||||
available customer-facing fields: `display_name`, `location_name`,
|
||||
`server_location`, `region`, `remote_server_name`, or `hostname`. Public IP
|
||||
fields are fallback labels only. Generic labels such as `Location 2` should not
|
||||
be shown when a real database name is available.
|
||||
|
||||
`server_status.php` is the website status page. It uses the configured Panel
|
||||
database as its remote-server source, keeps the Gameservers.World theme, and
|
||||
returns users to the website rather than the Panel dashboard.
|
||||
|
||||
## Deployment
|
||||
|
||||
Recommended:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue