Implement the SSL & Domain Features (Domain List, Real-time information for Expiration Date, Issuer, and Days Left.)
This commit is contained in:
+10
-1
@@ -15,6 +15,7 @@ import ServiceDetail from "./pages/ServiceDetail";
|
|||||||
import Settings from "./pages/Settings";
|
import Settings from "./pages/Settings";
|
||||||
import Profile from "./pages/Profile";
|
import Profile from "./pages/Profile";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
|
import SslDomain from "./pages/SslDomain";
|
||||||
|
|
||||||
// Create a Protected route component
|
// Create a Protected route component
|
||||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||||
@@ -93,6 +94,14 @@ const App = () => {
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/ssl-domain"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<SslDomain />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
@@ -104,4 +113,4 @@ const App = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
@@ -63,10 +63,10 @@ export const Sidebar = ({
|
|||||||
<Boxes className={`${mainIconSize} text-blue-400`} />
|
<Boxes className={`${mainIconSize} text-blue-400`} />
|
||||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("instanceMonitoring")}</span>}
|
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("instanceMonitoring")}</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
<Link to="/ssl-domain" className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg ${location.pathname === '/ssl-domain' ? theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent' : `hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'}`} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||||
<Radar className={`${mainIconSize} text-cyan-400`} />
|
<Radar className={`${mainIconSize} text-cyan-400`} />
|
||||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("sslDomain")}</span>}
|
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("sslDomain")}</span>}
|
||||||
</div>
|
</Link>
|
||||||
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
<div className={`${collapsed ? 'p-3' : 'p-2 pl-3'} mb-1 rounded-lg hover:${theme === 'dark' ? 'bg-gray-800' : 'bg-sidebar-accent'} flex items-center ${collapsed ? 'justify-center' : ''} transition-colors duration-200`}>
|
||||||
<Calendar className={`${mainIconSize} text-emerald-400`} />
|
<Calendar className={`${mainIconSize} text-emerald-400`} />
|
||||||
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("scheduleIncident")}</span>}
|
{!collapsed && <span className="ml-2.5 font-medium text-foreground tracking-wide text-[15px]">{t("scheduleIncident")}</span>}
|
||||||
@@ -138,4 +138,4 @@ export const Sidebar = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</div>}
|
</div>}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { SSLCertificate } from "@/types/ssl.types";
|
||||||
|
|
||||||
|
interface SSLCertificateStatusCardsProps {
|
||||||
|
certificates: SSLCertificate[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SSLCertificateStatusCards = ({ certificates }: SSLCertificateStatusCardsProps) => {
|
||||||
|
// Count certificates by status
|
||||||
|
const validCount = certificates.filter(cert => cert.status === 'valid').length;
|
||||||
|
const expiringCount = certificates.filter(cert => cert.status === 'expiring_soon').length;
|
||||||
|
const expiredCount = certificates.filter(cert => cert.status === 'expired').length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<Card className="p-6 flex items-center space-x-4">
|
||||||
|
<div className="bg-green-100 dark:bg-green-900/20 p-3 rounded-full">
|
||||||
|
<div className="h-12 w-12 rounded-full bg-green-500/20 flex items-center justify-center">
|
||||||
|
<div className="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center text-white">
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-muted-foreground">Valid Certificates</p>
|
||||||
|
<p className="text-3xl font-bold">{validCount}</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="p-6 flex items-center space-x-4">
|
||||||
|
<div className="bg-yellow-100 dark:bg-yellow-900/20 p-3 rounded-full">
|
||||||
|
<div className="h-12 w-12 rounded-full bg-yellow-500/20 flex items-center justify-center">
|
||||||
|
<div className="h-8 w-8 rounded-full bg-yellow-500 flex items-center justify-center text-white">
|
||||||
|
!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-muted-foreground">Expiring Soon</p>
|
||||||
|
<p className="text-3xl font-bold">{expiringCount}</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="p-6 flex items-center space-x-4">
|
||||||
|
<div className="bg-red-100 dark:bg-red-900/20 p-3 rounded-full">
|
||||||
|
<div className="h-12 w-12 rounded-full bg-red-500/20 flex items-center justify-center">
|
||||||
|
<div className="h-8 w-8 rounded-full bg-red-500 flex items-center justify-center text-white">
|
||||||
|
✗
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-muted-foreground">Expired</p>
|
||||||
|
<p className="text-3xl font-bold">{expiredCount}</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
TableHead,
|
||||||
|
TableBody,
|
||||||
|
TableCell
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { RefreshCw } from "lucide-react";
|
||||||
|
import { SSLCertificate } from "@/types/ssl.types";
|
||||||
|
import { SSLStatusBadge } from "./SSLStatusBadge";
|
||||||
|
|
||||||
|
interface SSLCertificatesTableProps {
|
||||||
|
certificates: SSLCertificate[];
|
||||||
|
onRefresh: (id: string) => void;
|
||||||
|
refreshingId: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SSLCertificatesTable = ({ certificates, onRefresh, refreshingId }: SSLCertificatesTableProps) => {
|
||||||
|
const calculateDaysLeft = (expirationDate: string) => {
|
||||||
|
try {
|
||||||
|
const expDate = new Date(expirationDate);
|
||||||
|
const today = new Date();
|
||||||
|
const diffTime = expDate.getTime() - today.getTime();
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
return diffDays > 0 ? diffDays : 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error calculating days left:", error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Domain</TableHead>
|
||||||
|
<TableHead>Issuer</TableHead>
|
||||||
|
<TableHead>Expiration Date</TableHead>
|
||||||
|
<TableHead>Days Left</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Last Notified</TableHead>
|
||||||
|
<TableHead>Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{certificates.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={7} className="text-center py-8">
|
||||||
|
No SSL certificates found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
certificates.map((certificate) => (
|
||||||
|
<TableRow key={certificate.id}>
|
||||||
|
<TableCell className="font-medium">{certificate.domain}</TableCell>
|
||||||
|
<TableCell>{certificate.issuer || 'Unknown'}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{certificate.expiration_date ?
|
||||||
|
format(new Date(certificate.expiration_date), "MMM dd, yyyy") :
|
||||||
|
'Unknown'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{certificate.expiration_date ?
|
||||||
|
calculateDaysLeft(certificate.expiration_date) :
|
||||||
|
'Unknown'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<SSLStatusBadge status={certificate.status} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{certificate.last_notified
|
||||||
|
? format(new Date(certificate.last_notified), "MMM dd, yyyy")
|
||||||
|
: "Never"}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onRefresh(certificate.id)}
|
||||||
|
disabled={refreshingId === certificate.id}
|
||||||
|
>
|
||||||
|
<RefreshCw className={`h-4 w-4 mr-1 ${refreshingId === certificate.id ? 'animate-spin' : ''}`} />
|
||||||
|
Check
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm">View</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Plus, RefreshCw } from "lucide-react";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
import { SSLCertificateStatusCards } from "./SSLCertificateStatusCards";
|
||||||
|
import { SSLCertificatesTable } from "./SSLCertificatesTable";
|
||||||
|
import { LoadingState } from "@/components/services/LoadingState";
|
||||||
|
import { fetchSSLCertificates, addSSLCertificate, checkAndUpdateCertificate } from "@/services/sslCertificateService";
|
||||||
|
import { AddSSLCertificateForm } from "./AddSSLCertificateForm";
|
||||||
|
import { AddSSLCertificateDto, SSLCertificate } from "@/types/ssl.types";
|
||||||
|
|
||||||
|
export const SSLDomainContent = () => {
|
||||||
|
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||||
|
const [refreshingId, setRefreshingId] = useState<string | null>(null);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { data: certificates = [], isLoading, error } = useQuery({
|
||||||
|
queryKey: ['ssl-certificates'],
|
||||||
|
queryFn: fetchSSLCertificates,
|
||||||
|
});
|
||||||
|
|
||||||
|
const addMutation = useMutation({
|
||||||
|
mutationFn: addSSLCertificate,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['ssl-certificates'] });
|
||||||
|
setIsAddDialogOpen(false);
|
||||||
|
toast.success("SSL certificate added successfully");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Error adding SSL certificate:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Failed to add SSL certificate");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshMutation = useMutation({
|
||||||
|
mutationFn: checkAndUpdateCertificate,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['ssl-certificates'] });
|
||||||
|
setRefreshingId(null);
|
||||||
|
toast.success("SSL certificate checked and updated successfully");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Error refreshing SSL certificate:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Failed to refresh SSL certificate");
|
||||||
|
setRefreshingId(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddCertificate = async (data: AddSSLCertificateDto) => {
|
||||||
|
addMutation.mutate(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefreshCertificate = (id: string) => {
|
||||||
|
setRefreshingId(id);
|
||||||
|
refreshMutation.mutate(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingState />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full gap-4 text-foreground">
|
||||||
|
<p>Error loading SSL certificate data.</p>
|
||||||
|
<Button onClick={() => window.location.reload()}>Retry</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex-1 flex flex-col overflow-auto bg-background p-6 pb-0">
|
||||||
|
<div className="flex flex-col flex-1">
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h2 className="text-2xl font-bold text-foreground">SSL & Domain Management</h2>
|
||||||
|
<Button
|
||||||
|
className="text-primary-foreground"
|
||||||
|
onClick={() => setIsAddDialogOpen(true)}
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 mr-2" /> Add Domain
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SSLCertificateStatusCards certificates={certificates} />
|
||||||
|
|
||||||
|
<div className="mt-6 flex-1 flex flex-col pb-6">
|
||||||
|
<SSLCertificatesTable
|
||||||
|
certificates={certificates}
|
||||||
|
onRefresh={handleRefreshCertificate}
|
||||||
|
refreshingId={refreshingId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Add SSL Certificate</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<AddSSLCertificateForm
|
||||||
|
onSubmit={handleAddCertificate}
|
||||||
|
onCancel={() => setIsAddDialogOpen(false)}
|
||||||
|
isPending={addMutation.isPending}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
|
interface SSLStatusBadgeProps {
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SSLStatusBadge: React.FC<SSLStatusBadgeProps> = ({ status }) => {
|
||||||
|
let variant = "";
|
||||||
|
let label = "";
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case "valid":
|
||||||
|
variant = "bg-green-500 hover:bg-green-600";
|
||||||
|
label = "Valid";
|
||||||
|
break;
|
||||||
|
case "expiring_soon":
|
||||||
|
variant = "bg-yellow-500 hover:bg-yellow-600";
|
||||||
|
label = "Expiring Soon";
|
||||||
|
break;
|
||||||
|
case "expired":
|
||||||
|
variant = "bg-red-500 hover:bg-red-600";
|
||||||
|
label = "Expired";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
variant = "bg-gray-500 hover:bg-gray-600";
|
||||||
|
label = status.charAt(0).toUpperCase() + status.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge className={`${variant} text-white`}>
|
||||||
|
{label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { authService } from "@/services/authService";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Header } from "@/components/dashboard/Header";
|
||||||
|
import { Sidebar } from "@/components/dashboard/Sidebar";
|
||||||
|
import { SSLDomainContent } from "@/components/ssl-domain/SSLDomainContent";
|
||||||
|
import { LoadingState } from "@/components/services/LoadingState";
|
||||||
|
|
||||||
|
const SslDomain = () => {
|
||||||
|
// State for sidebar collapse functionality
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false);
|
||||||
|
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
||||||
|
|
||||||
|
// Get current user
|
||||||
|
const currentUser = authService.getCurrentUser();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Handle logout
|
||||||
|
const handleLogout = () => {
|
||||||
|
authService.logout();
|
||||||
|
navigate("/login");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
||||||
|
<Sidebar collapsed={sidebarCollapsed} />
|
||||||
|
<div className="flex flex-col flex-1">
|
||||||
|
<Header
|
||||||
|
currentUser={currentUser}
|
||||||
|
onLogout={handleLogout}
|
||||||
|
sidebarCollapsed={sidebarCollapsed}
|
||||||
|
toggleSidebar={toggleSidebar}
|
||||||
|
/>
|
||||||
|
<SSLDomainContent />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SslDomain;
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
|
||||||
|
import { pb } from "@/lib/pocketbase";
|
||||||
|
import { AddSSLCertificateDto, SSLCertificate } from "@/types/ssl.types";
|
||||||
|
import { checkDomainSSL, determineSSLStatus } from "@/utils/sslUtils";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const fetchSSLCertificates = async (): Promise<SSLCertificate[]> => {
|
||||||
|
try {
|
||||||
|
// Using the PocketBase client to fetch SSL certificates
|
||||||
|
const response = await pb.collection('ssl_certificates').getList(1, 50, {
|
||||||
|
sort: '-created',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Transform the data if needed
|
||||||
|
return response.items.map(cert => ({
|
||||||
|
id: cert.id,
|
||||||
|
domain: cert.domain,
|
||||||
|
port: cert.port || 443,
|
||||||
|
issuer: cert.issuer,
|
||||||
|
expiration_date: cert.expiration_date,
|
||||||
|
status: determineSSLStatus(cert.expiration_date, cert.warning_threshold, cert.expiry_threshold),
|
||||||
|
last_notified: cert.last_notified,
|
||||||
|
warning_threshold: cert.warning_threshold,
|
||||||
|
expiry_threshold: cert.expiry_threshold,
|
||||||
|
notification_channel: cert.notification_channel,
|
||||||
|
created: cert.created,
|
||||||
|
updated: cert.updated
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching SSL certificates:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSSLCertificate = async (certificateData: AddSSLCertificateDto): Promise<SSLCertificate> => {
|
||||||
|
try {
|
||||||
|
// Perform actual SSL check on the domain
|
||||||
|
const sslCheck = await checkDomainSSL(certificateData.domain, certificateData.port);
|
||||||
|
|
||||||
|
if (sslCheck.error) {
|
||||||
|
throw new Error(`SSL certificate check failed: ${sslCheck.error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare data with the actual certificate information
|
||||||
|
const dataToSubmit = {
|
||||||
|
...certificateData,
|
||||||
|
issuer: sslCheck.issuer,
|
||||||
|
expiration_date: sslCheck.expirationDate,
|
||||||
|
status: determineSSLStatus(
|
||||||
|
sslCheck.expirationDate,
|
||||||
|
certificateData.warning_threshold,
|
||||||
|
certificateData.expiry_threshold
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await pb.collection('ssl_certificates').create(dataToSubmit);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: response.id,
|
||||||
|
domain: response.domain,
|
||||||
|
port: response.port,
|
||||||
|
issuer: response.issuer,
|
||||||
|
expiration_date: response.expiration_date,
|
||||||
|
status: determineSSLStatus(
|
||||||
|
response.expiration_date,
|
||||||
|
response.warning_threshold,
|
||||||
|
response.expiry_threshold
|
||||||
|
),
|
||||||
|
last_notified: response.last_notified || '',
|
||||||
|
warning_threshold: response.warning_threshold,
|
||||||
|
expiry_threshold: response.expiry_threshold,
|
||||||
|
notification_channel: response.notification_channel,
|
||||||
|
created: response.created,
|
||||||
|
updated: response.updated
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error adding SSL certificate:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Failed to add SSL certificate");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to check and update an existing certificate
|
||||||
|
const checkAndUpdateCertificate = async (certificateId: string): Promise<SSLCertificate> => {
|
||||||
|
try {
|
||||||
|
// Fetch the current certificate data
|
||||||
|
const currentCert = await pb.collection('ssl_certificates').getOne(certificateId);
|
||||||
|
|
||||||
|
// Perform SSL check
|
||||||
|
const sslCheck = await checkDomainSSL(currentCert.domain, currentCert.port);
|
||||||
|
|
||||||
|
if (sslCheck.error) {
|
||||||
|
throw new Error(`SSL certificate check failed: ${sslCheck.error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update with new information
|
||||||
|
const updatedData = {
|
||||||
|
issuer: sslCheck.issuer,
|
||||||
|
expiration_date: sslCheck.expirationDate,
|
||||||
|
status: determineSSLStatus(
|
||||||
|
sslCheck.expirationDate,
|
||||||
|
currentCert.warning_threshold,
|
||||||
|
currentCert.expiry_threshold
|
||||||
|
),
|
||||||
|
updated: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await pb.collection('ssl_certificates').update(certificateId, updatedData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: response.id,
|
||||||
|
domain: response.domain,
|
||||||
|
port: response.port,
|
||||||
|
issuer: response.issuer,
|
||||||
|
expiration_date: response.expiration_date,
|
||||||
|
status: determineSSLStatus(
|
||||||
|
response.expiration_date,
|
||||||
|
response.warning_threshold,
|
||||||
|
response.expiry_threshold
|
||||||
|
),
|
||||||
|
last_notified: response.last_notified || '',
|
||||||
|
warning_threshold: response.warning_threshold,
|
||||||
|
expiry_threshold: response.expiry_threshold,
|
||||||
|
notification_channel: response.notification_channel,
|
||||||
|
created: response.created,
|
||||||
|
updated: response.updated
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating SSL certificate:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : "Failed to update SSL certificate");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { fetchSSLCertificates, addSSLCertificate, checkAndUpdateCertificate };
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
export interface SSLCertificate {
|
||||||
|
id: string;
|
||||||
|
domain: string;
|
||||||
|
issuer: string;
|
||||||
|
expiration_date: string;
|
||||||
|
status: string;
|
||||||
|
last_notified: string;
|
||||||
|
warning_threshold: number;
|
||||||
|
expiry_threshold: number;
|
||||||
|
notification_channel: string;
|
||||||
|
created: string;
|
||||||
|
updated: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
import { SSLCertificate } from "@/types/ssl.types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and validates an SSL certificate for a domain
|
||||||
|
*/
|
||||||
|
export const checkDomainSSL = async (domain: string, port: number = 443): Promise<{
|
||||||
|
isValid: boolean;
|
||||||
|
issuer: string;
|
||||||
|
expirationDate: string;
|
||||||
|
error?: string;
|
||||||
|
}> => {
|
||||||
|
try {
|
||||||
|
// Make an actual API call to check the SSL certificate
|
||||||
|
// This uses a real SSL certificate checking API
|
||||||
|
const response = await fetch(`https://sslcheck-api.vercel.app/api/check?domain=${encodeURIComponent(domain)}&port=${port}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to check SSL certificate: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sslData = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: sslData.valid === true,
|
||||||
|
issuer: sslData.issuer || 'Unknown',
|
||||||
|
expirationDate: sslData.expires || new Date().toISOString(),
|
||||||
|
error: sslData.error
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('SSL check failed:', error);
|
||||||
|
throw new Error(error instanceof Error ? error.message : 'Failed to verify SSL certificate');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the status of an SSL certificate
|
||||||
|
*/
|
||||||
|
export const determineSSLStatus = (expiryDate: string, warningThreshold: number, expiryThreshold: number): string => {
|
||||||
|
const expirationDate = new Date(expiryDate);
|
||||||
|
const now = new Date();
|
||||||
|
const daysUntilExpiration = Math.ceil((expirationDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (daysUntilExpiration <= 0) {
|
||||||
|
return "expired";
|
||||||
|
} else if (daysUntilExpiration <= expiryThreshold) {
|
||||||
|
return "expiring_soon";
|
||||||
|
} else if (daysUntilExpiration > expiryThreshold) {
|
||||||
|
return "valid";
|
||||||
|
} else {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user