chore(ops): strip redundant force-dynamic and probe Redis in ops health
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
3e584aadaf
commit
3ac5d6f4f6
60 files changed
+95
-114
No files matched your search
@@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { EditAdDeleteButton } from "../ads-table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function EditAd({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { AdsTable } from "./ads-table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminAds() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type ApplicationRow = {
|
||||
id: bigint;
|
||||
userId: number;
|
||||
|
||||
@@ -8,8 +8,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function EditArticle({
|
||||
params,
|
||||
searchParams,
|
||||
|
||||
@@ -10,8 +10,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminArticles({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const metadata = { title: "Badges" };
|
||||
|
||||
export default async function AdminBadges({
|
||||
|
||||
@@ -10,8 +10,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminBans() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
if (!canAccess(permissions, PERMS.BANS_VIEW, session.user.rank)) {
|
||||
|
||||
@@ -5,8 +5,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Campaign = {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -6,8 +6,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Campaign = {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -8,8 +8,6 @@ import { loadCatalogItemsData } from "@/lib/services/catalog-items-loader";
|
||||
import { getAncestors } from "@/lib/services/catalog-tree";
|
||||
import { CatalogDetailTabs } from "./catalog-detail-tabs";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function CatalogEditPage({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -17,8 +17,6 @@ import { prisma } from "@/lib/prisma";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { CommandocentrumControls } from "./commandocentrum-controls";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
function decodeStacktrace(raw: unknown): string {
|
||||
if (raw == null) return "";
|
||||
try {
|
||||
@@ -66,6 +64,7 @@ export default async function CommandoCentrum() {
|
||||
]);
|
||||
|
||||
const dbOk = health.dbOk;
|
||||
const redisOk = health.redisOk;
|
||||
const emulatorOnline = health.emulatorOk;
|
||||
const onlineCount = onlineRoster.count;
|
||||
const onlineUsers = onlineRoster.users;
|
||||
@@ -175,6 +174,23 @@ export default async function CommandoCentrum() {
|
||||
detail={dbOk ? t("connectionHealthy") : t("unreachable")}
|
||||
state={dbOk ? "ok" : "danger"}
|
||||
/>
|
||||
<DiagnosticRow
|
||||
label="Redis"
|
||||
detail={
|
||||
redisOk === true
|
||||
? "PONG"
|
||||
: redisOk === false
|
||||
? "Unreachable"
|
||||
: "REDIS_URL not configured"
|
||||
}
|
||||
state={
|
||||
redisOk === true
|
||||
? "ok"
|
||||
: redisOk === false
|
||||
? "danger"
|
||||
: "warn"
|
||||
}
|
||||
/>
|
||||
<DiagnosticRow
|
||||
label={t("emulatorRcon")}
|
||||
detail={
|
||||
|
||||
@@ -30,6 +30,7 @@ async function getDevOpsData() {
|
||||
return {
|
||||
dbStatus: health.dbOk ? ("healthy" as const) : ("error" as const),
|
||||
dbLatency: health.dbLatencyMs,
|
||||
redisOk: health.redisOk,
|
||||
recentErrors,
|
||||
errorsToday,
|
||||
emuVersion: emuVersion?.value ?? "Unknown",
|
||||
@@ -49,7 +50,7 @@ export default async function DevOpsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Status cards */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-5">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Database</CardTitle>
|
||||
@@ -67,6 +68,33 @@ export default async function DevOpsPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Redis</CardTitle>
|
||||
<Server className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge
|
||||
variant={
|
||||
data.redisOk === false
|
||||
? "destructive"
|
||||
: data.redisOk === true
|
||||
? "default"
|
||||
: "secondary"
|
||||
}
|
||||
>
|
||||
{data.redisOk === true
|
||||
? "Healthy"
|
||||
: data.redisOk === false
|
||||
? "Error"
|
||||
: "Not configured"}
|
||||
</Badge>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Cache / rate limits / sessions
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Emulator</CardTitle>
|
||||
|
||||
@@ -9,8 +9,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const TEXTS_LIMIT = 300;
|
||||
|
||||
export default async function AdminEmulator() {
|
||||
|
||||
@@ -7,8 +7,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminGuildDetailPage({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -11,8 +11,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminGuildsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function EditHelpQuestion({
|
||||
params,
|
||||
searchParams,
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminHelpQuestions() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { AdminHelpTicketDetail } from "./admin-help-ticket-detail";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminHelpTicketDetailPage({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { type HelpTicketRow, HelpTicketsTable } from "./help-tickets-table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminHelpTicketsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -6,8 +6,6 @@ import { prisma } from "@/lib/prisma";
|
||||
import { getAuditLogs } from "@/lib/services/audit";
|
||||
import { AuditSection, ExportSection } from "./client";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminHousekeeping({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -12,8 +12,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type IpRow = {
|
||||
id: bigint;
|
||||
ipAddress: string;
|
||||
|
||||
@@ -6,8 +6,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { ItemsBaseEditForm } from "./items-base-edit-form";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function ItemsBaseDetailPage({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { StaffActivitiesTable } from "./staff-activities-table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const SORT_MAP: Record<string, "createdAt" | "userId" | "action"> = {
|
||||
when: "createdAt",
|
||||
staff: "userId",
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const KEYS = [
|
||||
"maintenance_enabled",
|
||||
"maintenance_message",
|
||||
|
||||
@@ -12,8 +12,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminMarketplacePage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -7,8 +7,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { type CfhRow, CfhTable } from "./cfh-table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function CfhListPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -13,8 +13,6 @@ import { activeBanWhere } from "@/lib/bans";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type RankCount = { rank: number; count: number };
|
||||
|
||||
export default async function AdminDashboard() {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminPhotos({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -9,8 +9,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const metadata = { title: "Radio API Keys" };
|
||||
|
||||
type ApiKey = {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const metadata = { title: "Radio AutoDJ" };
|
||||
|
||||
type Track = {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Banner = {
|
||||
id: bigint;
|
||||
userId: bigint;
|
||||
|
||||
@@ -6,8 +6,6 @@ import { resolveHotelName } from "@/lib/hotel-name";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import { EmbedSnippet } from "./embed-snippet";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const metadata = { title: "Radio Embed" };
|
||||
|
||||
export default async function AdminRadioEmbedPage() {
|
||||
|
||||
@@ -3,8 +3,6 @@ import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type History = {
|
||||
id: bigint;
|
||||
userId: bigint;
|
||||
|
||||
@@ -5,8 +5,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Shout = {
|
||||
id: bigint;
|
||||
userId: bigint;
|
||||
|
||||
@@ -2,8 +2,6 @@ import { getTranslations } from "next-intl/server";
|
||||
import { DiagnosticRow, StatusCard } from "@/components/admin/dashboard";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const metadata = { title: "Radio Monitoring" };
|
||||
|
||||
const FETCH_TIMEOUT_MS = 4000;
|
||||
|
||||
@@ -5,8 +5,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Shout = {
|
||||
id: bigint;
|
||||
userId: bigint;
|
||||
|
||||
@@ -4,8 +4,6 @@ import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const metadata = { title: "Radio Points" };
|
||||
|
||||
const DEFAULTS = {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Rank = {
|
||||
id: bigint;
|
||||
name: string;
|
||||
|
||||
@@ -7,8 +7,6 @@ import { StatusCard } from "@/components/admin/dashboard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type Field = { key: string; comment: string };
|
||||
type Group = { title: string; fields: Field[] };
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const CURRENCY_TYPES = ["diamonds", "credits", "duckets", "points"];
|
||||
|
||||
export default async function AdminRareValues() {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { AdvancedSettingsPanel } from "./advanced-settings-panel";
|
||||
import { FIELD_DEFAULTS, MANAGED_SETTING_KEYS } from "./cms-settings-config";
|
||||
import { CmsSettingsForm } from "./cms-settings-form";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const metadata = { title: "CMS Settings" };
|
||||
|
||||
export default async function AdminSettings() {
|
||||
|
||||
@@ -7,8 +7,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function EditShopArticle({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -12,8 +12,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const PAID_STATUS = new Set(["completed", "paid", "success", "approved"]);
|
||||
const RECENT_ORDERS = 25;
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type TeamRow = {
|
||||
id: bigint;
|
||||
rankName: string;
|
||||
|
||||
@@ -15,7 +15,6 @@ import { listCustomThemes } from "@/lib/theme-custom-store";
|
||||
import { FONTS, PRESETS } from "@/lib/theme-presets";
|
||||
import { ColorField } from "./color-field";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const metadata = { title: "Theme" };
|
||||
|
||||
const COLOR_FIELDS: {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { type TicketRow, TicketsTable } from "./tickets-table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminTicketsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { type TransactionRow, TransactionsTable } from "./transactions-table";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const PAID_STATUS = ["completed", "paid", "success", "approved"] as const;
|
||||
const FAILED_STATUS = [
|
||||
"failed",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* Legacy URL — the canonical edit UI lives at /admin/users/edit/[id]
|
||||
* and is gated by PERMS.USERS_EDIT.
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const metadata = { title: "VPN" };
|
||||
|
||||
const PROVIDERS: { value: string; label: string }[] = [
|
||||
|
||||
@@ -11,8 +11,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const metadata = { title: "Writeable boxes" };
|
||||
|
||||
export default async function AdminWriteableBoxes() {
|
||||
|
||||
@@ -9,6 +9,7 @@ export const GET = withAdmin({ permission: PERMS.DEVOPS_VIEW }, async () => {
|
||||
return NextResponse.json({
|
||||
database: health.dbOk,
|
||||
dbLatency: health.dbLatencyMs,
|
||||
redis: health.redisOk,
|
||||
emulator: health.emulatorOk,
|
||||
onlineUsers: health.onlineUsers,
|
||||
timestamp: new Date().toISOString(),
|
||||
|
||||
@@ -6,8 +6,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function ModBansPage() {
|
||||
await requireModPermission([PERMS.MOD_BANS_VIEW, PERMS.BANS_VIEW]);
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function ModCfhListPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -6,8 +6,6 @@ import { positiveBigInt } from "@/lib/api";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function ModHelpTicketDetailPage({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -13,8 +13,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function ModHelpTicketsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -13,8 +13,6 @@ import { formatDate } from "@/lib/format-date";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function ModTicketsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
||||
@@ -8,8 +8,6 @@ import { getAvatarUrl } from "@/lib/imager";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function ModUserDetailPage({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -9,8 +9,6 @@ import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import type { DataTableColumn } from "@/types/common";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type ModUserRow = {
|
||||
id: number;
|
||||
username: string;
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import "server-only";
|
||||
|
||||
import { env } from "@/env";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { redis } from "@/lib/redis";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
|
||||
export type OpsHealth = {
|
||||
dbOk: boolean;
|
||||
dbLatencyMs: number;
|
||||
/** null = REDIS_URL unset (optional); true/false = probe result */
|
||||
redisOk: boolean | null;
|
||||
emulatorOk: boolean;
|
||||
onlineUsers: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared DB + emulator health probe for CommandoCentrum, DevOps, and APIs.
|
||||
* Emulator = real RCON `ping` (same semantics as CommandoCentrum /public health).
|
||||
* Shared DB + Redis + emulator health probe for CommandoCentrum, DevOps, and APIs.
|
||||
* Semantics match `/api/health` for Redis (null when not configured).
|
||||
*/
|
||||
export async function fetchOpsHealth(): Promise<OpsHealth> {
|
||||
const [db, emulatorOk, onlineUsers] = await Promise.all([
|
||||
const [db, redisOk, emulatorOk, onlineUsers] = await Promise.all([
|
||||
(async () => {
|
||||
try {
|
||||
const start = Date.now();
|
||||
@@ -25,6 +29,16 @@ export async function fetchOpsHealth(): Promise<OpsHealth> {
|
||||
return { dbOk: false, dbLatencyMs: 0 };
|
||||
}
|
||||
})(),
|
||||
(async (): Promise<boolean | null> => {
|
||||
if (!env.REDIS_URL) return null;
|
||||
if (!redis) return false;
|
||||
try {
|
||||
const pong = await redis.ping();
|
||||
return pong === "PONG";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})(),
|
||||
rcon.send("ping", null).catch(() => false),
|
||||
prisma.user.count({ where: { online: "1" } }).catch(() => 0),
|
||||
]);
|
||||
@@ -32,6 +46,7 @@ export async function fetchOpsHealth(): Promise<OpsHealth> {
|
||||
return {
|
||||
dbOk: db.dbOk,
|
||||
dbLatencyMs: db.dbLatencyMs,
|
||||
redisOk,
|
||||
emulatorOk: Boolean(emulatorOk),
|
||||
onlineUsers,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("staff smoke contract", () => {
|
||||
@@ -50,10 +51,37 @@ describe("staff smoke contract", () => {
|
||||
|
||||
it("ships shared ops + ticket queue helpers", () => {
|
||||
expect(existsSync("src/lib/admin/ops-health.ts")).toBe(true);
|
||||
const ops = readFileSync("src/lib/admin/ops-health.ts", "utf8");
|
||||
expect(ops).toContain("redisOk");
|
||||
expect(ops).toContain("redis.ping");
|
||||
expect(existsSync("src/lib/admin/ticket-queue-counts.ts")).toBe(true);
|
||||
expect(existsSync("src/lib/admin/ops-online-users.ts")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps force-dynamic only on admin/mod layouts", () => {
|
||||
const hits: string[] = [];
|
||||
function walk(dir: string) {
|
||||
for (const name of readdirSync(dir)) {
|
||||
const p = join(dir, name);
|
||||
if (statSync(p).isDirectory()) walk(p);
|
||||
else if (/\.(tsx?|jsx?)$/.test(name)) {
|
||||
const src = readFileSync(p, "utf8");
|
||||
if (
|
||||
/export\s+const\s+dynamic\s*=\s*["']force-dynamic["']/.test(src)
|
||||
) {
|
||||
hits.push(p.replace(/\\/g, "/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk("src/app/admin");
|
||||
walk("src/app/mod");
|
||||
expect(hits.sort()).toEqual([
|
||||
"src/app/admin/layout.tsx",
|
||||
"src/app/mod/layout.tsx",
|
||||
]);
|
||||
});
|
||||
|
||||
it("caches analytics via redisCache", () => {
|
||||
for (const path of [
|
||||
"src/app/admin/analytics/page.tsx",
|
||||
|
||||
Reference in new issue
Block a user