Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions apps/web/src/components/pages/ACL.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from "react";
import { Suspense } from "react";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
Expand All @@ -14,6 +13,7 @@ import { Plus, Download, Upload, Trash2, Edit, Loader2, UserCog } from "lucide-r
import { ACLRule } from "@/types";
import { useToast } from "@/hooks/use-toast";
import { SkeletonTable } from "@/components/ui/skeletons";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import {
useSuspenseAclRules,
useCreateAclRule,
Expand All @@ -25,11 +25,11 @@ import {

// Component for ACL rules table with suspense
function AclRulesTable() {
const { t } = useTranslation();
const { toast } = useToast();
const { data: rules } = useSuspenseAclRules();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingRule, setEditingRule] = useState<ACLRule | null>(null);
const [ruleToDelete, setRuleToDelete] = useState<{ id: string; name: string } | null>(null);

const createAclRule = useCreateAclRule();
const updateAclRule = useUpdateAclRule();
Expand Down Expand Up @@ -117,10 +117,13 @@ function AclRulesTable() {
setIsDialogOpen(true);
};

const handleDelete = async (id: string) => {
const handleDelete = async () => {
if (!ruleToDelete) return;

try {
await deleteAclRule.mutateAsync(id);
await deleteAclRule.mutateAsync(ruleToDelete.id);
toast({ title: "Rule deleted successfully" });
setRuleToDelete(null);
} catch (error: any) {
toast({
title: "Error deleting rule",
Expand Down Expand Up @@ -365,7 +368,11 @@ function AclRulesTable() {
<Button variant="ghost" size="sm" onClick={() => handleEdit(rule)}>
<Edit className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" onClick={() => handleDelete(rule.id)}>
<Button
variant="ghost"
size="sm"
onClick={() => setRuleToDelete({ id: rule.id, name: rule.name })}
>
<Trash2 className="h-4 w-4" />
</Button>
</TableCell>
Expand All @@ -376,6 +383,24 @@ function AclRulesTable() {
</div>
</CardContent>
</Card>

<ConfirmDialog
open={!!ruleToDelete}
onOpenChange={(open) => !open && setRuleToDelete(null)}
title="Delete ACL Rule"
description={
<>
Are you sure you want to delete the rule <strong>{ruleToDelete?.name}</strong>?
<br />
This action cannot be undone and may affect access control to your domains.
</>
}
confirmText="Delete Rule"
cancelText="Cancel"
onConfirm={handleDelete}
isLoading={deleteAclRule.isPending}
variant="destructive"
/>
</>
);
}
Expand Down
71 changes: 60 additions & 11 deletions apps/web/src/components/pages/Alerts.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from "react";
import { Suspense } from "react";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
Expand All @@ -11,10 +10,10 @@ import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Plus, Send, Edit, Trash2, Mail, MessageSquare, Loader2, Bell } from "lucide-react";
import { NotificationChannel, AlertRule } from "@/types";
import { Plus, Send, Trash2, Mail, MessageSquare, Loader2, Bell } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { SkeletonTable } from "@/components/ui/skeletons";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import {
useSuspenseNotificationChannels,
useSuspenseAlertRules,
Expand All @@ -29,10 +28,10 @@ import {

// Component for notification channels with suspense
function NotificationChannelsTab() {
const { t } = useTranslation();
const { toast } = useToast();
const { data: channels } = useSuspenseNotificationChannels();
const [isChannelDialogOpen, setIsChannelDialogOpen] = useState(false);
const [channelToDelete, setChannelToDelete] = useState<{ id: string; name: string } | null>(null);

const createNotificationChannel = useCreateNotificationChannel();
const updateNotificationChannel = useUpdateNotificationChannel();
Expand Down Expand Up @@ -101,10 +100,13 @@ function NotificationChannelsTab() {
}
};

const handleDeleteChannel = async (id: string) => {
const handleDeleteChannel = async () => {
if (!channelToDelete) return;

try {
await deleteNotificationChannel.mutateAsync(id);
await deleteNotificationChannel.mutateAsync(channelToDelete.id);
toast({ title: "Channel deleted successfully" });
setChannelToDelete(null);
} catch (error: any) {
toast({
title: "Error deleting channel",
Expand Down Expand Up @@ -263,7 +265,11 @@ function NotificationChannelsTab() {
<Button variant="ghost" size="sm" onClick={() => handleTestNotification(channel.id)}>
<Send className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" onClick={() => handleDeleteChannel(channel.id)}>
<Button
variant="ghost"
size="sm"
onClick={() => setChannelToDelete({ id: channel.id, name: channel.name })}
>
<Trash2 className="h-4 w-4" />
</Button>
</TableCell>
Expand All @@ -272,18 +278,36 @@ function NotificationChannelsTab() {
</TableBody>
</Table>
</div>

<ConfirmDialog
open={!!channelToDelete}
onOpenChange={(open) => !open && setChannelToDelete(null)}
title="Delete Notification Channel"
description={
<>
Are you sure you want to delete the channel <strong>{channelToDelete?.name}</strong>?
<br />
This action cannot be undone and all associated alert rules will be affected.
</>
}
confirmText="Delete Channel"
cancelText="Cancel"
onConfirm={handleDeleteChannel}
isLoading={deleteNotificationChannel.isPending}
variant="destructive"
/>
</CardContent>
</Card>
);
}

// Component for alert rules with suspense
function AlertRulesTab() {
const { t } = useTranslation();
const { toast } = useToast();
const { data: channels } = useSuspenseNotificationChannels();
const { data: alertRules } = useSuspenseAlertRules();
const [isRuleDialogOpen, setIsRuleDialogOpen] = useState(false);
const [ruleToDelete, setRuleToDelete] = useState<{ id: string; name: string } | null>(null);

const createAlertRule = useCreateAlertRule();
const updateAlertRule = useUpdateAlertRule();
Expand Down Expand Up @@ -390,10 +414,13 @@ function AlertRulesTab() {
});
};

const handleDeleteRule = async (id: string) => {
const handleDeleteRule = async () => {
if (!ruleToDelete) return;

try {
await deleteAlertRule.mutateAsync(id);
await deleteAlertRule.mutateAsync(ruleToDelete.id);
toast({ title: "Rule deleted successfully" });
setRuleToDelete(null);
} catch (error: any) {
toast({
title: "Error deleting rule",
Expand Down Expand Up @@ -624,7 +651,11 @@ function AlertRulesTab() {
/>
</TableCell>
<TableCell className="text-right space-x-2">
<Button variant="ghost" size="sm" onClick={() => handleDeleteRule(rule.id)}>
<Button
variant="ghost"
size="sm"
onClick={() => setRuleToDelete({ id: rule.id, name: rule.name })}
>
<Trash2 className="h-4 w-4" />
</Button>
</TableCell>
Expand All @@ -633,6 +664,24 @@ function AlertRulesTab() {
</TableBody>
</Table>
</div>

<ConfirmDialog
open={!!ruleToDelete}
onOpenChange={(open) => !open && setRuleToDelete(null)}
title="Delete Alert Rule"
description={
<>
Are you sure you want to delete the rule <strong>{ruleToDelete?.name}</strong>?
<br />
This action cannot be undone and you will stop receiving alerts for this condition.
</>
}
confirmText="Delete Rule"
cancelText="Cancel"
onConfirm={handleDeleteRule}
isLoading={deleteAlertRule.isPending}
variant="destructive"
/>
</CardContent>
</Card>
);
Expand Down
52 changes: 41 additions & 11 deletions apps/web/src/components/pages/SSLTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,26 @@ import {
} from '@/components/ui/table';
import { AlertTriangle, CheckCircle2, RefreshCw, Trash2 } from 'lucide-react';
import { toast } from 'sonner';
import { SSLCertificate } from '@/types';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';

export function SSLTable() {
const [renewingId, setRenewingId] = useState<string | null>(null);
const { data: certificates } = useSuspenseSSLCertificates();
const renewMutation = useRenewSSLCertificate();
const deleteMutation = useDeleteSSLCertificate();
const [confirmDialog, setConfirmDialog] = useState<{
open: boolean;
title: string;
description: string;
onConfirm: () => void;
certificateId: string;
}>({
open: false,
title: '',
description: '',
onConfirm: () => {},
certificateId: '',
});

const handleRenew = async (id: string) => {
try {
Expand All @@ -34,15 +47,22 @@ export function SSLTable() {
}
};

const handleDelete = async (id: string) => {
if (!confirm('Are you sure you want to delete this certificate?')) return;

try {
await deleteMutation.mutateAsync(id);
toast.success('Certificate deleted successfully');
} catch (error: any) {
toast.error(error.response?.data?.message || 'Failed to delete certificate');
}
const handleDelete = (id: string, domainName: string) => {
setConfirmDialog({
open: true,
title: 'Delete SSL Certificate',
description: `Are you sure you want to delete the SSL certificate for ${domainName}? This action cannot be undone.`,
certificateId: id,
onConfirm: async () => {
try {
await deleteMutation.mutateAsync(id);
toast.success('Certificate deleted successfully');
setConfirmDialog(prev => ({ ...prev, open: false }));
} catch (error: any) {
toast.error(error.response?.data?.message || 'Failed to delete certificate');
}
},
});
};

const getStatusIcon = (status: string) => {
Expand Down Expand Up @@ -145,7 +165,7 @@ export function SSLTable() {
<Button
variant="destructive"
size="sm"
onClick={() => handleDelete(cert.id)}
onClick={() => handleDelete(cert.id, cert.domain?.name || cert.commonName)}
>
<Trash2 className="h-4 w-4" />
</Button>
Expand All @@ -158,6 +178,16 @@ export function SSLTable() {
</div>
)}
</CardContent>

<ConfirmDialog
open={confirmDialog.open}
onOpenChange={(open) => setConfirmDialog(prev => ({ ...prev, open }))}
title={confirmDialog.title}
description={confirmDialog.description}
onConfirm={confirmDialog.onConfirm}
confirmText="Delete"
isLoading={deleteMutation.isPending}
/>
</Card>
);
}
36 changes: 34 additions & 2 deletions apps/web/src/components/pages/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { UserPlus, Key, Trash2, Edit, Shield, Loader2, Users as UsersIcon, Copy,
import { useToast } from "@/hooks/use-toast";
import { useStore } from "@/store/useStore";
import { SkeletonStatsCard, SkeletonTable } from "@/components/ui/skeletons";
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
import {
useSuspenseUsers,
useSuspenseUserStats,
Expand Down Expand Up @@ -81,6 +82,11 @@ function UsersTable() {
const [editingUser, setEditingUser] = useState<any>(null);
const [resetPasswordDialog, setResetPasswordDialog] = useState<{ isOpen: boolean; userId: string; username: string; newPassword?: string; }>({ isOpen: false, userId: '', username: '' });
const [passwordCopied, setPasswordCopied] = useState(false);
const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; userId: string; username: string }>({
isOpen: false,
userId: '',
username: ''
});

const createUser = useCreateUser();
const updateUser = useUpdateUser();
Expand Down Expand Up @@ -190,10 +196,16 @@ function UsersTable() {
};

const handleDelete = async (id: string) => {
if (!confirm("Are you sure you want to delete this user?")) return;
const user = users.data.find(u => u.id === id);
if (!user) return;

setDeleteConfirm({ isOpen: true, userId: id, username: user.username });
};

const confirmDelete = async () => {
try {
await deleteUser.mutateAsync(id);
await deleteUser.mutateAsync(deleteConfirm.userId);
setDeleteConfirm({ isOpen: false, userId: '', username: '' });
toast({ title: "User deleted successfully" });
} catch (error: any) {
toast({
Expand Down Expand Up @@ -713,6 +725,26 @@ function UsersTable() {
</div>
</CardContent>
</Card>

{/* Delete User Confirmation Dialog */}
<ConfirmDialog
open={deleteConfirm.isOpen}
onOpenChange={(open) => !open && setDeleteConfirm({ isOpen: false, userId: '', username: '' })}
title="Delete User"
description={
<div className="space-y-2">
<p>Are you sure you want to delete user <strong>{deleteConfirm.username}</strong>?</p>
<p className="text-sm text-muted-foreground">
This action cannot be undone. All data associated with this user will be permanently removed.
</p>
</div>
}
confirmText="Delete User"
cancelText="Cancel"
onConfirm={confirmDelete}
isLoading={deleteUser.isPending}
variant="destructive"
/>
</>
);
}
Expand Down
Loading