feat: harden billing provisioning and admin service UI workflows
Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/6640cb6b-0d5a-4c91-bfaf-86dd1b71f701 Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
parent
87f1a943ec
commit
bb4fd8b44e
7 changed files with 481 additions and 81 deletions
|
|
@ -1302,8 +1302,9 @@ function gsp_panel_update_section()
|
|||
// ---- GitHub Unstable -----------------------------------------------------
|
||||
echo "<h3>GitHub Unstable</h3>\n";
|
||||
echo "<p>GitHub Unstable represents the latest development branch and may be unstable.</p>\n";
|
||||
echo "<p class='failure' style='display:inline-block;padding:5px 10px;'>"
|
||||
. "⚠ Warning: GitHub Unstable may contain bugs or incomplete features. Use with caution in production.</p><br><br>\n";
|
||||
echo "<p style='display:inline-block;margin:4px 0 10px;padding:6px 10px;border-radius:6px;"
|
||||
. "border:1px solid #d9b55a;background:#fff8e6;color:#6b5420;font-size:0.92em;'>"
|
||||
. "⚠ Cutting-edge updates may include unfinished changes. Use stable releases for production.</p><br>\n";
|
||||
echo "<form method='POST'>\n";
|
||||
echo "<input type='hidden' name='gsp_update_action' value='update_unstable'>\n";
|
||||
echo "<input type='hidden' name='gsp_update_csrf' value='" . htmlspecialchars($csrf_token) . "'>\n";
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
<title>Admin Service Configuration - GSP</title>
|
||||
<style>
|
||||
.svc-table { border-collapse: collapse; width: 100%; }
|
||||
.svc-table th, .svc-table td { border: 1px solid #4a6080; padding: 6px 8px; vertical-align: middle; }
|
||||
.svc-table th, .svc-table td { border: 1px solid rgba(86,105,130,0.6); padding: 8px 10px; vertical-align: middle; }
|
||||
/* Sticky header: stays visible while scrolling; dark background with light text for readability */
|
||||
.svc-table thead th { position: sticky; top: 0; z-index: 10; background: #2c3e50; color: #f0f0f0; white-space: nowrap; text-align: center; }
|
||||
.svc-table thead th { position: sticky; top: 0; z-index: 10; background: #26354a; color: #f0f0f0; white-space: nowrap; text-align: center; }
|
||||
.svc-table thead th.game-name { text-align: left; }
|
||||
.svc-table td.game-name { text-align: left; white-space: nowrap; }
|
||||
.price-input { width: 80px; }
|
||||
|
|
@ -19,10 +19,28 @@
|
|||
.img-fallback { display: none; max-width: 180px; margin-top: 4px; }
|
||||
.img-fallback.img-fallback-visible { display: block; }
|
||||
.muted { color: #999; font-size: 0.85em; }
|
||||
.flash-ok { background: #d4edda; border: 1px solid #c3e6cb; padding: 8px 12px; margin-bottom: 10px; border-radius: 4px; color: #155724; }
|
||||
.flash-err { background: #f8d7da; border: 1px solid #f5c6cb; padding: 8px 12px; margin-bottom: 10px; border-radius: 4px; color: #721c24; }
|
||||
.flash-ok { background: #d4edda; border: 1px solid #c3e6cb; padding: 10px 12px; margin-bottom: 10px; border-radius: 6px; color: #155724; }
|
||||
.flash-err { background: #f8d7da; border: 1px solid #f5c6cb; padding: 10px 12px; margin-bottom: 10px; border-radius: 6px; color: #721c24; }
|
||||
.servers-cell { text-align: left; }
|
||||
.server-cb-label { display: block; white-space: nowrap; margin: 2px 0; }
|
||||
.action-cell { text-align: center; min-width: 120px; }
|
||||
.btn-row-save, .btn-save-all {
|
||||
border: 1px solid #3e7ab8;
|
||||
border-radius: 6px;
|
||||
background: #2f6dac;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-save-all {
|
||||
padding: 9px 14px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.btn-row-save:hover, .btn-save-all:hover { background: #25598d; }
|
||||
.sort-link { color: #d8e7ff; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
|
||||
.sort-link:hover { text-decoration: underline; }
|
||||
.sort-active { color: #ffffff; font-weight: 700; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -53,6 +71,10 @@
|
|||
|
||||
require_once(__DIR__ . '/bootstrap.php');
|
||||
require_once(__DIR__ . '/includes/admin_auth.php');
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_name('opengamepanel_web');
|
||||
session_start();
|
||||
}
|
||||
|
||||
function h(mixed $s): string
|
||||
{
|
||||
|
|
@ -285,13 +307,47 @@ $syncMessages = sync_billing_services($db, $table_prefix);
|
|||
|
||||
$flash = [];
|
||||
$flashType = 'ok';
|
||||
$sort = strtolower((string)($_GET['sort'] ?? $_POST['sort'] ?? 'game'));
|
||||
$dir = strtolower((string)($_GET['dir'] ?? $_POST['dir'] ?? 'asc')) === 'desc' ? 'desc' : 'asc';
|
||||
$gameMode = strtolower((string)($_GET['game_mode'] ?? $_POST['game_mode'] ?? 'name'));
|
||||
if (!in_array($sort, ['game', 'config', 'enabled', 'day', 'month', 'year', 'servers'], true)) {
|
||||
$sort = 'game';
|
||||
}
|
||||
if (!in_array($gameMode, ['name', 'enabled'], true)) {
|
||||
$gameMode = 'name';
|
||||
}
|
||||
$sortQuery = http_build_query([
|
||||
'sort' => $sort,
|
||||
'dir' => $dir,
|
||||
'game_mode' => $gameMode,
|
||||
]);
|
||||
|
||||
function sort_link_params(string $column, string $sort, string $dir, string $gameMode): array
|
||||
{
|
||||
$nextDir = ($sort === $column && $dir === 'asc') ? 'desc' : 'asc';
|
||||
$nextGameMode = $gameMode;
|
||||
if ($column === 'game' && $sort === 'game' && $gameMode === 'name') {
|
||||
$nextGameMode = 'enabled';
|
||||
$nextDir = 'asc';
|
||||
} elseif ($column === 'game' && $sort === 'game' && $gameMode === 'enabled') {
|
||||
$nextGameMode = 'name';
|
||||
$nextDir = 'asc';
|
||||
} elseif ($column !== 'game') {
|
||||
$nextGameMode = 'name';
|
||||
}
|
||||
return [
|
||||
'sort' => $column,
|
||||
'dir' => $nextDir,
|
||||
'game_mode' => $nextGameMode,
|
||||
];
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
SAVE: service configuration form submitted
|
||||
Only admin-editable fields are updated; service_name and home_cfg_id
|
||||
are never overwritten here.
|
||||
----------------------------------------------------------------------- */
|
||||
if (isset($_POST['save_services'])) {
|
||||
if (isset($_POST['save_services']) || isset($_POST['save_row'])) {
|
||||
// Load valid remote server IDs for validation
|
||||
$validServerIds = [];
|
||||
$rsRes = $db->query("SELECT remote_server_id FROM `{$table_prefix}remote_servers`");
|
||||
|
|
@ -302,9 +358,14 @@ if (isset($_POST['save_services'])) {
|
|||
|
||||
$postedServices = $_POST['svc'] ?? [];
|
||||
$postedServers = $_POST['servers'] ?? [];
|
||||
$rowOnlyServiceId = isset($_POST['save_row']) ? (int)$_POST['save_row'] : 0;
|
||||
$updatedCount = 0;
|
||||
|
||||
foreach ((array)$postedServices as $sid => $svcData) {
|
||||
$sid = (int)$sid;
|
||||
if ($rowOnlyServiceId > 0 && $sid !== $rowOnlyServiceId) {
|
||||
continue;
|
||||
}
|
||||
$enabled = isset($svcData['enabled']) ? 1 : 0;
|
||||
$priceDaily = number_format((float)($svcData['price_daily'] ?? 0), 2, '.', '');
|
||||
$priceMonthly = number_format((float)($svcData['price_monthly'] ?? 0), 2, '.', '');
|
||||
|
|
@ -332,7 +393,7 @@ if (isset($_POST['save_services'])) {
|
|||
}
|
||||
$remoteServerIdStr = $db->real_escape_string(implode(',', $checkedIds));
|
||||
|
||||
$db->query(
|
||||
$ok = $db->query(
|
||||
"UPDATE `{$table_prefix}billing_services`
|
||||
SET enabled = {$enabled},
|
||||
price_daily = '{$priceDaily}',
|
||||
|
|
@ -342,12 +403,38 @@ if (isset($_POST['save_services'])) {
|
|||
slot_max_qty = {$slotMax},
|
||||
description = '{$description}',
|
||||
img_url = '{$imgUrl}',
|
||||
remote_server_id = '{$remoteServerIdStr}'
|
||||
WHERE service_id = {$sid}"
|
||||
remote_server_id = '{$remoteServerIdStr}'
|
||||
WHERE service_id = {$sid}"
|
||||
);
|
||||
if ($ok) {
|
||||
$updatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$flash[] = "Services saved.";
|
||||
if ($updatedCount > 0) {
|
||||
if ($rowOnlyServiceId > 0) {
|
||||
$flash[] = "Service row #{$rowOnlyServiceId} saved.";
|
||||
} else {
|
||||
$flash[] = "{$updatedCount} service row(s) saved.";
|
||||
}
|
||||
} else {
|
||||
$flashType = 'err';
|
||||
if ($rowOnlyServiceId > 0) {
|
||||
$flash[] = "No changes were saved for service row #{$rowOnlyServiceId}.";
|
||||
} else {
|
||||
$flash[] = "No service rows were updated.";
|
||||
}
|
||||
}
|
||||
$_SESSION['billing_adminserverlist_flash'] = ['type' => $flashType, 'messages' => $flash];
|
||||
header("Location: /adminserverlist.php?{$sortQuery}");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($_SESSION['billing_adminserverlist_flash'])) {
|
||||
$flashData = $_SESSION['billing_adminserverlist_flash'];
|
||||
unset($_SESSION['billing_adminserverlist_flash']);
|
||||
$flashType = ($flashData['type'] ?? 'ok') === 'err' ? 'err' : 'ok';
|
||||
$flash = array_values(array_filter((array)($flashData['messages'] ?? []), 'is_string'));
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
|
|
@ -371,12 +458,54 @@ $svcRes = $db->query(
|
|||
bs.remote_server_id, bs.description, bs.img_url,
|
||||
ch.home_cfg_file
|
||||
FROM `{$table_prefix}billing_services` bs
|
||||
LEFT JOIN `{$table_prefix}config_homes` ch ON ch.home_cfg_id = bs.home_cfg_id
|
||||
LEFT JOIN `{$table_prefix}config_homes` ch ON ch.home_cfg_id = bs.home_cfg_id
|
||||
ORDER BY bs.service_name"
|
||||
);
|
||||
while ($svcRes && ($row = $svcRes->fetch_assoc())) {
|
||||
$services[] = $row;
|
||||
}
|
||||
if (!empty($services)) {
|
||||
usort($services, function (array $a, array $b) use ($sort, $dir, $gameMode): int {
|
||||
$cmp = 0;
|
||||
switch ($sort) {
|
||||
case 'config':
|
||||
$cmp = strcasecmp((string)($a['home_cfg_file'] ?? ''), (string)($b['home_cfg_file'] ?? ''));
|
||||
break;
|
||||
case 'enabled':
|
||||
$cmp = ((int)($a['enabled'] ?? 0)) <=> ((int)($b['enabled'] ?? 0));
|
||||
break;
|
||||
case 'day':
|
||||
$cmp = ((float)($a['price_daily'] ?? 0)) <=> ((float)($b['price_daily'] ?? 0));
|
||||
break;
|
||||
case 'month':
|
||||
$cmp = ((float)($a['price_monthly'] ?? 0)) <=> ((float)($b['price_monthly'] ?? 0));
|
||||
break;
|
||||
case 'year':
|
||||
$cmp = ((float)($a['price_year'] ?? 0)) <=> ((float)($b['price_year'] ?? 0));
|
||||
break;
|
||||
case 'servers':
|
||||
$countA = trim((string)($a['remote_server_id'] ?? '')) === '' ? 0 : count(array_filter(explode(',', (string)$a['remote_server_id']), 'strlen'));
|
||||
$countB = trim((string)($b['remote_server_id'] ?? '')) === '' ? 0 : count(array_filter(explode(',', (string)$b['remote_server_id']), 'strlen'));
|
||||
$cmp = $countA <=> $countB;
|
||||
break;
|
||||
case 'game':
|
||||
default:
|
||||
if ($gameMode === 'enabled') {
|
||||
$cmp = ((int)($b['enabled'] ?? 0)) <=> ((int)($a['enabled'] ?? 0));
|
||||
if ($cmp === 0) {
|
||||
$cmp = strcasecmp((string)($a['service_name'] ?? ''), (string)($b['service_name'] ?? ''));
|
||||
}
|
||||
} else {
|
||||
$cmp = strcasecmp((string)($a['service_name'] ?? ''), (string)($b['service_name'] ?? ''));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($cmp === 0) {
|
||||
$cmp = ((int)($a['service_id'] ?? 0)) <=> ((int)($b['service_id'] ?? 0));
|
||||
}
|
||||
return $dir === 'desc' ? -$cmp : $cmp;
|
||||
});
|
||||
}
|
||||
?>
|
||||
|
||||
<?php foreach (array_merge((array)$syncMessages, (array)$flash) as $msg): ?>
|
||||
|
|
@ -398,22 +527,47 @@ while ($svcRes && ($row = $svcRes->fetch_assoc())) {
|
|||
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="save_services" value="1">
|
||||
<input type="hidden" name="sort" value="<?php echo h($sort); ?>">
|
||||
<input type="hidden" name="dir" value="<?php echo h($dir); ?>">
|
||||
<input type="hidden" name="game_mode" value="<?php echo h($gameMode); ?>">
|
||||
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="svc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="game-name">Game Name</th>
|
||||
<th>Config XML</th>
|
||||
<th>Enabled</th>
|
||||
<th class="game-name">
|
||||
<?php $p = sort_link_params('game', $sort, $dir, $gameMode); ?>
|
||||
<a class="sort-link <?php echo $sort === 'game' ? 'sort-active' : ''; ?>" href="/adminserverlist.php?<?php echo h(http_build_query($p)); ?>">Game Name</a>
|
||||
</th>
|
||||
<th>
|
||||
<?php $p = sort_link_params('config', $sort, $dir, $gameMode); ?>
|
||||
<a class="sort-link <?php echo $sort === 'config' ? 'sort-active' : ''; ?>" href="/adminserverlist.php?<?php echo h(http_build_query($p)); ?>">Config XML</a>
|
||||
</th>
|
||||
<th>
|
||||
<?php $p = sort_link_params('enabled', $sort, $dir, $gameMode); ?>
|
||||
<a class="sort-link <?php echo $sort === 'enabled' ? 'sort-active' : ''; ?>" href="/adminserverlist.php?<?php echo h(http_build_query($p)); ?>">Enabled</a>
|
||||
</th>
|
||||
<th>Min Slots</th>
|
||||
<th>Max Slots</th>
|
||||
<th>Price / Day ($)</th>
|
||||
<th>Price / Month ($)</th>
|
||||
<th>Price / Year ($)</th>
|
||||
<th>
|
||||
<?php $p = sort_link_params('day', $sort, $dir, $gameMode); ?>
|
||||
<a class="sort-link <?php echo $sort === 'day' ? 'sort-active' : ''; ?>" href="/adminserverlist.php?<?php echo h(http_build_query($p)); ?>">Price / Day ($)</a>
|
||||
</th>
|
||||
<th>
|
||||
<?php $p = sort_link_params('month', $sort, $dir, $gameMode); ?>
|
||||
<a class="sort-link <?php echo $sort === 'month' ? 'sort-active' : ''; ?>" href="/adminserverlist.php?<?php echo h(http_build_query($p)); ?>">Price / Month ($)</a>
|
||||
</th>
|
||||
<th>
|
||||
<?php $p = sort_link_params('year', $sort, $dir, $gameMode); ?>
|
||||
<a class="sort-link <?php echo $sort === 'year' ? 'sort-active' : ''; ?>" href="/adminserverlist.php?<?php echo h(http_build_query($p)); ?>">Price / Year ($)</a>
|
||||
</th>
|
||||
<th>Description</th>
|
||||
<th>Image</th>
|
||||
<th>Available Servers</th>
|
||||
<th>
|
||||
<?php $p = sort_link_params('servers', $sort, $dir, $gameMode); ?>
|
||||
<a class="sort-link <?php echo $sort === 'servers' ? 'sort-active' : ''; ?>" href="/adminserverlist.php?<?php echo h(http_build_query($p)); ?>">Available Servers</a>
|
||||
</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -541,6 +695,9 @@ while ($svcRes && ($row = $svcRes->fetch_assoc())) {
|
|||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="action-cell">
|
||||
<button type="submit" class="btn-row-save" name="save_row" value="<?php echo $sid; ?>">Save Row</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
|
|
@ -548,7 +705,7 @@ while ($svcRes && ($row = $svcRes->fetch_assoc())) {
|
|||
</div>
|
||||
|
||||
<div style="margin-top:14px;">
|
||||
<button type="submit">Save Services</button>
|
||||
<button type="submit" class="btn-save-all">Save All Services</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,137 @@ if (!function_exists('billing_invoke_provision')) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!function_exists('billing_get_remote_ip_ids')) {
|
||||
function billing_get_remote_ip_ids($db, string $db_prefix, int $remote_server_id): array
|
||||
{
|
||||
$rows = $db->resultQuery(
|
||||
"SELECT ip_id FROM `{$db_prefix}remote_server_ips` WHERE remote_server_id=" . $db->realEscapeSingle($remote_server_id) . " ORDER BY ip_id ASC"
|
||||
);
|
||||
$ipIds = array();
|
||||
foreach ((array)$rows as $row) {
|
||||
$ipId = intval($row['ip_id'] ?? 0);
|
||||
if ($ipId > 0) {
|
||||
$ipIds[] = $ipId;
|
||||
}
|
||||
}
|
||||
return $ipIds;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('billing_allocate_home_port')) {
|
||||
function billing_allocate_home_port($db, string $db_prefix, int $home_id, int $remote_server_id, int $home_cfg_id): array
|
||||
{
|
||||
$ipIds = billing_get_remote_ip_ids($db, $db_prefix, $remote_server_id);
|
||||
if (empty($ipIds)) {
|
||||
return array('ok' => false, 'error' => "No IP addresses are configured for remote server #{$remote_server_id}.");
|
||||
}
|
||||
|
||||
foreach ($ipIds as $ipId) {
|
||||
$ranges = $db->resultQuery(
|
||||
"SELECT start_port, end_port, port_increment
|
||||
FROM `{$db_prefix}arrange_ports`
|
||||
WHERE ip_id=" . $db->realEscapeSingle($ipId) . "
|
||||
AND home_cfg_id=" . $db->realEscapeSingle($home_cfg_id) . "
|
||||
ORDER BY range_id ASC"
|
||||
);
|
||||
if (empty($ranges)) {
|
||||
$ranges = $db->resultQuery(
|
||||
"SELECT start_port, end_port, port_increment
|
||||
FROM `{$db_prefix}arrange_ports`
|
||||
WHERE ip_id=" . $db->realEscapeSingle($ipId) . "
|
||||
AND home_cfg_id=0
|
||||
ORDER BY range_id ASC"
|
||||
);
|
||||
}
|
||||
if (empty($ranges)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$usedRows = $db->resultQuery(
|
||||
"SELECT port FROM `{$db_prefix}home_ip_ports` WHERE ip_id=" . $db->realEscapeSingle($ipId)
|
||||
);
|
||||
$usedPorts = array();
|
||||
foreach ((array)$usedRows as $usedRow) {
|
||||
$usedPorts[intval($usedRow['port'] ?? 0)] = true;
|
||||
}
|
||||
|
||||
foreach ((array)$ranges as $range) {
|
||||
$start = intval($range['start_port'] ?? 0);
|
||||
$end = intval($range['end_port'] ?? 0);
|
||||
$increment = max(1, intval($range['port_increment'] ?? 1));
|
||||
if ($start <= 0 || $end <= 0 || $start > $end) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($port = $start; $port <= $end; $port += $increment) {
|
||||
if (isset($usedPorts[$port])) {
|
||||
continue;
|
||||
}
|
||||
$safeIpId = $db->realEscapeSingle($ipId);
|
||||
$safePort = $db->realEscapeSingle($port);
|
||||
$safeHome = $db->realEscapeSingle($home_id);
|
||||
$insertOk = $db->query(
|
||||
"INSERT INTO `{$db_prefix}home_ip_ports` (`ip_id`, `port`, `home_id`)
|
||||
SELECT {$safeIpId}, {$safePort}, {$safeHome}
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM `{$db_prefix}home_ip_ports`
|
||||
WHERE ip_id = {$safeIpId}
|
||||
AND port = {$safePort}
|
||||
)"
|
||||
);
|
||||
if (!$insertOk) {
|
||||
continue;
|
||||
}
|
||||
$verify = $db->resultQuery(
|
||||
"SELECT home_id FROM `{$db_prefix}home_ip_ports`
|
||||
WHERE ip_id = {$safeIpId}
|
||||
AND port = {$safePort}
|
||||
AND home_id = {$safeHome}
|
||||
LIMIT 1"
|
||||
);
|
||||
if (!empty($verify)) {
|
||||
return array('ok' => true, 'ip_id' => $ipId, 'port' => intval($port));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array('ok' => false, 'error' => "No available port in arrange_ports for remote server #{$remote_server_id} and home_cfg_id #{$home_cfg_id}.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('billing_resolve_mod_cfg_id')) {
|
||||
function billing_resolve_mod_cfg_id($db, int $home_cfg_id, int $preferred_mod_cfg_id): array
|
||||
{
|
||||
$mods = $db->getCfgMods($home_cfg_id);
|
||||
if (empty($mods)) {
|
||||
return array('ok' => false, 'error' => "No config_mods rows found for home_cfg_id #{$home_cfg_id}.");
|
||||
}
|
||||
|
||||
$first = null;
|
||||
foreach ((array)$mods as $mod) {
|
||||
$modCfgId = intval($mod['mod_cfg_id'] ?? 0);
|
||||
if ($modCfgId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($first === null) {
|
||||
$first = $modCfgId;
|
||||
}
|
||||
if ($preferred_mod_cfg_id > 0 && $modCfgId === $preferred_mod_cfg_id) {
|
||||
return array('ok' => true, 'mod_cfg_id' => $modCfgId);
|
||||
}
|
||||
}
|
||||
|
||||
if ($first !== null) {
|
||||
return array('ok' => true, 'mod_cfg_id' => $first);
|
||||
}
|
||||
|
||||
return array('ok' => false, 'error' => "No usable mod_cfg_id found for home_cfg_id #{$home_cfg_id}.");
|
||||
}
|
||||
}
|
||||
|
||||
function exec_ogp_module()
|
||||
{
|
||||
global $db,$view,$settings,$table_prefix;
|
||||
|
|
@ -84,9 +215,13 @@ function exec_ogp_module()
|
|||
{
|
||||
$provisioned_count = 0;
|
||||
$failed_count = 0;
|
||||
$failed_messages = array();
|
||||
|
||||
foreach ((array)$orders as $order)
|
||||
{
|
||||
$home_id = 0;
|
||||
$order_failed = false;
|
||||
$order_failure_reason = '';
|
||||
$end_date = null;
|
||||
$end_date_str = null;
|
||||
$order_id = $order['order_id'];
|
||||
|
|
@ -125,13 +260,16 @@ function exec_ogp_module()
|
|||
$access_rights = $service[0]['access_rights'];
|
||||
}
|
||||
else
|
||||
return;
|
||||
{
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "Service ID {$service_id} not found.";
|
||||
}
|
||||
|
||||
if($alreadyProvisioned)
|
||||
if(!$order_failed && $alreadyProvisioned)
|
||||
{
|
||||
$home_id = intval($order['home_id']);
|
||||
}
|
||||
elseif($extended)
|
||||
elseif(!$order_failed && $extended)
|
||||
{
|
||||
$home_id = $order['home_id'];
|
||||
|
||||
|
|
@ -177,7 +315,7 @@ function exec_ogp_module()
|
|||
//end WEBHOOK Discord
|
||||
|
||||
}
|
||||
else
|
||||
elseif(!$order_failed)
|
||||
{
|
||||
//OPTIONS, change it at your choice;
|
||||
$extra_params = "";//no extra params defined by default
|
||||
|
|
@ -189,59 +327,125 @@ function exec_ogp_module()
|
|||
$rserver = $db->getRemoteServer($remote_server_id);
|
||||
$game_path = "/home/gameserver/";
|
||||
$home_id = $db->addGameHome( $remote_server_id, $user_id, $home_cfg_id, $game_path, $home_name, $remote_control_password, $ftp_password);
|
||||
if (!$home_id || intval($home_id) <= 0) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "Could not create server_homes row for order #{$order_id}.";
|
||||
}
|
||||
|
||||
//Add IP:Port Pair to the Game Home
|
||||
//need to get the IP_ID for this remote server.
|
||||
$result = $db->resultQuery("SELECT ip_id FROM `{$db_prefix}remote_server_ips` WHERE remote_server_id=".$ip);
|
||||
foreach ((array)$result as $rs)
|
||||
{
|
||||
$ip_id = $rs['ip_id'];
|
||||
}
|
||||
$add_port = $db->addGameIpPort( $home_id, $ip_id, $db->getNextAvailablePort($ip_id,$home_cfg_id) );
|
||||
// Add IP:Port pair with arrange_ports exact home_cfg_id preference and home_cfg_id=0 fallback.
|
||||
if (!$order_failed) {
|
||||
$allocatedPort = billing_allocate_home_port($db, $db_prefix, intval($home_id), intval($remote_server_id), intval($home_cfg_id));
|
||||
if (empty($allocatedPort['ok'])) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = (string)($allocatedPort['error'] ?? 'Port allocation failed.');
|
||||
$db->logger("Provisioning pending install for order #{$order_id}: {$order_failure_reason}");
|
||||
}
|
||||
}
|
||||
|
||||
//Assign the Game Mod to the Game Home
|
||||
$mod_id = $db->addModToGameHome( $home_id, $mod_cfg_id );
|
||||
$db->updateGameModParams( $max_players, $extra_params, $cpu_affinity, $nice, $home_id, $mod_cfg_id );
|
||||
$db->assignHomeTo( "user", $user_id, $home_id, $access_rights );
|
||||
$resolved_mod_cfg_id = intval($mod_cfg_id);
|
||||
if (!$order_failed) {
|
||||
$modResolution = billing_resolve_mod_cfg_id($db, intval($home_cfg_id), intval($mod_cfg_id));
|
||||
if (empty($modResolution['ok'])) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = (string)($modResolution['error'] ?? 'No mod profile available for base install.');
|
||||
} else {
|
||||
$resolved_mod_cfg_id = intval($modResolution['mod_cfg_id']);
|
||||
}
|
||||
}
|
||||
$mod_id = false;
|
||||
if (!$order_failed) {
|
||||
$mod_id = $db->addModToGameHome( $home_id, $resolved_mod_cfg_id );
|
||||
if ($mod_id === false) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "Could not attach mod_cfg_id {$resolved_mod_cfg_id} to home #{$home_id}.";
|
||||
}
|
||||
}
|
||||
if (!$order_failed) {
|
||||
$db->updateGameModParams( $max_players, $extra_params, $cpu_affinity, $nice, $home_id, $resolved_mod_cfg_id );
|
||||
$db->assignHomeTo( "user", $user_id, $home_id, $access_rights );
|
||||
}
|
||||
|
||||
//Get The home info without mods in 1 array (Necesary for remote connection).
|
||||
$home_info = $db->getGameHomeWithoutMods($home_id);
|
||||
if (!$order_failed) {
|
||||
$home_info = $db->getGameHomeWithoutMods($home_id);
|
||||
if (empty($home_info)) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "Could not load home info for home #{$home_id}.";
|
||||
}
|
||||
}
|
||||
|
||||
//Create the remote connection
|
||||
$remote = new OGPRemoteLibrary($home_info['agent_ip'],$home_info['agent_port'],$home_info['encryption_key'],$home_info['timeout']);
|
||||
if (!$order_failed) {
|
||||
$remote = new OGPRemoteLibrary($home_info['agent_ip'],$home_info['agent_port'],$home_info['encryption_key'],$home_info['timeout']);
|
||||
}
|
||||
|
||||
//Get Full home info in 1 array
|
||||
$home_info = $db->getGameHome($home_id);
|
||||
if (!$order_failed) {
|
||||
$home_info = $db->getGameHome($home_id);
|
||||
if (empty($home_info) || empty($home_info['mods'])) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "Mods are not configured for home #{$home_id}; base install profile could not be resolved.";
|
||||
}
|
||||
}
|
||||
|
||||
//Read the Game Config from the XML file
|
||||
$server_xml = read_server_config(SERVER_CONFIG_LOCATION."/".$home_info['home_cfg_file']);
|
||||
if (!$order_failed) {
|
||||
$server_xml = read_server_config(SERVER_CONFIG_LOCATION."/".$home_info['home_cfg_file']);
|
||||
if ($server_xml === false) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "Could not read server XML for home #{$home_id}.";
|
||||
}
|
||||
}
|
||||
|
||||
//Get Values from XML
|
||||
$modkey = $home_info['mods'][$mod_id]['mod_key'];
|
||||
$mod_xml = xml_get_mod($server_xml, $modkey);
|
||||
$installer_name = $mod_xml->installer_name;
|
||||
$mod_cfg_id = $home_info['mods'][$mod_id]['mod_cfg_id'];
|
||||
$mod_xml = false;
|
||||
$modkey = '';
|
||||
$installer_name = '';
|
||||
if (!$order_failed) {
|
||||
$selected_mod = $home_info['mods'][$mod_id] ?? reset($home_info['mods']);
|
||||
if (empty($selected_mod) || empty($selected_mod['mod_key'])) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "No valid mod profile found for home #{$home_id}.";
|
||||
} else {
|
||||
$modkey = (string)$selected_mod['mod_key'];
|
||||
$mod_xml = xml_get_mod($server_xml, $modkey);
|
||||
if ($mod_xml === false && isset($server_xml->mods->mod[0])) {
|
||||
$mod_xml = $server_xml->mods->mod[0];
|
||||
$modkey = (string)$mod_xml['key'];
|
||||
}
|
||||
if ($mod_xml === false) {
|
||||
$order_failed = true;
|
||||
$order_failure_reason = "No installable mod profile exists in XML for home #{$home_id}.";
|
||||
} else {
|
||||
$installer_name = (string)$mod_xml->installer_name;
|
||||
$resolved_mod_cfg_id = intval($selected_mod['mod_cfg_id'] ?? $resolved_mod_cfg_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Get Preinstall commands from xml
|
||||
$precmd = $server_xml->pre_install;
|
||||
$precmd = !$order_failed ? $server_xml->pre_install : '';
|
||||
|
||||
|
||||
//Get Postinstall commands from xml
|
||||
$postcmd = $server_xml->post_install;
|
||||
$postcmd = !$order_failed ? $server_xml->post_install : '';
|
||||
|
||||
|
||||
//Enable FTP account in remote server
|
||||
if ($ftp == "enabled")
|
||||
if (!$order_failed && $ftp == "enabled")
|
||||
{
|
||||
$remote->ftp_mgr("useradd", $home_info['home_id'], $home_info['ftp_password'], $home_info['home_path']);
|
||||
$db->changeFtpStatus('enabled',$home_info['home_id']);
|
||||
}
|
||||
|
||||
//Install files for this service in the remote server
|
||||
$exec_folder_path = clean_path($home_info['home_path'] . "/" . $server_xml->exe_location );
|
||||
$exec_path = clean_path($exec_folder_path . "/" . $server_xml->server_exec_name );
|
||||
if (!$order_failed) {
|
||||
$exec_folder_path = clean_path($home_info['home_path'] . "/" . $server_xml->exe_location );
|
||||
$exec_path = clean_path($exec_folder_path . "/" . $server_xml->server_exec_name );
|
||||
}
|
||||
|
||||
if ( (string)$server_xml->installer === "steamcmd" && !empty((string)$installer_name) )
|
||||
if (!$order_failed && (string)$server_xml->installer === "steamcmd" && !empty((string)$installer_name) )
|
||||
{
|
||||
if( preg_match("/win32/", $server_xml->game_key) OR preg_match("/win64/", $server_xml->game_key) )
|
||||
$cfg_os = "windows";
|
||||
|
|
@ -249,7 +453,7 @@ function exec_ogp_module()
|
|||
$cfg_os = "linux";
|
||||
|
||||
// Some games like L4D2 require anonymous login
|
||||
if($mod_xml->installer_login){
|
||||
if(!empty($mod_xml->installer_login)){
|
||||
$login = $mod_xml->installer_login;
|
||||
$pass = '';
|
||||
}else{
|
||||
|
|
@ -266,7 +470,7 @@ function exec_ogp_module()
|
|||
$betaname,$betapwd,$login,$pass,$settings['steam_guard'],
|
||||
$exec_folder_path,$exec_path,$precmd,$postcmd,$cfg_os,'',$arch);
|
||||
}
|
||||
else
|
||||
elseif (!$order_failed)
|
||||
{
|
||||
// No SteamCMD installer — run pre/post install scripts only.
|
||||
if (!empty((string)$precmd)) {
|
||||
|
|
@ -280,11 +484,13 @@ function exec_ogp_module()
|
|||
$db->logger("Script-only install: post_install script returned no output for home_id $home_id");
|
||||
}
|
||||
}
|
||||
echo "<h4><br><p>".get_lang('starting_installations')."</p></h4><br>";
|
||||
//PANEL LOG
|
||||
$db->logger( "CREATED NEW SERVER " . $home_id);
|
||||
if (!$order_failed) {
|
||||
echo "<h4><br><p>".get_lang('starting_installations')."</p></h4><br>";
|
||||
//PANEL LOG
|
||||
$db->logger( "CREATED NEW SERVER " . $home_id);
|
||||
}
|
||||
// SEND EMAIL to new server only
|
||||
if($order['end_date'] == 0){
|
||||
if(!$order_failed && $order['end_date'] == 0){
|
||||
$settings = $db->getSettings();
|
||||
$subject = "New Gameserver installed at " . $settings['panel_name'];
|
||||
$email = $db->resultQuery(" SELECT DISTINCT users_email
|
||||
|
|
@ -376,7 +582,14 @@ function exec_ogp_module()
|
|||
$end_date_str = date('Y-m-d H:i:s', $end_date);
|
||||
}
|
||||
|
||||
// Set order status to 'Active' (server provisioned and current)
|
||||
if ($home_id <= 0) {
|
||||
$order_failed = true;
|
||||
if ($order_failure_reason === '') {
|
||||
$order_failure_reason = "No home_id was produced for order #{$order_id}.";
|
||||
}
|
||||
}
|
||||
|
||||
// Set order status to 'Active' (billing active even if install is pending)
|
||||
$db->query("UPDATE `{$db_prefix}billing_orders`
|
||||
SET status='Active'
|
||||
WHERE order_id=".$db->realEscapeSingle($order_id));
|
||||
|
|
@ -394,25 +607,38 @@ function exec_ogp_module()
|
|||
|
||||
$db->query("UPDATE `{$db_prefix}billing_invoices`
|
||||
SET home_id=" . $db->realEscapeSingle($home_id) . ",
|
||||
billing_status='Active'
|
||||
billing_status='Active',
|
||||
status='paid'
|
||||
WHERE order_id=" . $db->realEscapeSingle($order_id));
|
||||
|
||||
$db->query("UPDATE `{$db_prefix}billing_transactions`
|
||||
SET home_id=" . $db->realEscapeSingle($home_id) . "
|
||||
WHERE invoice_id IN (SELECT invoice_id FROM `{$db_prefix}billing_invoices` WHERE order_id=" . $db->realEscapeSingle($order_id) . ")");
|
||||
|
||||
// Set billing_status and next_invoice_date on server_homes
|
||||
$db->query("UPDATE `{$db_prefix}server_homes`
|
||||
SET billing_status = 'Active',
|
||||
next_invoice_date = '" . $db->realEscapeSingle($end_date_str) . "',
|
||||
billing_enabled = 1
|
||||
WHERE home_id = " . $db->realEscapeSingle($home_id));
|
||||
|
||||
$provisioned_count++;
|
||||
if ($home_id > 0) {
|
||||
$db->query("UPDATE `{$db_prefix}game_mods`
|
||||
SET max_players=" . $db->realEscapeSingle($max_players) . "
|
||||
WHERE home_id=" . $db->realEscapeSingle($home_id));
|
||||
}
|
||||
|
||||
if ($home_id > 0) {
|
||||
// Set billing_status and next_invoice_date on server_homes
|
||||
$db->query("UPDATE `{$db_prefix}server_homes`
|
||||
SET billing_status = 'Active',
|
||||
next_invoice_date = '" . $db->realEscapeSingle($end_date_str) . "',
|
||||
billing_enabled = 1
|
||||
WHERE home_id = " . $db->realEscapeSingle($home_id));
|
||||
}
|
||||
|
||||
if ($order_failed) {
|
||||
$failed_count++;
|
||||
$failed_messages[] = "Order #{$order_id}: {$order_failure_reason}";
|
||||
$db->logger("Provisioning pending install for order #{$order_id}: {$order_failure_reason}");
|
||||
} else {
|
||||
$provisioned_count++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$db->query( "UPDATE `{$db_prefix}game_mods` SET max_players= ".$order['max_players']." WHERE home_id=".$db->realEscapeSingle($home_id));
|
||||
|
||||
// Show results and redirect
|
||||
if ($provisioned_count > 0) {
|
||||
|
|
@ -420,13 +646,29 @@ function exec_ogp_module()
|
|||
echo "<h3>Server Provisioning Complete</h3>";
|
||||
echo "<p>Successfully provisioned $provisioned_count server(s). Your server(s) are now active.</p>";
|
||||
echo "</div>";
|
||||
if ($failed_count > 0) {
|
||||
echo "<div class='failure'>";
|
||||
echo "<p>{$failed_count} order(s) were linked but left pending install:</p><ul>";
|
||||
foreach ((array)$failed_messages as $failed_message) {
|
||||
echo "<li>" . htmlspecialchars($failed_message, ENT_QUOTES, 'UTF-8') . "</li>";
|
||||
}
|
||||
echo "</ul></div>";
|
||||
}
|
||||
echo "<p><a href='home.php?m=gamemanager&p=game_monitor' class='btn'>View My Servers</a></p>";
|
||||
// Auto-redirect after 3 seconds
|
||||
echo "<script>setTimeout(function(){ window.location.href='home.php?m=gamemanager&p=game_monitor'; }, 3000);</script>";
|
||||
} else {
|
||||
echo "<div class='info'>";
|
||||
echo "<p>No servers to provision. All orders have already been processed.</p>";
|
||||
echo "</div>";
|
||||
if ($failed_count > 0) {
|
||||
echo "<div class='failure'><p>No servers were auto-installed. Orders are active but pending install:</p><ul>";
|
||||
foreach ((array)$failed_messages as $failed_message) {
|
||||
echo "<li>" . htmlspecialchars($failed_message, ENT_QUOTES, 'UTF-8') . "</li>";
|
||||
}
|
||||
echo "</ul></div>";
|
||||
} else {
|
||||
echo "<div class='info'>";
|
||||
echo "<p>No servers to provision. All orders have already been processed.</p>";
|
||||
echo "</div>";
|
||||
}
|
||||
echo "<p><a href='home.php?m=billing&p=my_orders' class='btn'>View My Orders</a></p>";
|
||||
}
|
||||
|
||||
|
|
@ -445,5 +687,3 @@ function exec_ogp_module()
|
|||
);
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
background: radial-gradient(circle at top, #1f3551 0%, #0f1724 58%, #0b111b 100%);
|
||||
min-height: 100vh;
|
||||
display: block;
|
||||
margin: 0;
|
||||
|
|
@ -165,13 +165,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
}
|
||||
|
||||
.login-container {
|
||||
background: #ffffff; /* explicit white */
|
||||
background: #f8fbff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.28);
|
||||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 32px 28px;
|
||||
border: 1px solid rgba(0,0,0,0.06);
|
||||
border: 1px solid rgba(40,70,110,0.25);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
|
|
@ -223,7 +223,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
.btn-login {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
background: linear-gradient(135deg, #3168a4 0%, #214978 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
|
|
@ -235,7 +235,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
|
||||
.btn-login:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
||||
box-shadow: 0 6px 20px rgba(38, 84, 136, 0.4);
|
||||
}
|
||||
|
||||
.btn-login:active {
|
||||
|
|
@ -268,7 +268,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
}
|
||||
|
||||
.footer-links a {
|
||||
color: #667eea;
|
||||
color: #3168a4;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
|
@ -295,7 +295,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
}
|
||||
|
||||
.login-links a {
|
||||
color: #667eea;
|
||||
color: #3168a4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Last Updated at 12:47pm on 2026-05-08
|
||||
Last Updated at 9:18pm on 2026-08-05
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue