diff --git a/Panel/modules/website/README.md b/Panel/modules/website/README.md index c30525d1..d6d3a310 100644 --- a/Panel/modules/website/README.md +++ b/Panel/modules/website/README.md @@ -189,6 +189,26 @@ secrets are masked after save. More detail: `docs/modules/website_billing_rebuild.md`. +## Service catalog display + +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 +internally for service lookup and documentation routing, but should not be shown +as the public platform label. + +`website_service_platform()` derives platform labels from service/config values: + +- Linux: `linux`, `linux32`, `linux64` +- Windows: `win`, `win32`, `win64`, `windows` +- Cross-platform: both Linux and Windows markers +- Unknown: no recognizable platform marker + +The order/configuration page keeps the card layout but displays the same derived +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. + ## Documentation source Customer documentation is read from the existing billing docs directory: diff --git a/Panel/modules/website/assets/css/site.css b/Panel/modules/website/assets/css/site.css index 02a915c8..35a37af7 100644 --- a/Panel/modules/website/assets/css/site.css +++ b/Panel/modules/website/assets/css/site.css @@ -596,6 +596,18 @@ textarea { font-weight: 600; } +.status-badge-neutral { + background: rgba(84, 166, 255, 0.1); + border-color: rgba(84, 166, 255, 0.22); + color: #cbe1ff; +} + +.status-badge-muted { + background: rgba(147, 168, 203, 0.08); + border-color: rgba(147, 168, 203, 0.18); + color: var(--muted); +} + .alert { padding: 16px 18px; border-radius: 6px; @@ -628,17 +640,31 @@ textarea { font-weight: 700; } -.website-form input { +.website-form input, +.website-form select, +.website-form textarea { width: 100%; - min-height: 44px; border: 1px solid var(--line-strong); border-radius: 6px; background: rgba(5, 17, 32, 0.78); color: var(--text); +} + +.website-form input, +.website-form select { + min-height: 44px; padding: 0 12px; } -.website-form input:focus { +.website-form textarea { + min-height: 96px; + padding: 10px 12px; + resize: vertical; +} + +.website-form input:focus, +.website-form select:focus, +.website-form textarea:focus { outline: 2px solid rgba(88, 171, 255, 0.45); outline-offset: 2px; } @@ -662,6 +688,190 @@ textarea { color: var(--muted); } +.catalog-list, +.staff-service-list { + display: grid; + gap: 8px; +} + +.catalog-list-heading, +.catalog-row, +.staff-service-heading, +.staff-service-row summary { + display: grid; + align-items: center; + gap: 14px; +} + +.catalog-list-heading, +.staff-service-heading { + padding: 0 16px 8px; + color: #b8cae5; + font-size: 0.78rem; + font-weight: 800; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.catalog-list-heading, +.catalog-row { + grid-template-columns: minmax(190px, 1.15fr) 130px minmax(260px, 1.8fr) 130px minmax(210px, auto); +} + +.catalog-row, +.staff-service-row { + background: linear-gradient(180deg, rgba(13, 24, 43, 0.92) 0%, rgba(10, 18, 32, 0.92) 100%); + border: 1px solid var(--line); + border-radius: var(--radius); + box-shadow: var(--shadow); +} + +.catalog-row { + padding: 14px 16px; +} + +.catalog-server { + display: grid; + grid-template-columns: 56px minmax(0, 1fr); + align-items: center; + gap: 12px; + min-width: 0; +} + +.catalog-thumb { + width: 56px; + height: 42px; + object-fit: cover; + border: 1px solid var(--line); + border-radius: 6px; + background: #08111f; +} + +.catalog-server h3, +.catalog-description { + margin: 0; +} + +.catalog-server h3 { + font-size: 1rem; +} + +.catalog-description { + color: var(--muted); +} + +.catalog-price { + color: var(--accent-strong); + font-weight: 800; +} + +.catalog-actions { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 8px; +} + +.catalog-actions .button { + min-height: 38px; + padding: 0 12px; +} + +.staff-services-form { + padding: 0; + border: 0; + background: transparent; +} + +.staff-service-heading, +.staff-service-row summary { + grid-template-columns: 70px 100px minmax(170px, 1.2fr) 100px 100px 90px 80px 90px 60px; +} + +.staff-service-row summary { + min-height: 58px; + padding: 12px 16px; + cursor: pointer; + list-style: none; +} + +.staff-service-row summary::-webkit-details-marker { + display: none; +} + +.staff-service-row summary::after { + content: "Edit"; + justify-self: end; + color: var(--accent-strong); + font-weight: 800; +} + +.staff-service-row[open] summary { + border-bottom: 1px solid var(--line); +} + +.staff-service-row[open] summary::after { + content: "Close"; +} + +.staff-service-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 800; +} + +.staff-service-editor { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; + padding: 16px; +} + +.staff-service-editor label, +.staff-service-editor fieldset { + display: grid; + gap: 8px; +} + +.staff-service-editor fieldset { + margin: 0; + padding: 12px; + border: 1px solid var(--line); + border-radius: 6px; +} + +.staff-service-editor legend, +.staff-service-editor label > span:first-child { + color: var(--text); + font-weight: 800; +} + +.staff-editor-wide { + grid-column: 1 / -1; +} + +.checkbox-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px 12px; +} + +.checkbox-grid label { + display: flex; + align-items: center; + gap: 8px; + color: var(--muted); + font-weight: 600; +} + +.checkbox-grid input[type="checkbox"], +.staff-service-editor input[type="checkbox"] { + width: auto; + min-height: 0; +} + .panel-preview { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(260px, 0.85fr); @@ -894,6 +1104,49 @@ textarea { margin-top: 4px; } + .catalog-list-heading, + .staff-service-heading { + display: none; + } + + .catalog-row, + .staff-service-row summary { + grid-template-columns: 1fr; + align-items: stretch; + gap: 10px; + } + + .catalog-row > *[data-label]::before, + .staff-service-row summary > span[data-label]::before { + content: attr(data-label) ": "; + color: #b8cae5; + font-size: 0.78rem; + font-weight: 800; + letter-spacing: 0.06em; + text-transform: uppercase; + } + + .catalog-actions { + justify-content: stretch; + } + + .catalog-actions .button { + flex: 1 1 160px; + } + + .staff-service-row summary::after { + justify-self: start; + } + + .staff-service-name { + white-space: normal; + } + + .staff-service-editor, + .checkbox-grid { + grid-template-columns: 1fr; + } + .hero, .page-heading { padding-top: 42px; diff --git a/Panel/modules/website/includes/bootstrap.php b/Panel/modules/website/includes/bootstrap.php index 5002a50c..c0f1fcaa 100644 --- a/Panel/modules/website/includes/bootstrap.php +++ b/Panel/modules/website/includes/bootstrap.php @@ -721,6 +721,37 @@ function website_service_name(array $service): string return $name === '' ? 'Game Server' : $name; } +function website_service_platform(array $service): string +{ + $parts = []; + foreach (['cfg_game_key', 'cfg_file', 'home_cfg_file', 'config_key', 'service_name'] as $key) { + $value = trim((string)($service[$key] ?? '')); + if ($value !== '') { + $parts[] = $value; + } + } + + $source = strtolower(implode(' ', $parts)); + if ($source === '') { + return 'Unknown'; + } + + $hasLinux = preg_match('/(^|[_\W])linux(32|64)?($|[_\W])|linux32|linux64/', $source) === 1; + $hasWindows = preg_match('/(^|[_\W])(win|windows)(32|64)?($|[_\W])|win32|win64/', $source) === 1; + + if ($hasLinux && $hasWindows) { + return 'Cross-platform'; + } + if ($hasLinux) { + return 'Linux'; + } + if ($hasWindows) { + return 'Windows'; + } + + return 'Unknown'; +} + function website_service_min_slots(array $service): int { foreach (['slot_min_qty', 'min_slots', 'minimum_slots', 'slots_min'] as $column) { diff --git a/Panel/modules/website/pages/game_servers.php b/Panel/modules/website/pages/game_servers.php index 2a43d12b..375d5d3c 100644 --- a/Panel/modules/website/pages/game_servers.php +++ b/Panel/modules/website/pages/game_servers.php @@ -43,27 +43,37 @@ $fixedCapNote = trim((string)($pricing['fixed_cap_note'] ?? ''));
-
+
+ -
- <?= website_escape($serviceName) ?> -
+
+
+

