diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index ac3d5b27..1062e66b 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -6,6 +6,7 @@ import type { ReactNode } from "react"; import { AdminHubChrome } from "@/components/admin/admin-hub-chrome"; import { AdminMobileWrapper } from "@/components/admin/admin-mobile-wrapper"; import { AdminNavLink } from "@/components/admin/admin-nav-link"; +import { AdminTopbar } from "@/components/admin/admin-topbar"; import { LanguageSwitcher } from "@/components/language-switcher"; import { ThemeSwitcher } from "@/components/theme-switcher"; import { requireStaff } from "@/lib/admin/guard"; @@ -38,7 +39,7 @@ export default async function AdminLayout({ className="flex flex-col min-w-0 p-5 lg:p-6 min-h-screen" style={{ backgroundColor: "var(--admin-canvas)" }} > - +
{children}
@@ -128,36 +129,3 @@ async function Sidebar({ ); } -async function Topbar({ - staff, -}: { - staff: { id: number; username: string; rank: number }; -}) { - const t = await getTranslations("pages.admin.nav"); - - return ( -
-
-

- {t("housekeeping")} -

- - - {t("hotelManagementPanel")} - -
-
-
- - -
- - {staff.username} - - - {t("rankLabel", { rank: staff.rank })} - -
-
- ); -} diff --git a/src/app/admin/logs/page.tsx b/src/app/admin/logs/page.tsx index ad1a5bed..01f9b9d6 100644 --- a/src/app/admin/logs/page.tsx +++ b/src/app/admin/logs/page.tsx @@ -1,28 +1,28 @@ import { getTranslations } from "next-intl/server"; +import { redirect } from "next/navigation"; import { StatusCard } from "@/components/admin/dashboard"; import { buildStaffActivityWhere } from "@/lib/admin/log-filters"; -import { prisma } from "@/lib/prisma"; -import { redirect } from "next/navigation"; +import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { prisma } from "@/lib/prisma"; +import { StaffActivitiesTable } from "./staff-activities-table"; export const dynamic = "force-dynamic"; -const PER_PAGE = 25; - function fromDate(d: Date | null | undefined): string { return d ? d.toISOString().slice(0, 16).replace("T", " ") : "—"; } +const SORT_MAP: Record = { + when: "createdAt", + staff: "userId", + action: "action", +}; + export default async function AdminStaffActivities({ searchParams, }: { - searchParams: Promise<{ - q?: string; - page?: string; - staffId?: string; - action?: string; - authorization?: string; - }>; + searchParams: Promise>; }) { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.LOGS_VIEW, session.user.rank)) { @@ -30,20 +30,22 @@ export default async function AdminStaffActivities({ } const t = await getTranslations("pages.admin.logs"); - - const sp = await searchParams; - const q = (sp.q ?? "").trim(); - const page = Math.max(1, Number(sp.page ?? "1") || 1); - const staffId = sp.staffId ? Number(sp.staffId) : null; - const action = sp.action ?? null; - const authorizationOnly = sp.authorization === "1"; + const raw = await searchParams; + const parsed = parseListParams(new URLSearchParams(raw)); + const authorizationOnly = raw.filter_authorization === "1"; const whereClause = buildStaffActivityWhere({ - q, - staffId, - action, + q: parsed.search, authorizationOnly, }); + const sortField = SORT_MAP[parsed.sort ?? ""] ?? "createdAt"; + + const total = await prisma.staffActivities + .count({ where: whereClause }) + .catch(() => 0); + + const pagination = calcPagination(total, parsed.page, parsed.perPage); + const activities = await prisma.staffActivities .findMany({ where: whereClause, @@ -57,9 +59,9 @@ export default async function AdminStaffActivities({ ipAddress: true, createdAt: true, }, - orderBy: { createdAt: "desc" }, - skip: (page - 1) * PER_PAGE, - take: PER_PAGE, + orderBy: { [sortField]: parsed.order }, + skip: pagination.offset, + take: pagination.perPage, }) .catch(() => []); @@ -77,19 +79,29 @@ export default async function AdminStaffActivities({ nameById = new Map(users.map((x) => [x.id, x.username])); } - const total = await prisma.staffActivities - .count({ where: whereClause }) - .catch(() => 0); - - const pages = Math.max(1, Math.ceil(total / PER_PAGE)); - - const onlineCount = activities.filter( - (a) => a.createdAt && new Date(a.createdAt).getTime() > Date.now() - 60000, + const recentCount = activities.filter( + (a) => a.createdAt && new Date(a.createdAt).getTime() > Date.now() - 60_000, ).length; + const rows = activities.map((a) => ({ + id: Number(a.id), + when: fromDate(a.createdAt), + staff: t("staffWithId", { + id: Number(a.userId), + name: nameById.get(Number(a.userId)) ?? "unknown", + }), + action: a.action, + description: a.description, + target: + a.targetType && a.targetId + ? `${a.targetType === "user" ? t("userPrefix") : a.targetType} #${String(a.targetId)}` + : "—", + ip: a.ipAddress || "—", + })); + return ( -
-
+
+
0 ? "ok" : "neutral"} + value={recentCount} + state={recentCount > 0 ? "ok" : "neutral"} icon="🟢" />
-
- - - - - - - {(q || staffId || action || authorizationOnly) && ( - - {t("clearFilters")} - - )} -
- -
-

- {t("activities", { count: total })} -

- {activities.length === 0 ? ( -
{t("noResults")}
- ) : ( -
- - - - - - - - - - - - - {activities.map((a) => ( - - - - - - - - - ))} - -
{t("colWhen")}{t("colStaff")}{t("colAction")}{t("colDescription")}{t("colTarget")}{t("colIp")}
- {fromDate(a.createdAt)} - - {t("staffWithId", { - id: Number(a.userId), - name: nameById.get(Number(a.userId)) ?? "unknown", - })} - - - {a.action} - - {a.description} - {a.targetType && a.targetId ? ( - - {a.targetType === "user" ? t("userPrefix") : ""}# - {String(a.targetId)} - - ) : ( - - — - - )} - - {a.ipAddress || "—"} -
-
- )} -
- -

- {t("pagination", { page, pages, total })} - {page < pages ? ( - <> - {" · "} - - {t("next")} → - - - ) : null} -

-
+ + ); } diff --git a/src/app/admin/logs/staff-activities-table.tsx b/src/app/admin/logs/staff-activities-table.tsx new file mode 100644 index 00000000..cdf6635b --- /dev/null +++ b/src/app/admin/logs/staff-activities-table.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { DataTable } from "@/components/admin/data-table"; +import { Badge } from "@/components/ui/badge"; +import type { DataTableColumn, PaginatedResult } from "@/types"; + +export interface StaffActivityRow { + id: number; + when: string; + staff: string; + action: string; + description: string; + target: string; + ip: string; +} + +export function StaffActivitiesTable({ + data, +}: { + data: PaginatedResult; +}) { + const t = useTranslations("pages.admin.logs"); + + const columns: DataTableColumn[] = [ + { key: "when", label: t("colWhen"), sortable: true }, + { key: "staff", label: t("colStaff"), sortable: true }, + { + key: "action", + label: t("colAction"), + sortable: true, + render: (value) => ( + + {String(value)} + + ), + }, + { key: "description", label: t("colDescription") }, + { key: "target", label: t("colTarget") }, + { key: "ip", label: t("colIp") }, + ]; + + return ( + + ); +} diff --git a/src/app/admin/settings/cms-settings-form.tsx b/src/app/admin/settings/cms-settings-form.tsx index d256732c..6bf12b49 100644 --- a/src/app/admin/settings/cms-settings-form.tsx +++ b/src/app/admin/settings/cms-settings-form.tsx @@ -11,6 +11,7 @@ import { Shield, UserPlus, } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { saveManagedSettings } from "@/actions/admin-settings"; import { Button } from "@/components/ui/button"; @@ -45,6 +46,7 @@ export function CmsSettingsForm({ }) { const [values, setValues] = useState(initialValues); const { isPending, run } = useServerAction(); + const t = useTranslations("pages.admin.settings"); function handleChange(key: string, value: string) { setValues((prev) => ({ ...prev, [key]: value })); @@ -58,8 +60,8 @@ export function CmsSettingsForm({ normalized[key] = values[key] === "1" ? "1" : "0"; } run(() => saveManagedSettings({ settings: normalized }), { - successMessage: "Settings saved.", - errorMessage: "Could not save settings.", + successMessage: t("saved"), + errorMessage: t("saveFailed"), }); } @@ -83,13 +85,11 @@ export function CmsSettingsForm({ ) : ( )} - Save settings + {isPending ? t("form.saving") : t("saveSettings")} ) : ( -

- You can view settings but need edit permission to change them. -

+

{t("viewOnly")}

)} ); diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index d311746b..b52bb35f 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -1,3 +1,4 @@ +import { getTranslations } from "next-intl/server"; import { redirect } from "next/navigation"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; @@ -18,6 +19,7 @@ export default async function AdminSettings() { PERMS.SETTINGS_EDIT, session.user.rank, ); + const t = await getTranslations("pages.admin.settings"); let settings: Awaited> = []; try { @@ -42,12 +44,14 @@ export default async function AdminSettings() {
- {settings.length} keys in database + {t("keysInDatabase", { count: settings.length })} - {MANAGED_SETTING_KEYS.length} managed in form + {t("managedInForm", { count: MANAGED_SETTING_KEYS.length })} + + + {t("documented", { count: documented })} - {documented} documented
diff --git a/src/components/admin/admin-hub-chrome.tsx b/src/components/admin/admin-hub-chrome.tsx index 952a1a3f..893e369f 100644 --- a/src/components/admin/admin-hub-chrome.tsx +++ b/src/components/admin/admin-hub-chrome.tsx @@ -1,6 +1,7 @@ "use client"; import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; import type { ReactNode } from "react"; import { AdminPageShell } from "@/components/admin/admin-page-shell"; import { AdminSectionTabs } from "@/components/admin/admin-section-tabs"; @@ -13,15 +14,23 @@ import { findAdminHub } from "@/lib/admin-nav"; export function AdminHubChrome({ children }: { children: ReactNode }) { const pathname = usePathname() ?? ""; const hub = findAdminHub(pathname); + const t = useTranslations("pages.admin.hubs"); if (!hub) return <>{children}; + const tabs = hub.tabs.map((tab) => ({ + href: tab.href, + label: t(`tabs.${tab.labelKey}`), + match: tab.match, + group: tab.group, + })); + return ( } + title={t(hub.titleKey)} + subtitle={t(hub.subtitleKey)} + tabs={} className="admin-hub-panel" > {children} diff --git a/src/components/admin/admin-section-tabs.tsx b/src/components/admin/admin-section-tabs.tsx index 5c9c5de4..ec923b45 100644 --- a/src/components/admin/admin-section-tabs.tsx +++ b/src/components/admin/admin-section-tabs.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; import { cn } from "@/lib/utils"; export interface AdminTab { @@ -13,11 +14,6 @@ export interface AdminTab { group?: string; } -const GROUP_LABELS: Record = { - primary: "", - tools: "Tools", -}; - function matchLength(pathname: string, prefix: string): number { if (pathname === prefix) return prefix.length + 1; // prefer exact if (pathname.startsWith(`${prefix}/`)) return prefix.length; @@ -71,6 +67,7 @@ function TabRow({ export function AdminSectionTabs({ tabs }: { tabs: AdminTab[] }) { const pathname = usePathname() ?? ""; + const t = useTranslations("pages.admin.hubs"); let bestIndex = -1; let bestScore = -1; @@ -87,9 +84,7 @@ export function AdminSectionTabs({ tabs }: { tabs: AdminTab[] }) { const hasGroups = tabs.some((t) => t.group); if (!hasGroups) { - return ( - - ); + return ; } const order: string[] = []; @@ -102,16 +97,18 @@ export function AdminSectionTabs({ tabs }: { tabs: AdminTab[] }) { return (
{order.map((groupId) => { - const groupTabs = tabs.filter((t) => (t.group ?? "primary") === groupId); + const groupTabs = tabs.filter((tab) => (tab.group ?? "primary") === groupId); const rowOffset = offset; offset += groupTabs.length; + const label = + groupId === "tools" ? t("tabs.toolsGroup") : undefined; return ( ); })} diff --git a/src/components/admin/admin-topbar.tsx b/src/components/admin/admin-topbar.tsx new file mode 100644 index 00000000..93c4b37c --- /dev/null +++ b/src/components/admin/admin-topbar.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { LanguageSwitcher } from "@/components/language-switcher"; +import { ThemeSwitcher } from "@/components/theme-switcher"; +import { findAdminHub } from "@/lib/admin-nav"; + +export function AdminTopbar({ + staff, +}: { + staff: { id: number; username: string; rank: number }; +}) { + const tNav = useTranslations("pages.admin.nav"); + const tHubs = useTranslations("pages.admin.hubs"); + const pathname = usePathname() ?? ""; + const hub = findAdminHub(pathname); + const title = hub ? tHubs(hub.titleKey) : tNav("housekeeping"); + + return ( +
+
+

+ {title} +

+ + + {tNav("hotelManagementPanel")} + +
+
+
+ + +
+ + {staff.username} + + + {tNav("rankLabel", { rank: staff.rank })} + +
+
+ ); +} diff --git a/src/lib/admin-nav.ts b/src/lib/admin-nav.ts index 88f905bb..5c0b21a5 100644 --- a/src/lib/admin-nav.ts +++ b/src/lib/admin-nav.ts @@ -19,7 +19,8 @@ import { export interface AdminHubTab { href: string; - label: string; + /** Key under pages.admin.hubs.tabs.* */ + labelKey: string; match?: string[]; /** Optional group id for multi-row tab strips (e.g. Radio primary/tools). */ group?: string; @@ -27,8 +28,9 @@ export interface AdminHubTab { export interface AdminHubDefinition { id: string; - title: string; - subtitle: string; + /** Key under pages.admin.hubs.* for title/subtitle */ + titleKey: string; + subtitleKey: string; icon: LucideIcon; /** Path prefixes belonging to this hub (for chrome + sidebar active). */ prefixes: string[]; @@ -52,8 +54,8 @@ export interface AdminNavGroup { export const ADMIN_HUBS: AdminHubDefinition[] = [ { id: "news-media", - title: "News & media", - subtitle: "Articles, photos, banners, ads and media library", + titleKey: "newsMedia", + subtitleKey: "newsMediaSubtitle", icon: Newspaper, prefixes: [ "/admin/articles", @@ -65,19 +67,19 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ tabs: [ { href: "/admin/articles", - label: "Articles", + labelKey: "articles", match: ["/admin/articles"], }, - { href: "/admin/photos", label: "Photos" }, - { href: "/admin/banners", label: "Banners" }, - { href: "/admin/ads", label: "Ads", match: ["/admin/ads"] }, - { href: "/admin/media", label: "Media" }, + { href: "/admin/photos", labelKey: "photos" }, + { href: "/admin/banners", labelKey: "banners" }, + { href: "/admin/ads", labelKey: "ads", match: ["/admin/ads"] }, + { href: "/admin/media", labelKey: "media" }, ], }, { id: "site-content", - title: "Site content", - subtitle: "Navigation, help center, boxes, tags and prefixes", + titleKey: "siteContent", + subtitleKey: "siteContentSubtitle", icon: Compass, prefixes: [ "/admin/navigation", @@ -87,39 +89,39 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ "/admin/prefixes", ], tabs: [ - { href: "/admin/navigation", label: "Navigator" }, + { href: "/admin/navigation", labelKey: "navigator" }, { href: "/admin/help-questions", - label: "Help", + labelKey: "help", match: ["/admin/help-questions"], }, - { href: "/admin/writeable-boxes", label: "Boxes" }, - { href: "/admin/tags", label: "Tags" }, - { href: "/admin/prefixes", label: "Prefixes" }, + { href: "/admin/writeable-boxes", labelKey: "boxes" }, + { href: "/admin/tags", labelKey: "tags" }, + { href: "/admin/prefixes", labelKey: "prefixes" }, ], }, { id: "engagement", - title: "Engagement", - subtitle: "Events, polls and support tickets", + titleKey: "engagement", + subtitleKey: "engagementSubtitle", icon: Calendar, prefixes: ["/admin/events", "/admin/polls", "/admin/tickets"], tabs: [ - { href: "/admin/events", label: "Events", match: ["/admin/events"] }, - { href: "/admin/events/types", label: "Event types" }, - { href: "/admin/polls", label: "Polls", match: ["/admin/polls"] }, + { href: "/admin/events", labelKey: "events", match: ["/admin/events"] }, + { href: "/admin/events/types", labelKey: "eventTypes" }, + { href: "/admin/polls", labelKey: "polls", match: ["/admin/polls"] }, { href: "/admin/tickets", - label: "Tickets", + labelKey: "tickets", match: ["/admin/tickets"], }, - { href: "/admin/tickets/templates", label: "Templates" }, + { href: "/admin/tickets/templates", labelKey: "templates" }, ], }, { id: "users", - title: "Users", - subtitle: "Directory, online players, applications and teams", + titleKey: "users", + subtitleKey: "usersSubtitle", icon: Users, prefixes: [ "/admin/users", @@ -130,19 +132,19 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ tabs: [ { href: "/admin/users", - label: "Directory", + labelKey: "directory", match: ["/admin/users"], }, - { href: "/admin/users/multi-accounts", label: "Multi-accounts" }, - { href: "/admin/online", label: "Online" }, - { href: "/admin/applications", label: "Applications" }, - { href: "/admin/teams", label: "Teams" }, + { href: "/admin/users/multi-accounts", labelKey: "multiAccounts" }, + { href: "/admin/online", labelKey: "online" }, + { href: "/admin/applications", labelKey: "applications" }, + { href: "/admin/teams", labelKey: "teams" }, ], }, { id: "access", - title: "Access & security", - subtitle: "Bans, IP, VPN, permissions and word filter", + titleKey: "access", + subtitleKey: "accessSubtitle", icon: Shield, prefixes: [ "/admin/bans", @@ -152,38 +154,38 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ "/admin/wordfilter", ], tabs: [ - { href: "/admin/bans", label: "Bans" }, - { href: "/admin/ip", label: "IP" }, - { href: "/admin/vpn", label: "VPN" }, + { href: "/admin/bans", labelKey: "bans" }, + { href: "/admin/ip", labelKey: "ip" }, + { href: "/admin/vpn", labelKey: "vpn" }, { href: "/admin/permissions", - label: "Permissions", + labelKey: "permissions", match: ["/admin/permissions"], }, - { href: "/admin/wordfilter", label: "Word filter" }, + { href: "/admin/wordfilter", labelKey: "wordFilter" }, ], }, { id: "moderation", - title: "Moderation", - subtitle: "Reports, actions and moderation team", + titleKey: "moderation", + subtitleKey: "moderationSubtitle", icon: Gavel, prefixes: ["/admin/moderation"], tabs: [ - { href: "/admin/moderation", label: "Overview" }, - { href: "/admin/moderation/actions", label: "Actions" }, + { href: "/admin/moderation", labelKey: "overview" }, + { href: "/admin/moderation/actions", labelKey: "actions" }, { href: "/admin/moderation/cfh", - label: "CFH", + labelKey: "cfh", match: ["/admin/moderation/cfh"], }, - { href: "/admin/moderation/team", label: "Team" }, + { href: "/admin/moderation/team", labelKey: "team" }, ], }, { id: "catalog", - title: "Catalog & rares", - subtitle: "Catalog, rares, badges, achievements and sounds", + titleKey: "catalog", + subtitleKey: "catalogSubtitle", icon: Store, prefixes: [ "/admin/catalog", @@ -193,17 +195,17 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ "/admin/sounds", ], tabs: [ - { href: "/admin/catalog", label: "Catalog", match: ["/admin/catalog"] }, - { href: "/admin/rare-values", label: "Rares" }, - { href: "/admin/badges", label: "Badges" }, - { href: "/admin/achievements", label: "Achievements" }, - { href: "/admin/sounds", label: "Sounds" }, + { href: "/admin/catalog", labelKey: "catalogTab", match: ["/admin/catalog"] }, + { href: "/admin/rare-values", labelKey: "rares" }, + { href: "/admin/badges", labelKey: "badges" }, + { href: "/admin/achievements", labelKey: "achievements" }, + { href: "/admin/sounds", labelKey: "sounds" }, ], }, { id: "shop", - title: "Shop & payments", - subtitle: "Shop, transactions, vouchers, subscriptions and calendar", + titleKey: "shop", + subtitleKey: "shopSubtitle", icon: ShoppingCart, prefixes: [ "/admin/shop", @@ -213,49 +215,49 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ "/admin/calendar", ], tabs: [ - { href: "/admin/shop", label: "Shop" }, - { href: "/admin/transactions", label: "Transactions" }, - { href: "/admin/vouchers", label: "Vouchers" }, - { href: "/admin/subscriptions", label: "Subscriptions" }, + { href: "/admin/shop", labelKey: "shopTab" }, + { href: "/admin/transactions", labelKey: "transactions" }, + { href: "/admin/vouchers", labelKey: "vouchers" }, + { href: "/admin/subscriptions", labelKey: "subscriptions" }, { href: "/admin/calendar", - label: "Calendar", + labelKey: "calendar", match: ["/admin/calendar"], }, ], }, { id: "radio", - title: "Radio", - subtitle: "Stream, DJ tools, embed and monitoring", + titleKey: "radio", + subtitleKey: "radioSubtitle", icon: Radio, prefixes: ["/admin/radio"], tabs: [ - { href: "/admin/radio", label: "Overview", group: "primary" }, - { href: "/admin/radio/history", label: "History", group: "primary" }, + { href: "/admin/radio", labelKey: "overview", group: "primary" }, + { href: "/admin/radio/history", labelKey: "history", group: "primary" }, { href: "/admin/radio/moderation", - label: "Moderation", + labelKey: "moderationTab", group: "primary", }, { href: "/admin/radio/monitoring", - label: "Monitoring", + labelKey: "monitoring", group: "primary", }, - { href: "/admin/radio/settings", label: "Settings", group: "primary" }, - { href: "/admin/radio/banners", label: "Banners", group: "tools" }, - { href: "/admin/radio/embed", label: "Embed", group: "tools" }, - { href: "/admin/radio/api-keys", label: "API keys", group: "tools" }, - { href: "/admin/radio/points", label: "Points", group: "tools" }, - { href: "/admin/radio/ranks", label: "Ranks", group: "tools" }, - { href: "/admin/radio/autodj", label: "Auto DJ", group: "tools" }, + { href: "/admin/radio/settings", labelKey: "settingsTab", group: "primary" }, + { href: "/admin/radio/banners", labelKey: "banners", group: "tools" }, + { href: "/admin/radio/embed", labelKey: "embed", group: "tools" }, + { href: "/admin/radio/api-keys", labelKey: "apiKeys", group: "tools" }, + { href: "/admin/radio/points", labelKey: "points", group: "tools" }, + { href: "/admin/radio/ranks", labelKey: "ranks", group: "tools" }, + { href: "/admin/radio/autodj", labelKey: "autoDj", group: "tools" }, ], }, { id: "settings", - title: "Settings", - subtitle: "CMS config, theme, maintenance and related tools", + titleKey: "settings", + subtitleKey: "settingsSubtitle", icon: Cog, prefixes: [ "/admin/settings", @@ -267,19 +269,19 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ "/admin/housekeeping", ], tabs: [ - { href: "/admin/settings", label: "CMS" }, - { href: "/admin/theme", label: "Theme" }, - { href: "/admin/maintenance", label: "Maintenance" }, - { href: "/admin/favicon", label: "Favicon" }, - { href: "/admin/emulator", label: "Emulator" }, - { href: "/admin/email-templates", label: "Email" }, - { href: "/admin/housekeeping", label: "Housekeeping" }, + { href: "/admin/settings", labelKey: "cms" }, + { href: "/admin/theme", labelKey: "theme" }, + { href: "/admin/maintenance", labelKey: "maintenance" }, + { href: "/admin/favicon", labelKey: "favicon" }, + { href: "/admin/emulator", labelKey: "emulator" }, + { href: "/admin/email-templates", labelKey: "email" }, + { href: "/admin/housekeeping", labelKey: "housekeeping" }, ], }, { id: "tools", - title: "Tools", - subtitle: "RCON, rooms, import and translations", + titleKey: "tools", + subtitleKey: "toolsSubtitle", icon: Wrench, prefixes: [ "/admin/commandocentrum", @@ -288,20 +290,20 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ "/admin/translations", ], tabs: [ - { href: "/admin/commandocentrum", label: "Commando" }, - { href: "/admin/rooms", label: "Rooms", match: ["/admin/rooms"] }, - { href: "/admin/import", label: "Import", match: ["/admin/import"] }, + { href: "/admin/commandocentrum", labelKey: "commando" }, + { href: "/admin/rooms", labelKey: "rooms", match: ["/admin/rooms"] }, + { href: "/admin/import", labelKey: "import", match: ["/admin/import"] }, { href: "/admin/translations", - label: "Translations", + labelKey: "translations", match: ["/admin/translations"], }, ], }, { id: "observability", - title: "Observability", - subtitle: "Logs, analytics, DevOps and hotel alerts", + titleKey: "observability", + subtitleKey: "observabilitySubtitle", icon: Monitor, prefixes: [ "/admin/logs", @@ -310,21 +312,21 @@ export const ADMIN_HUBS: AdminHubDefinition[] = [ "/admin/alerts", ], tabs: [ - { href: "/admin/logs", label: "Logs" }, - { href: "/admin/logs/audit", label: "Audit" }, - { href: "/admin/logs/chat", label: "Chat" }, - { href: "/admin/logs/commands", label: "Commands" }, - { href: "/admin/logs/trades", label: "Trades" }, + { href: "/admin/logs", labelKey: "logs" }, + { href: "/admin/logs/audit", labelKey: "audit" }, + { href: "/admin/logs/chat", labelKey: "chat" }, + { href: "/admin/logs/commands", labelKey: "commands" }, + { href: "/admin/logs/trades", labelKey: "trades" }, { href: "/admin/analytics", - label: "Analytics", + labelKey: "analytics", match: ["/admin/analytics"], }, - { href: "/admin/analytics/activity", label: "Activity" }, - { href: "/admin/analytics/economy", label: "Economy" }, - { href: "/admin/devops", label: "DevOps", match: ["/admin/devops"] }, - { href: "/admin/devops/errors", label: "Errors" }, - { href: "/admin/alerts", label: "Alerts" }, + { href: "/admin/analytics/activity", labelKey: "activity" }, + { href: "/admin/analytics/economy", labelKey: "economy" }, + { href: "/admin/devops", labelKey: "devops", match: ["/admin/devops"] }, + { href: "/admin/devops/errors", labelKey: "errors" }, + { href: "/admin/alerts", labelKey: "alerts" }, ], }, ]; diff --git a/src/messages/en.json b/src/messages/en.json index fc7c26d6..fd400b6c 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -1811,16 +1811,134 @@ "title": "Staff Activities", "subtitle": "Audit trail of staff actions in the admin panel", "noLogs": "No staff activity logged yet", + "noResults": "No matching activities", "colId": "ID", "colUser": "User", + "colStaff": "Staff", "colAction": "Action", "colTarget": "Target", "colDescription": "Description", "colIp": "IP", + "colWhen": "When", "colTimestamp": "Timestamp", "filterByUser": "Filter by user…", "filterByAction": "Filter by action…", - "clearFilters": "Clear filters" + "clearFilters": "Clear filters", + "searchPlaceholder": "Search action, description or IP…", + "totalActivities": "Total activities", + "onThisPage": "On this page", + "recent": "Last minute", + "authorizationErrors": "Authorization errors", + "authorizationOnly": "Auth errors only", + "allActivities": "All activities", + "staffId": "Staff ID", + "actionType": "Action type", + "filter": "Filter", + "activities": "{count} activities", + "staffWithId": "#{id} {name}", + "userPrefix": "user", + "pagination": "Page {page} of {pages} · {total} total", + "next": "Next", + "prev": "Previous" + }, + "hubs": { + "newsMedia": "News & media", + "newsMediaSubtitle": "Articles, photos, banners, ads and media library", + "siteContent": "Site content", + "siteContentSubtitle": "Navigation, help center, boxes, tags and prefixes", + "engagement": "Engagement", + "engagementSubtitle": "Events, polls and support tickets", + "users": "Users", + "usersSubtitle": "Directory, online players, applications and teams", + "access": "Access & security", + "accessSubtitle": "Bans, IP, VPN, permissions and word filter", + "moderation": "Moderation", + "moderationSubtitle": "Reports, actions and moderation team", + "catalog": "Catalog & rares", + "catalogSubtitle": "Catalog, rares, badges, achievements and sounds", + "shop": "Shop & payments", + "shopSubtitle": "Shop, transactions, vouchers, subscriptions and calendar", + "radio": "Radio", + "radioSubtitle": "Stream, DJ tools, embed and monitoring", + "settings": "Settings", + "settingsSubtitle": "CMS config, theme, maintenance and related tools", + "tools": "Tools", + "toolsSubtitle": "RCON, rooms, import and translations", + "observability": "Observability", + "observabilitySubtitle": "Logs, analytics, DevOps and hotel alerts", + "tabs": { + "articles": "Articles", + "photos": "Photos", + "banners": "Banners", + "ads": "Ads", + "media": "Media", + "navigator": "Navigator", + "help": "Help", + "boxes": "Boxes", + "tags": "Tags", + "prefixes": "Prefixes", + "events": "Events", + "eventTypes": "Event types", + "polls": "Polls", + "tickets": "Tickets", + "templates": "Templates", + "directory": "Directory", + "multiAccounts": "Multi-accounts", + "online": "Online", + "applications": "Applications", + "teams": "Teams", + "bans": "Bans", + "ip": "IP", + "vpn": "VPN", + "permissions": "Permissions", + "wordFilter": "Word filter", + "overview": "Overview", + "actions": "Actions", + "cfh": "CFH", + "team": "Team", + "catalogTab": "Catalog", + "rares": "Rares", + "badges": "Badges", + "achievements": "Achievements", + "sounds": "Sounds", + "shopTab": "Shop", + "transactions": "Transactions", + "vouchers": "Vouchers", + "subscriptions": "Subscriptions", + "calendar": "Calendar", + "history": "History", + "moderationTab": "Moderation", + "monitoring": "Monitoring", + "settingsTab": "Settings", + "embed": "Embed", + "apiKeys": "API keys", + "points": "Points", + "ranks": "Ranks", + "autoDj": "Auto DJ", + "cms": "CMS", + "theme": "Theme", + "maintenance": "Maintenance", + "favicon": "Favicon", + "emulator": "Emulator", + "email": "Email", + "housekeeping": "Housekeeping", + "commando": "Commando", + "rooms": "Rooms", + "import": "Import", + "translations": "Translations", + "logs": "Logs", + "audit": "Audit", + "chat": "Chat", + "commands": "Commands", + "trades": "Trades", + "analytics": "Analytics", + "activity": "Activity", + "economy": "Economy", + "devops": "DevOps", + "errors": "Errors", + "alerts": "Alerts", + "toolsGroup": "Tools" + } }, "wordfilter": { "title": "Word Filter", @@ -2149,6 +2267,13 @@ "delete": "Delete", "noSettings": "No settings found", "confirmDelete": "Delete setting \"{key}\"?", + "keysInDatabase": "{count} keys in database", + "managedInForm": "{count} managed in form", + "documented": "{count} documented", + "saved": "Settings saved.", + "saveFailed": "Could not save settings.", + "saveSettings": "Save settings", + "viewOnly": "You can view settings but need edit permission to change them.", "colKey": "Key", "colValue": "Value", "colUpdated": "Updated", diff --git a/src/messages/it.json b/src/messages/it.json index 1df378a5..e5770272 100644 --- a/src/messages/it.json +++ b/src/messages/it.json @@ -2055,6 +2055,105 @@ "copyUrl": "Copia URL", "urlCopied": "URL copiato negli appunti" }, + "hubs": { + "newsMedia": "News & media", + "newsMediaSubtitle": "Articoli, foto, banner, ads e media library", + "siteContent": "Contenuti sito", + "siteContentSubtitle": "Navigazione, help, box, tag e prefissi", + "engagement": "Engagement", + "engagementSubtitle": "Eventi, sondaggi e ticket di supporto", + "users": "Utenti", + "usersSubtitle": "Directory, online, candidature e team", + "access": "Accesso e sicurezza", + "accessSubtitle": "Ban, IP, VPN, permessi e word filter", + "moderation": "Moderazione", + "moderationSubtitle": "Report, azioni e team moderazione", + "catalog": "Catalogo e rare", + "catalogSubtitle": "Catalogo, rare, badge, achievement e suoni", + "shop": "Shop e pagamenti", + "shopSubtitle": "Shop, transazioni, voucher, abbonamenti e calendario", + "radio": "Radio", + "radioSubtitle": "Stream, tool DJ, embed e monitoring", + "settings": "Impostazioni", + "settingsSubtitle": "CMS, tema, manutenzione e strumenti correlati", + "tools": "Strumenti", + "toolsSubtitle": "RCON, stanze, import e traduzioni", + "observability": "Osservabilità", + "observabilitySubtitle": "Log, analytics, DevOps e alert hotel", + "tabs": { + "articles": "Articoli", + "photos": "Foto", + "banners": "Banner", + "ads": "Ads", + "media": "Media", + "navigator": "Navigator", + "help": "Help", + "boxes": "Box", + "tags": "Tag", + "prefixes": "Prefissi", + "events": "Eventi", + "eventTypes": "Tipi evento", + "polls": "Sondaggi", + "tickets": "Ticket", + "templates": "Template", + "directory": "Directory", + "multiAccounts": "Multi-account", + "online": "Online", + "applications": "Candidature", + "teams": "Team", + "bans": "Ban", + "ip": "IP", + "vpn": "VPN", + "permissions": "Permessi", + "wordFilter": "Word filter", + "overview": "Panoramica", + "actions": "Azioni", + "cfh": "CFH", + "team": "Team", + "catalogTab": "Catalogo", + "rares": "Rare", + "badges": "Badge", + "achievements": "Achievement", + "sounds": "Suoni", + "shopTab": "Shop", + "transactions": "Transazioni", + "vouchers": "Voucher", + "subscriptions": "Abbonamenti", + "calendar": "Calendario", + "history": "Cronologia", + "moderationTab": "Moderazione", + "monitoring": "Monitoring", + "settingsTab": "Impostazioni", + "embed": "Embed", + "apiKeys": "API key", + "points": "Punti", + "ranks": "Rank", + "autoDj": "Auto DJ", + "cms": "CMS", + "theme": "Tema", + "maintenance": "Manutenzione", + "favicon": "Favicon", + "emulator": "Emulator", + "email": "Email", + "housekeeping": "Housekeeping", + "commando": "Commando", + "rooms": "Stanze", + "import": "Import", + "translations": "Traduzioni", + "logs": "Log", + "audit": "Audit", + "chat": "Chat", + "commands": "Comandi", + "trades": "Trade", + "analytics": "Analytics", + "activity": "Attività", + "economy": "Economia", + "devops": "DevOps", + "errors": "Errori", + "alerts": "Alert", + "toolsGroup": "Strumenti" + } + }, "settings": { "title": "Impostazioni CMS", "subtitle": "Gestisci configurazione chiave/valore del CMS", @@ -2063,6 +2162,13 @@ "delete": "Elimina", "noSettings": "Nessuna impostazione trovata", "confirmDelete": "Eliminare l'impostazione \"{key}\"?", + "keysInDatabase": "{count} chiavi nel database", + "managedInForm": "{count} gestite nel form", + "documented": "{count} documentate", + "saved": "Impostazioni salvate.", + "saveFailed": "Impossibile salvare le impostazioni.", + "saveSettings": "Salva impostazioni", + "viewOnly": "Puoi visualizzare le impostazioni ma serve il permesso di modifica.", "colKey": "Chiave", "colValue": "Valore", "colUpdated": "Aggiornato",