"use client"; import React, { useEffect, useState } from "react"; import axios from "axios"; import IconRefresh from "@/components/icon/icon-refresh"; import IconPlus from "@/components/icon/icon-plus"; import IconTrashLines from "@/components/icon/icon-trash-lines"; import IconServer from "@/components/icon/icon-server"; import IconLoader from "@/components/icon/icon-loader"; const API_BASE_URL = "/api/hestia"; const SSLChecker = () => { const [domains, setDomains] = useState([]); const [filteredDomains, setFilteredDomains] = useState([]); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState<{ [key: string]: string | null }>({}); const [globalLoading, setGlobalLoading] = useState(false); const [checkingExpiry, setCheckingExpiry] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null); // Filter states const [search, setSearch] = useState(""); const [filterStatus, setFilterStatus] = useState<'all' | 'secured' | 'unsecured' | 'expiring'>('all'); const fetchDomains = async () => { setLoading(true); try { const res = await axios.get(`${API_BASE_URL}/dns/domains`); const rawParsed = res.data.rawParsed || []; // If rawParsed is empty but domains exists (simple list), mapped it to objects for consistency // but user provided rawParsed structure, so we prioritize that. setDomains(rawParsed); setFilteredDomains(rawParsed); } catch (error) { console.error("Error fetching domains:", error); setMessage({ type: 'error', text: "Failed to fetch domains." }); } finally { setLoading(false); } }; useEffect(() => { fetchDomains(); }, []); // Filter Logic useEffect(() => { let result = domains; // Search if (search) { result = result.filter(d => d.domain.toLowerCase().includes(search.toLowerCase())); } // Status Filter if (filterStatus === 'secured') { result = result.filter(d => d.ssl && d.ssl.success); } else if (filterStatus === 'unsecured') { result = result.filter(d => !d.ssl || !d.ssl.success); } else if (filterStatus === 'expiring') { const today = new Date(); const oneDayMs = 24 * 60 * 60 * 1000; result = result.filter(d => { if (!d.ssl || !d.ssl.success || !d.ssl.notAfter) return false; const expiryDate = new Date(d.ssl.notAfter); const diffTime = expiryDate.getTime() - today.getTime(); const diffDays = Math.ceil(diffTime / oneDayMs); return diffDays <= 7; // Show warning for 7 days in UI, though email is 1 day }); } setFilteredDomains(result); }, [search, filterStatus, domains]); const handleAction = async (domain: string, action: 'add' | 'remove' | 'refresh') => { setActionLoading(prev => ({ ...prev, [domain]: action })); setMessage(null); try { const endpoint = action === 'add' ? '/ssl/add' : action === 'remove' ? '/ssl/remove' : '/ssl/refresh'; // Note: The API likely expects just { domain: "example.com" } await axios.post(`${API_BASE_URL}${endpoint}`, { domain }); setMessage({ type: 'success', text: `Successfully ${action}ed SSL for ${domain}` }); // Refresh list to update status fetchDomains(); } catch (error: any) { console.error(`Error during ${action} SSL:`, error); setMessage({ type: 'error', text: `Failed to ${action} SSL for ${domain}: ${error.response?.data?.message || error.message}` }); } finally { setActionLoading(prev => ({ ...prev, [domain]: null })); } }; const handleRefreshAll = async () => { setGlobalLoading(true); setMessage(null); try { await axios.post(`${API_BASE_URL}/ssl/refresh-all`); setMessage({ type: 'success', text: "Initiated SSL refresh for all domains." }); setTimeout(fetchDomains, 2000); // Wait a bit then refresh list } catch (error: any) { console.error("Error refreshing all SSL:", error); setMessage({ type: 'error', text: `Failed to refresh all SSL: ${error.response?.data?.message || error.message}` }); } finally { setGlobalLoading(false); } }; const handleCheckExpiryAndNotify = async () => { setCheckingExpiry(true); setMessage(null); try { const res = await axios.post('/api/send-expiry-alert', { domains }); if (res.data.success) { setMessage({ type: 'success', text: res.data.message }); } else { setMessage({ type: 'error', text: "Failed to process expiry check." }); } } catch (error: any) { console.error("Error checking expiry:", error); setMessage({ type: 'error', text: "Error checking expiry: " + error.message }); } finally { setCheckingExpiry(false); } }; const getDaysRemaining = (dateStr?: string) => { if (!dateStr) return null; const today = new Date(); const expiryDate = new Date(dateStr); const diffTime = expiryDate.getTime() - today.getTime(); return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); }; return (
SSL Management
{message && (
{message.text}
)}
setSearch(e.target.value)} />
{loading ? ( ) : filteredDomains.length === 0 ? ( ) : ( filteredDomains.map((item: any, index: number) => { const hasSSL = item.ssl && item.ssl.success; const daysLeft = getDaysRemaining(item.ssl?.notAfter); const isExpiring = daysLeft !== null && daysLeft <= 30; // Visible warning for 30 days const isCritical = daysLeft !== null && daysLeft <= 3; // Critical warning return ( ); }) )}
Domain IP Address SSL Status Expiry Actions
No domains found matching criteria.
{item.domain} {item.ip} {hasSSL ? ( Secured ) : ( Not Secured )} {hasSSL && item.ssl.notAfter ? (
{new Date(item.ssl.notAfter).toLocaleDateString()} {daysLeft} days left
) : ( - )}
{!hasSSL && ( )}
); }; export default SSLChecker;