import { FormEvent, useCallback, useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { getTuleapCredentials, getGraylogCredentials, listAgents, listGraylogDetections, listGraylogSubjects, manualGraylogPoll, setTuleapCredentials, setGraylogCredentials, testTuleapConnection, testGraylogConnection, } from "../../lib/api"; import { getErrorMessage } from "../../lib/errors"; import type { Agent, GraylogCredentialsSafe, GraylogDetection, GraylogSubject, TuleapCredentialsSafe, } from "../../lib/types"; import { backLinkClass, buttonClass, cardContentClass, inputClass, noticeClass, pageStackClass, pageTitleClass, } from "../ui/primitives"; export default function ProjectGraylog() { const { projectId } = useParams<{ projectId: string }>(); const [agents, setAgents] = useState([]); const [tuleapCredentials, setTuleapCredentialsState] = useState(null); const [credentials, setCredentials] = useState(null); const [subjects, setSubjects] = useState([]); const [detections, setDetections] = useState([]); const [tuleapUrl, setTuleapUrl] = useState(""); const [tuleapUsername, setTuleapUsername] = useState(""); const [tuleapPassword, setTuleapPassword] = useState(""); const [baseUrl, setBaseUrl] = useState(""); const [apiToken, setApiToken] = useState(""); const [analystAgentId, setAnalystAgentId] = useState(""); const [developerAgentId, setDeveloperAgentId] = useState(""); const [reviewerAgentId, setReviewerAgentId] = useState(""); const [streamId, setStreamId] = useState(""); const [queryFilter, setQueryFilter] = useState("level:(critical OR error OR warning)"); const [pollingIntervalMinutes, setPollingIntervalMinutes] = useState(10); const [lookbackMinutes, setLookbackMinutes] = useState(30); const [scoreThreshold, setScoreThreshold] = useState(70); const [loading, setLoading] = useState(false); const [savingTuleap, setSavingTuleap] = useState(false); const [testingTuleap, setTestingTuleap] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const analysts = agents.filter((agent) => agent.role === "analyst"); const developers = agents.filter((agent) => agent.role === "developer"); const reviewers = agents.filter((agent) => agent.role === "reviewer"); const hasProjectScopedTuleapCredentials = Boolean(projectId) && tuleapCredentials?.project_id === projectId; const usingGlobalTuleapFallback = Boolean(projectId) && Boolean(tuleapCredentials) && tuleapCredentials?.project_id !== projectId; const refresh = useCallback(async () => { if (!projectId) return; setLoading(true); setError(null); try { const [agentList, tuleapCreds, creds, subjectList, detectionList] = await Promise.all([ listAgents(), getTuleapCredentials(projectId), getGraylogCredentials(projectId), listGraylogSubjects(projectId), listGraylogDetections(projectId), ]); setAgents(agentList); setTuleapCredentialsState(tuleapCreds); setCredentials(creds); setSubjects(subjectList); setDetections(detectionList); if (tuleapCreds) { setTuleapUrl(tuleapCreds.tuleap_url); setTuleapUsername(tuleapCreds.username); } else { setTuleapUrl(""); setTuleapUsername(""); } setTuleapPassword(""); if (creds) { setBaseUrl(creds.base_url); setAnalystAgentId(creds.analyst_agent_id); setDeveloperAgentId(creds.developer_agent_id); setReviewerAgentId(creds.reviewer_agent_id); setStreamId(creds.stream_id ?? ""); setQueryFilter(creds.query_filter); setPollingIntervalMinutes(creds.polling_interval_minutes); setLookbackMinutes(creds.lookback_minutes); setScoreThreshold(creds.score_threshold); } } catch (err: unknown) { setError(getErrorMessage(err)); } finally { setLoading(false); } }, [projectId]); useEffect(() => { void refresh(); }, [refresh]); async function handleSaveTuleap(event: FormEvent) { event.preventDefault(); if (!projectId) return; setSavingTuleap(true); setError(null); setSuccess(null); try { const saved = await setTuleapCredentials(projectId, tuleapUrl, tuleapUsername, tuleapPassword); setTuleapCredentialsState(saved); setTuleapPassword(""); setSuccess("Configuration Tuleap sauvegardée."); } catch (err: unknown) { setError(getErrorMessage(err)); } finally { setSavingTuleap(false); } } async function handleTestTuleap() { if (!projectId) return; setTestingTuleap(true); setError(null); setSuccess(null); try { const message = await testTuleapConnection(projectId); setSuccess(message); } catch (err: unknown) { setError(getErrorMessage(err)); } finally { setTestingTuleap(false); } } async function handleSave(event: FormEvent) { event.preventDefault(); if (!projectId) return; setSaving(true); setError(null); setSuccess(null); try { const saved = await setGraylogCredentials( projectId, baseUrl, apiToken, analystAgentId, developerAgentId, reviewerAgentId, streamId.trim() || null, queryFilter, pollingIntervalMinutes, lookbackMinutes, scoreThreshold ); setCredentials(saved); setApiToken(""); setSuccess("Configuration Graylog sauvegardée."); await refresh(); } catch (err: unknown) { setError(getErrorMessage(err)); } finally { setSaving(false); } } async function handleTestConnection() { if (!projectId) return; setError(null); setSuccess(null); try { const message = await testGraylogConnection(projectId); setSuccess(message); } catch (err: unknown) { setError(getErrorMessage(err)); } } async function handleManualPoll() { if (!projectId) return; setError(null); setSuccess(null); try { const triggeredCount = await manualGraylogPoll(projectId); setSuccess(`Polling Graylog manuel terminé: ${triggeredCount} sujet(s) déclenché(s).`); await refresh(); } catch (err: unknown) { setError(getErrorMessage(err)); } } return (
{projectId && ( Back )}

Intégrations

{error && (
{error}
)} {success && (
{success}
)}

Configuration Tuleap

{usingGlobalTuleapFallback && (
Aucun credential Tuleap spécifique au projet n'est configuré. Le fallback global est utilisé.
)}
{hasProjectScopedTuleapCredentials ? "Credentials Tuleap projet actifs." : "Credentials utilisés pour les trackers Tuleap du projet."}
setTuleapUrl(event.target.value)} placeholder="https://tuleap.example.com" required /> setTuleapUsername(event.target.value)} placeholder="Username Tuleap" required /> setTuleapPassword(event.target.value)} placeholder="Mot de passe Tuleap" required />

