Scanner: ohne Altersgrenze suchen, Filter Anzeigenalter, Alarm-Schlagwörter

- Suche ohne 21-Tage-Grenze (BA ohne veroeffentlichtseit, Adzuna ohne
  max_days_old); Einstellung Höchstalter entfällt
- Dashboard: Filter Anzeigenalter (bis/älter als 21 Tage), Sortierung
  älteste Anzeige, Filter nur Alarm, Zeitraum heißt jetzt Gefunden
- Suchagent: Schlagwörter mit Alarm; Treffer mit alarm = 1 melden sich
  über den AlarmWatcher (App-weit, jede Minute) mit Ton und Pop-up,
  Quittieren gilt für alle

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-29 13:28:29 +02:00
co-authored by Claude Opus 5.5
parent b34a1968df
commit 1ad774fdd2
16 changed files with 978 additions and 625 deletions
+25 -3
View File
@@ -479,6 +479,8 @@ export interface ScannerAgent {
keywords: string[];
sources: ScannerSourceId[];
excludes: string[];
/** Schlagwörter, deren Fund mit Ton und Pop-up gemeldet wird. */
alarm_keywords: string[];
is_active: boolean;
matches: number;
}
@@ -491,8 +493,21 @@ export interface ScannerSchedule {
to: string;
/** Minuten */
interval: number;
/** höchstes Alter einer Anzeige in Tagen */
maxAge: number;
}
export interface ScannerAlarm {
id: number;
score: number;
hits: string[];
created_at: string;
agent_name: string;
source: ScannerSourceId;
title: string;
company: string | null;
location: string | null;
url: string;
published_at: string | null;
company_id: number | null;
}
export interface ScannerSource {
@@ -525,6 +540,8 @@ export interface ScannerMatch {
score: number;
hits: string[];
status: ScannerMatchStatus;
/** Alarm-Schlagwort getroffen */
alarm: boolean;
created_at: string;
agent_id: number;
agent_name: string;
@@ -549,7 +566,10 @@ export interface ScannerSecrets {
adzunaKey: string | null;
}
export type ScannerAgentInput = Pick<ScannerAgent, 'name' | 'keywords' | 'sources' | 'excludes' | 'is_active'>;
export type ScannerAgentInput = Pick<
ScannerAgent,
'name' | 'keywords' | 'alarm_keywords' | 'sources' | 'excludes' | 'is_active'
>;
const put = <T>(path: string, body: unknown) =>
apiRequest<T>(path, { method: 'PUT', body: JSON.stringify(body) });
@@ -563,6 +583,8 @@ export const scannerApi = {
matches: (params: Record<string, unknown> = {}) =>
get<{ total: number; matches: ScannerMatch[] }>(`/scanner/matches${query(params)}`),
setStatus: (id: number, status: ScannerMatchStatus) => patch<{ ok: true }>(`/scanner/matches/${id}`, { status }),
alarms: () => get<{ alarms: ScannerAlarm[] }>('/scanner/alarms'),
ackAlarms: (ids: number[]) => post<{ ok: true }>('/scanner/alarms/ack', { ids }),
scan: () => post<{ started: true }>('/scanner/scan'),
settings: () => get<{ settings: ScannerSchedule; secrets: ScannerSecrets }>('/scanner/settings'),
saveSettings: (changes: Partial<ScannerSchedule> & Partial<Record<keyof ScannerSecrets, string>>) =>