feat(telemetry): session journey view replaces raw events table

- sessions listed from client_events grouped by session id (7d window)
- timeline sheet merges page views, client events, errors, and server-fn
  activities joined by player within the session window
- vitals excluded from timelines; gap markers over 5 minutes
This commit is contained in:
2026-08-25 22:56:09 -07:00
parent 5d7747839c
commit e4d1ba720a
17 changed files with 605 additions and 400 deletions
+22 -22
View File
@@ -48,9 +48,9 @@ import { Route as AuthedTournamentsIdPredictionsRouteImport } from './routes/_au
import { Route as AuthedTournamentsIdGroupsRouteImport } from './routes/_authed/tournaments/$id.groups' import { Route as AuthedTournamentsIdGroupsRouteImport } from './routes/_authed/tournaments/$id.groups'
import { Route as AuthedTournamentsIdBracketRouteImport } from './routes/_authed/tournaments/$id.bracket' import { Route as AuthedTournamentsIdBracketRouteImport } from './routes/_authed/tournaments/$id.bracket'
import { Route as AuthedAdminTelemetryUsageRouteImport } from './routes/_authed/admin/telemetry/usage' import { Route as AuthedAdminTelemetryUsageRouteImport } from './routes/_authed/admin/telemetry/usage'
import { Route as AuthedAdminTelemetrySessionsRouteImport } from './routes/_authed/admin/telemetry/sessions'
import { Route as AuthedAdminTelemetryPlayersRouteImport } from './routes/_authed/admin/telemetry/players' import { Route as AuthedAdminTelemetryPlayersRouteImport } from './routes/_authed/admin/telemetry/players'
import { Route as AuthedAdminTelemetryFunctionsRouteImport } from './routes/_authed/admin/telemetry/functions' import { Route as AuthedAdminTelemetryFunctionsRouteImport } from './routes/_authed/admin/telemetry/functions'
import { Route as AuthedAdminTelemetryEventsRouteImport } from './routes/_authed/admin/telemetry/events'
import { Route as AuthedAdminTelemetryErrorsRouteImport } from './routes/_authed/admin/telemetry/errors' import { Route as AuthedAdminTelemetryErrorsRouteImport } from './routes/_authed/admin/telemetry/errors'
import { Route as AuthedAdminTournamentsIdIndexRouteImport } from './routes/_authed/admin/tournaments/$id/index' import { Route as AuthedAdminTournamentsIdIndexRouteImport } from './routes/_authed/admin/tournaments/$id/index'
import { Route as ApiFilesCollectionRecordIdFileRouteImport } from './routes/api/files/$collection/$recordId/$file' import { Route as ApiFilesCollectionRecordIdFileRouteImport } from './routes/api/files/$collection/$recordId/$file'
@@ -263,6 +263,12 @@ const AuthedAdminTelemetryUsageRoute =
path: '/usage', path: '/usage',
getParentRoute: () => AuthedAdminTelemetryRoute, getParentRoute: () => AuthedAdminTelemetryRoute,
} as any) } as any)
const AuthedAdminTelemetrySessionsRoute =
AuthedAdminTelemetrySessionsRouteImport.update({
id: '/sessions',
path: '/sessions',
getParentRoute: () => AuthedAdminTelemetryRoute,
} as any)
const AuthedAdminTelemetryPlayersRoute = const AuthedAdminTelemetryPlayersRoute =
AuthedAdminTelemetryPlayersRouteImport.update({ AuthedAdminTelemetryPlayersRouteImport.update({
id: '/players', id: '/players',
@@ -275,12 +281,6 @@ const AuthedAdminTelemetryFunctionsRoute =
path: '/functions', path: '/functions',
getParentRoute: () => AuthedAdminTelemetryRoute, getParentRoute: () => AuthedAdminTelemetryRoute,
} as any) } as any)
const AuthedAdminTelemetryEventsRoute =
AuthedAdminTelemetryEventsRouteImport.update({
id: '/events',
path: '/events',
getParentRoute: () => AuthedAdminTelemetryRoute,
} as any)
const AuthedAdminTelemetryErrorsRoute = const AuthedAdminTelemetryErrorsRoute =
AuthedAdminTelemetryErrorsRouteImport.update({ AuthedAdminTelemetryErrorsRouteImport.update({
id: '/errors', id: '/errors',
@@ -370,9 +370,9 @@ export interface FileRoutesByFullPath {
'/admin/': typeof AuthedAdminIndexRoute '/admin/': typeof AuthedAdminIndexRoute
'/tournaments/': typeof AuthedTournamentsIndexRoute '/tournaments/': typeof AuthedTournamentsIndexRoute
'/admin/telemetry/errors': typeof AuthedAdminTelemetryErrorsRoute '/admin/telemetry/errors': typeof AuthedAdminTelemetryErrorsRoute
'/admin/telemetry/events': typeof AuthedAdminTelemetryEventsRoute
'/admin/telemetry/functions': typeof AuthedAdminTelemetryFunctionsRoute '/admin/telemetry/functions': typeof AuthedAdminTelemetryFunctionsRoute
'/admin/telemetry/players': typeof AuthedAdminTelemetryPlayersRoute '/admin/telemetry/players': typeof AuthedAdminTelemetryPlayersRoute
'/admin/telemetry/sessions': typeof AuthedAdminTelemetrySessionsRoute
'/admin/telemetry/usage': typeof AuthedAdminTelemetryUsageRoute '/admin/telemetry/usage': typeof AuthedAdminTelemetryUsageRoute
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute '/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute '/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute
@@ -420,9 +420,9 @@ export interface FileRoutesByTo {
'/admin': typeof AuthedAdminIndexRoute '/admin': typeof AuthedAdminIndexRoute
'/tournaments': typeof AuthedTournamentsIndexRoute '/tournaments': typeof AuthedTournamentsIndexRoute
'/admin/telemetry/errors': typeof AuthedAdminTelemetryErrorsRoute '/admin/telemetry/errors': typeof AuthedAdminTelemetryErrorsRoute
'/admin/telemetry/events': typeof AuthedAdminTelemetryEventsRoute
'/admin/telemetry/functions': typeof AuthedAdminTelemetryFunctionsRoute '/admin/telemetry/functions': typeof AuthedAdminTelemetryFunctionsRoute
'/admin/telemetry/players': typeof AuthedAdminTelemetryPlayersRoute '/admin/telemetry/players': typeof AuthedAdminTelemetryPlayersRoute
'/admin/telemetry/sessions': typeof AuthedAdminTelemetrySessionsRoute
'/admin/telemetry/usage': typeof AuthedAdminTelemetryUsageRoute '/admin/telemetry/usage': typeof AuthedAdminTelemetryUsageRoute
'/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute '/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute '/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute
@@ -474,9 +474,9 @@ export interface FileRoutesById {
'/_authed/admin/': typeof AuthedAdminIndexRoute '/_authed/admin/': typeof AuthedAdminIndexRoute
'/_authed/tournaments/': typeof AuthedTournamentsIndexRoute '/_authed/tournaments/': typeof AuthedTournamentsIndexRoute
'/_authed/admin/telemetry/errors': typeof AuthedAdminTelemetryErrorsRoute '/_authed/admin/telemetry/errors': typeof AuthedAdminTelemetryErrorsRoute
'/_authed/admin/telemetry/events': typeof AuthedAdminTelemetryEventsRoute
'/_authed/admin/telemetry/functions': typeof AuthedAdminTelemetryFunctionsRoute '/_authed/admin/telemetry/functions': typeof AuthedAdminTelemetryFunctionsRoute
'/_authed/admin/telemetry/players': typeof AuthedAdminTelemetryPlayersRoute '/_authed/admin/telemetry/players': typeof AuthedAdminTelemetryPlayersRoute
'/_authed/admin/telemetry/sessions': typeof AuthedAdminTelemetrySessionsRoute
'/_authed/admin/telemetry/usage': typeof AuthedAdminTelemetryUsageRoute '/_authed/admin/telemetry/usage': typeof AuthedAdminTelemetryUsageRoute
'/_authed/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute '/_authed/tournaments/$id/bracket': typeof AuthedTournamentsIdBracketRoute
'/_authed/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute '/_authed/tournaments/$id/groups': typeof AuthedTournamentsIdGroupsRoute
@@ -528,9 +528,9 @@ export interface FileRouteTypes {
| '/admin/' | '/admin/'
| '/tournaments/' | '/tournaments/'
| '/admin/telemetry/errors' | '/admin/telemetry/errors'
| '/admin/telemetry/events'
| '/admin/telemetry/functions' | '/admin/telemetry/functions'
| '/admin/telemetry/players' | '/admin/telemetry/players'
| '/admin/telemetry/sessions'
| '/admin/telemetry/usage' | '/admin/telemetry/usage'
| '/tournaments/$id/bracket' | '/tournaments/$id/bracket'
| '/tournaments/$id/groups' | '/tournaments/$id/groups'
@@ -578,9 +578,9 @@ export interface FileRouteTypes {
| '/admin' | '/admin'
| '/tournaments' | '/tournaments'
| '/admin/telemetry/errors' | '/admin/telemetry/errors'
| '/admin/telemetry/events'
| '/admin/telemetry/functions' | '/admin/telemetry/functions'
| '/admin/telemetry/players' | '/admin/telemetry/players'
| '/admin/telemetry/sessions'
| '/admin/telemetry/usage' | '/admin/telemetry/usage'
| '/tournaments/$id/bracket' | '/tournaments/$id/bracket'
| '/tournaments/$id/groups' | '/tournaments/$id/groups'
@@ -631,9 +631,9 @@ export interface FileRouteTypes {
| '/_authed/admin/' | '/_authed/admin/'
| '/_authed/tournaments/' | '/_authed/tournaments/'
| '/_authed/admin/telemetry/errors' | '/_authed/admin/telemetry/errors'
| '/_authed/admin/telemetry/events'
| '/_authed/admin/telemetry/functions' | '/_authed/admin/telemetry/functions'
| '/_authed/admin/telemetry/players' | '/_authed/admin/telemetry/players'
| '/_authed/admin/telemetry/sessions'
| '/_authed/admin/telemetry/usage' | '/_authed/admin/telemetry/usage'
| '/_authed/tournaments/$id/bracket' | '/_authed/tournaments/$id/bracket'
| '/_authed/tournaments/$id/groups' | '/_authed/tournaments/$id/groups'
@@ -948,6 +948,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedAdminTelemetryUsageRouteImport preLoaderRoute: typeof AuthedAdminTelemetryUsageRouteImport
parentRoute: typeof AuthedAdminTelemetryRoute parentRoute: typeof AuthedAdminTelemetryRoute
} }
'/_authed/admin/telemetry/sessions': {
id: '/_authed/admin/telemetry/sessions'
path: '/sessions'
fullPath: '/admin/telemetry/sessions'
preLoaderRoute: typeof AuthedAdminTelemetrySessionsRouteImport
parentRoute: typeof AuthedAdminTelemetryRoute
}
'/_authed/admin/telemetry/players': { '/_authed/admin/telemetry/players': {
id: '/_authed/admin/telemetry/players' id: '/_authed/admin/telemetry/players'
path: '/players' path: '/players'
@@ -962,13 +969,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthedAdminTelemetryFunctionsRouteImport preLoaderRoute: typeof AuthedAdminTelemetryFunctionsRouteImport
parentRoute: typeof AuthedAdminTelemetryRoute parentRoute: typeof AuthedAdminTelemetryRoute
} }
'/_authed/admin/telemetry/events': {
id: '/_authed/admin/telemetry/events'
path: '/events'
fullPath: '/admin/telemetry/events'
preLoaderRoute: typeof AuthedAdminTelemetryEventsRouteImport
parentRoute: typeof AuthedAdminTelemetryRoute
}
'/_authed/admin/telemetry/errors': { '/_authed/admin/telemetry/errors': {
id: '/_authed/admin/telemetry/errors' id: '/_authed/admin/telemetry/errors'
path: '/errors' path: '/errors'
@@ -1037,18 +1037,18 @@ declare module '@tanstack/react-router' {
interface AuthedAdminTelemetryRouteChildren { interface AuthedAdminTelemetryRouteChildren {
AuthedAdminTelemetryErrorsRoute: typeof AuthedAdminTelemetryErrorsRoute AuthedAdminTelemetryErrorsRoute: typeof AuthedAdminTelemetryErrorsRoute
AuthedAdminTelemetryEventsRoute: typeof AuthedAdminTelemetryEventsRoute
AuthedAdminTelemetryFunctionsRoute: typeof AuthedAdminTelemetryFunctionsRoute AuthedAdminTelemetryFunctionsRoute: typeof AuthedAdminTelemetryFunctionsRoute
AuthedAdminTelemetryPlayersRoute: typeof AuthedAdminTelemetryPlayersRoute AuthedAdminTelemetryPlayersRoute: typeof AuthedAdminTelemetryPlayersRoute
AuthedAdminTelemetrySessionsRoute: typeof AuthedAdminTelemetrySessionsRoute
AuthedAdminTelemetryUsageRoute: typeof AuthedAdminTelemetryUsageRoute AuthedAdminTelemetryUsageRoute: typeof AuthedAdminTelemetryUsageRoute
AuthedAdminTelemetryIndexRoute: typeof AuthedAdminTelemetryIndexRoute AuthedAdminTelemetryIndexRoute: typeof AuthedAdminTelemetryIndexRoute
} }
const AuthedAdminTelemetryRouteChildren: AuthedAdminTelemetryRouteChildren = { const AuthedAdminTelemetryRouteChildren: AuthedAdminTelemetryRouteChildren = {
AuthedAdminTelemetryErrorsRoute: AuthedAdminTelemetryErrorsRoute, AuthedAdminTelemetryErrorsRoute: AuthedAdminTelemetryErrorsRoute,
AuthedAdminTelemetryEventsRoute: AuthedAdminTelemetryEventsRoute,
AuthedAdminTelemetryFunctionsRoute: AuthedAdminTelemetryFunctionsRoute, AuthedAdminTelemetryFunctionsRoute: AuthedAdminTelemetryFunctionsRoute,
AuthedAdminTelemetryPlayersRoute: AuthedAdminTelemetryPlayersRoute, AuthedAdminTelemetryPlayersRoute: AuthedAdminTelemetryPlayersRoute,
AuthedAdminTelemetrySessionsRoute: AuthedAdminTelemetrySessionsRoute,
AuthedAdminTelemetryUsageRoute: AuthedAdminTelemetryUsageRoute, AuthedAdminTelemetryUsageRoute: AuthedAdminTelemetryUsageRoute,
AuthedAdminTelemetryIndexRoute: AuthedAdminTelemetryIndexRoute, AuthedAdminTelemetryIndexRoute: AuthedAdminTelemetryIndexRoute,
} }
+3 -3
View File
@@ -7,7 +7,7 @@ const TAB_ROUTES = {
usage: "/admin/telemetry/usage", usage: "/admin/telemetry/usage",
players: "/admin/telemetry/players", players: "/admin/telemetry/players",
functions: "/admin/telemetry/functions", functions: "/admin/telemetry/functions",
events: "/admin/telemetry/events", sessions: "/admin/telemetry/sessions",
errors: "/admin/telemetry/errors", errors: "/admin/telemetry/errors",
} as const; } as const;
@@ -50,8 +50,8 @@ function TelemetryLayout() {
<Tabs.Tab value="functions"> <Tabs.Tab value="functions">
<Trans>Functions</Trans> <Trans>Functions</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="events"> <Tabs.Tab value="sessions">
<Trans>Events</Trans> <Trans>Sessions</Trans>
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab value="errors"> <Tabs.Tab value="errors">
<Trans>Errors</Trans> <Trans>Errors</Trans>
@@ -1,28 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
import { Suspense } from "react";
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
import { telemetryQueries, defaultClientEventParams, ClientEventsTable } from "@/features/telemetry";
import { playerQueries } from "@/features/players";
import { ListSkeleton } from "@/components/skeletons";
export const Route = createFileRoute("/_authed/admin/telemetry/events")({
component: EventsTab,
beforeLoad: ({ context }) => {
const queryClient = context.queryClient;
prefetchServerQuery(queryClient, telemetryQueries.events(defaultClientEventParams));
prefetchServerQuery(queryClient, playerQueries.list());
},
loader: () => ({
withPadding: false,
fullWidth: true,
refresh: [telemetryQueries.events(defaultClientEventParams).queryKey],
}),
});
function EventsTab() {
return (
<Suspense fallback={<ListSkeleton rows={8} withSearch withSubtitle withTrailing="text" />}>
<ClientEventsTable />
</Suspense>
);
}
@@ -0,0 +1,25 @@
import { createFileRoute } from "@tanstack/react-router";
import { Suspense } from "react";
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
import { telemetryQueries, SessionsList, defaultSessionParams } from "@/features/telemetry";
import { ListSkeleton } from "@/components/skeletons";
export const Route = createFileRoute("/_authed/admin/telemetry/sessions")({
component: SessionsTab,
beforeLoad: ({ context }) => {
prefetchServerQuery(context.queryClient, telemetryQueries.sessions(defaultSessionParams));
},
loader: () => ({
withPadding: false,
fullWidth: true,
refresh: [telemetryQueries.sessions(defaultSessionParams).queryKey],
}),
});
function SessionsTab() {
return (
<Suspense fallback={<ListSkeleton rows={8} withSubtitle withTrailing="text" />}>
<SessionsList />
</Suspense>
);
}
@@ -1,157 +0,0 @@
import { useState, useMemo } from "react";
import { Trans, Plural, useLingui } from "@lingui/react/macro";
import { Code, Container, Stack } from "@mantine/core";
import { usePlayers } from "@/features/players/queries";
import { useSheet } from "@/hooks/use-sheet";
import { useClientEvents } from "../queries";
import type { ClientEvent, ClientEventSearchParams } from "../types";
import {
DetailSheet,
PaginatedList,
RelativeTime,
SearchFilterBar,
TelemetryListItem,
} from "./shared";
export const defaultClientEventParams: ClientEventSearchParams = {
page: 1,
perPage: 50,
sortBy: "-created",
};
export const ClientEventsTable = () => {
const { t, i18n } = useLingui();
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [playerFilter, setPlayerFilter] = useState<string | null>(null);
const [selectedEvent, setSelectedEvent] = useState<ClientEvent | null>(null);
const { isOpen: detailsOpened, open: openDetails, close: closeDetails } = useSheet();
const { data: players } = usePlayers();
const searchParams: ClientEventSearchParams = useMemo(
() => ({
page,
perPage: 50,
name: search || undefined,
player: playerFilter || undefined,
sortBy: "-created",
}),
[page, search, playerFilter]
);
const { data: result } = useClientEvents(searchParams);
const playerName = (event: ClientEvent) =>
typeof event.player === "object" && event.player
? `${event.player.first_name} ${event.player.last_name}`
: t`Anonymous`;
const handleEventClick = (event: ClientEvent) => {
setSelectedEvent(event);
openDetails();
};
const handleCloseDetails = () => {
setSelectedEvent(null);
closeDetails();
};
return (
<Container size="100%" px={0}>
<Stack gap="xs">
<SearchFilterBar
searchValue={search}
onSearchChange={(value) => {
setSearch(value);
setPage(1);
}}
searchPlaceholder={t`Event name`}
filters={[
{
placeholder: t`Player`,
value: playerFilter,
onChange: (value) => {
setPlayerFilter(value);
setPage(1);
},
data: players.map((player) => ({
value: player.id,
label: `${player.first_name} ${player.last_name}`,
})),
searchable: true,
},
]}
summary={
<Plural value={result.totalItems} one="# total event" other="# total events" />
}
/>
<PaginatedList
items={result.items}
keyOf={(event) => event.id}
empty={t`No Events Found`}
page={page}
totalPages={result.totalPages}
onPageChange={setPage}
renderItem={(event) => (
<TelemetryListItem
title={event.name}
meta={[
playerName(event),
event.route_id || event.path || "",
<RelativeTime date={event.created} />,
]}
onClick={() => handleEventClick(event)}
/>
)}
/>
</Stack>
<DetailSheet
title={t`Event Details`}
opened={detailsOpened}
onClose={handleCloseDetails}
fields={
selectedEvent
? [
{ label: <Trans>Name</Trans>, value: selectedEvent.name },
{ label: <Trans>Player</Trans>, value: playerName(selectedEvent) },
...(selectedEvent.path
? [{ label: <Trans>Path</Trans>, value: selectedEvent.path }]
: []),
...(selectedEvent.route_id
? [{ label: <Trans>Route</Trans>, value: selectedEvent.route_id }]
: []),
...(selectedEvent.session_id
? [{ label: <Trans>Session</Trans>, value: selectedEvent.session_id }]
: []),
...(selectedEvent.value !== undefined && selectedEvent.value !== null
? [{ label: <Trans>Value</Trans>, value: selectedEvent.value }]
: []),
...(selectedEvent.props
? [
{
label: <Trans>Props</Trans>,
value: (
<Code block style={{ fontSize: "11px" }}>
{JSON.stringify(selectedEvent.props, null, 2)}
</Code>
),
},
]
: []),
...(selectedEvent.user_agent
? [{ label: <Trans>User Agent</Trans>, value: selectedEvent.user_agent }]
: []),
{
label: <Trans>Created</Trans>,
value: new Date(selectedEvent.created).toLocaleString(i18n.locale),
},
]
: []
}
/>
</Container>
);
};
@@ -0,0 +1,99 @@
import { Fragment } from "react";
import { Box, Group, Stack, Text } from "@mantine/core";
import { Plural, useLingui } from "@lingui/react/macro";
import { useSessionTimeline } from "../../queries";
import type { TimelineEntry } from "../../types";
const GAP_MARKER_MS = 5 * 60 * 1000;
const parseTs = (ts: string) => Date.parse(ts.replace(" ", "T"));
const dotColor = (entry: TimelineEntry): string => {
if (entry.kind === "error" || entry.success === false) return "var(--mantine-color-red-6)";
if (entry.kind === "server_fn") return "var(--mantine-color-green-6)";
return "var(--mantine-color-dimmed)";
};
const Dot = ({ color }: { color: string }) => (
<Box
w={6}
h={6}
style={{ borderRadius: "50%", backgroundColor: color, flexShrink: 0 }}
/>
);
export const SessionTimelineView = ({ sessionId }: { sessionId: string }) => {
const { i18n } = useLingui();
const { data } = useSessionTimeline(sessionId);
const clock = (ts: string) =>
i18n.date(new Date(parseTs(ts)), { hour: "2-digit", minute: "2-digit", second: "2-digit" });
return (
<Stack gap={0}>
<Group gap="md" pb="sm">
<Text size="xs" c="dimmed">
{i18n.date(new Date(parseTs(data.session.started)), {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</Text>
<Text size="xs" c="dimmed">
<Plural value={data.session.pageViews} one="# page" other="# pages" />
</Text>
{data.session.errors > 0 && (
<Text size="xs" c="red">
<Plural value={data.session.errors} one="# error" other="# errors" />
</Text>
)}
</Group>
{data.entries.map((entry, index) => {
const prev = data.entries[index - 1];
const gapMs = prev ? parseTs(entry.ts) - parseTs(prev.ts) : 0;
return (
<Fragment key={`${entry.ts}-${index}`}>
{gapMs > GAP_MARKER_MS && (
<Text size="xs" c="dimmed" py={4} pl={46}>
+{Math.round(gapMs / 60000)}m
</Text>
)}
<Group gap="xs" py={5} wrap="nowrap" align="center">
<Text
size="xs"
c="dimmed"
w={64}
style={{ flexShrink: 0, fontVariantNumeric: "tabular-nums" }}
>
{clock(entry.ts)}
</Text>
<Dot color={dotColor(entry)} />
<Text
size="sm"
lineClamp={1}
c={entry.kind === "error" ? "red" : undefined}
style={{ minWidth: 0, flex: 1 }}
>
{entry.label}
</Text>
{entry.kind === "server_fn" && entry.success === false && (
<Text size="xs" c="red" style={{ flexShrink: 0 }}>
✗
</Text>
)}
{entry.kind === "server_fn" && typeof entry.duration === "number" && entry.duration > 0 && (
<Text size="xs" c="dimmed" style={{ flexShrink: 0, fontVariantNumeric: "tabular-nums" }}>
{entry.duration}ms
</Text>
)}
</Group>
</Fragment>
);
})}
</Stack>
);
};
export default SessionTimelineView;
@@ -0,0 +1,81 @@
import { Suspense, useState } from "react";
import { Plural, Trans, useLingui } from "@lingui/react/macro";
import { Text } from "@mantine/core";
import Sheet from "@/components/sheet/sheet";
import { ListSkeleton } from "@/components/skeletons";
import { useSessions } from "../../queries";
import type { SessionSummary } from "../../types";
import { PaginatedList, RelativeTime, TelemetryListItem } from "../shared";
import SessionTimelineView from "./session-timeline";
export const defaultSessionParams = { days: 7, page: 1 } as const;
const playerName = (player?: SessionSummary["player"]): string | null => {
if (!player) return null;
return [player.first_name, player.last_name].filter(Boolean).join(" ") || null;
};
const durationLabel = (session: SessionSummary): string => {
const ms =
Date.parse(session.lastSeen.replace(" ", "T")) -
Date.parse(session.started.replace(" ", "T"));
const minutes = Math.round(ms / 60000);
return minutes < 1 ? "<1m" : `${minutes}m`;
};
export const SessionsList = () => {
const { t } = useLingui();
const [page, setPage] = useState(1);
const [selected, setSelected] = useState<SessionSummary | null>(null);
const { data } = useSessions({ days: 7, page });
return (
<>
<PaginatedList
items={data.items}
keyOf={(session) => session.session_id}
empty={t`No sessions yet`}
page={data.page}
totalPages={data.totalPages}
onPageChange={setPage}
renderItem={(session) => (
<TelemetryListItem
title={playerName(session.player) ?? t`Anonymous`}
meta={[
<RelativeTime key="time" date={session.lastSeen} />,
<Plural key="pages" value={session.pageViews} one="# page" other="# pages" />,
durationLabel(session),
]}
error={
session.errors > 0 ? (
<Plural value={session.errors} one="# error" other="# errors" />
) : undefined
}
onClick={() => setSelected(session)}
/>
)}
/>
<Sheet
title={selected ? (playerName(selected.player) ?? t`Anonymous`) : ""}
opened={selected !== null}
onChange={() => setSelected(null)}
>
{selected && (
<Suspense fallback={<ListSkeleton rows={8} />}>
<SessionTimelineView sessionId={selected.session_id} />
</Suspense>
)}
</Sheet>
{data.totalItems > 0 && (
<Text size="xs" c="dimmed" px="md" py="sm">
<Trans>Last 7 days</Trans>
</Text>
)}
</>
);
};
export default SessionsList;
+2 -1
View File
@@ -3,6 +3,7 @@ export * from "./queries";
export * from "./range"; export * from "./range";
export * from "./components/charts"; export * from "./components/charts";
export * from "./components/shared"; export * from "./components/shared";
export { ClientEventsTable, defaultClientEventParams } from "./components/client-events-table"; export { SessionsList, defaultSessionParams } from "./components/sessions/sessions-list";
export { SessionTimelineView } from "./components/sessions/session-timeline";
export { ErrorBrowser } from "./components/error-browser"; export { ErrorBrowser } from "./components/error-browser";
export { UsageScreen, USAGE_METRICS } from "./components/usage-screen"; export { UsageScreen, USAGE_METRICS } from "./components/usage-screen";
+20
View File
@@ -2,7 +2,9 @@ import { useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
import { import {
getClientErrorGroups, getClientErrorGroups,
getRollupRange, getRollupRange,
getSessionTimeline,
getTelemetryRuntimeStatus, getTelemetryRuntimeStatus,
listSessions,
listTelemetryAlerts, listTelemetryAlerts,
searchClientErrors, searchClientErrors,
searchClientEvents, searchClientEvents,
@@ -30,6 +32,10 @@ export const telemetryKeys = {
errorGroups: (params: ErrorGroupParams) => ["telemetry", "errorGroups", params] as const, errorGroups: (params: ErrorGroupParams) => ["telemetry", "errorGroups", params] as const,
rollups: (params: RollupRangeParams) => ["telemetry", "rollups", params] as const, rollups: (params: RollupRangeParams) => ["telemetry", "rollups", params] as const,
alerts: (page: number) => ["telemetry", "alerts", page] as const, alerts: (page: number) => ["telemetry", "alerts", page] as const,
sessions: (params: { days?: number; page?: number }) =>
["telemetry", "sessions", params] as const,
sessionTimeline: (sessionId: string) =>
["telemetry", "sessionTimeline", sessionId] as const,
runtimeStatus: ["telemetry", "runtimeStatus"] as const, runtimeStatus: ["telemetry", "runtimeStatus"] as const,
}; };
@@ -54,6 +60,14 @@ export const telemetryQueries = {
queryKey: telemetryKeys.alerts(page), queryKey: telemetryKeys.alerts(page),
queryFn: () => listTelemetryAlerts({ data: { page } }), queryFn: () => listTelemetryAlerts({ data: { page } }),
}), }),
sessions: (params: { days?: number; page?: number } = {}) => ({
queryKey: telemetryKeys.sessions(params),
queryFn: () => listSessions({ data: params }),
}),
sessionTimeline: (sessionId: string) => ({
queryKey: telemetryKeys.sessionTimeline(sessionId),
queryFn: () => getSessionTimeline({ data: { sessionId } }),
}),
runtimeStatus: () => ({ runtimeStatus: () => ({
queryKey: telemetryKeys.runtimeStatus, queryKey: telemetryKeys.runtimeStatus,
queryFn: () => getTelemetryRuntimeStatus(), queryFn: () => getTelemetryRuntimeStatus(),
@@ -72,5 +86,11 @@ export const useClientErrorGroups = (params: ErrorGroupParams) =>
export const useRollupRange = (params: RollupRangeParams) => export const useRollupRange = (params: RollupRangeParams) =>
useServerSuspenseQuery(telemetryQueries.rollups(params)); useServerSuspenseQuery(telemetryQueries.rollups(params));
export const useSessions = (params: { days?: number; page?: number } = {}) =>
useServerSuspenseQuery(telemetryQueries.sessions(params));
export const useSessionTimeline = (sessionId: string) =>
useServerSuspenseQuery(telemetryQueries.sessionTimeline(sessionId));
export const useTelemetryRuntimeStatus = () => export const useTelemetryRuntimeStatus = () =>
useServerSuspenseQuery(telemetryQueries.runtimeStatus()); useServerSuspenseQuery(telemetryQueries.runtimeStatus());
+211
View File
@@ -15,9 +15,12 @@ import type {
ClientErrorGroup, ClientErrorGroup,
ClientEvent, ClientEvent,
RollupRow, RollupRow,
SessionSummary,
SessionTimeline,
TelemetryAlert, TelemetryAlert,
TelemetryListResult, TelemetryListResult,
TelemetryRuntimeStatus, TelemetryRuntimeStatus,
TimelineEntry,
} from "./types"; } from "./types";
const clientEventSearchSchema = z.object({ const clientEventSearchSchema = z.object({
@@ -155,6 +158,214 @@ export const listTelemetryAlerts = createServerFn()
) )
); );
const pbTime = (date: Date): string => date.toISOString().replace("T", " ");
const playerInfoById = async (ids: Set<string>) => {
if (ids.size === 0) return new Map<string, { id: string; first_name?: string; last_name?: string }>();
const players = await pbAdmin.listPlayers();
return new Map(
players
.filter((player) => ids.has(player.id))
.map((player) => [
player.id,
{ id: player.id, first_name: player.first_name, last_name: player.last_name },
])
);
};
export const listSessions = createServerFn()
.validator(
z.object({
days: z.number().min(1).max(90).optional(),
page: z.number().optional(),
perPage: z.number().max(100).optional(),
})
)
.middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<TelemetryListResult<SessionSummary>>(async () => {
const { days = 7, page = 1, perPage = 30 } = data;
const from = pbTime(new Date(Date.now() - days * 86400000));
const sessions = new Map<
string,
{ playerId?: string; started: string; lastSeen: string; pageViews: number; events: number; errors: number; user_agent?: string }
>();
await pbAdmin.pageCollection<{
session_id?: string;
player?: string;
name: string;
user_agent?: string;
created: string;
}>(
"client_events",
{ filter: `created >= '${from}'`, fields: "session_id,player,name,user_agent,created" },
(rows) => {
for (const row of rows) {
if (!row.session_id) continue;
const session = sessions.get(row.session_id) ?? {
playerId: row.player,
started: row.created,
lastSeen: row.created,
pageViews: 0,
events: 0,
errors: 0,
user_agent: row.user_agent,
};
session.playerId ||= row.player;
if (row.created < session.started) session.started = row.created;
if (row.created > session.lastSeen) session.lastSeen = row.created;
if (row.name === "page_view") session.pageViews++;
else if (!row.name.startsWith("vital.")) session.events++;
sessions.set(row.session_id, session);
}
}
);
await pbAdmin.pageCollection<{ session_id?: string; created: string }>(
"client_errors",
{ filter: `created >= '${from}'`, fields: "session_id,created" },
(rows) => {
for (const row of rows) {
if (!row.session_id) continue;
const session = sessions.get(row.session_id);
if (session) session.errors++;
}
}
);
const playerIds = new Set(
[...sessions.values()].map((s) => s.playerId).filter((id): id is string => !!id)
);
const players = await playerInfoById(playerIds);
const all: SessionSummary[] = [...sessions.entries()]
.map(([session_id, s]) => ({
session_id,
player: s.playerId ? players.get(s.playerId) : undefined,
started: s.started,
lastSeen: s.lastSeen,
pageViews: s.pageViews,
events: s.events,
errors: s.errors,
user_agent: s.user_agent,
}))
.sort((a, b) => (a.lastSeen < b.lastSeen ? 1 : -1));
const start = (page - 1) * perPage;
return {
items: all.slice(start, start + perPage),
page,
perPage,
totalPages: Math.max(1, Math.ceil(all.length / perPage)),
totalItems: all.length,
};
})
);
const SESSION_WINDOW_PAD_MS = 30_000;
const MAX_TIMELINE_ENTRIES = 500;
export const getSessionTimeline = createServerFn()
.validator(z.object({ sessionId: z.string().min(8).max(64) }))
.middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ data }) =>
toServerResult<SessionTimeline>(async () => {
const events = await pbAdmin.searchClientEvents({
sessionId: data.sessionId,
perPage: 200,
sortBy: "created",
});
const errors = await pbAdmin.searchClientErrors({
sessionId: data.sessionId,
perPage: 200,
sortBy: "created",
});
const sessionErrors = errors.items;
if (events.items.length === 0 && sessionErrors.length === 0) {
throw new Error("Session not found");
}
const timestamps = [
...events.items.map((e) => e.created),
...sessionErrors.map((e) => e.created),
].sort();
const started = timestamps[0];
const lastSeen = timestamps[timestamps.length - 1];
const playerId = events.items.find((e) => typeof e.player === "string" && e.player)
?.player as string | undefined;
const entries: TimelineEntry[] = [];
for (const event of events.items) {
if (event.name.startsWith("vital.")) continue;
entries.push({
ts: event.created,
kind: event.name === "page_view" ? "page_view" : "event",
label: event.name === "page_view" ? event.path || event.route_id || "?" : event.name,
detail: event.name === "page_view" ? undefined : event.path,
});
}
for (const error of sessionErrors) {
entries.push({
ts: error.created,
kind: "error",
label: error.message,
detail: error.path,
success: false,
});
}
// Server-fn activities have no session id; join by player inside the
// session's padded time window.
if (playerId) {
const windowFrom = pbTime(new Date(Date.parse(started.replace(" ", "T")) - SESSION_WINDOW_PAD_MS));
const windowTo = pbTime(new Date(Date.parse(lastSeen.replace(" ", "T")) + SESSION_WINDOW_PAD_MS));
const activities = await pbAdmin.searchActivities({
player: playerId,
from: windowFrom,
to: windowTo,
perPage: 200,
sortBy: "created",
});
for (const activity of activities.items) {
entries.push({
ts: activity.created,
kind: "server_fn",
label: activity.name,
detail: activity.error,
success: activity.success,
duration: activity.duration,
});
}
}
entries.sort((a, b) => (a.ts < b.ts ? -1 : 1));
const players = await playerInfoById(new Set(playerId ? [playerId] : []));
return {
session: {
session_id: data.sessionId,
player: playerId ? players.get(playerId) : undefined,
started,
lastSeen,
pageViews: events.items.filter((e) => e.name === "page_view").length,
events: events.items.filter(
(e) => e.name !== "page_view" && !e.name.startsWith("vital.")
).length,
errors: sessionErrors.length,
user_agent: events.items[0]?.user_agent,
},
entries: entries.slice(0, MAX_TIMELINE_ENTRIES),
};
})
);
export const getTelemetryRuntimeStatus = createServerFn() export const getTelemetryRuntimeStatus = createServerFn()
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensAdminFunctionMiddleware])
.handler(async () => .handler(async () =>
+27
View File
@@ -22,6 +22,33 @@ export interface ClientErrorGroup {
}; };
} }
export interface SessionSummary {
session_id: string;
player?: { id: string; first_name?: string; last_name?: string };
started: string;
lastSeen: string;
pageViews: number;
events: number;
errors: number;
user_agent?: string;
}
export type TimelineKind = "page_view" | "event" | "error" | "server_fn";
export interface TimelineEntry {
ts: string;
kind: TimelineKind;
label: string;
detail?: string;
success?: boolean;
duration?: number;
}
export interface SessionTimeline {
session: SessionSummary;
entries: TimelineEntry[];
}
export interface TelemetryRuntimeStatus { export interface TelemetryRuntimeStatus {
health: { health: {
status: "ok" | "degraded"; status: "ok" | "degraded";
+12
View File
@@ -40,6 +40,8 @@ export interface ActivitySearchParams {
name?: string; name?: string;
player?: string; player?: string;
success?: boolean; success?: boolean;
from?: string;
to?: string;
sortBy?: string; sortBy?: string;
} }
@@ -57,6 +59,8 @@ export function createActivitiesService(pb: PocketBase) {
name, name,
player, player,
success, success,
from,
to,
sortBy = "-created" sortBy = "-created"
} = params; } = params;
@@ -74,6 +78,14 @@ export function createActivitiesService(pb: PocketBase) {
filters.push(pbFilter(pb, "success = {:success}", { success })); filters.push(pbFilter(pb, "success = {:success}", { success }));
} }
if (from) {
filters.push(pbFilter(pb, "created >= {:from}", { from }));
}
if (to) {
filters.push(pbFilter(pb, "created < {:to}", { to }));
}
const filterString = filters.length > 0 ? filters.join(" && ") : ""; const filterString = filters.length > 0 ? filters.join(" && ") : "";
const result = await pb.collection("activities").getList<Activity>(page, perPage, { const result = await pb.collection("activities").getList<Activity>(page, perPage, {
+3 -1
View File
@@ -125,6 +125,7 @@ export interface ClientErrorSearchParams {
groupHash?: string; groupHash?: string;
resolved?: boolean; resolved?: boolean;
player?: string; player?: string;
sessionId?: string;
from?: string; from?: string;
to?: string; to?: string;
sortBy?: string; sortBy?: string;
@@ -175,12 +176,13 @@ export function createTelemetryService(pb: PocketBase) {
async searchClientErrors( async searchClientErrors(
params: ClientErrorSearchParams = {} params: ClientErrorSearchParams = {}
): Promise<TelemetryListResult<ClientErrorRecord>> { ): Promise<TelemetryListResult<ClientErrorRecord>> {
const { page = 1, perPage = 100, groupHash, resolved, player, from, to, sortBy = "-created" } = params; const { page = 1, perPage = 100, groupHash, resolved, player, sessionId, from, to, sortBy = "-created" } = params;
const filters: string[] = []; const filters: string[] = [];
if (groupHash) filters.push(pbFilter(pb, "group_hash = {:groupHash}", { groupHash })); if (groupHash) filters.push(pbFilter(pb, "group_hash = {:groupHash}", { groupHash }));
if (resolved !== undefined) filters.push(pbFilter(pb, "resolved = {:resolved}", { resolved })); if (resolved !== undefined) filters.push(pbFilter(pb, "resolved = {:resolved}", { resolved }));
if (player) filters.push(pbFilter(pb, "player = {:player}", { player })); if (player) filters.push(pbFilter(pb, "player = {:player}", { player }));
if (sessionId) filters.push(pbFilter(pb, "session_id = {:sessionId}", { sessionId }));
if (from) filters.push(pbFilter(pb, "created >= {:from}", { from })); if (from) filters.push(pbFilter(pb, "created >= {:from}", { from }));
if (to) filters.push(pbFilter(pb, "created < {:to}", { to })); if (to) filters.push(pbFilter(pb, "created < {:to}", { to }));
+25 -47
View File
@@ -35,6 +35,20 @@ msgstr "(Aktiv)"
msgid "{0, plural, one {# bracket in} other {# brackets in}}" msgid "{0, plural, one {# bracket in} other {# brackets in}}"
msgstr "{0, plural, one {# Bracket in} other {# Brackets in}}" msgstr "{0, plural, one {# Bracket in} other {# Brackets in}}"
#. placeholder {0}: data.session.errors
#. placeholder {0}: session.errors
#: src/features/telemetry/components/sessions/session-timeline.tsx:48
#: src/features/telemetry/components/sessions/sessions-list.tsx:52
msgid "{0, plural, one {# error} other {# errors}}"
msgstr "{0, plural, one {# Fehler} other {# Fehler}}"
#. placeholder {0}: data.session.pageViews
#. placeholder {0}: session.pageViews
#: src/features/telemetry/components/sessions/session-timeline.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:47
msgid "{0, plural, one {# page} other {# pages}}"
msgstr "{0, plural, one {# Seite} other {# Seiten}}"
#. placeholder {0}: enrolledPlayers.length #. placeholder {0}: enrolledPlayers.length
#. placeholder {0}: players.length #. placeholder {0}: players.length
#: src/features/players/components/players-activity-table.tsx:100 #: src/features/players/components/players-activity-table.tsx:100
@@ -57,11 +71,6 @@ msgstr "{0, plural, one {# Team} other {# Teams}}"
msgid "{0, plural, one {# total activity} other {# total activities}}" msgid "{0, plural, one {# total activity} other {# total activities}}"
msgstr "{0, plural, one {# Aktivität insgesamt} other {# Aktivitäten insgesamt}}" msgstr "{0, plural, one {# Aktivität insgesamt} other {# Aktivitäten insgesamt}}"
#. placeholder {0}: result.totalItems
#: src/features/telemetry/components/client-events-table.tsx:86
msgid "{0, plural, one {# total event} other {# total events}}"
msgstr "{0, plural, one {# Ereignis insgesamt} other {# Ereignisse insgesamt}}"
#. placeholder {0}: selectedConfig.total_group_matches #. placeholder {0}: selectedConfig.total_group_matches
#: src/features/tournaments/components/setup-group-stage.tsx:249 #: src/features/tournaments/components/setup-group-stage.tsx:249
msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}" msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}"
@@ -480,7 +489,8 @@ msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."
msgid "Announce matchup" msgid "Announce matchup"
msgstr "Matchup ansagen" msgstr "Matchup ansagen"
#: src/features/telemetry/components/client-events-table.tsx:48 #: src/features/telemetry/components/sessions/sessions-list.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:61
msgid "Anonymous" msgid "Anonymous"
msgstr "Anonym" msgstr "Anonym"
@@ -846,7 +856,6 @@ msgid "Create Tournament"
msgstr "Turnier erstellen" msgstr "Turnier erstellen"
#: src/features/activities/components/activities-table.tsx:198 #: src/features/activities/components/activities-table.tsx:198
#: src/features/telemetry/components/client-events-table.tsx:148
msgid "Created" msgid "Created"
msgstr "Erstellt" msgstr "Erstellt"
@@ -1101,18 +1110,6 @@ msgstr "Fehler: {errorMessage}"
msgid "Errors" msgid "Errors"
msgstr "Fehler" msgstr "Fehler"
#: src/features/telemetry/components/client-events-table.tsx:112
msgid "Event Details"
msgstr "Ereignisdetails"
#: src/features/telemetry/components/client-events-table.tsx:69
msgid "Event name"
msgstr "Ereignisname"
#: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Events"
msgstr "Ereignisse"
#: src/features/tournaments/components/group-stage-view.tsx:368 #: src/features/tournaments/components/group-stage-view.tsx:368
msgid "Expand standings" msgid "Expand standings"
msgstr "Tabelle erweitern" msgstr "Tabelle erweitern"
@@ -1429,6 +1426,10 @@ msgstr "N"
msgid "Language" msgid "Language"
msgstr "Sprache" msgstr "Sprache"
#: src/features/telemetry/components/sessions/sessions-list.tsx:74
msgid "Last 7 days"
msgstr "Letzte 7 Tage"
#: src/features/login/components/name-prompt.tsx:43 #: src/features/login/components/name-prompt.tsx:43
#: src/features/players/components/profile/name-form.tsx:68 #: src/features/players/components/profile/name-form.tsx:68
msgid "Last Name" msgid "Last Name"
@@ -1638,7 +1639,6 @@ msgid "MMR Calculation:"
msgstr "MMR-Berechnung:" msgstr "MMR-Berechnung:"
#: src/features/teams/components/team-form/index.tsx:200 #: src/features/teams/components/team-form/index.tsx:200
#: src/features/telemetry/components/client-events-table.tsx:118
#: src/features/tournaments/components/tournament-form.tsx:137 #: src/features/tournaments/components/tournament-form.tsx:137
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -1734,10 +1734,6 @@ msgstr "Keine Abzeichen verfügbar"
msgid "No devices found." msgid "No devices found."
msgstr "Keine Geräte gefunden." msgstr "Keine Geräte gefunden."
#: src/features/telemetry/components/client-events-table.tsx:93
msgid "No Events Found"
msgstr "Keine Ereignisse gefunden"
#: src/features/tournaments/components/group-stage-view.tsx:294 #: src/features/tournaments/components/group-stage-view.tsx:294
msgid "No groups have been created yet" msgid "No groups have been created yet"
msgstr "Es wurden noch keine Gruppen erstellt" msgstr "Es wurden noch keine Gruppen erstellt"
@@ -1776,6 +1772,10 @@ msgstr "Keine Tipps"
msgid "No results found" msgid "No results found"
msgstr "Keine Ergebnisse gefunden" msgstr "Keine Ergebnisse gefunden"
#: src/features/telemetry/components/sessions/sessions-list.tsx:38
msgid "No sessions yet"
msgstr "Noch keine Sitzungen"
#: src/app/routes/api/spotify/resume.ts:37 #: src/app/routes/api/spotify/resume.ts:37
msgid "No snapshot provided" msgid "No snapshot provided"
msgstr "Kein Snapshot angegeben" msgstr "Kein Snapshot angegeben"
@@ -1949,10 +1949,6 @@ msgstr "Partner-Zuweisungen"
msgid "Partners will be randomly assigned when enrollment closes" msgid "Partners will be randomly assigned when enrollment closes"
msgstr "Partner werden nach Anmeldeschluss zufällig zugewiesen" msgstr "Partner werden nach Anmeldeschluss zufällig zugewiesen"
#: src/features/telemetry/components/client-events-table.tsx:121
msgid "Path"
msgstr "Pfad"
#: src/features/spotify/components/spotify-controls-bar.tsx:116 #: src/features/spotify/components/spotify-controls-bar.tsx:116
msgid "Pause" msgid "Pause"
msgstr "Pause" msgstr "Pause"
@@ -1995,8 +1991,6 @@ msgstr "Wiedergabesteuerung fehlgeschlagen"
#: src/features/activities/components/activities-table.tsx:98 #: src/features/activities/components/activities-table.tsx:98
#: src/features/activities/components/activities-table.tsx:188 #: src/features/activities/components/activities-table.tsx:188
#: src/features/admin/components/award-badges.tsx:31 #: src/features/admin/components/award-badges.tsx:31
#: src/features/telemetry/components/client-events-table.tsx:72
#: src/features/telemetry/components/client-events-table.tsx:119
msgid "Player" msgid "Player"
msgstr "Spieler" msgstr "Spieler"
@@ -2076,10 +2070,6 @@ msgstr "Profil"
msgid "Progress " msgid "Progress "
msgstr "Fortschritt " msgstr "Fortschritt "
#: src/features/telemetry/components/client-events-table.tsx:135
msgid "Props"
msgstr "Props"
#: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106 #: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106
#: src/features/predictions/components/prediction-leaderboard.tsx:261 #: src/features/predictions/components/prediction-leaderboard.tsx:261
msgid "PTS" msgid "PTS"
@@ -2167,10 +2157,6 @@ msgstr "rofl"
msgid "Round {0}" msgid "Round {0}"
msgstr "Runde {0}" msgstr "Runde {0}"
#: src/features/telemetry/components/client-events-table.tsx:124
msgid "Route"
msgstr "Route"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123 #: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23 #: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules" msgid "Rules"
@@ -2351,10 +2337,7 @@ msgstr "Serverfunktionen"
msgid "serverFn name" msgid "serverFn name"
msgstr "serverFn-Name" msgstr "serverFn-Name"
#: src/features/telemetry/components/client-events-table.tsx:127 #: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Session"
msgstr "Sitzung"
#: src/features/telemetry/components/usage-screen.tsx:177 #: src/features/telemetry/components/usage-screen.tsx:177
msgid "Sessions" msgid "Sessions"
msgstr "Sitzungen" msgstr "Sitzungen"
@@ -2809,14 +2792,9 @@ msgid "Usage"
msgstr "Nutzung" msgstr "Nutzung"
#: src/features/activities/components/activities-table.tsx:202 #: src/features/activities/components/activities-table.tsx:202
#: src/features/telemetry/components/client-events-table.tsx:145
msgid "User Agent" msgid "User Agent"
msgstr "User-Agent" msgstr "User-Agent"
#: src/features/telemetry/components/client-events-table.tsx:130
msgid "Value"
msgstr "Wert"
#: src/features/bracket/components/match-card.tsx:201 #: src/features/bracket/components/match-card.tsx:201
#: src/features/bracket/components/match-card.tsx:226 #: src/features/bracket/components/match-card.tsx:226
msgid "versus" msgid "versus"
+25 -47
View File
@@ -35,6 +35,20 @@ msgstr "(Active)"
msgid "{0, plural, one {# bracket in} other {# brackets in}}" msgid "{0, plural, one {# bracket in} other {# brackets in}}"
msgstr "{0, plural, one {# bracket in} other {# brackets in}}" msgstr "{0, plural, one {# bracket in} other {# brackets in}}"
#. placeholder {0}: data.session.errors
#. placeholder {0}: session.errors
#: src/features/telemetry/components/sessions/session-timeline.tsx:48
#: src/features/telemetry/components/sessions/sessions-list.tsx:52
msgid "{0, plural, one {# error} other {# errors}}"
msgstr "{0, plural, one {# error} other {# errors}}"
#. placeholder {0}: data.session.pageViews
#. placeholder {0}: session.pageViews
#: src/features/telemetry/components/sessions/session-timeline.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:47
msgid "{0, plural, one {# page} other {# pages}}"
msgstr "{0, plural, one {# page} other {# pages}}"
#. placeholder {0}: enrolledPlayers.length #. placeholder {0}: enrolledPlayers.length
#. placeholder {0}: players.length #. placeholder {0}: players.length
#: src/features/players/components/players-activity-table.tsx:100 #: src/features/players/components/players-activity-table.tsx:100
@@ -57,11 +71,6 @@ msgstr "{0, plural, one {# team} other {# teams}}"
msgid "{0, plural, one {# total activity} other {# total activities}}" msgid "{0, plural, one {# total activity} other {# total activities}}"
msgstr "{0, plural, one {# total activity} other {# total activities}}" msgstr "{0, plural, one {# total activity} other {# total activities}}"
#. placeholder {0}: result.totalItems
#: src/features/telemetry/components/client-events-table.tsx:86
msgid "{0, plural, one {# total event} other {# total events}}"
msgstr "{0, plural, one {# total event} other {# total events}}"
#. placeholder {0}: selectedConfig.total_group_matches #. placeholder {0}: selectedConfig.total_group_matches
#: src/features/tournaments/components/setup-group-stage.tsx:249 #: src/features/tournaments/components/setup-group-stage.tsx:249
msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}" msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}"
@@ -480,7 +489,8 @@ msgstr "An unexpected error occurred. Please try again."
msgid "Announce matchup" msgid "Announce matchup"
msgstr "Announce matchup" msgstr "Announce matchup"
#: src/features/telemetry/components/client-events-table.tsx:48 #: src/features/telemetry/components/sessions/sessions-list.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:61
msgid "Anonymous" msgid "Anonymous"
msgstr "Anonymous" msgstr "Anonymous"
@@ -846,7 +856,6 @@ msgid "Create Tournament"
msgstr "Create Tournament" msgstr "Create Tournament"
#: src/features/activities/components/activities-table.tsx:198 #: src/features/activities/components/activities-table.tsx:198
#: src/features/telemetry/components/client-events-table.tsx:148
msgid "Created" msgid "Created"
msgstr "Created" msgstr "Created"
@@ -1101,18 +1110,6 @@ msgstr "Error: {errorMessage}"
msgid "Errors" msgid "Errors"
msgstr "Errors" msgstr "Errors"
#: src/features/telemetry/components/client-events-table.tsx:112
msgid "Event Details"
msgstr "Event Details"
#: src/features/telemetry/components/client-events-table.tsx:69
msgid "Event name"
msgstr "Event name"
#: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Events"
msgstr "Events"
#: src/features/tournaments/components/group-stage-view.tsx:368 #: src/features/tournaments/components/group-stage-view.tsx:368
msgid "Expand standings" msgid "Expand standings"
msgstr "Expand standings" msgstr "Expand standings"
@@ -1429,6 +1426,10 @@ msgstr "L"
msgid "Language" msgid "Language"
msgstr "Language" msgstr "Language"
#: src/features/telemetry/components/sessions/sessions-list.tsx:74
msgid "Last 7 days"
msgstr "Last 7 days"
#: src/features/login/components/name-prompt.tsx:43 #: src/features/login/components/name-prompt.tsx:43
#: src/features/players/components/profile/name-form.tsx:68 #: src/features/players/components/profile/name-form.tsx:68
msgid "Last Name" msgid "Last Name"
@@ -1638,7 +1639,6 @@ msgid "MMR Calculation:"
msgstr "MMR Calculation:" msgstr "MMR Calculation:"
#: src/features/teams/components/team-form/index.tsx:200 #: src/features/teams/components/team-form/index.tsx:200
#: src/features/telemetry/components/client-events-table.tsx:118
#: src/features/tournaments/components/tournament-form.tsx:137 #: src/features/tournaments/components/tournament-form.tsx:137
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -1734,10 +1734,6 @@ msgstr "No Badges Available"
msgid "No devices found." msgid "No devices found."
msgstr "No devices found." msgstr "No devices found."
#: src/features/telemetry/components/client-events-table.tsx:93
msgid "No Events Found"
msgstr "No Events Found"
#: src/features/tournaments/components/group-stage-view.tsx:294 #: src/features/tournaments/components/group-stage-view.tsx:294
msgid "No groups have been created yet" msgid "No groups have been created yet"
msgstr "No groups have been created yet" msgstr "No groups have been created yet"
@@ -1776,6 +1772,10 @@ msgstr "No predictions"
msgid "No results found" msgid "No results found"
msgstr "No results found" msgstr "No results found"
#: src/features/telemetry/components/sessions/sessions-list.tsx:38
msgid "No sessions yet"
msgstr "No sessions yet"
#: src/app/routes/api/spotify/resume.ts:37 #: src/app/routes/api/spotify/resume.ts:37
msgid "No snapshot provided" msgid "No snapshot provided"
msgstr "No snapshot provided" msgstr "No snapshot provided"
@@ -1949,10 +1949,6 @@ msgstr "Partner Assignments"
msgid "Partners will be randomly assigned when enrollment closes" msgid "Partners will be randomly assigned when enrollment closes"
msgstr "Partners will be randomly assigned when enrollment closes" msgstr "Partners will be randomly assigned when enrollment closes"
#: src/features/telemetry/components/client-events-table.tsx:121
msgid "Path"
msgstr "Path"
#: src/features/spotify/components/spotify-controls-bar.tsx:116 #: src/features/spotify/components/spotify-controls-bar.tsx:116
msgid "Pause" msgid "Pause"
msgstr "Pause" msgstr "Pause"
@@ -1995,8 +1991,6 @@ msgstr "Playback control failed"
#: src/features/activities/components/activities-table.tsx:98 #: src/features/activities/components/activities-table.tsx:98
#: src/features/activities/components/activities-table.tsx:188 #: src/features/activities/components/activities-table.tsx:188
#: src/features/admin/components/award-badges.tsx:31 #: src/features/admin/components/award-badges.tsx:31
#: src/features/telemetry/components/client-events-table.tsx:72
#: src/features/telemetry/components/client-events-table.tsx:119
msgid "Player" msgid "Player"
msgstr "Player" msgstr "Player"
@@ -2076,10 +2070,6 @@ msgstr "Profile"
msgid "Progress " msgid "Progress "
msgstr "Progress " msgstr "Progress "
#: src/features/telemetry/components/client-events-table.tsx:135
msgid "Props"
msgstr "Props"
#: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106 #: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106
#: src/features/predictions/components/prediction-leaderboard.tsx:261 #: src/features/predictions/components/prediction-leaderboard.tsx:261
msgid "PTS" msgid "PTS"
@@ -2167,10 +2157,6 @@ msgstr "rofl"
msgid "Round {0}" msgid "Round {0}"
msgstr "Round {0}" msgstr "Round {0}"
#: src/features/telemetry/components/client-events-table.tsx:124
msgid "Route"
msgstr "Route"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123 #: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23 #: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules" msgid "Rules"
@@ -2351,10 +2337,7 @@ msgstr "Server functions"
msgid "serverFn name" msgid "serverFn name"
msgstr "serverFn name" msgstr "serverFn name"
#: src/features/telemetry/components/client-events-table.tsx:127 #: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Session"
msgstr "Session"
#: src/features/telemetry/components/usage-screen.tsx:177 #: src/features/telemetry/components/usage-screen.tsx:177
msgid "Sessions" msgid "Sessions"
msgstr "Sessions" msgstr "Sessions"
@@ -2809,14 +2792,9 @@ msgid "Usage"
msgstr "Usage" msgstr "Usage"
#: src/features/activities/components/activities-table.tsx:202 #: src/features/activities/components/activities-table.tsx:202
#: src/features/telemetry/components/client-events-table.tsx:145
msgid "User Agent" msgid "User Agent"
msgstr "User Agent" msgstr "User Agent"
#: src/features/telemetry/components/client-events-table.tsx:130
msgid "Value"
msgstr "Value"
#: src/features/bracket/components/match-card.tsx:201 #: src/features/bracket/components/match-card.tsx:201
#: src/features/bracket/components/match-card.tsx:226 #: src/features/bracket/components/match-card.tsx:226
msgid "versus" msgid "versus"
+25 -47
View File
@@ -35,6 +35,20 @@ msgstr "(Activo)"
msgid "{0, plural, one {# bracket in} other {# brackets in}}" msgid "{0, plural, one {# bracket in} other {# brackets in}}"
msgstr "{0, plural, one {# bracket en} other {# brackets en}}" msgstr "{0, plural, one {# bracket en} other {# brackets en}}"
#. placeholder {0}: data.session.errors
#. placeholder {0}: session.errors
#: src/features/telemetry/components/sessions/session-timeline.tsx:48
#: src/features/telemetry/components/sessions/sessions-list.tsx:52
msgid "{0, plural, one {# error} other {# errors}}"
msgstr "{0, plural, one {# error} other {# errores}}"
#. placeholder {0}: data.session.pageViews
#. placeholder {0}: session.pageViews
#: src/features/telemetry/components/sessions/session-timeline.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:47
msgid "{0, plural, one {# page} other {# pages}}"
msgstr "{0, plural, one {# página} other {# páginas}}"
#. placeholder {0}: enrolledPlayers.length #. placeholder {0}: enrolledPlayers.length
#. placeholder {0}: players.length #. placeholder {0}: players.length
#: src/features/players/components/players-activity-table.tsx:100 #: src/features/players/components/players-activity-table.tsx:100
@@ -57,11 +71,6 @@ msgstr "{0, plural, one {# equipo} other {# equipos}}"
msgid "{0, plural, one {# total activity} other {# total activities}}" msgid "{0, plural, one {# total activity} other {# total activities}}"
msgstr "{0, plural, one {# actividad total} other {# actividades totales}}" msgstr "{0, plural, one {# actividad total} other {# actividades totales}}"
#. placeholder {0}: result.totalItems
#: src/features/telemetry/components/client-events-table.tsx:86
msgid "{0, plural, one {# total event} other {# total events}}"
msgstr "{0, plural, one {# evento total} other {# eventos totales}}"
#. placeholder {0}: selectedConfig.total_group_matches #. placeholder {0}: selectedConfig.total_group_matches
#: src/features/tournaments/components/setup-group-stage.tsx:249 #: src/features/tournaments/components/setup-group-stage.tsx:249
msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}" msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}"
@@ -480,7 +489,8 @@ msgstr "Ocurrió un error inesperado. Intenta de nuevo."
msgid "Announce matchup" msgid "Announce matchup"
msgstr "Anunciar enfrentamiento" msgstr "Anunciar enfrentamiento"
#: src/features/telemetry/components/client-events-table.tsx:48 #: src/features/telemetry/components/sessions/sessions-list.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:61
msgid "Anonymous" msgid "Anonymous"
msgstr "Anónimo" msgstr "Anónimo"
@@ -846,7 +856,6 @@ msgid "Create Tournament"
msgstr "Crear torneo" msgstr "Crear torneo"
#: src/features/activities/components/activities-table.tsx:198 #: src/features/activities/components/activities-table.tsx:198
#: src/features/telemetry/components/client-events-table.tsx:148
msgid "Created" msgid "Created"
msgstr "Creado" msgstr "Creado"
@@ -1101,18 +1110,6 @@ msgstr "Error: {errorMessage}"
msgid "Errors" msgid "Errors"
msgstr "Errores" msgstr "Errores"
#: src/features/telemetry/components/client-events-table.tsx:112
msgid "Event Details"
msgstr "Detalles del evento"
#: src/features/telemetry/components/client-events-table.tsx:69
msgid "Event name"
msgstr "Nombre del evento"
#: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Events"
msgstr "Eventos"
#: src/features/tournaments/components/group-stage-view.tsx:368 #: src/features/tournaments/components/group-stage-view.tsx:368
msgid "Expand standings" msgid "Expand standings"
msgstr "Expandir tabla de posiciones" msgstr "Expandir tabla de posiciones"
@@ -1429,6 +1426,10 @@ msgstr "D"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: src/features/telemetry/components/sessions/sessions-list.tsx:74
msgid "Last 7 days"
msgstr "Últimos 7 días"
#: src/features/login/components/name-prompt.tsx:43 #: src/features/login/components/name-prompt.tsx:43
#: src/features/players/components/profile/name-form.tsx:68 #: src/features/players/components/profile/name-form.tsx:68
msgid "Last Name" msgid "Last Name"
@@ -1638,7 +1639,6 @@ msgid "MMR Calculation:"
msgstr "Cálculo de MMR:" msgstr "Cálculo de MMR:"
#: src/features/teams/components/team-form/index.tsx:200 #: src/features/teams/components/team-form/index.tsx:200
#: src/features/telemetry/components/client-events-table.tsx:118
#: src/features/tournaments/components/tournament-form.tsx:137 #: src/features/tournaments/components/tournament-form.tsx:137
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
@@ -1734,10 +1734,6 @@ msgstr "No hay insignias disponibles"
msgid "No devices found." msgid "No devices found."
msgstr "No se encontraron dispositivos." msgstr "No se encontraron dispositivos."
#: src/features/telemetry/components/client-events-table.tsx:93
msgid "No Events Found"
msgstr "No se encontraron eventos"
#: src/features/tournaments/components/group-stage-view.tsx:294 #: src/features/tournaments/components/group-stage-view.tsx:294
msgid "No groups have been created yet" msgid "No groups have been created yet"
msgstr "Aún no se han creado grupos" msgstr "Aún no se han creado grupos"
@@ -1776,6 +1772,10 @@ msgstr "Sin predicciones"
msgid "No results found" msgid "No results found"
msgstr "No se encontraron resultados" msgstr "No se encontraron resultados"
#: src/features/telemetry/components/sessions/sessions-list.tsx:38
msgid "No sessions yet"
msgstr "Aún no hay sesiones"
#: src/app/routes/api/spotify/resume.ts:37 #: src/app/routes/api/spotify/resume.ts:37
msgid "No snapshot provided" msgid "No snapshot provided"
msgstr "No se proporcionó ninguna instantánea" msgstr "No se proporcionó ninguna instantánea"
@@ -1949,10 +1949,6 @@ msgstr "Asignación de compañeros"
msgid "Partners will be randomly assigned when enrollment closes" msgid "Partners will be randomly assigned when enrollment closes"
msgstr "Los compañeros se asignarán al azar cuando cierre la inscripción" msgstr "Los compañeros se asignarán al azar cuando cierre la inscripción"
#: src/features/telemetry/components/client-events-table.tsx:121
msgid "Path"
msgstr "Ruta"
#: src/features/spotify/components/spotify-controls-bar.tsx:116 #: src/features/spotify/components/spotify-controls-bar.tsx:116
msgid "Pause" msgid "Pause"
msgstr "Pausar" msgstr "Pausar"
@@ -1995,8 +1991,6 @@ msgstr "Falló el control de reproducción"
#: src/features/activities/components/activities-table.tsx:98 #: src/features/activities/components/activities-table.tsx:98
#: src/features/activities/components/activities-table.tsx:188 #: src/features/activities/components/activities-table.tsx:188
#: src/features/admin/components/award-badges.tsx:31 #: src/features/admin/components/award-badges.tsx:31
#: src/features/telemetry/components/client-events-table.tsx:72
#: src/features/telemetry/components/client-events-table.tsx:119
msgid "Player" msgid "Player"
msgstr "Jugador" msgstr "Jugador"
@@ -2076,10 +2070,6 @@ msgstr "Perfil"
msgid "Progress " msgid "Progress "
msgstr "Progreso " msgstr "Progreso "
#: src/features/telemetry/components/client-events-table.tsx:135
msgid "Props"
msgstr "Props"
#: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106 #: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106
#: src/features/predictions/components/prediction-leaderboard.tsx:261 #: src/features/predictions/components/prediction-leaderboard.tsx:261
msgid "PTS" msgid "PTS"
@@ -2167,10 +2157,6 @@ msgstr "carcajada"
msgid "Round {0}" msgid "Round {0}"
msgstr "Ronda {0}" msgstr "Ronda {0}"
#: src/features/telemetry/components/client-events-table.tsx:124
msgid "Route"
msgstr "Ruta"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123 #: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23 #: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules" msgid "Rules"
@@ -2351,10 +2337,7 @@ msgstr "Funciones del servidor"
msgid "serverFn name" msgid "serverFn name"
msgstr "nombre de serverFn" msgstr "nombre de serverFn"
#: src/features/telemetry/components/client-events-table.tsx:127 #: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Session"
msgstr "Sesión"
#: src/features/telemetry/components/usage-screen.tsx:177 #: src/features/telemetry/components/usage-screen.tsx:177
msgid "Sessions" msgid "Sessions"
msgstr "Sesiones" msgstr "Sesiones"
@@ -2809,14 +2792,9 @@ msgid "Usage"
msgstr "Uso" msgstr "Uso"
#: src/features/activities/components/activities-table.tsx:202 #: src/features/activities/components/activities-table.tsx:202
#: src/features/telemetry/components/client-events-table.tsx:145
msgid "User Agent" msgid "User Agent"
msgstr "User Agent" msgstr "User Agent"
#: src/features/telemetry/components/client-events-table.tsx:130
msgid "Value"
msgstr "Valor"
#: src/features/bracket/components/match-card.tsx:201 #: src/features/bracket/components/match-card.tsx:201
#: src/features/bracket/components/match-card.tsx:226 #: src/features/bracket/components/match-card.tsx:226
msgid "versus" msgid "versus"
+25 -47
View File
@@ -35,6 +35,20 @@ msgstr "(アクティブ)"
msgid "{0, plural, one {# bracket in} other {# brackets in}}" msgid "{0, plural, one {# bracket in} other {# brackets in}}"
msgstr "{0, plural, one {#ブラケット参加} other {#ブラケット参加}}" msgstr "{0, plural, one {#ブラケット参加} other {#ブラケット参加}}"
#. placeholder {0}: data.session.errors
#. placeholder {0}: session.errors
#: src/features/telemetry/components/sessions/session-timeline.tsx:48
#: src/features/telemetry/components/sessions/sessions-list.tsx:52
msgid "{0, plural, one {# error} other {# errors}}"
msgstr "{0, plural, one {# 件のエラー} other {# 件のエラー}}"
#. placeholder {0}: data.session.pageViews
#. placeholder {0}: session.pageViews
#: src/features/telemetry/components/sessions/session-timeline.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:47
msgid "{0, plural, one {# page} other {# pages}}"
msgstr "{0, plural, one {# ページ} other {# ページ}}"
#. placeholder {0}: enrolledPlayers.length #. placeholder {0}: enrolledPlayers.length
#. placeholder {0}: players.length #. placeholder {0}: players.length
#: src/features/players/components/players-activity-table.tsx:100 #: src/features/players/components/players-activity-table.tsx:100
@@ -57,11 +71,6 @@ msgstr "{0, plural, one {#チーム} other {#チーム}}"
msgid "{0, plural, one {# total activity} other {# total activities}}" msgid "{0, plural, one {# total activity} other {# total activities}}"
msgstr "{0, plural, one {アクティビティ合計#件} other {アクティビティ合計#件}}" msgstr "{0, plural, one {アクティビティ合計#件} other {アクティビティ合計#件}}"
#. placeholder {0}: result.totalItems
#: src/features/telemetry/components/client-events-table.tsx:86
msgid "{0, plural, one {# total event} other {# total events}}"
msgstr "{0, plural, one {イベント合計#件} other {イベント合計#件}}"
#. placeholder {0}: selectedConfig.total_group_matches #. placeholder {0}: selectedConfig.total_group_matches
#: src/features/tournaments/components/setup-group-stage.tsx:249 #: src/features/tournaments/components/setup-group-stage.tsx:249
msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}" msgid "{0, plural, one {# total group stage match} other {# total group stage matches}}"
@@ -480,7 +489,8 @@ msgstr "予期しないエラーが発生しました。もう一度お試しく
msgid "Announce matchup" msgid "Announce matchup"
msgstr "対戦カードを発表" msgstr "対戦カードを発表"
#: src/features/telemetry/components/client-events-table.tsx:48 #: src/features/telemetry/components/sessions/sessions-list.tsx:44
#: src/features/telemetry/components/sessions/sessions-list.tsx:61
msgid "Anonymous" msgid "Anonymous"
msgstr "匿名" msgstr "匿名"
@@ -846,7 +856,6 @@ msgid "Create Tournament"
msgstr "トーナメントを作成" msgstr "トーナメントを作成"
#: src/features/activities/components/activities-table.tsx:198 #: src/features/activities/components/activities-table.tsx:198
#: src/features/telemetry/components/client-events-table.tsx:148
msgid "Created" msgid "Created"
msgstr "作成日" msgstr "作成日"
@@ -1101,18 +1110,6 @@ msgstr "エラー: {errorMessage}"
msgid "Errors" msgid "Errors"
msgstr "エラー" msgstr "エラー"
#: src/features/telemetry/components/client-events-table.tsx:112
msgid "Event Details"
msgstr "イベント詳細"
#: src/features/telemetry/components/client-events-table.tsx:69
msgid "Event name"
msgstr "イベント名"
#: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Events"
msgstr "イベント"
#: src/features/tournaments/components/group-stage-view.tsx:368 #: src/features/tournaments/components/group-stage-view.tsx:368
msgid "Expand standings" msgid "Expand standings"
msgstr "順位表を展開" msgstr "順位表を展開"
@@ -1429,6 +1426,10 @@ msgstr "L"
msgid "Language" msgid "Language"
msgstr "言語" msgstr "言語"
#: src/features/telemetry/components/sessions/sessions-list.tsx:74
msgid "Last 7 days"
msgstr "過去7日間"
#: src/features/login/components/name-prompt.tsx:43 #: src/features/login/components/name-prompt.tsx:43
#: src/features/players/components/profile/name-form.tsx:68 #: src/features/players/components/profile/name-form.tsx:68
msgid "Last Name" msgid "Last Name"
@@ -1638,7 +1639,6 @@ msgid "MMR Calculation:"
msgstr "MMR計算:" msgstr "MMR計算:"
#: src/features/teams/components/team-form/index.tsx:200 #: src/features/teams/components/team-form/index.tsx:200
#: src/features/telemetry/components/client-events-table.tsx:118
#: src/features/tournaments/components/tournament-form.tsx:137 #: src/features/tournaments/components/tournament-form.tsx:137
msgid "Name" msgid "Name"
msgstr "名前" msgstr "名前"
@@ -1734,10 +1734,6 @@ msgstr "利用可能なバッジがありません"
msgid "No devices found." msgid "No devices found."
msgstr "デバイスが見つかりません。" msgstr "デバイスが見つかりません。"
#: src/features/telemetry/components/client-events-table.tsx:93
msgid "No Events Found"
msgstr "イベントが見つかりません"
#: src/features/tournaments/components/group-stage-view.tsx:294 #: src/features/tournaments/components/group-stage-view.tsx:294
msgid "No groups have been created yet" msgid "No groups have been created yet"
msgstr "まだグループが作成されていません" msgstr "まだグループが作成されていません"
@@ -1776,6 +1772,10 @@ msgstr "予想はありません"
msgid "No results found" msgid "No results found"
msgstr "結果が見つかりません" msgstr "結果が見つかりません"
#: src/features/telemetry/components/sessions/sessions-list.tsx:38
msgid "No sessions yet"
msgstr "まだセッションがありません"
#: src/app/routes/api/spotify/resume.ts:37 #: src/app/routes/api/spotify/resume.ts:37
msgid "No snapshot provided" msgid "No snapshot provided"
msgstr "スナップショットが提供されていません" msgstr "スナップショットが提供されていません"
@@ -1949,10 +1949,6 @@ msgstr "パートナー割り当て"
msgid "Partners will be randomly assigned when enrollment closes" msgid "Partners will be randomly assigned when enrollment closes"
msgstr "登録が締め切られると、パートナーはランダムに割り当てられます" msgstr "登録が締め切られると、パートナーはランダムに割り当てられます"
#: src/features/telemetry/components/client-events-table.tsx:121
msgid "Path"
msgstr "パス"
#: src/features/spotify/components/spotify-controls-bar.tsx:116 #: src/features/spotify/components/spotify-controls-bar.tsx:116
msgid "Pause" msgid "Pause"
msgstr "一時停止" msgstr "一時停止"
@@ -1995,8 +1991,6 @@ msgstr "再生コントロールに失敗しました"
#: src/features/activities/components/activities-table.tsx:98 #: src/features/activities/components/activities-table.tsx:98
#: src/features/activities/components/activities-table.tsx:188 #: src/features/activities/components/activities-table.tsx:188
#: src/features/admin/components/award-badges.tsx:31 #: src/features/admin/components/award-badges.tsx:31
#: src/features/telemetry/components/client-events-table.tsx:72
#: src/features/telemetry/components/client-events-table.tsx:119
msgid "Player" msgid "Player"
msgstr "プレイヤー" msgstr "プレイヤー"
@@ -2076,10 +2070,6 @@ msgstr "プロフィール"
msgid "Progress " msgid "Progress "
msgstr "進行状況 " msgstr "進行状況 "
#: src/features/telemetry/components/client-events-table.tsx:135
msgid "Props"
msgstr "プロパティ"
#: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106 #: src/app/routes/_authed/tournaments/$id.predictions_.$playerId.tsx:106
#: src/features/predictions/components/prediction-leaderboard.tsx:261 #: src/features/predictions/components/prediction-leaderboard.tsx:261
msgid "PTS" msgid "PTS"
@@ -2167,10 +2157,6 @@ msgstr "大笑い"
msgid "Round {0}" msgid "Round {0}"
msgstr "ラウンド{0}" msgstr "ラウンド{0}"
#: src/features/telemetry/components/client-events-table.tsx:124
msgid "Route"
msgstr "ルート"
#: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123 #: src/features/tournaments/components/started-tournament/sections/nav-grid.tsx:123
#: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23 #: src/features/tournaments/components/upcoming-tournament/rules-list-button.tsx:23
msgid "Rules" msgid "Rules"
@@ -2351,10 +2337,7 @@ msgstr "サーバー関数"
msgid "serverFn name" msgid "serverFn name"
msgstr "serverFn名" msgstr "serverFn名"
#: src/features/telemetry/components/client-events-table.tsx:127 #: src/app/routes/_authed/admin/telemetry.tsx:54
msgid "Session"
msgstr "セッション"
#: src/features/telemetry/components/usage-screen.tsx:177 #: src/features/telemetry/components/usage-screen.tsx:177
msgid "Sessions" msgid "Sessions"
msgstr "セッション数" msgstr "セッション数"
@@ -2809,14 +2792,9 @@ msgid "Usage"
msgstr "利用状況" msgstr "利用状況"
#: src/features/activities/components/activities-table.tsx:202 #: src/features/activities/components/activities-table.tsx:202
#: src/features/telemetry/components/client-events-table.tsx:145
msgid "User Agent" msgid "User Agent"
msgstr "ユーザーエージェント" msgstr "ユーザーエージェント"
#: src/features/telemetry/components/client-events-table.tsx:130
msgid "Value"
msgstr "値"
#: src/features/bracket/components/match-card.tsx:201 #: src/features/bracket/components/match-card.tsx:201
#: src/features/bracket/components/match-card.tsx:226 #: src/features/bracket/components/match-card.tsx:226
msgid "versus" msgid "versus"