Browse Source

Improve settings menu layout - 2

maziggy 1 month ago
parent
commit
da080c22d3

+ 8 - 3
frontend/src/components/Card.tsx

@@ -14,10 +14,15 @@ interface CardProps extends HTMLAttributes<HTMLDivElement> {
   className?: string;
   className?: string;
   onClick?: (e: MouseEvent) => void;
   onClick?: (e: MouseEvent) => void;
   onContextMenu?: (e: MouseEvent) => void;
   onContextMenu?: (e: MouseEvent) => void;
+}
+
+interface CardSectionProps {
+  children: ReactNode;
+  className?: string;
   dense?: boolean;
   dense?: boolean;
 }
 }
 
 
-export function Card({ children, className = '', onClick, onContextMenu, dense: _dense, ...rest }: CardProps) {
+export function Card({ children, className = '', onClick, onContextMenu, ...rest }: CardProps) {
   return (
   return (
     <div
     <div
       className={`bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary card-shadow ${className}`}
       className={`bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary card-shadow ${className}`}
@@ -30,7 +35,7 @@ export function Card({ children, className = '', onClick, onContextMenu, dense:
   );
   );
 }
 }
 
 
-export function CardHeader({ children, className = '', dense }: CardProps) {
+export function CardHeader({ children, className = '', dense }: CardSectionProps) {
   const ctxDense = useContext(CardDensityContext) === 'dense';
   const ctxDense = useContext(CardDensityContext) === 'dense';
   const isDense = dense ?? ctxDense;
   const isDense = dense ?? ctxDense;
   const padding = isDense ? 'px-4 py-2.5' : 'px-6 py-4';
   const padding = isDense ? 'px-4 py-2.5' : 'px-6 py-4';
@@ -41,7 +46,7 @@ export function CardHeader({ children, className = '', dense }: CardProps) {
   );
   );
 }
 }
 
 
-export function CardContent({ children, className = '', dense }: CardProps) {
+export function CardContent({ children, className = '', dense }: CardSectionProps) {
   const ctxDense = useContext(CardDensityContext) === 'dense';
   const ctxDense = useContext(CardDensityContext) === 'dense';
   const isDense = dense ?? ctxDense;
   const isDense = dense ?? ctxDense;
   const padding = isDense ? 'p-4' : 'p-6';
   const padding = isDense ? 'p-4' : 'p-6';

+ 232 - 56
frontend/src/pages/SettingsPage.tsx

@@ -1,5 +1,5 @@
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { Loader2, Plus, Plug, AlertTriangle, RotateCcw, Bell, Download, RefreshCw, ExternalLink, Globe, Droplets, Thermometer, FileText, Edit2, Send, CheckCircle, XCircle, History, Trash2, Zap, TrendingUp, Calendar, DollarSign, Power, PowerOff, Key, Copy, Database, X, Shield, Printer, Cylinder, Wifi, Home, Video, Users, Lock, Unlock, ChevronDown, Save, Mail, Flame, Layers, ListOrdered, Code } from 'lucide-react';
+import { Loader2, Plus, Plug, AlertTriangle, RotateCcw, Bell, Download, RefreshCw, ExternalLink, Globe, Droplets, Thermometer, FileText, Edit2, Send, CheckCircle, XCircle, History, Trash2, Zap, TrendingUp, Calendar, DollarSign, Power, PowerOff, Key, Copy, Database, X, Shield, Printer, Cylinder, Wifi, Home, Video, Users, Lock, Unlock, ChevronDown, Save, Mail, Flame, Layers, ListOrdered, Code, Search, Settings as SettingsIcon } from 'lucide-react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import { useNavigate, useSearchParams } from 'react-router-dom';
 import { useNavigate, useSearchParams } from 'react-router-dom';
 import { api } from '../api/client';
 import { api } from '../api/client';
@@ -92,6 +92,8 @@ export function SettingsPage() {
   const [showNotificationModal, setShowNotificationModal] = useState(false);
   const [showNotificationModal, setShowNotificationModal] = useState(false);
   const [editingProvider, setEditingProvider] = useState<NotificationProvider | null>(null);
   const [editingProvider, setEditingProvider] = useState<NotificationProvider | null>(null);
   const [editingTemplate, setEditingTemplate] = useState<NotificationTemplate | null>(null);
   const [editingTemplate, setEditingTemplate] = useState<NotificationTemplate | null>(null);
+  const [templateFilter, setTemplateFilter] = useState('');
+  const [settingsSearch, setSettingsSearch] = useState('');
   const [showLogViewer, setShowLogViewer] = useState(false);
   const [showLogViewer, setShowLogViewer] = useState(false);
   const [defaultView, setDefaultViewState] = useState<string>(getDefaultView());
   const [defaultView, setDefaultViewState] = useState<string>(getDefaultView());
 
 
@@ -964,29 +966,156 @@ 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' },
+    // 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' },
+  ];
+
+  const searchQuery = settingsSearch.trim().toLowerCase();
+  const searchResults = searchQuery
+    ? searchIndex.filter(
+        e =>
+          e.label.toLowerCase().includes(searchQuery) ||
+          e.keywords.toLowerCase().includes(searchQuery)
+      ).slice(0, 8)
+    : [];
+
+  const jumpToSetting = (entry: typeof searchIndex[number]) => {
+    handleTabChange(entry.tab);
+    if (entry.subTab) {
+      setUsersSubTab(entry.subTab);
+    }
+    setSettingsSearch('');
+    // Scroll to the card after the tab has rendered
+    setTimeout(() => {
+      const el = document.getElementById(entry.anchor);
+      if (el) {
+        el.scrollIntoView({ behavior: 'smooth', block: 'start' });
+        el.classList.add('ring-2', 'ring-bambu-green');
+        setTimeout(() => el.classList.remove('ring-2', 'ring-bambu-green'), 1500);
+      }
+    }, 50);
+  };
+
   return (
   return (
     <CardDensityProvider density="dense">
     <CardDensityProvider density="dense">
     <div className="p-4 md:p-6">
     <div className="p-4 md:p-6">
-      <div className="mb-4 flex items-baseline gap-3">
-        <h1 className="text-2xl font-bold text-white">{t('settings.title')}</h1>
-        <p className="text-sm text-bambu-gray hidden sm:block">{t('settings.configureBambuddy')}</p>
+      <div className="mb-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
+        <div className="flex items-baseline gap-3">
+          <h1 className="text-2xl font-bold text-white">{t('settings.title')}</h1>
+          <p className="text-sm text-bambu-gray hidden md:block">{t('settings.configureBambuddy')}</p>
+        </div>
+        {/* Cross-tab search */}
+        <div className="relative sm:w-72">
+          <Search className="w-4 h-4 text-bambu-gray absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" />
+          <input
+            type="text"
+            value={settingsSearch}
+            onChange={(e) => setSettingsSearch(e.target.value)}
+            placeholder={t('settings.searchPlaceholder', 'Search settings…')}
+            className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:outline-none focus:border-bambu-green"
+          />
+          {settingsSearch && (
+            <button
+              onClick={() => setSettingsSearch('')}
+              className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-bambu-gray hover:text-white"
+              aria-label="Clear"
+            >
+              <X className="w-3.5 h-3.5" />
+            </button>
+          )}
+          {searchResults.length > 0 && (
+            <div className="absolute top-full left-0 right-0 mt-1 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg shadow-xl z-30 overflow-hidden">
+              {searchResults.map((entry) => (
+                <button
+                  key={entry.anchor}
+                  onClick={() => jumpToSetting(entry)}
+                  className="w-full px-3 py-2 text-left hover:bg-bambu-dark-tertiary transition-colors border-b border-bambu-dark-tertiary last:border-b-0"
+                >
+                  <p className="text-sm text-white">{entry.label}</p>
+                  <p className="text-xs text-bambu-gray">
+                    {t(`settings.tabs.${entry.tab === 'virtual-printer' ? 'virtualPrinter' : entry.tab}`)}
+                    {entry.subTab ? ` › ${t(`settings.tabs.${entry.subTab}`, entry.subTab)}` : ''}
+                  </p>
+                </button>
+              ))}
+            </div>
+          )}
+          {searchQuery && searchResults.length === 0 && (
+            <div className="absolute top-full left-0 right-0 mt-1 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg shadow-xl z-30 p-3">
+              <p className="text-xs text-bambu-gray italic">{t('settings.noSearchResults', 'No matching settings.')}</p>
+            </div>
+          )}
+        </div>
       </div>
       </div>
 
 
-      {/* Tab Navigation */}
-      <div className="flex flex-wrap gap-1 mb-4 border-b border-bambu-dark-tertiary">
+      {/* Tab Navigation + content: horizontal tabs on mobile, vertical rail on lg+ */}
+      <div className="flex flex-col lg:flex-row gap-4 lg:gap-6">
+      <nav className="flex flex-wrap gap-1 border-b border-bambu-dark-tertiary lg:flex-col lg:flex-nowrap lg:gap-0 lg:border-b-0 lg:border-r lg:w-48 lg:flex-shrink-0 lg:self-start lg:sticky lg:top-4">
         <button
         <button
           onClick={() => handleTabChange('general')}
           onClick={() => handleTabChange('general')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'general'
             activeTab === 'general'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
           }`}
           }`}
         >
         >
+          <SettingsIcon className="w-4 h-4" />
           {t('settings.tabs.general')}
           {t('settings.tabs.general')}
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('plugs')}
           onClick={() => handleTabChange('plugs')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'plugs'
             activeTab === 'plugs'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1002,7 +1131,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('notifications')}
           onClick={() => handleTabChange('notifications')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'notifications'
             activeTab === 'notifications'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1018,7 +1147,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('queue')}
           onClick={() => handleTabChange('queue')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'queue'
             activeTab === 'queue'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1029,7 +1158,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('filament')}
           onClick={() => handleTabChange('filament')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'filament'
             activeTab === 'filament'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1040,7 +1169,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('network')}
           onClick={() => handleTabChange('network')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'network'
             activeTab === 'network'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1052,7 +1181,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('apikeys')}
           onClick={() => handleTabChange('apikeys')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'apikeys'
             activeTab === 'apikeys'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1068,7 +1197,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('virtual-printer')}
           onClick={() => handleTabChange('virtual-printer')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'virtual-printer'
             activeTab === 'virtual-printer'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1080,7 +1209,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('users')}
           onClick={() => handleTabChange('users')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'users'
             activeTab === 'users'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1094,7 +1223,7 @@ export function SettingsPage() {
         </button>
         </button>
         <button
         <button
           onClick={() => handleTabChange('backup')}
           onClick={() => handleTabChange('backup')}
-          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+          className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
             activeTab === 'backup'
             activeTab === 'backup'
               ? 'text-bambu-green border-bambu-green'
               ? 'text-bambu-green border-bambu-green'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
               : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -1104,12 +1233,13 @@ export function SettingsPage() {
           {t('settings.tabs.backup')}
           {t('settings.tabs.backup')}
           <span className={`w-2 h-2 rounded-full ${cloudAuthStatus?.is_authenticated && githubBackupStatus?.configured && githubBackupStatus?.enabled ? 'bg-green-400' : 'bg-gray-500'}`} />
           <span className={`w-2 h-2 rounded-full ${cloudAuthStatus?.is_authenticated && githubBackupStatus?.configured && githubBackupStatus?.enabled ? 'bg-green-400' : 'bg-gray-500'}`} />
         </button>
         </button>
-      </div>
+      </nav>
+      <div className="flex-1 min-w-0">
       {activeTab === 'general' && (
       {activeTab === 'general' && (
       <div className="flex flex-col lg:flex-row gap-4 lg:gap-6">
       <div className="flex flex-col lg:flex-row gap-4 lg:gap-6">
         {/* Left Column - General Settings */}
         {/* Left Column - General Settings */}
         <div className="space-y-3 flex-1 lg:max-w-xl">
         <div className="space-y-3 flex-1 lg:max-w-xl">
-          <Card>
+          <Card id="card-general">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white">{t('settings.general')}</h2>
               <h2 className="text-lg font-semibold text-white">{t('settings.general')}</h2>
             </CardHeader>
             </CardHeader>
@@ -1270,7 +1400,7 @@ export function SettingsPage() {
             </CardContent>
             </CardContent>
           </Card>
           </Card>
 
 
-          <Card>
+          <Card id="card-appearance">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <Palette className="w-5 h-5" />
                 <Palette className="w-5 h-5" />
@@ -1385,7 +1515,7 @@ export function SettingsPage() {
             </CardContent>
             </CardContent>
           </Card>
           </Card>
 
 
-          <Card>
+          <Card id="card-archive">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white">{t('settings.archiveSettings')}</h2>
               <h2 className="text-lg font-semibold text-white">{t('settings.archiveSettings')}</h2>
             </CardHeader>
             </CardHeader>
@@ -1460,7 +1590,7 @@ export function SettingsPage() {
         {/* Second Column - Camera, Cost, AMS & Spoolman */}
         {/* Second Column - Camera, Cost, AMS & Spoolman */}
         <div className="space-y-3 flex-1 lg:max-w-md">
         <div className="space-y-3 flex-1 lg:max-w-md">
           {/* Camera Settings */}
           {/* Camera Settings */}
-          <Card>
+          <Card id="card-camera">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <Video className="w-5 h-5 text-bambu-green" />
                 <Video className="w-5 h-5 text-bambu-green" />
@@ -1585,7 +1715,7 @@ export function SettingsPage() {
           </Card>
           </Card>
 
 
 
 
-          <Card>
+          <Card id="card-cost">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white">{t('settings.costTracking')}</h2>
               <h2 className="text-lg font-semibold text-white">{t('settings.costTracking')}</h2>
             </CardHeader>
             </CardHeader>
@@ -1666,7 +1796,7 @@ export function SettingsPage() {
           </Card>
           </Card>
 
 
           {/* File Manager Settings */}
           {/* File Manager Settings */}
-          <Card>
+          <Card id="card-filemanager">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <FileText className="w-5 h-5 text-bambu-green" />
                 <FileText className="w-5 h-5 text-bambu-green" />
@@ -1723,7 +1853,7 @@ export function SettingsPage() {
           {/* Sidebar Links */}
           {/* Sidebar Links */}
           <ExternalLinksSettings />
           <ExternalLinksSettings />
 
 
-          <Card>
+          <Card id="card-updates">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white">{t('settings.updates')}</h2>
               <h2 className="text-lg font-semibold text-white">{t('settings.updates')}</h2>
             </CardHeader>
             </CardHeader>
@@ -1898,7 +2028,7 @@ export function SettingsPage() {
           </Card>
           </Card>
 
 
           {/* Data Management */}
           {/* Data Management */}
-          <Card>
+          <Card id="card-data">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white">{t('settings.dataManagement')}</h2>
               <h2 className="text-lg font-semibold text-white">{t('settings.dataManagement')}</h2>
             </CardHeader>
             </CardHeader>
@@ -2064,7 +2194,7 @@ export function SettingsPage() {
         {/* Left Column - External URL & FTP Retry */}
         {/* Left Column - External URL & FTP Retry */}
         <div className="flex-1 lg:max-w-xl space-y-3">
         <div className="flex-1 lg:max-w-xl space-y-3">
           {/* External URL */}
           {/* External URL */}
-          <Card>
+          <Card id="card-externalurl">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <Globe className="w-5 h-5 text-blue-400" />
                 <Globe className="w-5 h-5 text-blue-400" />
@@ -2093,7 +2223,7 @@ export function SettingsPage() {
             </CardContent>
             </CardContent>
           </Card>
           </Card>
 
 
-          <Card>
+          <Card id="card-ftpretry">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <RefreshCw className="w-5 h-5 text-blue-400" />
                 <RefreshCw className="w-5 h-5 text-blue-400" />
@@ -2190,7 +2320,7 @@ export function SettingsPage() {
         {/* Right Column - Home Assistant & MQTT Publishing */}
         {/* Right Column - Home Assistant & MQTT Publishing */}
         <div className="flex-1 lg:max-w-xl space-y-3">
         <div className="flex-1 lg:max-w-xl space-y-3">
           {/* Home Assistant Integration */}
           {/* Home Assistant Integration */}
-          <Card>
+          <Card id="card-ha">
             <CardHeader>
             <CardHeader>
               <div className="flex items-center justify-between">
               <div className="flex items-center justify-between">
                 <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <h2 className="text-lg font-semibold text-white flex items-center gap-2">
@@ -2337,7 +2467,7 @@ export function SettingsPage() {
           </Card>
           </Card>
 
 
           {/* MQTT Publishing */}
           {/* MQTT Publishing */}
-          <Card>
+          <Card id="card-mqtt">
             <CardHeader>
             <CardHeader>
               <div className="flex items-center justify-between">
               <div className="flex items-center justify-between">
                 <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <h2 className="text-lg font-semibold text-white flex items-center gap-2">
@@ -2495,7 +2625,7 @@ export function SettingsPage() {
 
 
         {/* Third Column - Prometheus Metrics */}
         {/* Third Column - Prometheus Metrics */}
         <div className="flex-1 lg:max-w-md space-y-3">
         <div className="flex-1 lg:max-w-md space-y-3">
-          <Card>
+          <Card id="card-prometheus">
             <CardHeader>
             <CardHeader>
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
                 <TrendingUp className="w-5 h-5 text-orange-400" />
                 <TrendingUp className="w-5 h-5 text-orange-400" />
@@ -2594,7 +2724,7 @@ export function SettingsPage() {
 
 
       {/* Smart Plugs Tab */}
       {/* Smart Plugs Tab */}
       {activeTab === 'plugs' && (
       {activeTab === 'plugs' && (
-        <div>
+        <div id="card-plugs">
           <div className="flex items-start justify-between mb-6">
           <div className="flex items-start justify-between mb-6">
             <div>
             <div>
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
               <h2 className="text-lg font-semibold text-white flex items-center gap-2">
@@ -2789,7 +2919,7 @@ export function SettingsPage() {
           {/* Left Column: Providers */}
           {/* Left Column: Providers */}
           <div>
           <div>
             <div className="flex items-center justify-between mb-4">
             <div className="flex items-center justify-between mb-4">
-              <h2 className="text-lg font-semibold text-white flex items-center gap-2">
+              <h2 className="text-lg font-semibold text-white flex items-center gap-2" id="card-providers">
                 <Bell className="w-5 h-5 text-bambu-green" />
                 <Bell className="w-5 h-5 text-bambu-green" />
                 {t('settings.providers')}
                 {t('settings.providers')}
               </h2>
               </h2>
@@ -2986,21 +3116,59 @@ export function SettingsPage() {
 
 
           {/* Right Column: Templates */}
           {/* Right Column: Templates */}
           <div>
           <div>
-            <h2 className="text-lg font-semibold text-white flex items-center gap-2 mb-4">
+            <h2 className="text-lg font-semibold text-white flex items-center gap-2 mb-2" id="card-templates">
               <FileText className="w-5 h-5 text-bambu-green" />
               <FileText className="w-5 h-5 text-bambu-green" />
               {t('settings.messageTemplates')}
               {t('settings.messageTemplates')}
             </h2>
             </h2>
-            <p className="text-sm text-bambu-gray mb-4">
+            <p className="text-sm text-bambu-gray mb-3">
               {t('settings.messageTemplatesDescription')}
               {t('settings.messageTemplatesDescription')}
             </p>
             </p>
 
 
+            {/* Filter input */}
+            <div className="relative mb-3">
+              <Search className="w-4 h-4 text-bambu-gray absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" />
+              <input
+                type="text"
+                value={templateFilter}
+                onChange={(e) => setTemplateFilter(e.target.value)}
+                placeholder={t('settings.filterTemplates', 'Filter templates…')}
+                className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:outline-none focus:border-bambu-green"
+              />
+              {templateFilter && (
+                <button
+                  onClick={() => setTemplateFilter('')}
+                  className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-bambu-gray hover:text-white"
+                  aria-label="Clear filter"
+                >
+                  <X className="w-3.5 h-3.5" />
+                </button>
+              )}
+            </div>
+
             {templatesLoading ? (
             {templatesLoading ? (
               <div className="flex justify-center py-8">
               <div className="flex justify-center py-8">
                 <Loader2 className="w-6 h-6 text-bambu-green animate-spin" />
                 <Loader2 className="w-6 h-6 text-bambu-green animate-spin" />
               </div>
               </div>
             ) : notificationTemplates && notificationTemplates.length > 0 ? (
             ) : notificationTemplates && notificationTemplates.length > 0 ? (
+              (() => {
+                const filter = templateFilter.trim().toLowerCase();
+                const filtered = [...notificationTemplates]
+                  .sort((a, b) => a.name.localeCompare(b.name))
+                  .filter(tpl =>
+                    !filter ||
+                    tpl.name.toLowerCase().includes(filter) ||
+                    (tpl.title_template || '').toLowerCase().includes(filter)
+                  );
+                if (filtered.length === 0) {
+                  return (
+                    <p className="text-sm text-bambu-gray italic text-center py-6">
+                      {t('settings.noTemplatesMatch', 'No templates match your filter.')}
+                    </p>
+                  );
+                }
+                return (
               <div className="space-y-2">
               <div className="space-y-2">
-                {[...notificationTemplates].sort((a, b) => a.name.localeCompare(b.name)).map((template) => (
+                {filtered.map((template) => (
                   <Card
                   <Card
                     key={template.id}
                     key={template.id}
                     className="cursor-pointer hover:border-bambu-green/50 transition-colors"
                     className="cursor-pointer hover:border-bambu-green/50 transition-colors"
@@ -3028,6 +3196,8 @@ export function SettingsPage() {
                   </Card>
                   </Card>
                 ))}
                 ))}
               </div>
               </div>
+                );
+              })()
             ) : (
             ) : (
               <Card>
               <Card>
                 <CardContent className="py-8">
                 <CardContent className="py-8">
@@ -3049,7 +3219,7 @@ export function SettingsPage() {
           <div>
           <div>
             <div className="flex items-start justify-between gap-4 mb-6">
             <div className="flex items-start justify-between gap-4 mb-6">
               <div className="flex-1">
               <div className="flex-1">
-                <h2 className="text-lg font-semibold text-white flex items-center gap-2">
+                <h2 className="text-lg font-semibold text-white flex items-center gap-2" id="card-createapi">
                   <Key className="w-5 h-5 text-bambu-green" />
                   <Key className="w-5 h-5 text-bambu-green" />
                   {t('settings.apiKeys')}
                   {t('settings.apiKeys')}
                 </h2>
                 </h2>
@@ -3275,7 +3445,7 @@ export function SettingsPage() {
             {/* Webhook Documentation */}
             {/* Webhook Documentation */}
             <Card className="mt-6">
             <Card className="mt-6">
               <CardHeader>
               <CardHeader>
-                <h3 className="text-base font-semibold text-white">{t('settings.webhookEndpoints')}</h3>
+                <h3 className="text-base font-semibold text-white" id="card-webhooks">{t('settings.webhookEndpoints')}</h3>
               </CardHeader>
               </CardHeader>
               <CardContent className="space-y-3 text-sm">
               <CardContent className="space-y-3 text-sm">
                 <p className="text-bambu-gray">
                 <p className="text-bambu-gray">
@@ -3320,7 +3490,7 @@ export function SettingsPage() {
           {/* Right Column - API Browser */}
           {/* Right Column - API Browser */}
           <div>
           <div>
             <div className="mb-6">
             <div className="mb-6">
-              <h2 className="text-lg font-semibold text-white flex items-center gap-2">
+              <h2 className="text-lg font-semibold text-white flex items-center gap-2" id="card-apibrowser">
                 <Globe className="w-5 h-5 text-bambu-green" />
                 <Globe className="w-5 h-5 text-bambu-green" />
                 {t('settings.apiBrowser')}
                 {t('settings.apiBrowser')}
               </h2>
               </h2>
@@ -3353,7 +3523,9 @@ export function SettingsPage() {
 
 
       {/* Virtual Printer Tab */}
       {/* Virtual Printer Tab */}
       {activeTab === 'virtual-printer' && (
       {activeTab === 'virtual-printer' && (
-        <VirtualPrinterList />
+        <div id="card-vp">
+          <VirtualPrinterList />
+        </div>
       )}
       )}
 
 
       {/* Filament Tab */}
       {/* Filament Tab */}
@@ -3363,7 +3535,7 @@ export function SettingsPage() {
           {/* Left Column */}
           {/* Left Column */}
           <div className="lg:w-1/2 space-y-3">
           <div className="lg:w-1/2 space-y-3">
           {/* Default Print Options */}
           {/* Default Print Options */}
-          <Card>
+          <Card id="card-print-options">
             <CardHeader>
             <CardHeader>
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
                 <ListOrdered className="w-4 h-4 text-bambu-green" />
                 <ListOrdered className="w-4 h-4 text-bambu-green" />
@@ -3401,7 +3573,7 @@ export function SettingsPage() {
           </Card>
           </Card>
 
 
           {/* Staggered Batch Start */}
           {/* Staggered Batch Start */}
-          <Card>
+          <Card id="card-staggered">
             <CardHeader>
             <CardHeader>
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
                 <Layers className="w-4 h-4 text-bambu-green" />
                 <Layers className="w-4 h-4 text-bambu-green" />
@@ -3450,7 +3622,7 @@ export function SettingsPage() {
           </Card>
           </Card>
 
 
           {/* Plate-Clear Confirmation */}
           {/* Plate-Clear Confirmation */}
-          <Card>
+          <Card id="card-plate">
             <CardHeader>
             <CardHeader>
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
                 <Shield className="w-4 h-4 text-bambu-green" />
                 <Shield className="w-4 h-4 text-bambu-green" />
@@ -3481,7 +3653,7 @@ export function SettingsPage() {
           </Card>
           </Card>
 
 
           {/* G-code Injection (#422) */}
           {/* G-code Injection (#422) */}
-          <Card>
+          <Card id="card-gcode">
             <CardHeader>
             <CardHeader>
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
               <h3 className="text-base font-semibold text-white flex items-center gap-2">
                 <Code className="w-4 h-4 text-bambu-green" />
                 <Code className="w-4 h-4 text-bambu-green" />
@@ -3593,7 +3765,7 @@ export function SettingsPage() {
           {/* Auto-Drying */}
           {/* Auto-Drying */}
           <Card>
           <Card>
             <CardHeader>
             <CardHeader>
-              <h3 className="text-base font-semibold text-white flex items-center gap-2">
+              <h3 className="text-base font-semibold text-white flex items-center gap-2" id="card-drying">
                 <Flame className="w-4 h-4 text-amber-400" />
                 <Flame className="w-4 h-4 text-amber-400" />
                 {t('settings.queueDrying')}
                 {t('settings.queueDrying')}
               </h3>
               </h3>
@@ -3760,7 +3932,7 @@ export function SettingsPage() {
           <div className="lg:w-1/3 space-y-3">
           <div className="lg:w-1/3 space-y-3">
             <SpoolmanSettings />
             <SpoolmanSettings />
 
 
-            <Card>
+            <Card id="card-filamentchecks">
               <CardHeader>
               <CardHeader>
                 <h2 className="text-lg font-semibold text-white">{t('settings.filamentChecks')}</h2>
                 <h2 className="text-lg font-semibold text-white">{t('settings.filamentChecks')}</h2>
               </CardHeader>
               </CardHeader>
@@ -3803,7 +3975,7 @@ export function SettingsPage() {
             </Card>
             </Card>
 
 
             {/* Per-Printer Mapping Default */}
             {/* Per-Printer Mapping Default */}
-            <Card>
+            <Card id="card-printmodal">
               <CardHeader>
               <CardHeader>
                 <h2 className="text-lg font-semibold text-white">{t('settings.printModal')}</h2>
                 <h2 className="text-lg font-semibold text-white">{t('settings.printModal')}</h2>
               </CardHeader>
               </CardHeader>
@@ -3828,7 +4000,7 @@ export function SettingsPage() {
               </CardContent>
               </CardContent>
             </Card>
             </Card>
 
 
-            <Card>
+            <Card id="card-amsthresholds">
               <CardHeader>
               <CardHeader>
                 <h2 className="text-lg font-semibold text-white">{t('settings.amsDisplayThresholds')}</h2>
                 <h2 className="text-lg font-semibold text-white">{t('settings.amsDisplayThresholds')}</h2>
               </CardHeader>
               </CardHeader>
@@ -4137,7 +4309,7 @@ export function SettingsPage() {
           <div className="flex gap-1 border-b border-bambu-dark-tertiary">
           <div className="flex gap-1 border-b border-bambu-dark-tertiary">
             <button
             <button
               onClick={() => setUsersSubTab('users')}
               onClick={() => setUsersSubTab('users')}
-              className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+              className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
                 usersSubTab === 'users'
                 usersSubTab === 'users'
                   ? 'text-bambu-green border-bambu-green'
                   ? 'text-bambu-green border-bambu-green'
                   : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
                   : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -4148,7 +4320,7 @@ export function SettingsPage() {
             </button>
             </button>
             <button
             <button
               onClick={() => setUsersSubTab('email')}
               onClick={() => setUsersSubTab('email')}
-              className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+              className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
                 usersSubTab === 'email'
                 usersSubTab === 'email'
                   ? 'text-bambu-green border-bambu-green'
                   ? 'text-bambu-green border-bambu-green'
                   : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
                   : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -4162,7 +4334,7 @@ export function SettingsPage() {
             </button>
             </button>
             <button
             <button
               onClick={() => setUsersSubTab('ldap')}
               onClick={() => setUsersSubTab('ldap')}
-              className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px flex items-center gap-2 ${
+              className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px lg:border-b-0 lg:border-l-2 lg:-ml-px lg:mb-0 lg:justify-start flex items-center gap-2 ${
                 usersSubTab === 'ldap'
                 usersSubTab === 'ldap'
                   ? 'text-bambu-green border-bambu-green'
                   ? 'text-bambu-green border-bambu-green'
                   : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
                   : 'text-bambu-gray hover:text-gray-900 dark:hover:text-white border-transparent'
@@ -4243,7 +4415,7 @@ export function SettingsPage() {
                   <Card>
                   <Card>
                     <CardHeader>
                     <CardHeader>
                       <div className="flex items-center justify-between">
                       <div className="flex items-center justify-between">
-                        <h3 className="text-lg font-semibold text-white flex items-center gap-2">
+                        <h3 className="text-lg font-semibold text-white flex items-center gap-2" id="card-currentuser">
                           <Users className="w-5 h-5 text-bambu-green" />
                           <Users className="w-5 h-5 text-bambu-green" />
                           {t('settings.currentUser')}
                           {t('settings.currentUser')}
                         </h3>
                         </h3>
@@ -4292,7 +4464,7 @@ export function SettingsPage() {
                 <Card>
                 <Card>
                   <CardHeader>
                   <CardHeader>
                     <div className="flex items-center justify-between">
                     <div className="flex items-center justify-between">
-                      <h3 className="text-lg font-semibold text-white flex items-center gap-2">
+                      <h3 className="text-lg font-semibold text-white flex items-center gap-2" id="card-users">
                         <Users className="w-5 h-5 text-bambu-green" />
                         <Users className="w-5 h-5 text-bambu-green" />
                         {t('settings.users')}
                         {t('settings.users')}
                       </h3>
                       </h3>
@@ -4377,7 +4549,7 @@ export function SettingsPage() {
                 <Card>
                 <Card>
                   <CardHeader>
                   <CardHeader>
                     <div className="flex items-center justify-between">
                     <div className="flex items-center justify-between">
-                      <h3 className="text-lg font-semibold text-white flex items-center gap-2">
+                      <h3 className="text-lg font-semibold text-white flex items-center gap-2" id="card-groups">
                         <Shield className="w-5 h-5 text-bambu-green" />
                         <Shield className="w-5 h-5 text-bambu-green" />
                         {t('settings.groups')}
                         {t('settings.groups')}
                       </h3>
                       </h3>
@@ -4490,13 +4662,13 @@ export function SettingsPage() {
 
 
           {/* Email Auth Sub-tab */}
           {/* Email Auth Sub-tab */}
           {usersSubTab === 'email' && (
           {usersSubTab === 'email' && (
-            <div className="max-w-5xl">
+            <div className="max-w-5xl" id="card-smtp">
               <EmailSettings />
               <EmailSettings />
             </div>
             </div>
           )}
           )}
 
 
           {usersSubTab === 'ldap' && (
           {usersSubTab === 'ldap' && (
-            <div className="max-w-5xl">
+            <div className="max-w-5xl" id="card-ldap">
               <LDAPSettings />
               <LDAPSettings />
             </div>
             </div>
           )}
           )}
@@ -4964,7 +5136,9 @@ export function SettingsPage() {
 
 
       {/* Backup Tab */}
       {/* Backup Tab */}
       {activeTab === 'backup' && (
       {activeTab === 'backup' && (
-        <GitHubBackupSettings />
+        <div id="card-backup">
+          <GitHubBackupSettings />
+        </div>
       )}
       )}
 
 
       {/* Disable Authentication Confirmation Modal */}
       {/* Disable Authentication Confirmation Modal */}
@@ -5125,6 +5299,8 @@ export function SettingsPage() {
           </Card>
           </Card>
         </div>
         </div>
       )}
       )}
+      </div>
+      </div>
     </div>
     </div>
     </CardDensityProvider>
     </CardDensityProvider>
   );
   );

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-Caj-77TJ.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-OM4EoHsE.js


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BUj9NFXp.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-BKDWdPbe.css">
+    <script type="module" crossorigin src="/assets/index-OM4EoHsE.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-Caj-77TJ.css">
   </head>
   </head>
   <body>
   <body>
     <div id="root"></div>
     <div id="root"></div>

Some files were not shown because too many files changed in this diff