feat(awooop): link telegram alerts to incident runs
This commit is contained in:
@@ -116,6 +116,7 @@ interface RecentEventsResponse {
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "";
|
||||
const PER_PAGE = 50;
|
||||
const AUTO_REFRESH_INTERVAL = 30_000; // 30 秒
|
||||
const INCIDENT_ID_FILTER_RE = /^INC-\d{8}-[A-Z0-9]{4,}$/;
|
||||
|
||||
const STATE_CONFIG: Record<
|
||||
RunState,
|
||||
@@ -518,10 +519,20 @@ export default function RunsPage() {
|
||||
const [projectFilter, setProjectFilter] = useState<string>("");
|
||||
const [statusFilter, setStatusFilter] = useState<string>("");
|
||||
const [evidenceFilter, setEvidenceFilter] = useState<"" | RemediationStatus>("");
|
||||
const [incidentFilter, setIncidentFilter] = useState<string>("");
|
||||
const [page, setPage] = useState(1);
|
||||
const [lastRefresh, setLastRefresh] = useState<Date | null>(null);
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const linkedProject = params.get("project_id") || "";
|
||||
const linkedIncident = params.get("incident_id") || "";
|
||||
if (linkedProject) setProjectFilter(linkedProject);
|
||||
if (linkedIncident) setIncidentFilter(linkedIncident);
|
||||
if (linkedProject || linkedIncident) setPage(1);
|
||||
}, []);
|
||||
|
||||
// 取得租戶清單
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/api/v1/platform/tenants`)
|
||||
@@ -540,6 +551,10 @@ export default function RunsPage() {
|
||||
if (projectFilter) params.set("project_id", projectFilter);
|
||||
if (statusFilter) params.set("state", statusFilter);
|
||||
if (evidenceFilter) params.set("remediation_status", evidenceFilter);
|
||||
const normalizedIncidentFilter = incidentFilter.trim().toUpperCase();
|
||||
if (INCIDENT_ID_FILTER_RE.test(normalizedIncidentFilter)) {
|
||||
params.set("incident_id", normalizedIncidentFilter);
|
||||
}
|
||||
params.set("page", String(page));
|
||||
params.set("per_page", String(PER_PAGE));
|
||||
|
||||
@@ -572,7 +587,7 @@ export default function RunsPage() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [projectFilter, statusFilter, evidenceFilter, page]);
|
||||
}, [projectFilter, statusFilter, evidenceFilter, incidentFilter, page]);
|
||||
|
||||
// 初次載入
|
||||
useEffect(() => {
|
||||
@@ -791,6 +806,18 @@ export default function RunsPage() {
|
||||
</select>
|
||||
<ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground pointer-events-none" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
{/* Incident Filter */}
|
||||
<input
|
||||
value={incidentFilter}
|
||||
onChange={(e) => {
|
||||
setIncidentFilter(e.target.value.trim().toUpperCase());
|
||||
setPage(1);
|
||||
}}
|
||||
className="min-w-[260px] rounded-lg border border-border bg-background px-3 py-1.5 font-mono text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-brand-accent/50"
|
||||
aria-label={tEvidence("filters.incidentLabel")}
|
||||
placeholder={tEvidence("filters.incidentPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Error State */}
|
||||
|
||||
Reference in New Issue
Block a user