Add search functionality to the Dashboard targets list

Implemented a search input to filter targets based on name, host, or database fields. Updated the UI to show filtered results and display a message if no targets match the search. Adjusted styles for improved responsiveness and usability.
This commit is contained in:
2026-02-12 12:27:53 +01:00
parent afd30e3897
commit c6da398574
2 changed files with 67 additions and 10 deletions

View File

@@ -6,6 +6,7 @@ import { useAuth } from "../state";
export function DashboardPage() { export function DashboardPage() {
const { tokens, refresh } = useAuth(); const { tokens, refresh } = useAuth();
const [targets, setTargets] = useState([]); const [targets, setTargets] = useState([]);
const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
@@ -31,6 +32,15 @@ export function DashboardPage() {
const alerts = targets.filter((t) => !t.host || !t.dbname).length; const alerts = targets.filter((t) => !t.host || !t.dbname).length;
const okCount = Math.max(0, targets.length - alerts); const okCount = Math.max(0, targets.length - alerts);
const filteredTargets = targets.filter((t) => {
const q = search.trim().toLowerCase();
if (!q) return true;
return (
(t.name || "").toLowerCase().includes(q) ||
(t.host || "").toLowerCase().includes(q) ||
(t.dbname || "").toLowerCase().includes(q)
);
});
return ( return (
<div className="dashboard-page"> <div className="dashboard-page">
@@ -56,12 +66,21 @@ export function DashboardPage() {
<div className="card dashboard-targets-card"> <div className="card dashboard-targets-card">
<div className="dashboard-targets-head"> <div className="dashboard-targets-head">
<h3>Targets</h3> <div>
<span>{targets.length} registered</span> <h3>Targets</h3>
<span>{filteredTargets.length} shown of {targets.length} registered</span>
</div>
<div className="dashboard-target-search">
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search by name, host, or database..."
/>
</div>
</div> </div>
<div className="dashboard-target-list"> <div className="dashboard-target-list">
{targets.map((t) => { {filteredTargets.map((t) => {
const hasAlert = !t.host || !t.dbname; const hasAlert = !t.host || !t.dbname;
return ( return (
<article className="dashboard-target-card" key={t.id}> <article className="dashboard-target-card" key={t.id}>
@@ -81,6 +100,9 @@ export function DashboardPage() {
</article> </article>
); );
})} })}
{filteredTargets.length === 0 && (
<div className="dashboard-empty">No targets match your search.</div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -291,9 +291,10 @@ button {
.dashboard-targets-head { .dashboard-targets-head {
display: flex; display: flex;
align-items: center; align-items: flex-end;
justify-content: space-between; justify-content: space-between;
margin-bottom: 12px; margin-bottom: 12px;
gap: 12px;
} }
.dashboard-targets-head h3 { .dashboard-targets-head h3 {
@@ -305,6 +306,18 @@ button {
font-size: 13px; font-size: 13px;
} }
.dashboard-target-search {
min-width: 320px;
max-width: 420px;
width: 100%;
}
.dashboard-target-search input {
width: 100%;
height: 36px;
padding: 8px 10px;
}
.kpi-orb { .kpi-orb {
position: absolute; position: absolute;
right: 14px; right: 14px;
@@ -334,7 +347,10 @@ button {
.dashboard-target-list { .dashboard-target-list {
display: grid; display: grid;
gap: 14px; gap: 10px;
max-height: 460px;
overflow: auto;
padding-right: 2px;
} }
.dashboard-targets-card { .dashboard-targets-card {
@@ -348,7 +364,7 @@ button {
align-items: center; align-items: center;
border: 1px solid #2b5b8f; border: 1px solid #2b5b8f;
border-radius: 12px; border-radius: 12px;
padding: 16px; padding: 12px 14px;
background: linear-gradient(180deg, #143462, #102a4f); background: linear-gradient(180deg, #143462, #102a4f);
box-shadow: inset 0 1px 0 #6fc5ff1a; box-shadow: inset 0 1px 0 #6fc5ff1a;
transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
@@ -362,15 +378,15 @@ button {
.target-main h4 { .target-main h4 {
margin: 0; margin: 0;
font-size: 24px; font-size: 20px;
font-weight: 800; font-weight: 800;
letter-spacing: 0.01em; letter-spacing: 0.01em;
} }
.target-main p { .target-main p {
margin: 6px 0 0 0; margin: 4px 0 0 0;
color: #d5e7ff; color: #d5e7ff;
font-size: 17px; font-size: 15px;
} }
.target-title-row { .target-title-row {
@@ -402,7 +418,7 @@ button {
.details-btn { .details-btn {
display: inline-block; display: inline-block;
padding: 10px 14px; padding: 7px 12px;
border-radius: 10px; border-radius: 10px;
font-weight: 700; font-weight: 700;
border: 1px solid #4db8f1; border: 1px solid #4db8f1;
@@ -416,6 +432,14 @@ button {
filter: brightness(1.05); filter: brightness(1.05);
} }
.dashboard-empty {
border: 1px dashed #34689f;
border-radius: 10px;
padding: 14px;
color: #9fb9d8;
text-align: center;
}
.query-insights-page .query-toolbar { .query-insights-page .query-toolbar {
display: grid; display: grid;
grid-template-columns: minmax(220px, 320px) minmax(320px, 1fr); grid-template-columns: minmax(220px, 320px) minmax(320px, 1fr);
@@ -1020,4 +1044,15 @@ select:-webkit-autofill {
height: auto; height: auto;
overflow: visible; overflow: visible;
} }
.dashboard-target-search {
min-width: 0;
}
.dashboard-targets-head {
flex-direction: column;
align-items: stretch;
}
.dashboard-target-list {
max-height: none;
overflow: visible;
}
} }