Expand Settings search with module-level registry

Search field at the top of Settings now finds Sidebar Links,
  Spoolman, Spool/Color Catalog, all four Failure Detection
  sections, Email auth (Advanced + SMTP test), 2FA (TOTP, Email
  OTP, Linked Accounts), SSO/OIDC, LDAP Server Config, and the
  four Backup sub-cards (GitHub, History, Local, Scheduled).

  Replaces the hardcoded searchIndex array in SettingsPage.tsx
  with a module-level registry (frontend/src/lib/settingsSearch.ts).
  Each settings card calls registerSettingsSearch(...) at module
  scope, so adding a new card means adding one colocated line
  instead of editing a distant central array. Anchor ids were
  added to the corresponding Card elements in the affected
  components so scrollIntoView lands on the right section.
This commit is contained in:
maziggy
2026-04-13 16:13:04 +02:00
parent 487d11fa94
commit 180db8e8ad
16 changed files with 601 additions and 9273 deletions
@@ -312,7 +312,7 @@ export function ColorCatalogSettings() {
};
return (
<Card>
<Card id="card-color-catalog">
<CardHeader>
<div className="flex items-center gap-2 mb-3">
<Palette className="w-5 h-5 text-bambu-gray" />
+3 -3
View File
@@ -176,7 +176,7 @@ export function EmailSettings() {
return (
<div className="space-y-3">
{/* Advanced Authentication Toggle - Always visible */}
<Card>
<Card id="card-email-advanced-auth">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -245,7 +245,7 @@ export function EmailSettings() {
{/* SMTP Config + Test SMTP side-by-side on lg+ */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<div className="lg:col-span-2">
<Card>
<Card id="card-smtp-config">
<CardHeader>
<h2 className="text-lg font-semibold text-white">
{t('settings.email.smtpSettings') || 'SMTP Configuration'}
@@ -393,7 +393,7 @@ export function EmailSettings() {
{/* Test SMTP */}
<div>
<Card>
<Card id="card-email-test">
<CardHeader>
<h2 className="text-lg font-semibold text-white">
{t('settings.email.testConnection') || 'Test SMTP Connection'}
@@ -82,7 +82,7 @@ export function ExternalLinksSettings() {
return (
<>
<Card>
<Card id="card-sidebar-links">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -126,7 +126,7 @@ export function FailureDetectionSettings() {
return (
<div className="flex flex-col lg:flex-row gap-4 lg:gap-6">
<div className="space-y-3 flex-1 lg:max-w-xl">
<Card>
<Card id="card-fd-ml">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -233,7 +233,7 @@ export function FailureDetectionSettings() {
</CardContent>
</Card>
<Card>
<Card id="card-fd-perprinter">
<CardHeader>
<h2 className="text-lg font-semibold text-white">{t('failureDetection.perPrinterTitle')}</h2>
<p className="text-sm text-bambu-gray mt-1">{t('failureDetection.perPrinterHint')}</p>
@@ -268,7 +268,7 @@ export function FailureDetectionSettings() {
</div>
<div className="space-y-3 flex-1 lg:max-w-xl">
<Card>
<Card id="card-fd-status">
<CardHeader>
<h2 className="text-lg font-semibold text-white">{t('failureDetection.statusTitle')}</h2>
</CardHeader>
@@ -329,7 +329,7 @@ export function FailureDetectionSettings() {
</CardContent>
</Card>
<Card>
<Card id="card-fd-history">
<CardHeader>
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">{t('failureDetection.historyTitle')}</h2>
@@ -464,7 +464,7 @@ export function GitHubBackupSettings() {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left Column - GitHub Backup */}
<div className="space-y-6">
<Card>
<Card id="card-backup-github">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -736,7 +736,7 @@ export function GitHubBackupSettings() {
{/* Backup History - only show if configured and has logs */}
{logs && logs.length > 0 && (
<Card>
<Card id="card-backup-history">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -796,7 +796,7 @@ export function GitHubBackupSettings() {
{/* Right Column - Local Backup */}
<div className="space-y-6">
<Card>
<Card id="card-backup-local">
<CardHeader>
<div className="flex items-center gap-2">
<Database className="w-5 h-5 text-gray-400" />
@@ -924,7 +924,7 @@ export function GitHubBackupSettings() {
</Card>
{/* Scheduled Local Backups */}
<Card>
<Card id="card-backup-scheduled">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
+2 -2
View File
@@ -175,7 +175,7 @@ export function LDAPSettings() {
return (
<div className="space-y-3">
{/* LDAP Toggle */}
<Card>
<Card id="card-ldap-toggle">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -239,7 +239,7 @@ export function LDAPSettings() {
</Card>
{/* LDAP Server Configuration */}
<Card>
<Card id="card-ldap-server">
<CardHeader>
<h2 className="text-lg font-semibold text-white">
{t('settings.ldap.serverConfig') || 'LDAP Server Configuration'}
@@ -186,7 +186,7 @@ export function OIDCProviderSettings() {
return (
<div className="space-y-6">
{/* Header */}
<Card>
<Card id="card-oidc">
<CardHeader>
<div className="flex items-center justify-between">
<div>
@@ -219,7 +219,7 @@ export function OIDCProviderSettings() {
{/* Provider list */}
{providers && providers.length === 0 && !showCreate && (
<Card>
<Card id="card-oidc-empty">
<CardContent>
<div className="text-center py-8 space-y-3">
<Globe className="w-12 h-12 text-bambu-gray mx-auto" />
@@ -199,7 +199,7 @@ export function SpoolCatalogSettings() {
};
return (
<Card>
<Card id="card-spool-catalog">
<CardHeader>
<div className="flex items-center gap-2 mb-3">
<Database className="w-5 h-5 text-bambu-gray" />
+2 -2
View File
@@ -161,7 +161,7 @@ export function SpoolmanSettings() {
if (settingsLoading) {
return (
<Card>
<Card id="card-spoolman">
<CardHeader>
<div className="flex items-center gap-2">
<Database className="w-5 h-5 text-bambu-green" />
@@ -178,7 +178,7 @@ export function SpoolmanSettings() {
}
return (
<Card>
<Card id="card-spoolman">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
@@ -286,7 +286,7 @@ export function TwoFactorSettings() {
return (
<div className="space-y-6">
{/* ── TOTP ─────────────────────────────────────────────────────────── */}
<Card>
<Card id="card-2fa-totp">
<CardHeader>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${status?.totp_enabled ? 'bg-green-500/20' : 'bg-gray-500/20'}`}>
@@ -391,7 +391,7 @@ export function TwoFactorSettings() {
</Card>
{/* ── Email OTP ─────────────────────────────────────────────────────── */}
<Card>
<Card id="card-2fa-emailotp">
<CardHeader>
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${status?.email_otp_enabled ? 'bg-green-500/20' : 'bg-gray-500/20'}`}>
@@ -513,7 +513,7 @@ export function TwoFactorSettings() {
{/* ── Linked SSO accounts ───────────────────────────────────────────── */}
{oidcLinks && oidcLinks.length > 0 && (
<Card>
<Card id="card-2fa-linked">
<CardHeader>
<h3 className="text-white font-semibold">{t('settings.twoFa.linkedAccounts')}</h3>
<p className="text-bambu-gray text-sm">{t('settings.twoFa.linkedAccountsDesc')}</p>
+48
View File
@@ -0,0 +1,48 @@
// Settings search registry.
//
// Each settings card/section registers itself at module-import time by calling
// `registerSettingsSearch(...)` at module scope (NOT inside a component).
// SettingsPage reads the accumulated registry to power its cross-tab search.
//
// Convention: co-locate the registration call with the component/section that
// owns the `anchor` id. When you add a new settings card, add one call here
// next to it — no central index to forget to update.
export type SettingsSearchTab =
| 'general'
| 'plugs'
| 'notifications'
| 'queue'
| 'filament'
| 'network'
| 'apikeys'
| 'virtual-printer'
| 'spoolbuddy'
| 'users'
| 'backup'
| 'failure-detection';
export type SettingsSearchSubTab = 'users' | 'email' | 'ldap' | 'oidc' | 'twofa';
export interface SettingsSearchEntry {
/** i18n key for the label. Resolved with t() at render time. */
labelKey: string;
/** Fallback label if the i18n key is missing. */
labelFallback?: string;
tab: SettingsSearchTab;
subTab?: SettingsSearchSubTab;
/** Space-separated extra search terms (lowercase). */
keywords: string;
/** DOM id attached to the target card — used for scrollIntoView. */
anchor: string;
}
const entries = new Map<string, SettingsSearchEntry>();
export function registerSettingsSearch(entry: SettingsSearchEntry): void {
entries.set(entry.anchor, entry);
}
export function getSettingsSearchEntries(): SettingsSearchEntry[] {
return Array.from(entries.values());
}
+75 -58
View File
@@ -39,11 +39,78 @@ import { useToast } from '../contexts/ToastContext';
import { useTheme, type ThemeStyle, type DarkBackground, type LightBackground, type ThemeAccent } from '../contexts/ThemeContext';
import { useState, useEffect, useRef, useCallback } from 'react';
import { Palette } from 'lucide-react';
import { registerSettingsSearch, getSettingsSearchEntries } from '../lib/settingsSearch';
const validTabs = ['general', 'plugs', 'notifications', 'queue', 'filament', 'network', 'apikeys', 'virtual-printer', 'spoolbuddy', 'failure-detection', 'users', 'backup'] as const;
type TabType = typeof validTabs[number];
type UsersSubTab = 'users' | 'email' | 'ldap' | 'twofa' | 'oidc';
// Cross-tab search registrations for cards rendered inline in this file.
// Adding a new settings card? Register it here (or, if the card lives in its
// own component file, call registerSettingsSearch at that file's module scope).
registerSettingsSearch({ labelKey: 'settings.general', tab: 'general', keywords: 'language date time format printer model printers cards', anchor: 'card-general' });
registerSettingsSearch({ labelKey: 'settings.appearance', tab: 'general', keywords: 'theme dark light mode colors', anchor: 'card-appearance' });
registerSettingsSearch({ labelKey: 'settings.archiveSettings', tab: 'general', keywords: 'archive auto save thumbnails captures', anchor: 'card-archive' });
registerSettingsSearch({ labelKey: 'settings.camera', tab: 'general', keywords: 'camera external video stream', anchor: 'card-camera' });
registerSettingsSearch({ labelKey: 'settings.costTracking', tab: 'general', keywords: 'currency filament cost energy kwh price', anchor: 'card-cost' });
registerSettingsSearch({ labelKey: 'settings.fileManager', tab: 'general', keywords: 'file manager archive mode disk warning storage', anchor: 'card-filemanager' });
registerSettingsSearch({ labelKey: 'settings.updates', tab: 'general', keywords: 'updates version firmware beta check', anchor: 'card-updates' });
registerSettingsSearch({ labelKey: 'settings.dataManagement', tab: 'general', keywords: 'data reset clear logs notifications preferences', anchor: 'card-data' });
registerSettingsSearch({ labelKey: 'settings.smartPlugs', tab: 'plugs', keywords: 'smart plug energy power automation tapo kasa tplink shelly', anchor: 'card-plugs' });
registerSettingsSearch({ labelKey: 'settings.providers', tab: 'notifications', keywords: 'telegram discord email notification providers webhook', anchor: 'card-providers' });
registerSettingsSearch({ labelKey: 'settings.messageTemplates', tab: 'notifications', keywords: 'message templates notification text edit', anchor: 'card-templates' });
registerSettingsSearch({ labelKey: 'settings.defaultPrintOptions', labelFallback: 'Default Print Options', tab: 'queue', keywords: 'print bed leveling flow calibration vibration first layer timelapse', anchor: 'card-print-options' });
registerSettingsSearch({ labelKey: 'settings.staggeredStart', labelFallback: 'Staggered Start', tab: 'queue', keywords: 'staggered batch delay start queue group', anchor: 'card-staggered' });
registerSettingsSearch({ labelKey: 'settings.plateClear', labelFallback: 'Plate-Clear Confirmation', tab: 'queue', keywords: 'plate clear confirm auto queue', anchor: 'card-plate' });
registerSettingsSearch({ labelKey: 'settings.gcodeInjection', labelFallback: 'G-code Injection', tab: 'queue', keywords: 'gcode injection start end autoprint farmloop swapmod autoclear printflow', anchor: 'card-gcode' });
registerSettingsSearch({ labelKey: 'settings.queueDrying', tab: 'queue', keywords: 'drying presets temperature time humidity ams', anchor: 'card-drying' });
registerSettingsSearch({ labelKey: 'settings.filamentChecks', tab: 'filament', keywords: 'filament check warning runout remaining', anchor: 'card-filamentchecks' });
registerSettingsSearch({ labelKey: 'settings.printModal', tab: 'filament', keywords: 'print modal custom mapping', anchor: 'card-printmodal' });
registerSettingsSearch({ labelKey: 'settings.amsDisplayThresholds', tab: 'filament', keywords: 'ams humidity temperature threshold history retention', anchor: 'card-amsthresholds' });
registerSettingsSearch({ labelKey: 'settings.externalUrl', tab: 'network', keywords: 'external url reverse proxy public notification link', anchor: 'card-externalurl' });
registerSettingsSearch({ labelKey: 'settings.ftpRetry', tab: 'network', keywords: 'ftp retry upload retries backoff', anchor: 'card-ftpretry' });
registerSettingsSearch({ labelKey: 'settings.homeAssistant', tab: 'network', keywords: 'home assistant ha hass mqtt integration', anchor: 'card-ha' });
registerSettingsSearch({ labelKey: 'settings.mqttPublishing', tab: 'network', keywords: 'mqtt publish broker topic', anchor: 'card-mqtt' });
registerSettingsSearch({ labelKey: 'settings.prometheusMetrics', tab: 'network', keywords: 'prometheus metrics grafana monitoring bearer token', anchor: 'card-prometheus' });
registerSettingsSearch({ labelKey: 'settings.createNewApiKey', tab: 'apikeys', keywords: 'api key create permission scope', anchor: 'card-createapi' });
registerSettingsSearch({ labelKey: 'settings.webhookEndpoints', tab: 'apikeys', keywords: 'webhook endpoint post http', anchor: 'card-webhooks' });
registerSettingsSearch({ labelKey: 'settings.apiBrowser', tab: 'apikeys', keywords: 'api browser endpoint documentation test', anchor: 'card-apibrowser' });
registerSettingsSearch({ labelKey: 'settings.tabs.virtualPrinter', tab: 'virtual-printer', keywords: 'virtual printer proxy archive slicer bambustudio orcaslicer ip bind', anchor: 'card-vp' });
registerSettingsSearch({ labelKey: 'settings.tabs.spoolbuddy', tab: 'spoolbuddy', keywords: 'spoolbuddy device scale nfc rfid kiosk unregister', anchor: 'card-spoolbuddy' });
registerSettingsSearch({ labelKey: 'settings.currentUser', tab: 'users', subTab: 'users', keywords: 'current user profile password change', anchor: 'card-currentuser' });
registerSettingsSearch({ labelKey: 'settings.users', tab: 'users', subTab: 'users', keywords: 'users accounts list', anchor: 'card-users' });
registerSettingsSearch({ labelKey: 'settings.groups', tab: 'users', subTab: 'users', keywords: 'groups roles permissions administrators operators viewers', anchor: 'card-groups' });
registerSettingsSearch({ labelKey: 'settings.email.smtpSettings', labelFallback: 'SMTP Configuration', tab: 'users', subTab: 'email', keywords: 'smtp email send server port password auth starttls ssl', anchor: 'card-smtp' });
registerSettingsSearch({ labelKey: 'settings.ldap.title', labelFallback: 'LDAP Authentication', tab: 'users', subTab: 'ldap', keywords: 'ldap active directory ad authentication bind dn search base group mapping', anchor: 'card-ldap' });
registerSettingsSearch({ labelKey: 'settings.tabs.backup', tab: 'backup', keywords: 'backup github restore download cloud sync profiles archives', anchor: 'card-backup' });
// Sidebar Links (external links settings is rendered in the General tab)
registerSettingsSearch({ labelKey: 'externalLinks.title', labelFallback: 'Sidebar Links', tab: 'general', keywords: 'sidebar links external custom navigation url add', anchor: 'card-sidebar-links' });
// Filament tab — integrations
registerSettingsSearch({ labelKey: 'settings.filamentTracking', tab: 'filament', keywords: 'spoolman filament tracking inventory sync remote integration', anchor: 'card-spoolman' });
registerSettingsSearch({ labelKey: 'settings.catalog.spoolCatalog', labelFallback: 'Spool Catalog', tab: 'filament', keywords: 'spool catalog entries brand material reset import export', anchor: 'card-spool-catalog' });
registerSettingsSearch({ labelKey: 'settings.colorCatalog.title', labelFallback: 'Color Catalog', tab: 'filament', keywords: 'color catalog hex swatch palette sync reset', anchor: 'card-color-catalog' });
// Failure detection sub-cards
registerSettingsSearch({ labelKey: 'settings.tabs.failureDetection', labelFallback: 'Failure Detection', tab: 'failure-detection', keywords: 'failure detection ai ml obico spaghetti detect monitoring', anchor: 'card-fd-ml' });
registerSettingsSearch({ labelKey: 'failureDetection.perPrinterTitle', labelFallback: 'Per-Printer Settings', tab: 'failure-detection', keywords: 'failure detection per printer enable per-printer sensitivity', anchor: 'card-fd-perprinter' });
registerSettingsSearch({ labelKey: 'failureDetection.statusTitle', labelFallback: 'Detection Status', tab: 'failure-detection', keywords: 'failure detection status running connection', anchor: 'card-fd-status' });
registerSettingsSearch({ labelKey: 'failureDetection.historyTitle', labelFallback: 'Detection History', tab: 'failure-detection', keywords: 'failure detection history log events', anchor: 'card-fd-history' });
// Email auth sub-cards (subTab=email)
registerSettingsSearch({ labelKey: 'settings.email.advancedAuth', labelFallback: 'Advanced Email Authentication', tab: 'users', subTab: 'email', keywords: 'email authentication advanced password reset self-service forgot', anchor: 'card-email-advanced-auth' });
registerSettingsSearch({ labelKey: 'settings.email.testConnection', labelFallback: 'Test SMTP Connection', tab: 'users', subTab: 'email', keywords: 'email smtp test connection send check', anchor: 'card-email-test' });
// Two-Factor sub-cards (subTab=twofa)
registerSettingsSearch({ labelKey: 'settings.twoFa.totpTitle', labelFallback: 'Authenticator App (TOTP)', tab: 'users', subTab: 'twofa', keywords: 'two factor 2fa totp authenticator app google authy otp', anchor: 'card-2fa-totp' });
registerSettingsSearch({ labelKey: 'settings.twoFa.emailOtpTitle', labelFallback: 'Email One-Time Codes', tab: 'users', subTab: 'twofa', keywords: 'two factor 2fa email otp one time code', anchor: 'card-2fa-emailotp' });
registerSettingsSearch({ labelKey: 'settings.twoFa.linkedAccounts', labelFallback: 'Linked Accounts', tab: 'users', subTab: 'twofa', keywords: 'two factor 2fa linked accounts sso oidc provider google github', anchor: 'card-2fa-linked' });
// OIDC / SSO (subTab=oidc)
registerSettingsSearch({ labelKey: 'settings.oidc.title', labelFallback: 'Single Sign-On (OIDC)', tab: 'users', subTab: 'oidc', keywords: 'sso oidc openid single sign-on pocketid authentik keycloak google okta azure provider', anchor: 'card-oidc' });
// LDAP server config card (complements existing card-ldap)
registerSettingsSearch({ labelKey: 'settings.ldap.serverConfig', labelFallback: 'LDAP Server Configuration', tab: 'users', subTab: 'ldap', keywords: 'ldap server url bind dn user search base group filter tls', anchor: 'card-ldap-server' });
// Backup sub-cards
registerSettingsSearch({ labelKey: 'backup.githubBackup', labelFallback: 'GitHub Backup', tab: 'backup', keywords: 'github backup cloud remote sync profiles token', anchor: 'card-backup-github' });
registerSettingsSearch({ labelKey: 'backup.history', labelFallback: 'Backup History', tab: 'backup', keywords: 'backup history log runs github commits', anchor: 'card-backup-history' });
registerSettingsSearch({ labelKey: 'backup.localBackup', labelFallback: 'Local Backup', tab: 'backup', keywords: 'local backup download zip manual export', anchor: 'card-backup-local' });
registerSettingsSearch({ labelKey: 'backup.scheduledBackup', labelFallback: 'Scheduled Backups', tab: 'backup', keywords: 'scheduled backup automatic hourly daily weekly retention local path', anchor: 'card-backup-scheduled' });
const STORAGE_CATEGORY_COLORS: Record<string, string> = {
database: 'bg-blue-600',
library_files: 'bg-green-500',
@@ -999,62 +1066,12 @@ export function SettingsPage() {
);
}
// Cross-tab settings search index. Keep labels short — matched against user's query.
// anchor is the DOM id attached to the target card (also wired to scrollIntoView).
const searchIndex: Array<{
label: string;
tab: TabType;
subTab?: UsersSubTab;
keywords: string;
anchor: string;
}> = [
// General
{ label: t('settings.general'), tab: 'general', keywords: 'language date time format printer model printers cards', anchor: 'card-general' },
{ label: t('settings.appearance'), tab: 'general', keywords: 'theme dark light mode colors', anchor: 'card-appearance' },
{ label: t('settings.archiveSettings'), tab: 'general', keywords: 'archive auto save thumbnails captures', anchor: 'card-archive' },
{ label: t('settings.camera'), tab: 'general', keywords: 'camera external video stream', anchor: 'card-camera' },
{ label: t('settings.costTracking'), tab: 'general', keywords: 'currency filament cost energy kwh price', anchor: 'card-cost' },
{ label: t('settings.fileManager'), tab: 'general', keywords: 'file manager archive mode disk warning storage', anchor: 'card-filemanager' },
{ label: t('settings.updates'), tab: 'general', keywords: 'updates version firmware beta check', anchor: 'card-updates' },
{ label: t('settings.dataManagement'), tab: 'general', keywords: 'data reset clear logs notifications preferences', anchor: 'card-data' },
// Smart Plugs
{ label: t('settings.smartPlugs'), tab: 'plugs', keywords: 'smart plug energy power automation tapo kasa tplink shelly', anchor: 'card-plugs' },
// Notifications
{ label: t('settings.providers'), tab: 'notifications', keywords: 'telegram discord email notification providers webhook', anchor: 'card-providers' },
{ label: t('settings.messageTemplates'), tab: 'notifications', keywords: 'message templates notification text edit', anchor: 'card-templates' },
// Queue / Workflow
{ label: t('settings.defaultPrintOptions', 'Default Print Options'), tab: 'queue', keywords: 'print bed leveling flow calibration vibration first layer timelapse', anchor: 'card-print-options' },
{ label: t('settings.staggeredStart', 'Staggered Start'), tab: 'queue', keywords: 'staggered batch delay start queue group', anchor: 'card-staggered' },
{ label: t('settings.plateClear', 'Plate-Clear Confirmation'), tab: 'queue', keywords: 'plate clear confirm auto queue', anchor: 'card-plate' },
{ label: t('settings.gcodeInjection', 'G-code Injection'), tab: 'queue', keywords: 'gcode injection start end autoprint farmloop swapmod autoclear printflow', anchor: 'card-gcode' },
{ label: t('settings.queueDrying'), tab: 'queue', keywords: 'drying presets temperature time humidity ams', anchor: 'card-drying' },
// Filament
{ label: t('settings.filamentChecks'), tab: 'filament', keywords: 'filament check warning runout remaining', anchor: 'card-filamentchecks' },
{ label: t('settings.printModal'), tab: 'filament', keywords: 'print modal custom mapping', anchor: 'card-printmodal' },
{ label: t('settings.amsDisplayThresholds'), tab: 'filament', keywords: 'ams humidity temperature threshold history retention', anchor: 'card-amsthresholds' },
// Network
{ label: t('settings.externalUrl'), tab: 'network', keywords: 'external url reverse proxy public notification link', anchor: 'card-externalurl' },
{ label: t('settings.ftpRetry'), tab: 'network', keywords: 'ftp retry upload retries backoff', anchor: 'card-ftpretry' },
{ label: t('settings.homeAssistant'), tab: 'network', keywords: 'home assistant ha hass mqtt integration', anchor: 'card-ha' },
{ label: t('settings.mqttPublishing'), tab: 'network', keywords: 'mqtt publish broker topic', anchor: 'card-mqtt' },
{ label: t('settings.prometheusMetrics'), tab: 'network', keywords: 'prometheus metrics grafana monitoring bearer token', anchor: 'card-prometheus' },
// API Keys
{ label: t('settings.createNewApiKey'), tab: 'apikeys', keywords: 'api key create permission scope', anchor: 'card-createapi' },
{ label: t('settings.webhookEndpoints'), tab: 'apikeys', keywords: 'webhook endpoint post http', anchor: 'card-webhooks' },
{ label: t('settings.apiBrowser'), tab: 'apikeys', keywords: 'api browser endpoint documentation test', anchor: 'card-apibrowser' },
// Virtual Printer
{ label: t('settings.tabs.virtualPrinter'), tab: 'virtual-printer', keywords: 'virtual printer proxy archive slicer bambustudio orcaslicer ip bind', anchor: 'card-vp' },
// SpoolBuddy
{ label: t('settings.tabs.spoolbuddy'), tab: 'spoolbuddy', keywords: 'spoolbuddy device scale nfc rfid kiosk unregister', anchor: 'card-spoolbuddy' },
// Users / Auth
{ label: t('settings.currentUser'), tab: 'users', subTab: 'users', keywords: 'current user profile password change', anchor: 'card-currentuser' },
{ label: t('settings.users'), tab: 'users', subTab: 'users', keywords: 'users accounts list', anchor: 'card-users' },
{ label: t('settings.groups'), tab: 'users', subTab: 'users', keywords: 'groups roles permissions administrators operators viewers', anchor: 'card-groups' },
{ label: t('settings.email.smtpSettings', 'SMTP Configuration'), tab: 'users', subTab: 'email', keywords: 'smtp email send server port password auth starttls ssl', anchor: 'card-smtp' },
{ label: t('settings.ldap.title', 'LDAP Authentication'), tab: 'users', subTab: 'ldap', keywords: 'ldap active directory ad authentication bind dn search base group mapping', anchor: 'card-ldap' },
// Backup
{ label: t('settings.tabs.backup'), tab: 'backup', keywords: 'backup github restore download cloud sync profiles archives', anchor: 'card-backup' },
];
// Cross-tab search is powered by the module-level registry in lib/settingsSearch.
// Resolve i18n labels here so language changes take effect without re-registering.
const searchIndex = getSettingsSearchEntries().map(e => ({
...e,
label: t(e.labelKey, e.labelFallback ?? e.labelKey),
}));
const searchQuery = settingsSearch.trim().toLowerCase();
const searchResults = searchQuery
@@ -1066,9 +1083,9 @@ export function SettingsPage() {
: [];
const jumpToSetting = (entry: typeof searchIndex[number]) => {
handleTabChange(entry.tab);
handleTabChange(entry.tab as TabType);
if (entry.subTab) {
setUsersSubTab(entry.subTab);
setUsersSubTab(entry.subTab as UsersSubTab);
}
setSettingsSearch('');
// Scroll to the card after the tab has rendered