Fix: Improve SSL certificate details and View Action button
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { SSLCertificate } from "@/types/ssl.types";
|
||||||
import { triggerImmediateCheck } from "@/services/sslCertificateService";
|
import { triggerImmediateCheck } from "@/services/sslCertificateService";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -15,12 +15,14 @@ import { useLanguage } from "@/contexts/LanguageContext";
|
|||||||
|
|
||||||
interface SSLCertificateActionsProps {
|
interface SSLCertificateActionsProps {
|
||||||
certificate: SSLCertificate;
|
certificate: SSLCertificate;
|
||||||
|
onView: (certificate: SSLCertificate) => void;
|
||||||
onEdit: (certificate: SSLCertificate) => void;
|
onEdit: (certificate: SSLCertificate) => void;
|
||||||
onDelete: (certificate: SSLCertificate) => void;
|
onDelete: (certificate: SSLCertificate) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SSLCertificateActions = ({
|
export const SSLCertificateActions = ({
|
||||||
certificate,
|
certificate,
|
||||||
|
onView,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete
|
onDelete
|
||||||
}: SSLCertificateActionsProps) => {
|
}: SSLCertificateActionsProps) => {
|
||||||
@@ -43,6 +45,10 @@ export const SSLCertificateActions = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => onView(certificate)}>
|
||||||
|
<Eye className="mr-2 h-4 w-4" />
|
||||||
|
{t('view')}
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={handleCheck}>
|
<DropdownMenuItem onClick={handleCheck}>
|
||||||
<RefreshCw className="mr-2 h-4 w-4" />
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
Check
|
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 React, { useState } from "react";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
@@ -33,6 +32,7 @@ import { SSLStatusBadge } from "./SSLStatusBadge";
|
|||||||
import { AddSSLCertificateForm } from "./AddSSLCertificateForm";
|
import { AddSSLCertificateForm } from "./AddSSLCertificateForm";
|
||||||
import { EditSSLCertificateForm } from "./EditSSLCertificateForm";
|
import { EditSSLCertificateForm } from "./EditSSLCertificateForm";
|
||||||
import { SSLCertificateActions } from "./SSLCertificateActions";
|
import { SSLCertificateActions } from "./SSLCertificateActions";
|
||||||
|
import { SSLCertificateDetailDialog } from "./SSLCertificateDetailDialog";
|
||||||
import { fetchSSLCertificates, addSSLCertificate, deleteSSLCertificate } from "@/services/sslCertificateService";
|
import { fetchSSLCertificates, addSSLCertificate, deleteSSLCertificate } from "@/services/sslCertificateService";
|
||||||
import { pb } from "@/lib/pocketbase";
|
import { pb } from "@/lib/pocketbase";
|
||||||
import { SSLCertificate } from "@/types/ssl.types";
|
import { SSLCertificate } from "@/types/ssl.types";
|
||||||
@@ -45,6 +45,7 @@ export const SSLCertificatesTable = () => {
|
|||||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||||
const [showEditDialog, setShowEditDialog] = useState(false);
|
const [showEditDialog, setShowEditDialog] = useState(false);
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
|
const [showViewDialog, setShowViewDialog] = useState(false);
|
||||||
const [selectedCertificate, setSelectedCertificate] = useState<SSLCertificate | null>(null);
|
const [selectedCertificate, setSelectedCertificate] = useState<SSLCertificate | null>(null);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
@@ -111,6 +112,11 @@ export const SSLCertificatesTable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openViewDialog = (certificate: SSLCertificate) => {
|
||||||
|
setSelectedCertificate(certificate);
|
||||||
|
setShowViewDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
const openEditDialog = (certificate: SSLCertificate) => {
|
const openEditDialog = (certificate: SSLCertificate) => {
|
||||||
setSelectedCertificate(certificate);
|
setSelectedCertificate(certificate);
|
||||||
setShowEditDialog(true);
|
setShowEditDialog(true);
|
||||||
@@ -168,6 +174,7 @@ export const SSLCertificatesTable = () => {
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<SSLCertificateActions
|
<SSLCertificateActions
|
||||||
certificate={certificate}
|
certificate={certificate}
|
||||||
|
onView={openViewDialog}
|
||||||
onEdit={openEditDialog}
|
onEdit={openEditDialog}
|
||||||
onDelete={openDeleteDialog}
|
onDelete={openDeleteDialog}
|
||||||
/>
|
/>
|
||||||
@@ -185,6 +192,13 @@ export const SSLCertificatesTable = () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* View Certificate Dialog */}
|
||||||
|
<SSLCertificateDetailDialog
|
||||||
|
certificate={selectedCertificate}
|
||||||
|
open={showViewDialog}
|
||||||
|
onOpenChange={setShowViewDialog}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Add Certificate Dialog */}
|
{/* Add Certificate Dialog */}
|
||||||
<Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>
|
<Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user