mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-30 03:59:41 +00:00
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:
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user