diff --git a/src/app/domains/page.tsx b/src/app/domains/page.tsx
index 2a72d2c..2feeb2a 100644
--- a/src/app/domains/page.tsx
+++ b/src/app/domains/page.tsx
@@ -2,6 +2,7 @@
import ConfirmActionDialog from "@/components/ConfirmActionDialog";
import DashboardLayout from "@/components/DashboardLayout";
+import SearchField from "@/components/SearchField";
import ToastViewport from "@/components/ToastViewport";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Globe, Loader2, Lock, Plus, RefreshCw } from "lucide-react";
@@ -51,6 +52,7 @@ export default function DomainsPage() {
const [data, setData] = useState([]);
const [serverList, setServerList] = useState([]);
const [selectedServerId, setSelectedServerId] = useState("");
+ const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const [syncing, setSyncing] = useState(false);
const [syncMessage, setSyncMessage] = useState("");
@@ -294,10 +296,28 @@ export default function DomainsPage() {
.length,
};
const hasPendingSslAction = syncing || renewing !== null || deleting !== null;
- const domainsWithSsl = visibleData.filter((d) => d.sslEnabled && d.sslCert);
+ const filteredData = useMemo(() => {
+ const query = search.trim().toLowerCase();
+ if (!query) return visibleData;
+
+ return visibleData.filter((domain) =>
+ [
+ domain.name,
+ domain.type,
+ domain.value,
+ domain.proxy,
+ domain.server?.name ?? "",
+ domain.sslCert?.status ?? "",
+ domain.sslCert?.issuer ?? "",
+ ].some((value) => value.toLowerCase().includes(query)),
+ );
+ }, [search, visibleData]);
+ const domainsWithSsl = filteredData.filter(
+ (domain) => domain.sslEnabled && domain.sslCert,
+ );
const pagination = useTablePagination({
- items: visibleData,
- resetKey: `${activeTab}|${selectedServerId}`,
+ items: filteredData,
+ resetKey: `${activeTab}|${selectedServerId}|${search}`,
});
return (
@@ -427,6 +447,18 @@ export default function DomainsPage() {
*/}
+
+ setSearch(event.target.value)}
+ containerStyle={{ flex: "1 1 360px", minWidth: 240 }}
+ />
+
+
{/* Tab switcher */}
@@ -575,9 +607,34 @@ export default function DomainsPage() {
)}
- {!loading && visibleData.length > 0 && activeTab === "domains" && (
+ {!loading && visibleData.length > 0 && filteredData.length === 0 && (
+
+
+
+ No domain records match your search.
+
+
+ )}
+
+ {!loading && filteredData.length > 0 && activeTab === "domains" && (
)}
- {!loading && activeTab === "ssl" && (
+ {!loading && filteredData.length > 0 && activeTab === "ssl" && (
+
+
+ {activeTab === "certs"
+ ? "No SSL certificates match your search."
+ : "No domains waiting for SSL match your search."}
+
+
+ );
+ }
+
return activeTab === "domains" ? (
([]);
const [serverList, setServerList] = useState([]);
const [selectedServerId, setSelectedServerId] = useState("");
+ const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const [syncing, setSyncing] = useState(false);
const [showAdd, setShowAdd] = useState(false);
@@ -176,6 +178,34 @@ export default function SSLPage() {
[domains, selectedServerId],
);
+ const filteredCerts = useMemo(() => {
+ const query = search.trim().toLowerCase();
+ if (!query) return visibleCerts;
+
+ return visibleCerts.filter((cert) =>
+ [
+ cert.domain?.name ?? "",
+ cert.domain?.server?.name ?? "",
+ cert.issuer,
+ cert.status,
+ ].some((value) => value.toLowerCase().includes(query)),
+ );
+ }, [search, visibleCerts]);
+
+ const filteredIssueableDomains = useMemo(() => {
+ const query = search.trim().toLowerCase();
+ if (!query) return issueableDomains;
+
+ return issueableDomains.filter((domain) =>
+ [
+ domain.name,
+ domain.value,
+ domain.proxy,
+ domain.server?.name ?? "",
+ ].some((value) => value.toLowerCase().includes(query)),
+ );
+ }, [issueableDomains, search]);
+
const handleQuickIssue = async (domain: Domain) => {
setIssuingDomainId(domain.id);
try {
@@ -409,6 +439,22 @@ export default function SSLPage() {
onChange={handleServerChange}
/>
+
+ setSearch(event.target.value)}
+ containerStyle={{ flex: "1 1 360px", minWidth: 240 }}
+ />
+
+
0 && search.trim()
+ ? "no-results"
+ : "empty"
+ }
activeTab={activeTab}
selectedServerId={selectedServerId}
/>
) : null}
- {!loading && activeTab === "domains" && issueableDomains.length > 0 ? (
+ {!loading && activeTab === "domains" && filteredIssueableDomains.length > 0 ? (
) : null}
- {!loading && activeTab === "certs" && visibleCerts.length === 0 ? (
+ {!loading && activeTab === "certs" && filteredCerts.length === 0 ? (
0 && search.trim()
+ ? "no-results"
+ : "empty"
+ }
activeTab={activeTab}
selectedServerId={selectedServerId}
/>
) : null}
- {!loading && activeTab === "certs" && visibleCerts.length > 0 ? (
+ {!loading && activeTab === "certs" && filteredCerts.length > 0 ? (