From 51d1284950db12bc80f1424171db44a246f86dfe Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 5 Sep 2026 20:15:35 +0200 Subject: [PATCH] feat(admin): organize command center controls and diagnostics --- .../commandocentrum-controls.tsx | 180 +++++++++++------- src/app/admin/commandocentrum/page.tsx | 125 +++++++----- .../admin/commandocentrum/refresh-status.tsx | 20 ++ src/messages/en.json | 20 +- src/messages/it.json | 20 +- 5 files changed, 252 insertions(+), 113 deletions(-) create mode 100644 src/app/admin/commandocentrum/refresh-status.tsx diff --git a/src/app/admin/commandocentrum/commandocentrum-controls.tsx b/src/app/admin/commandocentrum/commandocentrum-controls.tsx index e0bd61f431..b41caf329d 100644 --- a/src/app/admin/commandocentrum/commandocentrum-controls.tsx +++ b/src/app/admin/commandocentrum/commandocentrum-controls.tsx @@ -28,6 +28,7 @@ export function CommandocentrumControls() { const t = useTranslations("pages.admin.commandocentrum"); const { run, isPending } = useServerAction(); + const [category, setCategory] = useState("hotel"); const [hotelMessage, setHotelMessage] = useState(""); const [disconnect, setDisconnect] = useState({ userId: "", username: "" }); const [alert, setAlert] = useState({ userId: "", message: "" }); @@ -46,10 +47,30 @@ export function CommandocentrumControls() { }); return ( - <> -
-

{t("emulatorControls")}

-
+
+

{t("emulatorControls")}

+
+ {["hotel", "players", "rewards", "advanced"].map((group) => ( + + ))} +
+

+ {isPending ? t("executing") : t("chooseCommand")} +

+
+ {category === "hotel" && (

{t("updateCatalog")} @@ -71,7 +92,8 @@ export function CommandocentrumControls() { {t("updateCatalog")}

- + )} + {category === "hotel" && (

{t("updateWordFilter")} @@ -93,7 +115,8 @@ export function CommandocentrumControls() { {t("updateWordFilter")}

- + )} + {category === "hotel" && (

{t("updateNavigator")} @@ -115,50 +138,44 @@ export function CommandocentrumControls() { {t("updateNavigator")}

-
- -
-

- {t("hotelAlert")} -

-

- {t("hotelAlertDesc")} -

-
- setHotelMessage(e.target.value)} - required - maxLength={512} - placeholder={t("messageToBroadcast")} - className="flex-1 min-w-[220px]" - disabled={isPending} - /> - + )} + {category === "hotel" && ( +
+

+ {t("hotelAlert")} +

+

+ {t("hotelAlertDesc")} +

+
+ setHotelMessage(e.target.value)} + required + maxLength={512} + aria-label={t("messageToBroadcast")} + placeholder={t("messageToBroadcast")} + className="flex-1 min-w-0 w-full sm:w-auto" + disabled={isPending} + /> + +
-
-
- -
-

{t("userActions")}

-

- {t("userActionsDesc")} -

- -
+ )} + {category === "players" && (

{t("actions.disconnect")} @@ -170,6 +187,7 @@ export function CommandocentrumControls() { @@ -180,6 +198,7 @@ export function CommandocentrumControls() { /> @@ -215,7 +234,8 @@ export function CommandocentrumControls() {

- + )} + {category === "players" && (

{t("actions.alert")} @@ -227,6 +247,7 @@ export function CommandocentrumControls() { @@ -237,6 +258,7 @@ export function CommandocentrumControls() { /> @@ -270,7 +292,8 @@ export function CommandocentrumControls() {

- + )} + {category === "players" && (

{t("forwardToRoom")} @@ -282,6 +305,7 @@ export function CommandocentrumControls() { @@ -293,6 +317,7 @@ export function CommandocentrumControls() { @@ -325,9 +350,8 @@ export function CommandocentrumControls() {

- - -
+ )} + {category === "rewards" && (

{t("giveCredits")} @@ -339,6 +363,7 @@ export function CommandocentrumControls() { @@ -350,6 +375,7 @@ export function CommandocentrumControls() { @@ -382,7 +408,8 @@ export function CommandocentrumControls() {

- + )} + {category === "rewards" && (

{t("giveDuckets")} @@ -394,6 +421,7 @@ export function CommandocentrumControls() { @@ -405,6 +433,7 @@ export function CommandocentrumControls() { @@ -437,7 +466,8 @@ export function CommandocentrumControls() {

- + )} + {category === "rewards" && (

{t("giveDiamonds")} @@ -449,6 +479,7 @@ export function CommandocentrumControls() { @@ -460,6 +491,7 @@ export function CommandocentrumControls() { @@ -492,9 +524,8 @@ export function CommandocentrumControls() {

- - -
+ )} + {category === "rewards" && (

{t("giveBadge")} @@ -506,6 +537,7 @@ export function CommandocentrumControls() { @@ -516,6 +548,7 @@ export function CommandocentrumControls() { /> @@ -549,7 +582,8 @@ export function CommandocentrumControls() {

- + )} + {category === "players" && (

{t("setMotto")} @@ -561,6 +595,7 @@ export function CommandocentrumControls() { @@ -571,6 +606,7 @@ export function CommandocentrumControls() { /> @@ -604,7 +640,8 @@ export function CommandocentrumControls() {

- + )} + {category === "players" && (

{t("setRank")} @@ -616,6 +653,7 @@ export function CommandocentrumControls() { @@ -628,6 +666,7 @@ export function CommandocentrumControls() { type="number" min={0} max={10} + aria-label={t("rankPlaceholder")} placeholder={t("rankPlaceholder")} value={rank.rank} onChange={(e) => @@ -660,9 +699,8 @@ export function CommandocentrumControls() {

- - -
+ )} + {category === "advanced" && (

{t("executeCommand")} @@ -674,6 +712,7 @@ export function CommandocentrumControls() { @@ -684,6 +723,7 @@ export function CommandocentrumControls() { /> @@ -719,7 +759,8 @@ export function CommandocentrumControls() {

- + )} + {category === "rewards" && (

{t("sendGift")} @@ -731,6 +772,7 @@ export function CommandocentrumControls() { @@ -742,6 +784,7 @@ export function CommandocentrumControls() { @@ -752,6 +795,7 @@ export function CommandocentrumControls() { /> @@ -791,8 +835,8 @@ export function CommandocentrumControls() {

- -
- + )} + + ); } diff --git a/src/app/admin/commandocentrum/page.tsx b/src/app/admin/commandocentrum/page.tsx index 4463c861d9..80b18a8257 100644 --- a/src/app/admin/commandocentrum/page.tsx +++ b/src/app/admin/commandocentrum/page.tsx @@ -10,6 +10,8 @@ import { OnlineUsersWidget, StatusCard, } from "@/components/admin/dashboard"; +import Link from "@/components/link"; +import { env } from "@/env"; import { fetchOpsHealth } from "@/lib/admin/ops-health"; import { fetchOpsOnlineUsers } from "@/lib/admin/ops-online-users"; import { db, EmulatorErrors, StaffActivities, User } from "@/lib/db"; @@ -17,6 +19,7 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { siteSettings } from "@/lib/services/site-settings"; import { CommandocentrumControls } from "./commandocentrum-controls"; +import { RefreshStatus } from "./refresh-status"; function decodeStacktrace(raw: unknown): string { if (raw == null) return ""; @@ -47,28 +50,36 @@ export default async function CommandoCentrum() { const t = await getTranslations("pages.admin.commandocentrum"); - const [totalUsersRows, errors, health, maintenance, activity, onlineRoster] = - await Promise.all([ - db - .select({ total: count() }) - .from(User) - .catch(() => [{ total: -1 }]), - db - .select() - .from(EmulatorErrors) - .orderBy(desc(EmulatorErrors.timestamp)) - .limit(50) - .catch(() => []), - fetchOpsHealth(), - siteSettings.getBool("maintenance_enabled", false), - db - .select() - .from(StaffActivities) - .orderBy(desc(StaffActivities.createdAt)) - .limit(12) - .catch(() => [] as (typeof StaffActivities.$inferSelect)[]), - fetchOpsOnlineUsers(40), - ]); + const [ + totalUsersRows, + errorRows, + health, + maintenance, + activity, + onlineRoster, + ] = await Promise.all([ + db + .select({ total: count() }) + .from(User) + .catch(() => [{ total: -1 }]), + db + .select() + .from(EmulatorErrors) + .orderBy(desc(EmulatorErrors.timestamp)) + .limit(50) + .catch(() => null), + fetchOpsHealth(), + siteSettings.getBool("maintenance_enabled", false), + db + .select() + .from(StaffActivities) + .orderBy(desc(StaffActivities.createdAt)) + .limit(12) + .catch(() => [] as (typeof StaffActivities.$inferSelect)[]), + fetchOpsOnlineUsers(40), + ]); + const errors = errorRows ?? []; + const errorsAvailable = errorRows !== null; const totalUsers = Number(totalUsersRows[0]?.total ?? -1); const dbOk = health.dbOk; @@ -91,12 +102,30 @@ export default async function CommandoCentrum() { } const appKeySet = !!process.env.APP_KEY; - const smtpSet = !!process.env.SMTP_HOST; - const rconSet = !!process.env.RCON_HOST; + const smtpSet = !!(env.SMTP_HOST || env.RESEND_API_KEY); + const rconSet = + env.EMULATOR_MODE === "api" ? !!env.EMULATOR_API_URL : !!env.RCON_HOST; return ( -
+
+
+

+ {dbOk && emulatorOnline && redisOk !== false + ? t("servicesHealthy") + : t("serviceAttention")}{" "} + ·{" "} + {t("checkedAt", { + time: formatDate(new Date(), "datetime-seconds"), + })} +

+ + {canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank) ? ( + + {t("cmsErrors")} + + ) : null} +
{/* ── Live status ────────────────────────────────────────── */}
= 0 ? t("totalAccounts", { count: totalUsers }) @@ -129,13 +158,15 @@ export default async function CommandoCentrum() { />
+ + {/* ── Online users widget ───────────────────────────────── */}

{t("onlineUsers")}

@@ -144,8 +175,10 @@ export default async function CommandoCentrum() { {/* ── Server info + diagnostics ──────────────────────────── */}
-
-

{t("serverInfo")}

+
+ + {t("serverInfo")} +
-
+

{t("systemDiagnostics")}

@@ -206,8 +239,8 @@ export default async function CommandoCentrum() { state={emulatorOnline ? "ok" : "warn"} />
- - {/* ── Staff activity ─────────────────────────────────────── */}

{t("recentStaffActivity")}

@@ -277,7 +308,9 @@ export default async function CommandoCentrum() { {/* ── Emulator errors ────────────────────────────────────── */}

{t("recentEmulatorErrors")}

- {errors.length === 0 ? ( + {!errorsAvailable ? ( +
{t("readFailed")}
+ ) : errors.length === 0 ? (
{t("noEmulatorErrors")}
) : (
@@ -312,9 +345,15 @@ export default async function CommandoCentrum() { {e.version} -
-														{snippet}
-													
+
+ + {trace.split("\n")[0]?.slice(0, 120) || + t("empty")} + +
+															{snippet}
+														
+
); @@ -324,7 +363,7 @@ export default async function CommandoCentrum() {
)}
-
+
); } diff --git a/src/app/admin/commandocentrum/refresh-status.tsx b/src/app/admin/commandocentrum/refresh-status.tsx new file mode 100644 index 0000000000..1f82b2d567 --- /dev/null +++ b/src/app/admin/commandocentrum/refresh-status.tsx @@ -0,0 +1,20 @@ +"use client"; +import { useRouter } from "next/navigation"; +import { useTransition } from "react"; +import { Button } from "@/components/ui/button"; +export function RefreshStatus({ label }: { label: string }) { + const router = useRouter(); + const [pending, startTransition] = useTransition(); + return ( + + ); +} diff --git a/src/messages/en.json b/src/messages/en.json index 31e03e7429..9567ea4837 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -2431,7 +2431,25 @@ }, "messagePlaceholder": "Message (for alerts)…", "amountPlaceholder": "Amount…", - "badgePlaceholder": "Badge code…" + "badgePlaceholder": "Badge code…", + "commandCategories": "Command categories", + "groups": { + "hotel": "Hotel", + "players": "Players", + "rewards": "Currency and gifts", + "advanced": "Advanced" + }, + "executing": "Executing command…", + "chooseCommand": "Choose an operation. Values entered are preserved when switching categories.", + "refreshStatus": "Refresh status", + "checkedAt": "Checked at {time}", + "serviceAttention": "Some services need attention", + "servicesHealthy": "Core services are responding", + "cmsErrors": "CMS error center", + "readFailed": "Data could not be loaded", + "emulatorConfig": "Emulator connection", + "emailConfig": "Email delivery", + "configured": "Configured" }, "emulator": { "title": "Emulator Config", diff --git a/src/messages/it.json b/src/messages/it.json index cb9f1a0931..59905ec00d 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -2313,7 +2313,25 @@ }, "messagePlaceholder": "Messaggio (per avvisi)…", "amountPlaceholder": "Importo…", - "badgePlaceholder": "Codice distintivo…" + "badgePlaceholder": "Codice distintivo…", + "commandCategories": "Categorie comandi", + "groups": { + "hotel": "Hotel", + "players": "Giocatori", + "rewards": "Valute e regali", + "advanced": "Avanzati" + }, + "executing": "Esecuzione comando…", + "chooseCommand": "Scegli un’operazione. I valori inseriti restano salvati cambiando categoria.", + "refreshStatus": "Aggiorna stato", + "checkedAt": "Verificato alle {time}", + "serviceAttention": "Alcuni servizi richiedono attenzione", + "servicesHealthy": "I servizi principali rispondono", + "cmsErrors": "Centro errori CMS", + "readFailed": "Impossibile caricare i dati", + "emulatorConfig": "Connessione emulatore", + "emailConfig": "Invio email", + "configured": "Configurato" }, "emulator": { "title": "Configurazione emulatore",