Configuration Graylog

setBaseUrl(event.target.value)} placeholder="https://graylog.example.com" required /> setApiToken(event.target.value)} placeholder={ credentials ? "Laisser vide pour conserver le token actuel" : "Token API Graylog" } required={!credentials} />
setStreamId(event.target.value)} placeholder="stream_id (optionnel)" /> setQueryFilter(event.target.value)} placeholder="Filtre query Graylog" />
setPollingIntervalMinutes(Number(event.target.value))} min={1} /> setLookbackMinutes(Number(event.target.value))} min={1} /> setScoreThreshold(Number(event.target.value))} min={1} max={100} />

Sujets détectés

{loading ? (
Chargement...
) : subjects.length === 0 ? (
Aucun sujet détecté.
) : (
{subjects.map((subject) => (
{subject.source}
{subject.normalized_message}
Score: {subject.last_score} | Statut: {subject.status} | Last seen: {" "} {new Date(subject.last_seen_at).toLocaleString()}
))}
)}

Dernières détections

{detections.length === 0 ? (
Aucune détection enregistrée.
) : (
{detections.slice(0, 20).map((detection) => (
score={detection.score} total={detection.total_count} triggered={" "} {String(detection.triggered)} at={" "} {new Date(detection.created_at).toLocaleString()}
))}
)}
); }