306 lines
8.3 KiB
TypeScript
306 lines
8.3 KiB
TypeScript
import type { IconName } from './components/Icon';
|
|
|
|
/* The console's table of contents, and the only place a page is declared.
|
|
*
|
|
* The rail, the page search and the router all read it, so a page cannot be in the menu
|
|
* and 404, or be reachable and unnamed. It used to have to agree with a matching list in
|
|
* Go as well — the gateway rendered the rail and decided which /admin URLs were legal —
|
|
* which is a duplication the single-page console removes: the gateway now serves the
|
|
* console for any /admin path and the routing is entirely here.
|
|
*
|
|
* `hidden` marks a destination that is reachable and titled but not in the rail: a page
|
|
* about one person or one television belongs to the thing it is about, not to a menu. */
|
|
|
|
export interface NavItem {
|
|
id: string;
|
|
path: string;
|
|
label: string;
|
|
title: string;
|
|
intro: string;
|
|
icon?: IconName;
|
|
hidden?: boolean;
|
|
/** badge names a live count the rail should show beside this item — today only the
|
|
* unread activity count. */
|
|
badge?: 'notifications';
|
|
}
|
|
|
|
export interface NavGroup {
|
|
id: string;
|
|
label?: string;
|
|
items: NavItem[];
|
|
}
|
|
|
|
export const nav: NavGroup[] = [
|
|
{
|
|
id: 'overview',
|
|
items: [
|
|
{
|
|
id: 'overview',
|
|
path: '/admin',
|
|
label: 'Overview',
|
|
title: 'Overview',
|
|
intro: 'What the gateway is doing right now.',
|
|
icon: 'overview',
|
|
},
|
|
{
|
|
id: 'activity',
|
|
path: '/admin/activity',
|
|
label: 'Activity',
|
|
title: 'Activity',
|
|
intro: 'Every administrative event, newest first.',
|
|
icon: 'bell',
|
|
badge: 'notifications',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'people',
|
|
label: 'People',
|
|
items: [
|
|
{
|
|
id: 'accounts',
|
|
path: '/admin/accounts',
|
|
label: 'Users',
|
|
title: 'Memby users',
|
|
intro: 'Who uses Memby, and the devices they are signed in on.',
|
|
icon: 'people',
|
|
},
|
|
{
|
|
id: 'account',
|
|
path: '/admin/accounts/:userId',
|
|
label: 'User',
|
|
title: 'User',
|
|
intro: 'Devices, recommendation setup and synced settings for one person.',
|
|
hidden: true,
|
|
},
|
|
{
|
|
id: 'settings-history',
|
|
path: '/admin/accounts/:userId/settings',
|
|
label: 'Settings history',
|
|
title: 'Settings history',
|
|
intro: "Every change to one person's synced settings, and which devices took it.",
|
|
hidden: true,
|
|
},
|
|
{
|
|
id: 'clients',
|
|
path: '/admin/clients',
|
|
label: 'Devices',
|
|
title: 'Devices',
|
|
intro: 'Which sets have reported in, what they are running and what their build understands.',
|
|
icon: 'tv',
|
|
},
|
|
{
|
|
id: 'logins',
|
|
path: '/admin/logins',
|
|
label: 'Sign-ins',
|
|
title: 'Sign-in history',
|
|
intro: 'Every connection attempt: who, which television, from where, and whether it got in.',
|
|
icon: 'key',
|
|
},
|
|
{
|
|
id: 'device',
|
|
path: '/admin/devices/:deviceId',
|
|
label: 'Device',
|
|
title: 'Device',
|
|
intro: 'One television: how often it connects, at what times, and from which addresses.',
|
|
hidden: true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'content',
|
|
label: 'Content',
|
|
items: [
|
|
{
|
|
id: 'library',
|
|
path: '/admin/library',
|
|
label: 'Library',
|
|
title: 'Library',
|
|
intro: 'Import and inspect the catalogue Memby ranks.',
|
|
icon: 'library',
|
|
},
|
|
{
|
|
id: 'ratings',
|
|
path: '/admin/ratings',
|
|
label: 'Movie ratings',
|
|
title: 'Movie ratings',
|
|
intro: 'Optional MDBList scores on films and shows.',
|
|
icon: 'star',
|
|
},
|
|
{
|
|
id: 'requests',
|
|
path: '/admin/requests',
|
|
label: 'Media requests',
|
|
title: 'Media requests',
|
|
intro: 'Who can ask for something the library does not have.',
|
|
icon: 'inbox',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'personalisation',
|
|
label: 'Personalisation',
|
|
items: [
|
|
{
|
|
id: 'recommendations',
|
|
path: '/admin/recommendations',
|
|
label: 'For You',
|
|
title: 'For You',
|
|
intro: 'The prepared pools personalised rows are drawn from.',
|
|
icon: 'sparkle',
|
|
},
|
|
{
|
|
id: 'inspector',
|
|
path: '/admin/inspector',
|
|
label: 'Score inspector',
|
|
title: 'Score inspector',
|
|
intro: 'Re-run the ranker for one person and read every component.',
|
|
icon: 'search',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'experience',
|
|
label: 'Experience',
|
|
items: [
|
|
{
|
|
id: 'hero',
|
|
path: '/admin/hero',
|
|
label: 'Home hero',
|
|
title: 'Home hero',
|
|
intro:
|
|
'Choose films or television shows for the launcher spotlight while recent releases fill the remaining places.',
|
|
icon: 'star',
|
|
},
|
|
{
|
|
id: 'features',
|
|
path: '/admin/features',
|
|
label: 'Features',
|
|
title: 'Features',
|
|
intro: 'Roll out, stop and recover optional behaviour with no app release.',
|
|
icon: 'sliders',
|
|
},
|
|
{
|
|
id: 'playback',
|
|
path: '/admin/playback',
|
|
label: 'Playback',
|
|
title: 'Playback',
|
|
intro: 'Presentation policy sent with every playback launch.',
|
|
icon: 'play',
|
|
},
|
|
{
|
|
id: 'subtitles',
|
|
path: '/admin/subtitles',
|
|
label: 'Subtitles',
|
|
title: 'Subtitles',
|
|
intro: 'Which providers a viewer may fetch a missing subtitle from.',
|
|
icon: 'captions',
|
|
},
|
|
{
|
|
id: 'updates',
|
|
path: '/admin/updates',
|
|
label: 'App updates',
|
|
title: 'App updates',
|
|
intro: 'Publish an optional or a required client update.',
|
|
icon: 'upload',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'operations',
|
|
label: 'Operations',
|
|
items: [
|
|
{
|
|
id: 'tasks',
|
|
path: '/admin/tasks',
|
|
label: 'Scheduled tasks',
|
|
title: 'Scheduled tasks',
|
|
intro: 'What the gateway does in the background, when it last ran and whether it worked.',
|
|
icon: 'clock',
|
|
},
|
|
{
|
|
id: 'integrations',
|
|
path: '/admin/integrations',
|
|
label: 'Integrations',
|
|
title: 'Integrations',
|
|
intro: 'Send administrative events to Discord and, in time, elsewhere.',
|
|
icon: 'plug',
|
|
},
|
|
{
|
|
id: 'maintenance',
|
|
path: '/admin/maintenance',
|
|
label: 'Maintenance',
|
|
title: 'Maintenance',
|
|
intro: 'Take Memby offline for every television.',
|
|
icon: 'wrench',
|
|
},
|
|
{
|
|
id: 'imports',
|
|
path: '/admin/imports',
|
|
label: 'Imports',
|
|
title: 'Imports',
|
|
intro: 'Catalogue synchronisation history.',
|
|
icon: 'database',
|
|
},
|
|
{
|
|
id: 'logs',
|
|
path: '/admin/logs',
|
|
label: 'Server logs',
|
|
title: 'Server logs',
|
|
intro: 'Structured gateway events as they happen.',
|
|
icon: 'list',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: 'reporting',
|
|
label: 'Reporting',
|
|
items: [
|
|
{
|
|
id: 'views',
|
|
path: '/admin/views',
|
|
label: 'Views',
|
|
title: 'App views',
|
|
intro: 'Home-screen visits, viewers and the times Memby is used.',
|
|
icon: 'overview',
|
|
},
|
|
{
|
|
id: 'journeys',
|
|
path: '/admin/journeys',
|
|
label: 'Journeys',
|
|
title: 'User journeys',
|
|
intro: 'How viewers move through Memby, use features and complete flows.',
|
|
icon: 'journey',
|
|
},
|
|
{
|
|
id: 'engagement',
|
|
path: '/admin/engagement',
|
|
label: 'Row engagement',
|
|
title: 'Row engagement',
|
|
intro: 'Impressions, focus, dwell and selections per launcher row.',
|
|
icon: 'chart',
|
|
},
|
|
{
|
|
id: 'searches',
|
|
path: '/admin/searches',
|
|
label: 'Searches',
|
|
title: 'Searches',
|
|
intro: 'What the household has been looking for, and what it searched just now.',
|
|
icon: 'search',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
export const allNavItems: NavItem[] = nav.flatMap((group) => group.items);
|
|
|
|
/** searchableNavItems is what the omni search offers: every page that has a URL of its
|
|
* own, which is every page that is not addressed by an id in the path. */
|
|
export const searchableNavItems = nav.flatMap((group) =>
|
|
group.items.filter((item) => !item.hidden).map((item) => ({ ...item, group: group.label ?? '' })),
|
|
);
|
|
|
|
export function navItem(id: string): NavItem | undefined {
|
|
return allNavItems.find((item) => item.id === id);
|
|
}
|