feat(domains, ssl): add consistent search filters

This commit is contained in:
KodekaTeam committed 2026-08-05 12:14:34 +07:00
1 parent eab0678f28
commit fd4585843f
3 files changed
+148 -15

No files matched your search

+63 -6
View File
@@ -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<Domain[]>([]);
const [serverList, setServerList] = useState<Server[]>([]);
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() {
</p> */}
</div>
<div
className="card ui-responsive-toolbar"
style={{ padding: "12px 16px" }}
>
<SearchField
placeholder="Search domains, targets, or SSL status..."
value={search}
onChange={(event) => setSearch(event.target.value)}
containerStyle={{ flex: "1 1 360px", minWidth: 240 }}
/>
</div>
{/* Tab switcher */}
<div className="ui-tab-shell">
<div className="ui-tab-scroll no-scrollbar" style={{}}>
@@ -575,9 +607,34 @@ export default function DomainsPage() {
</div>
)}
{!loading && visibleData.length > 0 && activeTab === "domains" && (
{!loading && visibleData.length > 0 && filteredData.length === 0 && (
<div
className="card"
style={{
padding: 48,
textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Globe
size={36}
style={{
color: "var(--text-muted)",
marginBottom: 12,
opacity: 0.4,
}}
/>
<p style={{ color: "var(--text-muted)", fontSize: 14 }}>
No domain records match your search.
</p>
</div>
)}
{!loading && filteredData.length > 0 && activeTab === "domains" && (
<DomainsTable
domains={visibleData}
domains={filteredData}
pagination={pagination}
deleting={deleting}
renewing={renewing}
@@ -587,7 +644,7 @@ export default function DomainsPage() {
/>
)}
{!loading && activeTab === "ssl" && (
{!loading && filteredData.length > 0 && activeTab === "ssl" && (
<SslCertificatesGrid
domains={domainsWithSsl}
renewing={renewing}
+23 -1
View File
@@ -2,7 +2,7 @@ import { Globe, Loader2, Lock } from "lucide-react";
import { SSLTabKey } from "@/app/ssl/components/ssl-utils";
interface SSLStatePanelProps {
mode: "loading" | "empty";
mode: "loading" | "empty" | "no-results";
activeTab: SSLTabKey;
selectedServerId: string;
}
@@ -29,6 +29,28 @@ export default function SSLStatePanel({
);
}
if (mode === "no-results") {
const Icon = activeTab === "certs" ? Lock : Globe;
return (
<div className="card" style={{ padding: 42, textAlign: "center" }}>
<Icon
size={34}
style={{
color: "var(--text-muted)",
margin: "0 auto 10px",
opacity: 0.4,
}}
/>
<p style={{ color: "var(--text-muted)", fontSize: 14 }}>
{activeTab === "certs"
? "No SSL certificates match your search."
: "No domains waiting for SSL match your search."}
</p>
</div>
);
}
return activeTab === "domains" ? (
<div className="card" style={{ padding: 42, textAlign: "center" }}>
<Globe
+62 -8
View File
@@ -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 {
@@ -54,6 +55,7 @@ export default function SSLPage() {
const [domains, setDomains] = useState<Domain[]>([]);
const [serverList, setServerList] = useState<Server[]>([]);
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}
/>
<div
className="card ui-responsive-toolbar"
style={{ padding: "12px 16px" }}
>
<SearchField
placeholder={
activeTab === "certs"
? "Search certificates, domains, or servers..."
: "Search domains, targets, or servers..."
}
value={search}
onChange={(event) => setSearch(event.target.value)}
containerStyle={{ flex: "1 1 360px", minWidth: 240 }}
/>
</div>
<SSLToolbar
activeTab={activeTab}
syncing={syncing}
@@ -430,34 +476,42 @@ export default function SSLPage() {
{!loading &&
activeTab === "domains" &&
issueableDomains.length === 0 ? (
filteredIssueableDomains.length === 0 ? (
<SSLStatePanel
mode="empty"
mode={
issueableDomains.length > 0 && search.trim()
? "no-results"
: "empty"
}
activeTab={activeTab}
selectedServerId={selectedServerId}
/>
) : null}
{!loading && activeTab === "domains" && issueableDomains.length > 0 ? (
{!loading && activeTab === "domains" && filteredIssueableDomains.length > 0 ? (
<SSLDomainsGrid
domains={issueableDomains}
domains={filteredIssueableDomains}
issuingDomainId={issuingDomainId}
syncing={syncing}
onQuickIssue={handleQuickIssue}
/>
) : null}
{!loading && activeTab === "certs" && visibleCerts.length === 0 ? (
{!loading && activeTab === "certs" && filteredCerts.length === 0 ? (
<SSLStatePanel
mode="empty"
mode={
visibleCerts.length > 0 && search.trim()
? "no-results"
: "empty"
}
activeTab={activeTab}
selectedServerId={selectedServerId}
/>
) : null}
{!loading && activeTab === "certs" && visibleCerts.length > 0 ? (
{!loading && activeTab === "certs" && filteredCerts.length > 0 ? (
<SSLCertificatesGrid
certs={visibleCerts}
certs={filteredCerts}
busyId={busyId}
renewBusy={isRenewInProgress}
renewOperations={renewOperations}