feat(admin): analytics redis cache, shared ops health, ticket queue clarity
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
474de0717b
commit
58fae1f90f
38 files changed
+348
-245
No files matched your search
@@ -5,6 +5,7 @@ import { StatsCard } from "@/components/admin/stats-card";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redisCache } from "@/lib/redis-cache";
|
||||||
|
|
||||||
type DayRow = {
|
type DayRow = {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -192,7 +193,9 @@ export default async function ActivityPage() {
|
|||||||
const locale = await getLocale();
|
const locale = await getLocale();
|
||||||
const t = await getTranslations("pages.admin.analytics.activity");
|
const t = await getTranslations("pages.admin.analytics.activity");
|
||||||
const tRoot = await getTranslations("pages.admin.analytics");
|
const tRoot = await getTranslations("pages.admin.analytics");
|
||||||
const data = await getActivityData(locale);
|
const data = await redisCache(`admin:analytics:activity:${locale}`, 120, () =>
|
||||||
|
getActivityData(locale),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { CurrencyIcon } from "@/components/shared/currency-icon";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redisCache } from "@/lib/redis-cache";
|
||||||
|
|
||||||
type EconomyData = {
|
type EconomyData = {
|
||||||
totalCredits: number;
|
totalCredits: number;
|
||||||
@@ -172,7 +173,11 @@ export default async function EconomyPage() {
|
|||||||
const locale = await getLocale();
|
const locale = await getLocale();
|
||||||
const t = await getTranslations("pages.admin.analytics.economy");
|
const t = await getTranslations("pages.admin.analytics.economy");
|
||||||
const tRoot = await getTranslations("pages.admin.analytics");
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { StatsCard } from "@/components/admin/stats-card";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redisCache } from "@/lib/redis-cache";
|
||||||
|
|
||||||
type AnalyticsData = {
|
type AnalyticsData = {
|
||||||
totalUsers: number;
|
totalUsers: number;
|
||||||
@@ -202,7 +203,11 @@ export default async function AnalyticsPage() {
|
|||||||
|
|
||||||
const locale = await getLocale();
|
const locale = await getLocale();
|
||||||
const t = await getTranslations("pages.admin.analytics");
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import {
|
|||||||
OnlineUsersWidget,
|
OnlineUsersWidget,
|
||||||
StatusCard,
|
StatusCard,
|
||||||
} from "@/components/admin/dashboard";
|
} from "@/components/admin/dashboard";
|
||||||
|
import { fetchOpsHealth } from "@/lib/admin/ops-health";
|
||||||
import { fetchOpsOnlineUsers } from "@/lib/admin/ops-online-users";
|
import { fetchOpsOnlineUsers } from "@/lib/admin/ops-online-users";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
import { CommandocentrumControls } from "./commandocentrum-controls";
|
import { CommandocentrumControls } from "./commandocentrum-controls";
|
||||||
|
|
||||||
@@ -46,33 +46,25 @@ export default async function CommandoCentrum() {
|
|||||||
|
|
||||||
const t = await getTranslations("pages.admin.commandocentrum");
|
const t = await getTranslations("pages.admin.commandocentrum");
|
||||||
|
|
||||||
const [
|
const [totalUsers, errors, health, maintenance, activity, onlineRoster] =
|
||||||
totalUsers,
|
await Promise.all([
|
||||||
errors,
|
prisma.user.count().catch(() => -1),
|
||||||
dbOk,
|
prisma.emulatorErrors
|
||||||
emulatorOnline,
|
.findMany({ orderBy: { timestamp: "desc" }, take: 50 })
|
||||||
maintenance,
|
.catch(() => []),
|
||||||
activity,
|
fetchOpsHealth(),
|
||||||
onlineRoster,
|
siteSettings.getBool("maintenance_enabled", false),
|
||||||
] = await Promise.all([
|
prisma.staffActivities
|
||||||
prisma.user.count().catch(() => -1),
|
.findMany({ orderBy: { createdAt: "desc" }, take: 12 })
|
||||||
prisma.emulatorErrors
|
.catch(
|
||||||
.findMany({ orderBy: { timestamp: "desc" }, take: 50 })
|
() =>
|
||||||
.catch(() => []),
|
[] as Awaited<ReturnType<typeof prisma.staffActivities.findMany>>,
|
||||||
prisma.websiteSetting
|
),
|
||||||
.count()
|
fetchOpsOnlineUsers(40),
|
||||||
.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<ReturnType<typeof prisma.staffActivities.findMany>>,
|
|
||||||
),
|
|
||||||
fetchOpsOnlineUsers(40),
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
const dbOk = health.dbOk;
|
||||||
|
const emulatorOnline = health.emulatorOk;
|
||||||
const onlineCount = onlineRoster.count;
|
const onlineCount = onlineRoster.count;
|
||||||
const onlineUsers = onlineRoster.users;
|
const onlineUsers = onlineRoster.users;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { redirect } from "next/navigation";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { fetchOpsHealth } from "@/lib/admin/ops-health";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { HealthCheckClient } from "./health-check-client";
|
import { HealthCheckClient } from "./health-check-client";
|
||||||
@@ -12,42 +13,27 @@ async function getDevOpsData() {
|
|||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const dayAgo = now - 86400;
|
const dayAgo = now - 86400;
|
||||||
|
|
||||||
// DB health — simple count query
|
const [health, recentErrors, errorsToday, emuVersion] = await Promise.all([
|
||||||
let dbStatus: "healthy" | "error" = "healthy";
|
fetchOpsHealth(),
|
||||||
let dbLatency = 0;
|
prisma.emulatorErrors.findMany({
|
||||||
try {
|
orderBy: { id: "desc" },
|
||||||
const start = Date.now();
|
take: 20,
|
||||||
await prisma.user.count({ take: 1 });
|
}),
|
||||||
dbLatency = Date.now() - start;
|
prisma.emulatorErrors.count({
|
||||||
} catch {
|
where: { timestamp: { gte: dayAgo } },
|
||||||
dbStatus = "error";
|
}),
|
||||||
}
|
prisma.emulatorSettings.findFirst({
|
||||||
|
where: { key: "version" },
|
||||||
// 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" } });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dbStatus,
|
dbStatus: health.dbOk ? ("healthy" as const) : ("error" as const),
|
||||||
dbLatency,
|
dbLatency: health.dbLatencyMs,
|
||||||
recentErrors,
|
recentErrors,
|
||||||
errorsToday,
|
errorsToday,
|
||||||
emuVersion: emuVersion?.value ?? "Unknown",
|
emuVersion: emuVersion?.value ?? "Unknown",
|
||||||
onlineUsers,
|
onlineUsers: health.onlineUsers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { notFound, redirect } from "next/navigation";
|
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 { getMinStaffRank } from "@/lib/admin/min-staff-rank";
|
||||||
import { positiveBigInt } from "@/lib/api";
|
import { positiveBigInt } from "@/lib/api";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
@@ -17,6 +19,7 @@ export default async function AdminHelpTicketDetailPage({
|
|||||||
redirect("/admin");
|
redirect("/admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const t = await getTranslations("pages.admin.helpTickets");
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const ticketId = positiveBigInt(id);
|
const ticketId = positiveBigInt(id);
|
||||||
if (!ticketId) notFound();
|
if (!ticketId) notFound();
|
||||||
@@ -103,24 +106,31 @@ export default async function AdminHelpTicketDetailPage({
|
|||||||
const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank);
|
const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminHelpTicketDetail
|
<div className="space-y-6">
|
||||||
ticket={{
|
<TicketQueueBanner
|
||||||
id: String(ticket.id),
|
hint={t("queueHint")}
|
||||||
title: ticket.title,
|
otherHref="/admin/tickets"
|
||||||
open: ticket.open,
|
otherLabel={t("queueOtherCta")}
|
||||||
createdAt: (ticket.createdAt ?? new Date()).toISOString(),
|
/>
|
||||||
updatedAt: (ticket.updatedAt ?? new Date()).toISOString(),
|
<AdminHelpTicketDetail
|
||||||
creator: creator
|
ticket={{
|
||||||
? {
|
id: String(ticket.id),
|
||||||
id: creator.id,
|
title: ticket.title,
|
||||||
username: creator.username,
|
open: ticket.open,
|
||||||
rank: creator.rank,
|
createdAt: (ticket.createdAt ?? new Date()).toISOString(),
|
||||||
mail: creator.mail ?? "",
|
updatedAt: (ticket.updatedAt ?? new Date()).toISOString(),
|
||||||
}
|
creator: creator
|
||||||
: null,
|
? {
|
||||||
}}
|
id: creator.id,
|
||||||
messages={messages}
|
username: creator.username,
|
||||||
canEdit={canEdit}
|
rank: creator.rank,
|
||||||
/>
|
mail: creator.mail ?? "",
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
}}
|
||||||
|
messages={messages}
|
||||||
|
canEdit={canEdit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
||||||
import type { Prisma } from "@/generated/prisma/client";
|
import type { Prisma } from "@/generated/prisma/client";
|
||||||
|
import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
@@ -82,7 +83,7 @@ export default async function AdminHelpTicketsPage({
|
|||||||
[orderField]: parsed.order,
|
[orderField]: parsed.order,
|
||||||
} as Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput;
|
} 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 }).catch(() => 0),
|
||||||
prisma.websiteHelpCenterTickets
|
prisma.websiteHelpCenterTickets
|
||||||
.count({ where: { open: true } })
|
.count({ where: { open: true } })
|
||||||
@@ -90,6 +91,7 @@ export default async function AdminHelpTicketsPage({
|
|||||||
prisma.websiteHelpCenterTickets
|
prisma.websiteHelpCenterTickets
|
||||||
.count({ where: { open: false } })
|
.count({ where: { open: false } })
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
|
fetchTicketQueueOpenCounts(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
||||||
@@ -165,6 +167,10 @@ export default async function AdminHelpTicketsPage({
|
|||||||
hint={t("queueHint")}
|
hint={t("queueHint")}
|
||||||
otherHref="/admin/tickets"
|
otherHref="/admin/tickets"
|
||||||
otherLabel={t("queueOtherCta")}
|
otherLabel={t("queueOtherCta")}
|
||||||
|
countsLine={t("queueCounts", {
|
||||||
|
here: queues.helpOpen,
|
||||||
|
other: queues.cmsOpen,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
||||||
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { notFound, redirect } from "next/navigation";
|
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 { getMinStaffRank } from "@/lib/admin/min-staff-rank";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
@@ -14,6 +16,7 @@ export default async function AdminTicketDetailPage({
|
|||||||
redirect("/admin");
|
redirect("/admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const t = await getTranslations("pages.admin.tickets");
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const ticketId = Number(id);
|
const ticketId = Number(id);
|
||||||
if (Number.isNaN(ticketId)) notFound();
|
if (Number.isNaN(ticketId)) notFound();
|
||||||
@@ -57,43 +60,50 @@ export default async function AdminTicketDetailPage({
|
|||||||
const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank);
|
const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminTicketDetail
|
<div className="space-y-6">
|
||||||
ticket={{
|
<TicketQueueBanner
|
||||||
id: ticket.id,
|
hint={t("queueHint")}
|
||||||
subject: ticket.subject,
|
otherHref="/admin/help-tickets"
|
||||||
category: ticket.category,
|
otherLabel={t("queueOtherCta")}
|
||||||
priority: ticket.priority,
|
/>
|
||||||
status: ticket.status,
|
<AdminTicketDetail
|
||||||
createdAt: ticket.createdAt.toISOString(),
|
ticket={{
|
||||||
closedAt: ticket.closedAt?.toISOString() ?? null,
|
id: ticket.id,
|
||||||
creator: {
|
subject: ticket.subject,
|
||||||
id: ticket.creator.id,
|
category: ticket.category,
|
||||||
username: ticket.creator.username,
|
priority: ticket.priority,
|
||||||
look: ticket.creator.look,
|
status: ticket.status,
|
||||||
rank: ticket.creator.rank,
|
createdAt: ticket.createdAt.toISOString(),
|
||||||
mail: ticket.creator.mail ?? "",
|
closedAt: ticket.closedAt?.toISOString() ?? null,
|
||||||
ipRegister: ticket.creator.ipRegister,
|
creator: {
|
||||||
accountCreated: ticket.creator.accountCreated,
|
id: ticket.creator.id,
|
||||||
},
|
username: ticket.creator.username,
|
||||||
assignee: ticket.assignee
|
look: ticket.creator.look,
|
||||||
? { id: ticket.assignee.id, username: ticket.assignee.username }
|
rank: ticket.creator.rank,
|
||||||
: null,
|
mail: ticket.creator.mail ?? "",
|
||||||
}}
|
ipRegister: ticket.creator.ipRegister,
|
||||||
messages={ticket.messages.map((m) => ({
|
accountCreated: ticket.creator.accountCreated,
|
||||||
id: m.id,
|
},
|
||||||
message: m.message,
|
assignee: ticket.assignee
|
||||||
isStaff: m.isStaff === 1,
|
? { id: ticket.assignee.id, username: ticket.assignee.username }
|
||||||
createdAt: m.createdAt.toISOString(),
|
: null,
|
||||||
user: {
|
}}
|
||||||
id: m.user.id,
|
messages={ticket.messages.map((m) => ({
|
||||||
username: m.user.username,
|
id: m.id,
|
||||||
look: m.user.look,
|
message: m.message,
|
||||||
rank: m.user.rank,
|
isStaff: m.isStaff === 1,
|
||||||
},
|
createdAt: m.createdAt.toISOString(),
|
||||||
}))}
|
user: {
|
||||||
staffMembers={staffMembers}
|
id: m.user.id,
|
||||||
currentUserId={session.user.id}
|
username: m.user.username,
|
||||||
canEdit={canEdit}
|
look: m.user.look,
|
||||||
/>
|
rank: m.user.rank,
|
||||||
|
},
|
||||||
|
}))}
|
||||||
|
staffMembers={staffMembers}
|
||||||
|
currentUserId={session.user.id}
|
||||||
|
canEdit={canEdit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
||||||
import type { Prisma } from "@/generated/prisma/client";
|
import type { Prisma } from "@/generated/prisma/client";
|
||||||
|
import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
@@ -71,7 +72,7 @@ export default async function AdminTicketsPage({
|
|||||||
[orderField]: parsed.order,
|
[orderField]: parsed.order,
|
||||||
} as Prisma.WebsiteTicketOrderByWithRelationInput;
|
} as Prisma.WebsiteTicketOrderByWithRelationInput;
|
||||||
|
|
||||||
const [total, openCount, inProgressCount, waitingCount, closedCount] =
|
const [total, openCount, inProgressCount, waitingCount, closedCount, queues] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
prisma.websiteTicket.count({ where }).catch(() => 0),
|
prisma.websiteTicket.count({ where }).catch(() => 0),
|
||||||
prisma.websiteTicket.count({ where: { status: "open" } }).catch(() => 0),
|
prisma.websiteTicket.count({ where: { status: "open" } }).catch(() => 0),
|
||||||
@@ -84,6 +85,7 @@ export default async function AdminTicketsPage({
|
|||||||
prisma.websiteTicket
|
prisma.websiteTicket
|
||||||
.count({ where: { status: "closed" } })
|
.count({ where: { status: "closed" } })
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
|
fetchTicketQueueOpenCounts(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
||||||
@@ -121,6 +123,10 @@ export default async function AdminTicketsPage({
|
|||||||
hint={t("queueHint")}
|
hint={t("queueHint")}
|
||||||
otherHref="/admin/help-tickets"
|
otherHref="/admin/help-tickets"
|
||||||
otherLabel={t("queueOtherCta")}
|
otherLabel={t("queueOtherCta")}
|
||||||
|
countsLine={t("queueCounts", {
|
||||||
|
here: queues.cmsOpen,
|
||||||
|
other: queues.helpOpen,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
||||||
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
||||||
|
|||||||
@@ -1,59 +1,16 @@
|
|||||||
import net from "node:net";
|
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { env } from "@/env";
|
import { fetchOpsHealth } from "@/lib/admin/ops-health";
|
||||||
import { withAdmin } from "@/lib/api-handler";
|
import { withAdmin } from "@/lib/api-handler";
|
||||||
import { PERMS } from "@/lib/permissions";
|
import { PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
function checkRcon(): Promise<boolean> {
|
|
||||||
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 () => {
|
export const GET = withAdmin({ permission: PERMS.DEVOPS_VIEW }, async () => {
|
||||||
// DB health
|
const health = await fetchOpsHealth();
|
||||||
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" } });
|
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
database: dbOk,
|
database: health.dbOk,
|
||||||
dbLatency,
|
dbLatency: health.dbLatencyMs,
|
||||||
emulator,
|
emulator: health.emulatorOk,
|
||||||
onlineUsers,
|
onlineUsers: health.onlineUsers,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -7,6 +7,7 @@ import { StatusCard } from "@/components/admin/dashboard";
|
|||||||
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
||||||
import type { Prisma } from "@/generated/prisma/client";
|
import type { Prisma } from "@/generated/prisma/client";
|
||||||
import { requireModPermission } from "@/lib/admin/guard";
|
import { requireModPermission } from "@/lib/admin/guard";
|
||||||
|
import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { PERMS } from "@/lib/permissions";
|
import { PERMS } from "@/lib/permissions";
|
||||||
@@ -82,7 +83,7 @@ export default async function ModHelpTicketsPage({
|
|||||||
[orderField]: parsed.order,
|
[orderField]: parsed.order,
|
||||||
} as Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput;
|
} 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 }).catch(() => 0),
|
||||||
prisma.websiteHelpCenterTickets
|
prisma.websiteHelpCenterTickets
|
||||||
.count({ where: { open: true } })
|
.count({ where: { open: true } })
|
||||||
@@ -90,6 +91,7 @@ export default async function ModHelpTicketsPage({
|
|||||||
prisma.websiteHelpCenterTickets
|
prisma.websiteHelpCenterTickets
|
||||||
.count({ where: { open: false } })
|
.count({ where: { open: false } })
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
|
fetchTicketQueueOpenCounts(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
||||||
@@ -165,6 +167,10 @@ export default async function ModHelpTicketsPage({
|
|||||||
hint={t("queueHint")}
|
hint={t("queueHint")}
|
||||||
otherHref="/mod/tickets"
|
otherHref="/mod/tickets"
|
||||||
otherLabel={t("queueOtherCta")}
|
otherLabel={t("queueOtherCta")}
|
||||||
|
countsLine={t("queueCounts", {
|
||||||
|
here: queues.helpOpen,
|
||||||
|
other: queues.cmsOpen,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
||||||
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { StatusCard } from "@/components/admin/dashboard";
|
|||||||
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
||||||
import type { Prisma } from "@/generated/prisma/client";
|
import type { Prisma } from "@/generated/prisma/client";
|
||||||
import { requireModPermission } from "@/lib/admin/guard";
|
import { requireModPermission } from "@/lib/admin/guard";
|
||||||
|
import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts";
|
||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
import { formatDate } from "@/lib/format-date";
|
import { formatDate } from "@/lib/format-date";
|
||||||
import { PERMS } from "@/lib/permissions";
|
import { PERMS } from "@/lib/permissions";
|
||||||
@@ -71,7 +72,7 @@ export default async function ModTicketsPage({
|
|||||||
[orderField]: parsed.order,
|
[orderField]: parsed.order,
|
||||||
} as Prisma.WebsiteTicketOrderByWithRelationInput;
|
} as Prisma.WebsiteTicketOrderByWithRelationInput;
|
||||||
|
|
||||||
const [total, openCount, inProgressCount, waitingCount, closedCount] =
|
const [total, openCount, inProgressCount, waitingCount, closedCount, queues] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
prisma.websiteTicket.count({ where }).catch(() => 0),
|
prisma.websiteTicket.count({ where }).catch(() => 0),
|
||||||
prisma.websiteTicket.count({ where: { status: "open" } }).catch(() => 0),
|
prisma.websiteTicket.count({ where: { status: "open" } }).catch(() => 0),
|
||||||
@@ -84,6 +85,7 @@ export default async function ModTicketsPage({
|
|||||||
prisma.websiteTicket
|
prisma.websiteTicket
|
||||||
.count({ where: { status: "closed" } })
|
.count({ where: { status: "closed" } })
|
||||||
.catch(() => 0),
|
.catch(() => 0),
|
||||||
|
fetchTicketQueueOpenCounts(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
const pagination = calcPagination(total, parsed.page, parsed.perPage);
|
||||||
@@ -121,6 +123,10 @@ export default async function ModTicketsPage({
|
|||||||
hint={t("queueHint")}
|
hint={t("queueHint")}
|
||||||
otherHref="/mod/help-tickets"
|
otherHref="/mod/help-tickets"
|
||||||
otherLabel={t("queueOtherCta")}
|
otherLabel={t("queueOtherCta")}
|
||||||
|
countsLine={t("queueCounts", {
|
||||||
|
here: queues.cmsOpen,
|
||||||
|
other: queues.helpOpen,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
|
||||||
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
<StatusCard label={t("statusOpen")} value={openCount} icon="🎫" />
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ export function TicketQueueBanner({
|
|||||||
hint,
|
hint,
|
||||||
otherHref,
|
otherHref,
|
||||||
otherLabel,
|
otherLabel,
|
||||||
|
countsLine,
|
||||||
}: {
|
}: {
|
||||||
hint: string;
|
hint: string;
|
||||||
otherHref: string;
|
otherHref: string;
|
||||||
otherLabel: string;
|
otherLabel: string;
|
||||||
|
/** Optional preformatted open-count summary (locale-aware from caller). */
|
||||||
|
countsLine?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -16,6 +19,9 @@ export function TicketQueueBanner({
|
|||||||
role="status"
|
role="status"
|
||||||
>
|
>
|
||||||
<p className="m-0">{hint}</p>
|
<p className="m-0">{hint}</p>
|
||||||
|
{countsLine ? (
|
||||||
|
<p className="m-0 mt-1 text-muted-foreground">{countsLine}</p>
|
||||||
|
) : null}
|
||||||
<p className="m-0 mt-1">
|
<p className="m-0 mt-1">
|
||||||
<Link
|
<Link
|
||||||
href={otherHref}
|
href={otherHref}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
||||||
import { join, relative } from "node:path";
|
import { join } from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
const ROUTES: Array<[string, string]> = [
|
const ROUTES: Array<[string, string]> = [
|
||||||
@@ -135,22 +135,26 @@ describe("admin operations route contract", () => {
|
|||||||
expect(offenders).toEqual([]);
|
expect(offenders).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has no requireStaff left in admin pages", () => {
|
it("caches analytics full-scans via redisCache", () => {
|
||||||
const root = "src/app/admin";
|
for (const path of [
|
||||||
const offenders: string[] = [];
|
"src/app/admin/analytics/page.tsx",
|
||||||
function walk(dir: string) {
|
"src/app/admin/analytics/activity/page.tsx",
|
||||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
"src/app/admin/analytics/economy/page.tsx",
|
||||||
const full = join(dir, entry.name);
|
]) {
|
||||||
if (entry.isDirectory()) walk(full);
|
expect(readFileSync(path, "utf8"), path).toContain("redisCache");
|
||||||
else if (entry.name === "page.tsx") {
|
|
||||||
const source = readFileSync(full, "utf8");
|
|
||||||
if (/await requireStaff\(/.test(source)) {
|
|
||||||
offenders.push(relative(process.cwd(), full).replaceAll("\\", "/"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -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<OpsHealth> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 };
|
||||||
|
}
|
||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2433,7 +2433,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2697,7 +2698,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -1496,6 +1496,7 @@
|
|||||||
"subtitle": "Internal CMS tickets (assignee, priority, staff workflow). Not the same as help-center tickets from /help.",
|
"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.",
|
"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}",
|
||||||
"noTickets": "No active tickets",
|
"noTickets": "No active tickets",
|
||||||
"searchPlaceholder": "Search subject, user, category or ID…",
|
"searchPlaceholder": "Search subject, user, category or ID…",
|
||||||
"presetActive": "Active",
|
"presetActive": "Active",
|
||||||
@@ -1515,6 +1516,7 @@
|
|||||||
"subtitle": "Tickets players open at /help/tickets. Separate from the CMS support desk.",
|
"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.",
|
"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}",
|
||||||
"noTickets": "No open help center tickets",
|
"noTickets": "No open help center tickets",
|
||||||
"searchPlaceholder": "Search title, user or ID…",
|
"searchPlaceholder": "Search title, user or ID…",
|
||||||
"presetOpen": "Open",
|
"presetOpen": "Open",
|
||||||
@@ -2461,8 +2463,8 @@
|
|||||||
"events": "Events",
|
"events": "Events",
|
||||||
"eventTypes": "Event types",
|
"eventTypes": "Event types",
|
||||||
"polls": "Polls",
|
"polls": "Polls",
|
||||||
"tickets": "CMS tickets",
|
"tickets": "Support desk",
|
||||||
"helpTickets": "Help-center tickets",
|
"helpTickets": "Help center",
|
||||||
"templates": "Templates",
|
"templates": "Templates",
|
||||||
"directory": "Directory",
|
"directory": "Directory",
|
||||||
"multiAccounts": "Multi-accounts",
|
"multiAccounts": "Multi-accounts",
|
||||||
@@ -3008,7 +3010,7 @@
|
|||||||
"bans": "Bans",
|
"bans": "Bans",
|
||||||
"fullAdmin": "Full admin",
|
"fullAdmin": "Full admin",
|
||||||
"backToSite": "Back to site",
|
"backToSite": "Back to site",
|
||||||
"tickets": "CMS tickets",
|
"tickets": "Support desk",
|
||||||
"helpTickets": "Help-center tickets",
|
"helpTickets": "Help-center tickets",
|
||||||
"users": "Users",
|
"users": "Users",
|
||||||
"team": "Team"
|
"team": "Team"
|
||||||
|
|||||||
@@ -2433,7 +2433,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2697,7 +2698,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2433,7 +2433,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2697,7 +2698,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
+10
-8
@@ -1482,8 +1482,9 @@
|
|||||||
"colAssigned": "Assegnato",
|
"colAssigned": "Assegnato",
|
||||||
"colDate": "Data",
|
"colDate": "Data",
|
||||||
"meta": "di {user} · {count} msg · {category}",
|
"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.",
|
"queueHint": "Questa coda e il supporto CMS. I ticket del help center pubblici stanno nella tab Help center.",
|
||||||
"queueOtherCta": "Apri help-center tickets"
|
"queueOtherCta": "Apri help center",
|
||||||
|
"queueCounts": "Aperti qui: {here} · Help center: {other}"
|
||||||
},
|
},
|
||||||
"helpTickets": {
|
"helpTickets": {
|
||||||
"title": "Ticket help center",
|
"title": "Ticket help center",
|
||||||
@@ -1519,8 +1520,9 @@
|
|||||||
"closed": "Ticket chiuso.",
|
"closed": "Ticket chiuso.",
|
||||||
"reopened": "Ticket riaperto."
|
"reopened": "Ticket riaperto."
|
||||||
},
|
},
|
||||||
"queueHint": "Questa coda e il help center pubblico. I ticket del supporto CMS stanno nella tab Support desk.",
|
"queueHint": "Questa coda e il help center pubblico. I ticket del supporto CMS stanno nella tab Supporto CMS.",
|
||||||
"queueOtherCta": "Apri supporto CMS"
|
"queueOtherCta": "Apri supporto CMS",
|
||||||
|
"queueCounts": "Aperti qui: {here} · Supporto CMS: {other}"
|
||||||
},
|
},
|
||||||
"cfh": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2732,8 +2734,8 @@
|
|||||||
"events": "Eventi",
|
"events": "Eventi",
|
||||||
"eventTypes": "Tipi evento",
|
"eventTypes": "Tipi evento",
|
||||||
"polls": "Sondaggi",
|
"polls": "Sondaggi",
|
||||||
"tickets": "Ticket CMS",
|
"tickets": "Supporto CMS",
|
||||||
"helpTickets": "Ticket centro assistenza",
|
"helpTickets": "Help center",
|
||||||
"templates": "Template",
|
"templates": "Template",
|
||||||
"directory": "Directory",
|
"directory": "Directory",
|
||||||
"multiAccounts": "Multi-account",
|
"multiAccounts": "Multi-account",
|
||||||
@@ -3015,8 +3017,8 @@
|
|||||||
"bans": "Ban",
|
"bans": "Ban",
|
||||||
"fullAdmin": "Admin completo",
|
"fullAdmin": "Admin completo",
|
||||||
"backToSite": "Torna al sito",
|
"backToSite": "Torna al sito",
|
||||||
"tickets": "Ticket CMS",
|
"tickets": "Supporto CMS",
|
||||||
"helpTickets": "Ticket help center",
|
"helpTickets": "Help center",
|
||||||
"users": "Utenti",
|
"users": "Utenti",
|
||||||
"team": "Team"
|
"team": "Team"
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-8
@@ -2779,8 +2779,8 @@
|
|||||||
"events": "Evenementen",
|
"events": "Evenementen",
|
||||||
"eventTypes": "Evenementtypen",
|
"eventTypes": "Evenementtypen",
|
||||||
"polls": "Polls",
|
"polls": "Polls",
|
||||||
"tickets": "CMS-tickets",
|
"tickets": "Supportbalie",
|
||||||
"helpTickets": "Helpcentrum-tickets",
|
"helpTickets": "Helpcentrum",
|
||||||
"templates": "Sjablonen",
|
"templates": "Sjablonen",
|
||||||
"directory": "Adresboek",
|
"directory": "Adresboek",
|
||||||
"multiAccounts": "Multi-accounts",
|
"multiAccounts": "Multi-accounts",
|
||||||
@@ -2866,8 +2866,9 @@
|
|||||||
"colAssigned": "Toegewezen",
|
"colAssigned": "Toegewezen",
|
||||||
"colDate": "Datum",
|
"colDate": "Datum",
|
||||||
"meta": "door {user} · {count} berichten · {category}",
|
"meta": "door {user} · {count} berichten · {category}",
|
||||||
"queueHint": "Deze wachtrij is de CMS support desk. Speler-helpcenter tickets staan onder Help-center tickets.",
|
"queueHint": "Deze wachtrij is de CMS support desk. Speler-helpcenter tickets staan onder Helpcentrum.",
|
||||||
"queueOtherCta": "Open helpcenter-tickets"
|
"queueOtherCta": "Open helpcentrum-tickets",
|
||||||
|
"queueCounts": "Open hier: {here} · Helpcentrum: {other}"
|
||||||
},
|
},
|
||||||
"helpTickets": {
|
"helpTickets": {
|
||||||
"title": "Helpcenter-tickets",
|
"title": "Helpcenter-tickets",
|
||||||
@@ -2903,8 +2904,9 @@
|
|||||||
"closed": "Ticket gesloten.",
|
"closed": "Ticket gesloten.",
|
||||||
"reopened": "Ticket heropend."
|
"reopened": "Ticket heropend."
|
||||||
},
|
},
|
||||||
"queueHint": "Deze wachtrij is het publieke helpcenter. CMS support desk tickets staan onder Support desk.",
|
"queueHint": "Deze wachtrij is het publieke helpcenter. CMS support desk tickets staan onder Supportbalie.",
|
||||||
"queueOtherCta": "Open CMS support desk"
|
"queueOtherCta": "Open CMS support desk",
|
||||||
|
"queueCounts": "Open hier: {here} · Supportbalie: {other}"
|
||||||
},
|
},
|
||||||
"cfh": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -3013,8 +3015,8 @@
|
|||||||
"bans": "Bans",
|
"bans": "Bans",
|
||||||
"fullAdmin": "Volledige admin",
|
"fullAdmin": "Volledige admin",
|
||||||
"backToSite": "Terug naar site",
|
"backToSite": "Terug naar site",
|
||||||
"tickets": "CMS-tickets",
|
"tickets": "Supportbalie",
|
||||||
"helpTickets": "Helpcenter-tickets",
|
"helpTickets": "Helpcentrum",
|
||||||
"users": "Gebruikers",
|
"users": "Gebruikers",
|
||||||
"team": "Team"
|
"team": "Team"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2461,7 +2461,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2725,7 +2726,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
@@ -2463,7 +2463,8 @@
|
|||||||
"colDate": "Date",
|
"colDate": "Date",
|
||||||
"meta": "by {user} · {count} msg · {category}",
|
"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.",
|
"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": {
|
"cfh": {
|
||||||
"title": "Call for Help",
|
"title": "Call for Help",
|
||||||
@@ -2727,7 +2728,8 @@
|
|||||||
"reopened": "Ticket reopened."
|
"reopened": "Ticket reopened."
|
||||||
},
|
},
|
||||||
"queueHint": "This queue is the public help center. CMS support desk tickets are in the Support desk tab.",
|
"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": {
|
"marketplace": {
|
||||||
"title": "Marketplace",
|
"title": "Marketplace",
|
||||||
|
|||||||
Reference in new issue
Block a user