feat(domains, ssl): add consistent search filters
This commit is contained in:
1 parent
eab0678f28
commit
fd4585843f
3 files changed
+148
-15
No files matched your search
@@ -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}
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
Reference in new issue
Block a user