-
-
-

-
-
0 ? '$' . number_format($price, 2) . ' / month' : 'Contact for pricing' ?>
-
+
+
+ +
+

+
0 ? '$' . number_format($price, 2) . ' / month' : 'Contact for pricing' ?>
+
Order Now - + Documentation Documentation diff --git a/Panel/modules/website/pages/home.php b/Panel/modules/website/pages/home.php index c8154efa..bcf65e1c 100644 --- a/Panel/modules/website/pages/home.php +++ b/Panel/modules/website/pages/home.php @@ -78,7 +78,8 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
@@ -86,6 +87,7 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe']; <?= website_escape($serviceName) ?>

+
Platform:
0 ? '$' . number_format($price, 2) . ' / month' : 'See order page' ?>
diff --git a/Panel/modules/website/pages/order.php b/Panel/modules/website/pages/order.php index db2ad0b8..b6154b28 100644 --- a/Panel/modules/website/pages/order.php +++ b/Panel/modules/website/pages/order.php @@ -22,7 +22,8 @@ if ($service === null): return; endif; -$serviceName = trim((string)($service['cfg_game_name'] ?? $service['service_name'] ?? 'Game Server')); +$serviceName = website_service_name($service); +$platformLabel = website_service_platform($service); $description = trim((string)($service['description'] ?? '')); $price = (float)($service['price_monthly'] ?? 0); $selectedSlots = max((int)$minSlots, (int)($_POST['slots'] ?? $minSlots)); @@ -43,7 +44,7 @@ $selectedSlots = max((int)$minSlots, (int)($_POST['slots'] ?? $minSlots));

