diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx
index 03cbc03..939a412 100644
--- a/frontend/src/pages/DashboardPage.jsx
+++ b/frontend/src/pages/DashboardPage.jsx
@@ -6,6 +6,7 @@ import { useAuth } from "../state";
export function DashboardPage() {
const { tokens, refresh } = useAuth();
const [targets, setTargets] = useState([]);
+ const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
@@ -31,6 +32,15 @@ export function DashboardPage() {
const alerts = targets.filter((t) => !t.host || !t.dbname).length;
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 (
@@ -56,12 +66,21 @@ export function DashboardPage() {
-
Targets
-
{targets.length} registered
+
+
Targets
+ {filteredTargets.length} shown of {targets.length} registered
+
+
+ setSearch(e.target.value)}
+ placeholder="Search by name, host, or database..."
+ />
+
- {targets.map((t) => {
+ {filteredTargets.map((t) => {
const hasAlert = !t.host || !t.dbname;
return (
@@ -81,6 +100,9 @@ export function DashboardPage() {
);
})}
+ {filteredTargets.length === 0 && (
+
No targets match your search.
+ )}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 558626d..7193d38 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -291,9 +291,10 @@ button {
.dashboard-targets-head {
display: flex;
- align-items: center;
+ align-items: flex-end;
justify-content: space-between;
margin-bottom: 12px;
+ gap: 12px;
}
.dashboard-targets-head h3 {
@@ -305,6 +306,18 @@ button {
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 {
position: absolute;
right: 14px;
@@ -334,7 +347,10 @@ button {
.dashboard-target-list {
display: grid;
- gap: 14px;
+ gap: 10px;
+ max-height: 460px;
+ overflow: auto;
+ padding-right: 2px;
}
.dashboard-targets-card {
@@ -348,7 +364,7 @@ button {
align-items: center;
border: 1px solid #2b5b8f;
border-radius: 12px;
- padding: 16px;
+ padding: 12px 14px;
background: linear-gradient(180deg, #143462, #102a4f);
box-shadow: inset 0 1px 0 #6fc5ff1a;
transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
@@ -362,15 +378,15 @@ button {
.target-main h4 {
margin: 0;
- font-size: 24px;
+ font-size: 20px;
font-weight: 800;
letter-spacing: 0.01em;
}
.target-main p {
- margin: 6px 0 0 0;
+ margin: 4px 0 0 0;
color: #d5e7ff;
- font-size: 17px;
+ font-size: 15px;
}
.target-title-row {
@@ -402,7 +418,7 @@ button {
.details-btn {
display: inline-block;
- padding: 10px 14px;
+ padding: 7px 12px;
border-radius: 10px;
font-weight: 700;
border: 1px solid #4db8f1;
@@ -416,6 +432,14 @@ button {
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 {
display: grid;
grid-template-columns: minmax(220px, 320px) minmax(320px, 1fr);
@@ -1020,4 +1044,15 @@ select:-webkit-autofill {
height: auto;
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;
+ }
}