Portiere PHP-Cronjobs auf Spring-Tasks und ergänze Legacy-Tests

Migriert die weiterhin benötigten Zeitaufgaben einschließlich SOAP-Importen, Betriebsstatus und Wiederanlaufkontrolle. Aktiviert 115 native Spring-Tasks im Docker-Image ohne Cron-Daemon oder PHP und dokumentiert die entfallenen HHA-Aufrufe. Ergänzt PHP-/UI-Testwerkzeuge und aktualisiert den Projektstand auf Version 0.2.0.
This commit is contained in:
Sven committed 2026-10-27 09:53:23 +01:00
1 parent 781a2b82dc
commit 9de2358480
57 files changed
+3251 -7

No files matched your search

+16
View File
@@ -4,8 +4,10 @@ import {request} from './api';
type Worker={id:string;label:string;configured:boolean;state:string;message:string;scope:string;lastRun:string;ageSeconds:number;intervalSeconds:number;values:Record<string,number>};
type Source<T>={available:boolean;message?:string;data?:T};
type CronTask={id:string;label:string;group:string;cron:string;zone:string;source:string;enabled:boolean;state:string;message:string;lastRun:string;slot:string;processed:number;nextRun:string};
type Snapshot={
at:string;startedAt:string;headquartersId:number;workers:Worker[];
cron?:{items:CronTask[];unresolved:{source:string;cron:string;reason:string}[]};
signals:Source<{databaseTime:string;items:{id:string;label:string;state:string;at:string;ageSeconds:number|null}[]}>;
database:Source<{day:string;jobs:{total:number;latest:number|null;finished:number;activeToday:number;active:number};couriers:{available:number;reserved:number}}>;
history:Source<{day:string;deviceCompletions:number}>;
@@ -16,6 +18,8 @@ const date=(value:string)=>value?new Date(value).toLocaleString('de-DE'):'Noch k
const number=(value:number|null|undefined)=>value==null?'—':value.toLocaleString('de-DE');
export function OperationsMonitoring({refresh}:{refresh:number}){
const [data,setData]=useState<Snapshot|null>(null),[error,setError]=useState(''),[loading,setLoading]=useState(true),[version,setVersion]=useState(0);
const [checkedTasks,setCheckedTasks]=useState<Set<string>>(new Set()),[reviewing,setReviewing]=useState('');
async function reviewTask(task:CronTask){setReviewing(task.id);setError('');try{await request(`/administration/cron/${task.id}/review`,{method:'POST',body:JSON.stringify({slot:task.slot,reviewed:true})});setCheckedTasks(new Set());setVersion(v=>v+1);}catch(e){setError(e instanceof Error?e.message:'Freigabe fehlgeschlagen.');}finally{setReviewing('');}}
useEffect(()=>{const controller=new AbortController();setData(null);setError('');setLoading(true);
request<Snapshot>('/administration/operations',{signal:controller.signal}).then(value=>{if(!controller.signal.aborted)setData(value);}).catch(e=>{if(!controller.signal.aborted)setError(e.message);}).finally(()=>{if(!controller.signal.aborted)setLoading(false);});
return()=>controller.abort();
@@ -31,6 +35,18 @@ export function OperationsMonitoring({refresh}:{refresh:number}){
<div className="admin-table-wrap"><table className="admin-table"><thead><tr><th>Dienst</th><th>Status</th><th>Letzter abgeschlossener Lauf</th><th>Ergebnis des letzten Laufs</th></tr></thead><tbody>
{data.workers.map(w=><tr key={w.id}><td><strong>{w.label}</strong><p>{w.scope}</p><small>Prüfabstand: {w.intervalSeconds} Sekunden nach Laufende</small></td><td><strong>{labels[w.state]||w.state}</strong><p>{w.message}</p></td><td>{date(w.lastRun)}</td><td>{w.lastRun?<dl>{Object.entries(w.values).map(([key,value])=><div key={key}><dt>{counters[key]||key}</dt><dd>{number(value)}</dd></div>)}</dl>:'—'}</td></tr>)}
</tbody></table></div>
{data.cron&&<>
<h3>Zeitgesteuerte Aufgaben</h3>
<p>Feste Termine nach Europe/Berlin. Laufbelege bleiben nach einem Neustart erhalten. Unbestätigte Vorgänge werden nicht automatisch wiederholt.</p>
{[...new Set(data.cron.items.map(t=>t.group))].map(group=><details key={group}><summary>{group} · {data.cron!.items.filter(t=>t.group===group&&t.enabled).length} aktiv / {data.cron!.items.filter(t=>t.group===group).length} Aufgaben</summary>
<div className="admin-table-wrap"><table className="admin-table"><thead><tr><th>Aufgabe</th><th>Status</th><th>Letzter Lauf</th><th>Nächster Termin</th></tr></thead><tbody>
{data.cron!.items.filter(t=>t.group===group).map(t=><tr key={t.id}><td><strong>{t.label}</strong><p>{t.source}</p></td><td><strong>{labels[t.state]||({WAITING:'Wartet',RUNNING:'Gestartet / Abschluss offen',UNCERTAIN:'Ergebnis prüfen',REVIEWED:'Geprüft'} as Record<string,string>)[t.state]||t.state}</strong><p>{t.message}</p><small>{number(t.processed)} verarbeitet</small>
{['RUNNING','UNCERTAIN'].includes(t.state)&&<div><label><input type="checkbox" checked={checkedTasks.has(t.id)} onChange={e=>setCheckedTasks(before=>{const next=new Set(before);if(e.target.checked)next.add(t.id);else next.delete(t.id);return next;})}/>Bisherige Teilwirkungen und Versandprotokolle geprüft</label><button className="button" disabled={!checkedTasks.has(t.id)||!!reviewing} onClick={()=>reviewTask(t)}>Nächsten Termin freigeben</button></div>}
</td><td>{date(t.lastRun)}</td><td>{t.nextRun?date(t.nextRun):'Ausgeschaltet'}<p><code>{t.cron}</code> · {t.zone}</p></td></tr>)}
</tbody></table></div>
</details>)}
{data.cron.unresolved.length>0&&<details><summary>Noch nicht umgestellte Cron-Aufgaben ({data.cron.unresolved.length})</summary><ul>{data.cron.unresolved.map(t=><li key={t.source}><strong>{t.source}</strong>: {t.reason}</li>)}</ul></details>}
</>}
<h3>Gemeinsame Lebenszeichen</h3>
<p>Diese Zeitstempel stammen aus der gemeinsamen Datenbank. PHP und NG können denselben Eintrag aktualisieren. Ein aktueller Eintrag bestätigt weder den zuständigen Prozess noch die erfolgreiche Verarbeitung.</p>
{!data.signals.available?<p role="status">{data.signals.message}</p>:data.signals.data&&<>