diff --git a/src/app/admin/analytics/activity/page.tsx b/src/app/admin/analytics/activity/page.tsx index cb0ff7a2..c1935630 100644 --- a/src/app/admin/analytics/activity/page.tsx +++ b/src/app/admin/analytics/activity/page.tsx @@ -5,6 +5,7 @@ import { StatsCard } from "@/components/admin/stats-card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; +import { redisCache } from "@/lib/redis-cache"; type DayRow = { label: string; @@ -192,7 +193,9 @@ export default async function ActivityPage() { const locale = await getLocale(); const t = await getTranslations("pages.admin.analytics.activity"); const tRoot = await getTranslations("pages.admin.analytics"); - const data = await getActivityData(locale); + const data = await redisCache(`admin:analytics:activity:${locale}`, 120, () => + getActivityData(locale), + ); return (
diff --git a/src/app/admin/analytics/economy/page.tsx b/src/app/admin/analytics/economy/page.tsx index b025756f..1bf5ce69 100644 --- a/src/app/admin/analytics/economy/page.tsx +++ b/src/app/admin/analytics/economy/page.tsx @@ -7,6 +7,7 @@ import { CurrencyIcon } from "@/components/shared/currency-icon"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; +import { redisCache } from "@/lib/redis-cache"; type EconomyData = { totalCredits: number; @@ -172,7 +173,11 @@ export default async function EconomyPage() { const locale = await getLocale(); const t = await getTranslations("pages.admin.analytics.economy"); const tRoot = await getTranslations("pages.admin.analytics"); - const { data, degraded } = await getEconomyData(locale); + const { data, degraded } = await redisCache( + `admin:analytics:economy:${locale}`, + 120, + () => getEconomyData(locale), + ); return (
diff --git a/src/app/admin/analytics/page.tsx b/src/app/admin/analytics/page.tsx index 45cb5210..eba1dd5a 100644 --- a/src/app/admin/analytics/page.tsx +++ b/src/app/admin/analytics/page.tsx @@ -14,6 +14,7 @@ import { StatsCard } from "@/components/admin/stats-card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; +import { redisCache } from "@/lib/redis-cache"; type AnalyticsData = { totalUsers: number; @@ -202,7 +203,11 @@ export default async function AnalyticsPage() { const locale = await getLocale(); const t = await getTranslations("pages.admin.analytics"); - const { data, degraded } = await getAnalyticsData(locale); + const { data, degraded } = await redisCache( + `admin:analytics:overview:${locale}`, + 120, + () => getAnalyticsData(locale), + ); return (
diff --git a/src/app/admin/commandocentrum/page.tsx b/src/app/admin/commandocentrum/page.tsx index 08df04c2..652910fb 100644 --- a/src/app/admin/commandocentrum/page.tsx +++ b/src/app/admin/commandocentrum/page.tsx @@ -7,11 +7,11 @@ import { OnlineUsersWidget, StatusCard, } from "@/components/admin/dashboard"; +import { fetchOpsHealth } from "@/lib/admin/ops-health"; import { fetchOpsOnlineUsers } from "@/lib/admin/ops-online-users"; import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -import { rcon } from "@/lib/services/rcon"; import { siteSettings } from "@/lib/services/site-settings"; import { CommandocentrumControls } from "./commandocentrum-controls"; @@ -46,33 +46,25 @@ export default async function CommandoCentrum() { const t = await getTranslations("pages.admin.commandocentrum"); - const [ - totalUsers, - errors, - dbOk, - emulatorOnline, - maintenance, - activity, - onlineRoster, - ] = await Promise.all([ - prisma.user.count().catch(() => -1), - prisma.emulatorErrors - .findMany({ orderBy: { timestamp: "desc" }, take: 50 }) - .catch(() => []), - prisma.websiteSetting - .count() - .then(() => true) - .catch(() => false), - rcon.send("ping", null).catch(() => false), - siteSettings.getBool("maintenance_enabled", false), - prisma.staffActivities - .findMany({ orderBy: { createdAt: "desc" }, take: 12 }) - .catch( - () => [] as Awaited>, - ), - fetchOpsOnlineUsers(40), - ]); + const [totalUsers, errors, health, maintenance, activity, onlineRoster] = + await Promise.all([ + prisma.user.count().catch(() => -1), + prisma.emulatorErrors + .findMany({ orderBy: { timestamp: "desc" }, take: 50 }) + .catch(() => []), + fetchOpsHealth(), + siteSettings.getBool("maintenance_enabled", false), + prisma.staffActivities + .findMany({ orderBy: { createdAt: "desc" }, take: 12 }) + .catch( + () => + [] as Awaited>, + ), + fetchOpsOnlineUsers(40), + ]); + const dbOk = health.dbOk; + const emulatorOnline = health.emulatorOk; const onlineCount = onlineRoster.count; const onlineUsers = onlineRoster.users; diff --git a/src/app/admin/devops/page.tsx b/src/app/admin/devops/page.tsx index f166aba4..45953615 100644 --- a/src/app/admin/devops/page.tsx +++ b/src/app/admin/devops/page.tsx @@ -4,6 +4,7 @@ import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { fetchOpsHealth } from "@/lib/admin/ops-health"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { HealthCheckClient } from "./health-check-client"; @@ -12,42 +13,27 @@ async function getDevOpsData() { const now = Math.floor(Date.now() / 1000); const dayAgo = now - 86400; - // DB health — simple count query - let dbStatus: "healthy" | "error" = "healthy"; - let dbLatency = 0; - try { - const start = Date.now(); - await prisma.user.count({ take: 1 }); - dbLatency = Date.now() - start; - } catch { - dbStatus = "error"; - } - - // Recent emulator errors - const recentErrors = await prisma.emulatorErrors.findMany({ - orderBy: { id: "desc" }, - take: 20, - }); - - const errorsToday = await prisma.emulatorErrors.count({ - where: { timestamp: { gte: dayAgo } }, - }); - - // Emulator settings (version info) - const emuVersion = await prisma.emulatorSettings.findFirst({ - where: { key: "version" }, - }); - - // Online user count as proxy for emulator health - const onlineUsers = await prisma.user.count({ where: { online: "1" } }); + const [health, recentErrors, errorsToday, emuVersion] = await Promise.all([ + fetchOpsHealth(), + prisma.emulatorErrors.findMany({ + orderBy: { id: "desc" }, + take: 20, + }), + prisma.emulatorErrors.count({ + where: { timestamp: { gte: dayAgo } }, + }), + prisma.emulatorSettings.findFirst({ + where: { key: "version" }, + }), + ]); return { - dbStatus, - dbLatency, + dbStatus: health.dbOk ? ("healthy" as const) : ("error" as const), + dbLatency: health.dbLatencyMs, recentErrors, errorsToday, emuVersion: emuVersion?.value ?? "Unknown", - onlineUsers, + onlineUsers: health.onlineUsers, }; } diff --git a/src/app/admin/help-tickets/[id]/page.tsx b/src/app/admin/help-tickets/[id]/page.tsx index 81bc6b67..a15bd33a 100644 --- a/src/app/admin/help-tickets/[id]/page.tsx +++ b/src/app/admin/help-tickets/[id]/page.tsx @@ -1,4 +1,6 @@ import { notFound, redirect } from "next/navigation"; +import { getTranslations } from "next-intl/server"; +import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import { getMinStaffRank } from "@/lib/admin/min-staff-rank"; import { positiveBigInt } from "@/lib/api"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; @@ -17,6 +19,7 @@ export default async function AdminHelpTicketDetailPage({ redirect("/admin"); } + const t = await getTranslations("pages.admin.helpTickets"); const { id } = await params; const ticketId = positiveBigInt(id); if (!ticketId) notFound(); @@ -103,24 +106,31 @@ export default async function AdminHelpTicketDetailPage({ const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank); return ( - +
+ + +
); } diff --git a/src/app/admin/help-tickets/page.tsx b/src/app/admin/help-tickets/page.tsx index 0adec5d4..f2b6444f 100644 --- a/src/app/admin/help-tickets/page.tsx +++ b/src/app/admin/help-tickets/page.tsx @@ -3,6 +3,7 @@ import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import type { Prisma } from "@/generated/prisma/client"; +import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; @@ -82,7 +83,7 @@ export default async function AdminHelpTicketsPage({ [orderField]: parsed.order, } as Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput; - const [total, openCount, closedCount] = await Promise.all([ + const [total, openCount, closedCount, queues] = await Promise.all([ prisma.websiteHelpCenterTickets.count({ where }).catch(() => 0), prisma.websiteHelpCenterTickets .count({ where: { open: true } }) @@ -90,6 +91,7 @@ export default async function AdminHelpTicketsPage({ prisma.websiteHelpCenterTickets .count({ where: { open: false } }) .catch(() => 0), + fetchTicketQueueOpenCounts(), ]); const pagination = calcPagination(total, parsed.page, parsed.perPage); @@ -165,6 +167,10 @@ export default async function AdminHelpTicketsPage({ hint={t("queueHint")} otherHref="/admin/tickets" otherLabel={t("queueOtherCta")} + countsLine={t("queueCounts", { + here: queues.helpOpen, + other: queues.cmsOpen, + })} />
diff --git a/src/app/admin/tickets/[id]/page.tsx b/src/app/admin/tickets/[id]/page.tsx index 5116be91..2caf28f9 100644 --- a/src/app/admin/tickets/[id]/page.tsx +++ b/src/app/admin/tickets/[id]/page.tsx @@ -1,4 +1,6 @@ import { notFound, redirect } from "next/navigation"; +import { getTranslations } from "next-intl/server"; +import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import { getMinStaffRank } from "@/lib/admin/min-staff-rank"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; @@ -14,6 +16,7 @@ export default async function AdminTicketDetailPage({ redirect("/admin"); } + const t = await getTranslations("pages.admin.tickets"); const { id } = await params; const ticketId = Number(id); if (Number.isNaN(ticketId)) notFound(); @@ -57,43 +60,50 @@ export default async function AdminTicketDetailPage({ const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank); return ( - ({ - id: m.id, - message: m.message, - isStaff: m.isStaff === 1, - createdAt: m.createdAt.toISOString(), - user: { - id: m.user.id, - username: m.user.username, - look: m.user.look, - rank: m.user.rank, - }, - }))} - staffMembers={staffMembers} - currentUserId={session.user.id} - canEdit={canEdit} - /> +
+ + ({ + id: m.id, + message: m.message, + isStaff: m.isStaff === 1, + createdAt: m.createdAt.toISOString(), + user: { + id: m.user.id, + username: m.user.username, + look: m.user.look, + rank: m.user.rank, + }, + }))} + staffMembers={staffMembers} + currentUserId={session.user.id} + canEdit={canEdit} + /> +
); } diff --git a/src/app/admin/tickets/page.tsx b/src/app/admin/tickets/page.tsx index 258ca191..32a72de6 100644 --- a/src/app/admin/tickets/page.tsx +++ b/src/app/admin/tickets/page.tsx @@ -3,6 +3,7 @@ import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import type { Prisma } from "@/generated/prisma/client"; +import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; @@ -71,7 +72,7 @@ export default async function AdminTicketsPage({ [orderField]: parsed.order, } as Prisma.WebsiteTicketOrderByWithRelationInput; - const [total, openCount, inProgressCount, waitingCount, closedCount] = + const [total, openCount, inProgressCount, waitingCount, closedCount, queues] = await Promise.all([ prisma.websiteTicket.count({ where }).catch(() => 0), prisma.websiteTicket.count({ where: { status: "open" } }).catch(() => 0), @@ -84,6 +85,7 @@ export default async function AdminTicketsPage({ prisma.websiteTicket .count({ where: { status: "closed" } }) .catch(() => 0), + fetchTicketQueueOpenCounts(), ]); const pagination = calcPagination(total, parsed.page, parsed.perPage); @@ -121,6 +123,10 @@ export default async function AdminTicketsPage({ hint={t("queueHint")} otherHref="/admin/help-tickets" otherLabel={t("queueOtherCta")} + countsLine={t("queueCounts", { + here: queues.cmsOpen, + other: queues.helpOpen, + })} />
diff --git a/src/app/api/admin/devops/health/route.ts b/src/app/api/admin/devops/health/route.ts index 58c62f9e..93ed2996 100644 --- a/src/app/api/admin/devops/health/route.ts +++ b/src/app/api/admin/devops/health/route.ts @@ -1,59 +1,16 @@ -import net from "node:net"; import { NextResponse } from "next/server"; -import { env } from "@/env"; +import { fetchOpsHealth } from "@/lib/admin/ops-health"; import { withAdmin } from "@/lib/api-handler"; import { PERMS } from "@/lib/permissions"; -import { prisma } from "@/lib/prisma"; - -function checkRcon(): Promise { - return new Promise((resolve) => { - const host = env.RCON_HOST; - const port = env.RCON_PORT; - - const socket = new net.Socket(); - const timeout = setTimeout(() => { - socket.destroy(); - resolve(false); - }, 3000); - - socket.connect(port, host, () => { - clearTimeout(timeout); - socket.destroy(); - resolve(true); - }); - - socket.on("error", () => { - clearTimeout(timeout); - socket.destroy(); - resolve(false); - }); - }); -} export const GET = withAdmin({ permission: PERMS.DEVOPS_VIEW }, async () => { - // DB health - let dbOk = false; - let dbLatency = 0; - try { - const start = Date.now(); - await prisma.$queryRaw`SELECT 1`; - dbLatency = Date.now() - start; - dbOk = true; - } catch { - dbOk = false; - } - - // Emulator health (RCON TCP check) - const emulator = await checkRcon(); - - // Online users - const onlineUsers = await prisma.user.count({ where: { online: "1" } }); + const health = await fetchOpsHealth(); return NextResponse.json({ - database: dbOk, - dbLatency, - emulator, - onlineUsers, + database: health.dbOk, + dbLatency: health.dbLatencyMs, + emulator: health.emulatorOk, + onlineUsers: health.onlineUsers, timestamp: new Date().toISOString(), }); }); diff --git a/src/app/mod/help-tickets/page.tsx b/src/app/mod/help-tickets/page.tsx index 7ff3ffda..4cbf4a6e 100644 --- a/src/app/mod/help-tickets/page.tsx +++ b/src/app/mod/help-tickets/page.tsx @@ -7,6 +7,7 @@ import { StatusCard } from "@/components/admin/dashboard"; import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import type { Prisma } from "@/generated/prisma/client"; import { requireModPermission } from "@/lib/admin/guard"; +import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { formatDate } from "@/lib/format-date"; import { PERMS } from "@/lib/permissions"; @@ -82,7 +83,7 @@ export default async function ModHelpTicketsPage({ [orderField]: parsed.order, } as Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput; - const [total, openCount, closedCount] = await Promise.all([ + const [total, openCount, closedCount, queues] = await Promise.all([ prisma.websiteHelpCenterTickets.count({ where }).catch(() => 0), prisma.websiteHelpCenterTickets .count({ where: { open: true } }) @@ -90,6 +91,7 @@ export default async function ModHelpTicketsPage({ prisma.websiteHelpCenterTickets .count({ where: { open: false } }) .catch(() => 0), + fetchTicketQueueOpenCounts(), ]); const pagination = calcPagination(total, parsed.page, parsed.perPage); @@ -165,6 +167,10 @@ export default async function ModHelpTicketsPage({ hint={t("queueHint")} otherHref="/mod/tickets" otherLabel={t("queueOtherCta")} + countsLine={t("queueCounts", { + here: queues.helpOpen, + other: queues.cmsOpen, + })} />
diff --git a/src/app/mod/tickets/page.tsx b/src/app/mod/tickets/page.tsx index 6fbd8b3d..1b1305b4 100644 --- a/src/app/mod/tickets/page.tsx +++ b/src/app/mod/tickets/page.tsx @@ -7,6 +7,7 @@ import { StatusCard } from "@/components/admin/dashboard"; import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import type { Prisma } from "@/generated/prisma/client"; import { requireModPermission } from "@/lib/admin/guard"; +import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { formatDate } from "@/lib/format-date"; import { PERMS } from "@/lib/permissions"; @@ -71,7 +72,7 @@ export default async function ModTicketsPage({ [orderField]: parsed.order, } as Prisma.WebsiteTicketOrderByWithRelationInput; - const [total, openCount, inProgressCount, waitingCount, closedCount] = + const [total, openCount, inProgressCount, waitingCount, closedCount, queues] = await Promise.all([ prisma.websiteTicket.count({ where }).catch(() => 0), prisma.websiteTicket.count({ where: { status: "open" } }).catch(() => 0), @@ -84,6 +85,7 @@ export default async function ModTicketsPage({ prisma.websiteTicket .count({ where: { status: "closed" } }) .catch(() => 0), + fetchTicketQueueOpenCounts(), ]); const pagination = calcPagination(total, parsed.page, parsed.perPage); @@ -121,6 +123,10 @@ export default async function ModTicketsPage({ hint={t("queueHint")} otherHref="/mod/help-tickets" otherLabel={t("queueOtherCta")} + countsLine={t("queueCounts", { + here: queues.cmsOpen, + other: queues.helpOpen, + })} />
diff --git a/src/components/admin/ticket-queue-banner.tsx b/src/components/admin/ticket-queue-banner.tsx index 0cf88947..d62058c9 100644 --- a/src/components/admin/ticket-queue-banner.tsx +++ b/src/components/admin/ticket-queue-banner.tsx @@ -5,10 +5,13 @@ export function TicketQueueBanner({ hint, otherHref, otherLabel, + countsLine, }: { hint: string; otherHref: string; otherLabel: string; + /** Optional preformatted open-count summary (locale-aware from caller). */ + countsLine?: string; }) { return (

{hint}

+ {countsLine ? ( +

{countsLine}

+ ) : null}

= [ @@ -135,22 +135,26 @@ describe("admin operations route contract", () => { expect(offenders).toEqual([]); }); - it("has no requireStaff left in admin pages", () => { - const root = "src/app/admin"; - const offenders: string[] = []; - function walk(dir: string) { - for (const entry of readdirSync(dir, { withFileTypes: true })) { - const full = join(dir, entry.name); - if (entry.isDirectory()) walk(full); - else if (entry.name === "page.tsx") { - const source = readFileSync(full, "utf8"); - if (/await requireStaff\(/.test(source)) { - offenders.push(relative(process.cwd(), full).replaceAll("\\", "/")); - } - } - } + it("caches analytics full-scans via redisCache", () => { + for (const path of [ + "src/app/admin/analytics/page.tsx", + "src/app/admin/analytics/activity/page.tsx", + "src/app/admin/analytics/economy/page.tsx", + ]) { + expect(readFileSync(path, "utf8"), path).toContain("redisCache"); } - walk(root); - expect(offenders).toEqual([]); + }); + + it("shares ops health probe across CC / DevOps / API", () => { + expect(existsSync("src/lib/admin/ops-health.ts")).toBe(true); + expect( + readFileSync("src/app/admin/commandocentrum/page.tsx", "utf8"), + ).toContain("fetchOpsHealth"); + expect(readFileSync("src/app/admin/devops/page.tsx", "utf8")).toContain( + "fetchOpsHealth", + ); + expect( + readFileSync("src/app/api/admin/devops/health/route.ts", "utf8"), + ).toContain("fetchOpsHealth"); }); }); diff --git a/src/lib/admin/ops-health.ts b/src/lib/admin/ops-health.ts new file mode 100644 index 00000000..8a5a4506 --- /dev/null +++ b/src/lib/admin/ops-health.ts @@ -0,0 +1,38 @@ +import "server-only"; + +import { prisma } from "@/lib/prisma"; +import { rcon } from "@/lib/services/rcon"; + +export type OpsHealth = { + dbOk: boolean; + dbLatencyMs: number; + emulatorOk: boolean; + onlineUsers: number; +}; + +/** + * Shared DB + emulator health probe for CommandoCentrum, DevOps, and APIs. + * Emulator = real RCON `ping` (same semantics as CommandoCentrum /public health). + */ +export async function fetchOpsHealth(): Promise { + const [db, emulatorOk, onlineUsers] = await Promise.all([ + (async () => { + try { + const start = Date.now(); + await prisma.$queryRaw`SELECT 1`; + return { dbOk: true, dbLatencyMs: Date.now() - start }; + } catch { + return { dbOk: false, dbLatencyMs: 0 }; + } + })(), + rcon.send("ping", null).catch(() => false), + prisma.user.count({ where: { online: "1" } }).catch(() => 0), + ]); + + return { + dbOk: db.dbOk, + dbLatencyMs: db.dbLatencyMs, + emulatorOk: Boolean(emulatorOk), + onlineUsers, + }; +} diff --git a/src/lib/admin/ticket-queue-counts.ts b/src/lib/admin/ticket-queue-counts.ts new file mode 100644 index 00000000..c831344c --- /dev/null +++ b/src/lib/admin/ticket-queue-counts.ts @@ -0,0 +1,19 @@ +import "server-only"; + +import { prisma } from "@/lib/prisma"; + +/** Open-queue sizes for dual ticket products (CMS desk vs help-center). */ +export async function fetchTicketQueueOpenCounts(): Promise<{ + cmsOpen: number; + helpOpen: number; +}> { + const [cmsOpen, helpOpen] = await Promise.all([ + prisma.websiteTicket + .count({ where: { status: { not: "closed" } } }) + .catch(() => 0), + prisma.websiteHelpCenterTickets + .count({ where: { open: true } }) + .catch(() => 0), + ]); + return { cmsOpen, helpOpen }; +} diff --git a/src/messages/bg.json b/src/messages/bg.json index 0b69d2ee..5c83353e 100644 --- a/src/messages/bg.json +++ b/src/messages/bg.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/cs.json b/src/messages/cs.json index e89c865b..0d0529cb 100644 --- a/src/messages/cs.json +++ b/src/messages/cs.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/da.json b/src/messages/da.json index f5da6cca..b5ba343a 100644 --- a/src/messages/da.json +++ b/src/messages/da.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/de.json b/src/messages/de.json index 2bb4a88d..e9adb9ff 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -2433,7 +2433,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2697,7 +2698,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/el.json b/src/messages/el.json index a85c9cb5..0e9381ff 100644 --- a/src/messages/el.json +++ b/src/messages/el.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/en.json b/src/messages/en.json index b40e6b93..89859a82 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -1496,6 +1496,7 @@ "subtitle": "Internal CMS tickets (assignee, priority, staff workflow). Not the same as help-center tickets from /help.", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}", "noTickets": "No active tickets", "searchPlaceholder": "Search subject, user, category or ID…", "presetActive": "Active", @@ -1515,6 +1516,7 @@ "subtitle": "Tickets players open at /help/tickets. Separate from the CMS support desk.", "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}", "noTickets": "No open help center tickets", "searchPlaceholder": "Search title, user or ID…", "presetOpen": "Open", @@ -2461,8 +2463,8 @@ "events": "Events", "eventTypes": "Event types", "polls": "Polls", - "tickets": "CMS tickets", - "helpTickets": "Help-center tickets", + "tickets": "Support desk", + "helpTickets": "Help center", "templates": "Templates", "directory": "Directory", "multiAccounts": "Multi-accounts", @@ -3008,7 +3010,7 @@ "bans": "Bans", "fullAdmin": "Full admin", "backToSite": "Back to site", - "tickets": "CMS tickets", + "tickets": "Support desk", "helpTickets": "Help-center tickets", "users": "Users", "team": "Team" diff --git a/src/messages/es.json b/src/messages/es.json index 031946a2..f65547ee 100644 --- a/src/messages/es.json +++ b/src/messages/es.json @@ -2433,7 +2433,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2697,7 +2698,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/fr.json b/src/messages/fr.json index bd256aa8..cbe801c1 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -2433,7 +2433,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2697,7 +2698,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/hr.json b/src/messages/hr.json index 0d40e45f..25b24f2a 100644 --- a/src/messages/hr.json +++ b/src/messages/hr.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/hu.json b/src/messages/hu.json index 82441198..069d13f4 100644 --- a/src/messages/hu.json +++ b/src/messages/hu.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/it.json b/src/messages/it.json index e3aa7593..abc5f439 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -1482,8 +1482,9 @@ "colAssigned": "Assegnato", "colDate": "Data", "meta": "di {user} · {count} msg · {category}", - "queueHint": "Questa coda e il supporto CMS. I ticket del help center pubblici stanno nella tab Help-center tickets.", - "queueOtherCta": "Apri help-center tickets" + "queueHint": "Questa coda e il supporto CMS. I ticket del help center pubblici stanno nella tab Help center.", + "queueOtherCta": "Apri help center", + "queueCounts": "Aperti qui: {here} · Help center: {other}" }, "helpTickets": { "title": "Ticket help center", @@ -1519,8 +1520,9 @@ "closed": "Ticket chiuso.", "reopened": "Ticket riaperto." }, - "queueHint": "Questa coda e il help center pubblico. I ticket del supporto CMS stanno nella tab Support desk.", - "queueOtherCta": "Apri supporto CMS" + "queueHint": "Questa coda e il help center pubblico. I ticket del supporto CMS stanno nella tab Supporto CMS.", + "queueOtherCta": "Apri supporto CMS", + "queueCounts": "Aperti qui: {here} · Supporto CMS: {other}" }, "cfh": { "title": "Call for Help", @@ -2732,8 +2734,8 @@ "events": "Eventi", "eventTypes": "Tipi evento", "polls": "Sondaggi", - "tickets": "Ticket CMS", - "helpTickets": "Ticket centro assistenza", + "tickets": "Supporto CMS", + "helpTickets": "Help center", "templates": "Template", "directory": "Directory", "multiAccounts": "Multi-account", @@ -3015,8 +3017,8 @@ "bans": "Ban", "fullAdmin": "Admin completo", "backToSite": "Torna al sito", - "tickets": "Ticket CMS", - "helpTickets": "Ticket help center", + "tickets": "Supporto CMS", + "helpTickets": "Help center", "users": "Utenti", "team": "Team" }, diff --git a/src/messages/nl.json b/src/messages/nl.json index 53f5a098..7b11d38d 100644 --- a/src/messages/nl.json +++ b/src/messages/nl.json @@ -2779,8 +2779,8 @@ "events": "Evenementen", "eventTypes": "Evenementtypen", "polls": "Polls", - "tickets": "CMS-tickets", - "helpTickets": "Helpcentrum-tickets", + "tickets": "Supportbalie", + "helpTickets": "Helpcentrum", "templates": "Sjablonen", "directory": "Adresboek", "multiAccounts": "Multi-accounts", @@ -2866,8 +2866,9 @@ "colAssigned": "Toegewezen", "colDate": "Datum", "meta": "door {user} · {count} berichten · {category}", - "queueHint": "Deze wachtrij is de CMS support desk. Speler-helpcenter tickets staan onder Help-center tickets.", - "queueOtherCta": "Open helpcenter-tickets" + "queueHint": "Deze wachtrij is de CMS support desk. Speler-helpcenter tickets staan onder Helpcentrum.", + "queueOtherCta": "Open helpcentrum-tickets", + "queueCounts": "Open hier: {here} · Helpcentrum: {other}" }, "helpTickets": { "title": "Helpcenter-tickets", @@ -2903,8 +2904,9 @@ "closed": "Ticket gesloten.", "reopened": "Ticket heropend." }, - "queueHint": "Deze wachtrij is het publieke helpcenter. CMS support desk tickets staan onder Support desk.", - "queueOtherCta": "Open CMS support desk" + "queueHint": "Deze wachtrij is het publieke helpcenter. CMS support desk tickets staan onder Supportbalie.", + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open hier: {here} · Supportbalie: {other}" }, "cfh": { "title": "Call for Help", @@ -3013,8 +3015,8 @@ "bans": "Bans", "fullAdmin": "Volledige admin", "backToSite": "Terug naar site", - "tickets": "CMS-tickets", - "helpTickets": "Helpcenter-tickets", + "tickets": "Supportbalie", + "helpTickets": "Helpcentrum", "users": "Gebruikers", "team": "Team" }, diff --git a/src/messages/no.json b/src/messages/no.json index d751ac2e..559ef45a 100644 --- a/src/messages/no.json +++ b/src/messages/no.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/pl.json b/src/messages/pl.json index 3c81a9b8..6e3539b2 100644 --- a/src/messages/pl.json +++ b/src/messages/pl.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/pt.json b/src/messages/pt.json index b5b1cb81..39e3da8b 100644 --- a/src/messages/pt.json +++ b/src/messages/pt.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/ro.json b/src/messages/ro.json index 9892bfa6..e7b21cac 100644 --- a/src/messages/ro.json +++ b/src/messages/ro.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/ru.json b/src/messages/ru.json index d61416cb..dacfe432 100644 --- a/src/messages/ru.json +++ b/src/messages/ru.json @@ -2461,7 +2461,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2725,7 +2726,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/sk.json b/src/messages/sk.json index 8c86ee26..3ea48123 100644 --- a/src/messages/sk.json +++ b/src/messages/sk.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/sr.json b/src/messages/sr.json index f080667d..606aa675 100644 --- a/src/messages/sr.json +++ b/src/messages/sr.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/sv.json b/src/messages/sv.json index c36f6768..c56cc101 100644 --- a/src/messages/sv.json +++ b/src/messages/sv.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/tr.json b/src/messages/tr.json index 83ff59f1..b9fe14f5 100644 --- a/src/messages/tr.json +++ b/src/messages/tr.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace", diff --git a/src/messages/uk.json b/src/messages/uk.json index d1e67f1b..3661f456 100644 --- a/src/messages/uk.json +++ b/src/messages/uk.json @@ -2463,7 +2463,8 @@ "colDate": "Date", "meta": "by {user} · {count} msg · {category}", "queueHint": "This queue is the CMS support desk. Player help-center tickets live in the Help-center tickets tab.", - "queueOtherCta": "Open help-center tickets" + "queueOtherCta": "Open help-center tickets", + "queueCounts": "Open here: {here} · Help center: {other}" }, "cfh": { "title": "Call for Help", @@ -2727,7 +2728,8 @@ "reopened": "Ticket reopened." }, "queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.", - "queueOtherCta": "Open CMS support desk" + "queueOtherCta": "Open CMS support desk", + "queueCounts": "Open here: {here} · Support desk: {other}" }, "marketplace": { "title": "Marketplace",