Fix: Improve SSL certificate details and View Action button

This commit is contained in:
Tola Leng
2025-06-20 17:26:13 +07:00
parent 614c10188d
commit 220be3bea4
3 changed files with 241 additions and 2 deletions
@@ -7,7 +7,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { MoreHorizontal, RefreshCw, Edit, Trash2 } from "lucide-react";
import { MoreHorizontal, RefreshCw, Edit, Trash2, Eye } from "lucide-react";
import { SSLCertificate } from "@/types/ssl.types";
import { triggerImmediateCheck } from "@/services/sslCertificateService";
import { toast } from "sonner";
@@ -15,12 +15,14 @@ import { useLanguage } from "@/contexts/LanguageContext";
interface SSLCertificateActionsProps {
certificate: SSLCertificate;
onView: (certificate: SSLCertificate) => void;
onEdit: (certificate: SSLCertificate) => void;
onDelete: (certificate: SSLCertificate) => void;
}
export const SSLCertificateActions = ({
certificate,
onView,
onEdit,
onDelete
}: SSLCertificateActionsProps) => {
@@ -43,6 +45,10 @@ export const SSLCertificateActions = ({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onView(certificate)}>
<Eye className="mr-2 h-4 w-4" />
{t('view')}
</DropdownMenuItem>
<DropdownMenuItem onClick={handleCheck}>
<RefreshCw className="mr-2 h-4 w-4" />
Check
@@ -0,0 +1,219 @@
import React from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { SSLCertificate } from "@/types/ssl.types";
import { SSLStatusBadge } from "./SSLStatusBadge";
import { useLanguage } from "@/contexts/LanguageContext";
interface SSLCertificateDetailDialogProps {
certificate: SSLCertificate | null;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const SSLCertificateDetailDialog = ({
certificate,
open,
onOpenChange,
}: SSLCertificateDetailDialogProps) => {
const { t } = useLanguage();
if (!certificate) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-xl font-bold">{t('sslCertificateDetails')}</DialogTitle>
<DialogDescription className="text-sm text-muted-foreground">
{t('viewDetailedInformation')} {certificate.domain}
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* Domain & Status Overview */}
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center justify-between">
<span>{certificate.domain}</span>
<SSLStatusBadge status={certificate.status} />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="space-y-3">
<div>
<p className="text-sm font-medium text-muted-foreground">{t('status')}</p>
<SSLStatusBadge status={certificate.status} />
</div>
<div>
<p className="text-sm font-medium text-muted-foreground">{t('daysLeft')}</p>
<Badge variant={certificate.days_left <= 7 ? 'destructive' : certificate.days_left <= 30 ? 'secondary' : 'default'}>
{certificate.days_left} {t('days')}
</Badge>
</div>
</div>
<div className="space-y-3">
<div>
<p className="text-sm font-medium text-muted-foreground">{t('validFrom')}</p>
<p className="text-sm">
{certificate.valid_from ? new Date(certificate.valid_from).toLocaleString() : 'N/A'}
</p>
</div>
<div>
<p className="text-sm font-medium text-muted-foreground">{t('validUntil')}</p>
<p className="text-sm">
{certificate.valid_till ? new Date(certificate.valid_till).toLocaleString() : 'N/A'}
</p>
</div>
</div>
<div className="space-y-3">
<div>
<p className="text-sm font-medium text-muted-foreground">{t('validityPeriod')}</p>
<p className="text-sm">{certificate.validity_days || 0} {t('days')}</p>
</div>
{certificate.resolved_ip && (
<div>
<p className="text-sm font-medium text-muted-foreground">{t('resolvedIP')}</p>
<p className="text-sm font-mono bg-muted px-2 py-1 rounded">{certificate.resolved_ip}</p>
</div>
)}
</div>
</div>
</CardContent>
</Card>
{/* Certificate Information */}
<Card>
<CardHeader>
<CardTitle className="text-lg">{t('certificateDetails')}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-muted-foreground">{t('issuedTo')}</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{certificate.issued_to || 'N/A'}</p>
</div>
<div>
<p className="text-sm font-medium text-muted-foreground">{t('issuer')}</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{certificate.issuer_o || certificate.issuer_cn || 'N/A'}</p>
</div>
{certificate.issuer_cn && certificate.issuer_cn !== certificate.issuer_o && (
<div>
<p className="text-sm font-medium text-muted-foreground">Issuer Common Name</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{certificate.issuer_cn}</p>
</div>
)}
</div>
<div className="space-y-4">
{certificate.serial_number && (
<div>
<p className="text-sm font-medium text-muted-foreground">{t('serialNumber')}</p>
<p className="text-xs font-mono bg-muted px-3 py-2 rounded break-all">{certificate.serial_number}</p>
</div>
)}
{certificate.cert_alg && (
<div>
<p className="text-sm font-medium text-muted-foreground">{t('algorithm')}</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{certificate.cert_alg}</p>
</div>
)}
</div>
</div>
{certificate.cert_sans && (
<div className="mt-4">
<p className="text-sm font-medium text-muted-foreground mb-2">{t('subjectAlternativeNames')}</p>
<div className="bg-muted px-3 py-2 rounded">
<p className="text-sm whitespace-pre-wrap">{certificate.cert_sans}</p>
</div>
</div>
)}
</CardContent>
</Card>
{/* Monitoring Configuration */}
<Card>
<CardHeader>
<CardTitle className="text-lg">{t('monitoringConfiguration')}</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-muted px-3 py-2 rounded">
<p className="text-sm font-medium text-muted-foreground">{t('warningThreshold')}</p>
<p className="text-sm font-semibold">{certificate.warning_threshold} {t('days')}</p>
</div>
<div className="bg-muted px-3 py-2 rounded">
<p className="text-sm font-medium text-muted-foreground">{t('expiryThreshold')}</p>
<p className="text-sm font-semibold">{certificate.expiry_threshold} {t('days')}</p>
</div>
<div className="bg-muted px-3 py-2 rounded">
<p className="text-sm font-medium text-muted-foreground">Check Interval</p>
<p className="text-sm font-semibold">{certificate.check_interval || 1} {t('days')}</p>
</div>
<div className="bg-muted px-3 py-2 rounded">
<p className="text-sm font-medium text-muted-foreground">{t('notificationChannel')}</p>
<p className="text-sm font-semibold">{certificate.notification_channel || 'N/A'}</p>
</div>
</div>
{certificate.last_notified && (
<div className="mt-4">
<p className="text-sm font-medium text-muted-foreground">{t('lastNotified')}</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{new Date(certificate.last_notified).toLocaleString()}</p>
</div>
)}
</CardContent>
</Card>
{/* System Information */}
<Card>
<CardHeader>
<CardTitle className="text-lg">{t('technicalInformation')}</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-3">
{certificate.check_at && (
<div>
<p className="text-sm font-medium text-muted-foreground">Next Check</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{new Date(certificate.check_at).toLocaleString()}</p>
</div>
)}
{certificate.created && (
<div>
<p className="text-sm font-medium text-muted-foreground">{t('created')}</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{new Date(certificate.created).toLocaleString()}</p>
</div>
)}
</div>
<div className="space-y-3">
{certificate.updated && (
<div>
<p className="text-sm font-medium text-muted-foreground">{t('lastUpdated')}</p>
<p className="text-sm bg-muted px-3 py-2 rounded">{new Date(certificate.updated).toLocaleString()}</p>
</div>
)}
<div>
<p className="text-sm font-medium text-muted-foreground">Certificate ID</p>
<p className="text-xs font-mono bg-muted px-3 py-2 rounded break-all">{certificate.id}</p>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</DialogContent>
</Dialog>
);
};
@@ -1,4 +1,3 @@
import React, { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Plus } from "lucide-react";
@@ -33,6 +32,7 @@ import { SSLStatusBadge } from "./SSLStatusBadge";
import { AddSSLCertificateForm } from "./AddSSLCertificateForm";
import { EditSSLCertificateForm } from "./EditSSLCertificateForm";
import { SSLCertificateActions } from "./SSLCertificateActions";
import { SSLCertificateDetailDialog } from "./SSLCertificateDetailDialog";
import { fetchSSLCertificates, addSSLCertificate, deleteSSLCertificate } from "@/services/sslCertificateService";
import { pb } from "@/lib/pocketbase";
import { SSLCertificate } from "@/types/ssl.types";
@@ -45,6 +45,7 @@ export const SSLCertificatesTable = () => {
const [showAddDialog, setShowAddDialog] = useState(false);
const [showEditDialog, setShowEditDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [showViewDialog, setShowViewDialog] = useState(false);
const [selectedCertificate, setSelectedCertificate] = useState<SSLCertificate | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -111,6 +112,11 @@ export const SSLCertificatesTable = () => {
}
};
const openViewDialog = (certificate: SSLCertificate) => {
setSelectedCertificate(certificate);
setShowViewDialog(true);
};
const openEditDialog = (certificate: SSLCertificate) => {
setSelectedCertificate(certificate);
setShowEditDialog(true);
@@ -168,6 +174,7 @@ export const SSLCertificatesTable = () => {
<TableCell>
<SSLCertificateActions
certificate={certificate}
onView={openViewDialog}
onEdit={openEditDialog}
onDelete={openDeleteDialog}
/>
@@ -185,6 +192,13 @@ export const SSLCertificatesTable = () => {
</CardContent>
</Card>
{/* View Certificate Dialog */}
<SSLCertificateDetailDialog
certificate={selectedCertificate}
open={showViewDialog}
onOpenChange={setShowViewDialog}
/>
{/* Add Certificate Dialog */}
<Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>
<DialogContent>