fix: replace native title tooltips with Radix tooltips on Schedules and Settings pages

Converted Run now, Execution history, Edit, Delete buttons on Schedules;
Download/Delete on Cloud Backup; Send test/Edit/Delete on Notification
Routing; Reset 2FA on Users; and Edit/Remove suppression on
Suppressions panel. Skipped SettingsSection/Modal title props which
are React component props, not native HTML attributes.
This commit is contained in:
SaelixCode
2026-07-03 19:42:48 -04:00
parent 5a18be8667
commit dd6874f1ae
5 changed files with 180 additions and 98 deletions
@@ -11,6 +11,7 @@ import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { Clock, Plus, Pencil, Trash2, History, RefreshCw, Play, ChevronLeft, ChevronRight, Download, CalendarClock, Table2 } from 'lucide-react'; import { Clock, Plus, Pencil, Trash2, History, RefreshCw, Play, ChevronLeft, ChevronRight, Download, CalendarClock, Table2 } from 'lucide-react';
import { toast } from '@/components/ui/toast-store'; import { toast } from '@/components/ui/toast-store';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { apiFetch, fetchForNode } from '@/lib/api'; import { apiFetch, fetchForNode } from '@/lib/api';
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { SegmentedControl } from '@/components/ui/segmented-control'; import { SegmentedControl } from '@/components/ui/segmented-control';
@@ -730,18 +731,46 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter, p
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex items-center justify-end gap-1"> <div className="flex items-center justify-end gap-1">
<Button variant="ghost" size="sm" onClick={() => handleRunNow(task)} title="Run now" disabled={runningTaskId === task.id}> <TooltipProvider>
<Play className={`w-4 h-4 ${runningTaskId === task.id ? 'animate-pulse' : ''}`} strokeWidth={1.5} /> <Tooltip>
</Button> <TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={() => openRuns(task)} title="Execution history"> <Button variant="ghost" size="sm" onClick={() => handleRunNow(task)} disabled={runningTaskId === task.id}>
<History className="w-4 h-4" strokeWidth={1.5} /> <Play className={`w-4 h-4 ${runningTaskId === task.id ? 'animate-pulse' : ''}`} strokeWidth={1.5} />
</Button> </Button>
<Button variant="ghost" size="sm" onClick={() => openEdit(task)} title="Edit"> </TooltipTrigger>
<Pencil className="w-4 h-4" strokeWidth={1.5} /> <TooltipContent>Run now</TooltipContent>
</Button> </Tooltip>
<Button variant="ghost" size="sm" onClick={() => setDeleteTarget(task)} title="Delete" className="text-destructive/60 hover:bg-destructive hover:text-destructive-foreground"> </TooltipProvider>
<Trash2 className="w-4 h-4" strokeWidth={1.5} /> <TooltipProvider>
</Button> <Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={() => openRuns(task)}>
<History className="w-4 h-4" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent>Execution history</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={() => openEdit(task)}>
<Pencil className="w-4 h-4" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent>Edit</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={() => setDeleteTarget(task)} className="text-destructive/60 hover:bg-destructive hover:text-destructive-foreground">
<Trash2 className="w-4 h-4" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent>Delete</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -7,6 +7,7 @@ import { Combobox } from '@/components/ui/combobox';
import { TogglePill } from '@/components/ui/toggle-pill'; import { TogglePill } from '@/components/ui/toggle-pill';
import { ConfirmModal } from '@/components/ui/modal'; import { ConfirmModal } from '@/components/ui/modal';
import { toast } from '@/components/ui/toast-store'; import { toast } from '@/components/ui/toast-store';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { apiFetch } from '@/lib/api'; import { apiFetch } from '@/lib/api';
import { formatBytes } from '@/lib/utils'; import { formatBytes } from '@/lib/utils';
import { useLicense } from '@/context/LicenseContext'; import { useLicense } from '@/context/LicenseContext';
@@ -496,41 +497,53 @@ export function CloudBackupSection() {
</div> </div>
</div> </div>
<div className="flex items-center gap-1 shrink-0"> <div className="flex items-center gap-1 shrink-0">
<Button <TooltipProvider>
size="icon" <Tooltip>
variant="ghost" <TooltipTrigger asChild>
className="h-7 w-7" <Button
onClick={async () => { size="icon"
const encoded = btoa(s.objectKey).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); variant="ghost"
try { className="h-7 w-7"
const res = await apiFetch(`/cloud-backup/object/${encoded}/download`); onClick={async () => {
if (!res.ok) { const encoded = btoa(s.objectKey).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const err = await res.json().catch(() => ({})); try {
throw new Error(err?.error || `Download failed (${res.status})`); const res = await apiFetch(`/cloud-backup/object/${encoded}/download`);
} if (!res.ok) {
const blob = await res.blob(); const err = await res.json().catch(() => ({}));
const link = document.createElement('a'); throw new Error(err?.error || `Download failed (${res.status})`);
link.href = URL.createObjectURL(blob); }
link.download = s.objectKey.split('/').pop() || 'snapshot.tar.gz'; const blob = await res.blob();
link.click(); const link = document.createElement('a');
URL.revokeObjectURL(link.href); link.href = URL.createObjectURL(blob);
} catch (err) { link.download = s.objectKey.split('/').pop() || 'snapshot.tar.gz';
toast.error((err as Error)?.message || 'Download failed.'); link.click();
} URL.revokeObjectURL(link.href);
}} } catch (err) {
title="Download" toast.error((err as Error)?.message || 'Download failed.');
> }
<Download className="w-3.5 h-3.5" strokeWidth={1.5} /> }}
</Button> >
<Button <Download className="w-3.5 h-3.5" strokeWidth={1.5} />
size="icon" </Button>
variant="ghost" </TooltipTrigger>
className="h-7 w-7" <TooltipContent>Download</TooltipContent>
onClick={() => setDeleteKey(s.objectKey)} </Tooltip>
title="Delete" </TooltipProvider>
> <TooltipProvider>
<Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} /> <Tooltip>
</Button> <TooltipTrigger asChild>
<Button
size="icon"
variant="ghost"
className="h-7 w-7"
onClick={() => setDeleteKey(s.objectKey)}
>
<Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent>Delete</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
</li> </li>
))} ))}
@@ -12,6 +12,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { springs } from '@/lib/motion'; import { springs } from '@/lib/motion';
import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal'; import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal';
import { toast } from '@/components/ui/toast-store'; import { toast } from '@/components/ui/toast-store';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { apiFetch } from '@/lib/api'; import { apiFetch } from '@/lib/api';
import { useNodes } from '@/context/NodeContext'; import { useNodes } from '@/context/NodeContext';
import { CapabilityGate } from '@/components/CapabilityGate'; import { CapabilityGate } from '@/components/CapabilityGate';
@@ -528,31 +529,50 @@ export function NotificationRoutingSection() {
onChange={() => handleToggleEnabled(route)} onChange={() => handleToggleEnabled(route)}
className="scale-75" className="scale-75"
/> />
<Button <TooltipProvider>
variant="ghost" <Tooltip>
size="sm" <TooltipTrigger asChild>
onClick={() => handleTest(route.id)} <Button
disabled={testingId === route.id} variant="ghost"
title="Send test notification" size="sm"
> onClick={() => handleTest(route.id)}
{testingId === route.id ? ( disabled={testingId === route.id}
<RefreshCw className="w-4 h-4 animate-spin" /> >
) : ( {testingId === route.id ? (
<Zap className="w-4 h-4" strokeWidth={1.5} /> <RefreshCw className="w-4 h-4 animate-spin" />
)} ) : (
</Button> <Zap className="w-4 h-4" strokeWidth={1.5} />
<Button variant="ghost" size="sm" onClick={() => startEdit(route)} title="Edit"> )}
<Pencil className="w-4 h-4" strokeWidth={1.5} /> </Button>
</Button> </TooltipTrigger>
<Button <TooltipContent>Send test notification</TooltipContent>
variant="ghost" </Tooltip>
size="sm" </TooltipProvider>
className="text-destructive/60 hover:bg-destructive hover:text-destructive-foreground" <TooltipProvider>
title="Delete" <Tooltip>
onClick={() => setDeleteRouteId(route.id)} <TooltipTrigger asChild>
> <Button variant="ghost" size="sm" onClick={() => startEdit(route)}>
<Trash2 className="w-4 h-4" strokeWidth={1.5} /> <Pencil className="w-4 h-4" strokeWidth={1.5} />
</Button> </Button>
</TooltipTrigger>
<TooltipContent>Edit</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="text-destructive/60 hover:bg-destructive hover:text-destructive-foreground"
onClick={() => setDeleteRouteId(route.id)}
>
<Trash2 className="w-4 h-4" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent>Delete</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
</div> </div>
<div className="flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground"> <div className="flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground">
@@ -8,6 +8,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal'; import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from '@/components/ui/modal';
import { ChevronLeft, ChevronRight, Plus, Pencil, Trash2, Download } from 'lucide-react'; import { ChevronLeft, ChevronRight, Plus, Pencil, Trash2, Download } from 'lucide-react';
import { toast } from '@/components/ui/toast-store'; import { toast } from '@/components/ui/toast-store';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { apiFetch } from '@/lib/api'; import { apiFetch } from '@/lib/api';
import { FleetTabHeading } from '@/components/fleet/FleetEmptyState'; import { FleetTabHeading } from '@/components/fleet/FleetEmptyState';
import type { CveSuppression } from '@/types/security'; import type { CveSuppression } from '@/types/security';
@@ -290,24 +291,36 @@ export function SuppressionsPanel({ isReplica }: SuppressionsPanelProps) {
</div> </div>
{isAdmin && !isReplica && row.replicated_from_control === 0 && ( {isAdmin && !isReplica && row.replicated_from_control === 0 && (
<div className="flex items-center gap-1 shrink-0"> <div className="flex items-center gap-1 shrink-0">
<Button <TooltipProvider>
variant="ghost" <Tooltip>
size="icon" <TooltipTrigger asChild>
className="h-8 w-8 text-stat-subtitle hover:text-stat-value" <Button
onClick={() => openEdit(row)} variant="ghost"
title="Edit suppression" size="icon"
> className="h-8 w-8 text-stat-subtitle hover:text-stat-value"
<Pencil className="w-3.5 h-3.5" strokeWidth={1.5} /> onClick={() => openEdit(row)}
</Button> >
<Button <Pencil className="w-3.5 h-3.5" strokeWidth={1.5} />
variant="ghost" </Button>
size="icon" </TooltipTrigger>
className="h-8 w-8 text-destructive/60 hover:bg-destructive hover:text-destructive-foreground" <TooltipContent>Edit suppression</TooltipContent>
onClick={() => setDeleteRow(row)} </Tooltip>
title="Remove suppression" </TooltipProvider>
> <TooltipProvider>
<Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} /> <Tooltip>
</Button> <TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-destructive/60 hover:bg-destructive hover:text-destructive-foreground"
onClick={() => setDeleteRow(row)}
>
<Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent>Remove suppression</TooltipContent>
</Tooltip>
</TooltipProvider>
</div> </div>
)} )}
</li> </li>
@@ -7,6 +7,7 @@ import { Skeleton } from '@/components/ui/skeleton';
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { ConfirmModal } from '@/components/ui/modal'; import { ConfirmModal } from '@/components/ui/modal';
import { toast } from '@/components/ui/toast-store'; import { toast } from '@/components/ui/toast-store';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { apiFetch } from '@/lib/api'; import { apiFetch } from '@/lib/api';
import { useAuth, type UserRole } from '@/context/AuthContext'; import { useAuth, type UserRole } from '@/context/AuthContext';
import { useLicense } from '@/context/LicenseContext'; import { useLicense } from '@/context/LicenseContext';
@@ -454,14 +455,20 @@ export function UsersSection() {
<Pencil className="w-3.5 h-3.5" strokeWidth={1.5} /> <Pencil className="w-3.5 h-3.5" strokeWidth={1.5} />
</Button> </Button>
{u.mfaEnabled && ( {u.mfaEnabled && (
<Button <TooltipProvider>
variant="ghost" <Tooltip>
size="sm" <TooltipTrigger asChild>
title="Reset 2FA" <Button
onClick={() => setResetMfaTarget(u)} variant="ghost"
> size="sm"
<ShieldOff className="w-3.5 h-3.5 text-warning" strokeWidth={1.5} /> onClick={() => setResetMfaTarget(u)}
</Button> >
<ShieldOff className="w-3.5 h-3.5 text-warning" strokeWidth={1.5} />
</Button>
</TooltipTrigger>
<TooltipContent>Reset 2FA</TooltipContent>
</Tooltip>
</TooltipProvider>
)} )}
<Button <Button
variant="ghost" variant="ghost"