Plan

-

Service ID:

+

Platform:

0 ? 'Catalog price: $' . website_escape(number_format($price, 2)) . ' / month' : 'Catalog price: contact for pricing' ?>

diff --git a/Panel/modules/website/pages/pricing.php b/Panel/modules/website/pages/pricing.php index ec4870ab..069987b7 100644 --- a/Panel/modules/website/pages/pricing.php +++ b/Panel/modules/website/pages/pricing.php @@ -47,11 +47,13 @@ $platformSummary = trim((string)($platform['summary'] ?? ''));

+
Platform:

0 ? '$' . number_format($price, 2) . ' / month' : 'Contact for pricing' ?>
diff --git a/Panel/modules/website/pages/staff_services.php b/Panel/modules/website/pages/staff_services.php index a7cc6640..cf8580c5 100644 --- a/Panel/modules/website/pages/staff_services.php +++ b/Panel/modules/website/pages/staff_services.php @@ -1,18 +1,123 @@ -

Manage Game Services

Update public catalog status, prices, slot limits, images, and location availability.

-
-
-

No services found

Run migrations and import or create billing services before editing catalog data.

-
- -

#

- - - - - - - - -
-
+
+
+

Manage Game Services

+

Update public catalog status, prices, slot limits, images, and location availability.

+
+
+ +
+
+
+
+ + +
+

No services found

+

Run migrations and import or create billing services before editing catalog data.

+
+ +
+ +
+ + + +
+ + # + + + + 0 ? '$' . website_escape(number_format($price, 2)) : 'Contact' ?> + 0 ? '-' . website_escape((string)$maxSlots) : '+' ?> + + + +
+ + + + + + + + + + + + + + +
+ Locations +
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
diff --git a/docs/modules/website.md b/docs/modules/website.md index 969477fc..495484ad 100644 --- a/docs/modules/website.md +++ b/docs/modules/website.md @@ -69,6 +69,28 @@ Checkout creates due invoices and pending-payment orders after login. PayPal ord Paid orders appear in the website provisioning queue. The queue is the handoff point for Panel-side server creation; provisioning must remain idempotent and must not run before payment or approval. +## Service Catalog Display + +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 +action. Raw XML/config keys such as `*_linux64` or `*_win32` are internal +identifiers and should not be prominent public labels. + +Platform labels are produced by `website_service_platform()` from service/config +data such as `cfg_game_key`, `cfg_file`, `home_cfg_file`, or related service +fields: + +- Linux: keys/files containing `linux`, `linux32`, or `linux64` +- Windows: keys/files containing `win`, `win32`, `win64`, or `windows` +- Cross-platform: both platform families detected +- Unknown: no platform marker detected + +Order/configuration pages may retain card-style layout, but should also show the +derived platform label instead of the raw XML/config key. Website staff service +management uses a compact expandable row list with service ID, status, display +name, platform, price, slot range, image status, and location count visible. + ## Navigation Website footer account links are state-aware: