132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import axios from "axios";
|
|
|
|
interface DomainStatus {
|
|
domain: string;
|
|
validTo: string;
|
|
expired: boolean;
|
|
}
|
|
|
|
const SSLChecker: React.FC = () => {
|
|
const [data, setData] = useState<DomainStatus[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [search, setSearch] = useState("");
|
|
const [lastChecked, setLastChecked] = useState<string>("");
|
|
|
|
// Fetch SSL Data
|
|
const loadSSL = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
const response = await axios.get("http://localhost:3010/api/ssl/status");
|
|
setData(response.data);
|
|
|
|
const now = new Date().toLocaleString();
|
|
setLastChecked(now);
|
|
} catch (error) {
|
|
console.error("SSL API Error:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// Auto load on mount
|
|
useEffect(() => {
|
|
loadSSL();
|
|
}, []);
|
|
|
|
// Filter domains
|
|
const filteredData = data.filter((item) =>
|
|
item.domain.toLowerCase().includes(search.toLowerCase())
|
|
);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 p-8 text-gray-900">
|
|
<h1 className="text-3xl font-bold text-center mb-6">
|
|
SSL Expiry Status Checker
|
|
</h1>
|
|
|
|
{/* Search + Refresh */}
|
|
<div className="flex items-center justify-between mb-5">
|
|
<input
|
|
type="text"
|
|
placeholder="Search domain..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
className="w-1/3 p-2 border border-gray-300 rounded-md focus:ring focus:ring-blue-300"
|
|
/>
|
|
|
|
<button
|
|
onClick={loadSSL}
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition"
|
|
>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
|
|
{/* Last checked time */}
|
|
<p className="mb-4 text-sm text-gray-600">
|
|
Last checked: <span className="font-semibold">{lastChecked}</span>
|
|
</p>
|
|
|
|
{/* Loading Shimmer */}
|
|
{loading ? (
|
|
<div className="p-10 bg-white rounded-xl shadow-md animate-pulse text-center text-gray-700">
|
|
Checking SSL certificates...
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto bg-white rounded-xl shadow-lg border">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="bg-gray-100 border-b">
|
|
<th className="px-6 py-3 text-left font-semibold text-gray-700">
|
|
Domain
|
|
</th>
|
|
<th className="px-6 py-3 text-left font-semibold text-gray-700">
|
|
Valid Until
|
|
</th>
|
|
<th className="px-6 py-3 text-left font-semibold text-gray-700">
|
|
Status
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
{filteredData.map((d) => (
|
|
<tr
|
|
key={d.domain}
|
|
className="border-b hover:bg-gray-50 transition"
|
|
>
|
|
<td className="px-6 py-4">{d.domain}</td>
|
|
<td className="px-6 py-4">{d.validTo}</td>
|
|
<td className="px-6 py-4">
|
|
<span
|
|
className={`px-3 py-1 rounded-full text-sm font-semibold ${
|
|
d.expired
|
|
? "bg-red-100 text-red-700"
|
|
: "bg-green-100 text-green-700"
|
|
}`}
|
|
>
|
|
{d.expired ? "Expired" : "Valid"}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
|
|
{filteredData.length === 0 && (
|
|
<p className="text-center py-6 text-gray-500">
|
|
No matching domains found.
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SSLChecker;
|