From ee9d00efa44cadc7ebb3b293218859f10eac01f4 Mon Sep 17 00:00:00 2001 From: thibaud-lclr Date: Tue, 21 Apr 2026 15:36:34 +0200 Subject: [PATCH] feat(ui): regrouper la configuration Tuleap et Graylog --- src/components/projects/ProjectDashboard.tsx | 4 +- src/components/projects/ProjectGraylog.tsx | 129 ++++++++++++++++++- src/components/projects/ProjectModules.tsx | 2 +- 3 files changed, 129 insertions(+), 6 deletions(-) diff --git a/src/components/projects/ProjectDashboard.tsx b/src/components/projects/ProjectDashboard.tsx index a261684..8a5296d 100644 --- a/src/components/projects/ProjectDashboard.tsx +++ b/src/components/projects/ProjectDashboard.tsx @@ -517,9 +517,9 @@ export default function ProjectDashboard() { to={`/projects/${project.id}/graylog`} className="rounded-lg border border-gray-200 bg-white p-4 hover:border-gray-300" > -
Graylog
+
Intégrations
- Configure le polling Graylog et surveille les sujets scorés. + Configure Tuleap et Graylog au même endroit.
diff --git a/src/components/projects/ProjectGraylog.tsx b/src/components/projects/ProjectGraylog.tsx index 9935461..3e6acfa 100644 --- a/src/components/projects/ProjectGraylog.tsx +++ b/src/components/projects/ProjectGraylog.tsx @@ -1,12 +1,15 @@ 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"; @@ -15,6 +18,7 @@ import type { GraylogCredentialsSafe, GraylogDetection, GraylogSubject, + TuleapCredentialsSafe, } from "../../lib/types"; import { backLinkClass, @@ -30,10 +34,14 @@ 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(""); @@ -45,12 +53,18 @@ export default function ProjectGraylog() { 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 hasProjectScopedTuleapCredentials = + Boolean(projectId) && tuleapCredentials?.project_id === projectId; + const usingGlobalTuleapFallback = + Boolean(projectId) && Boolean(tuleapCredentials) && tuleapCredentials?.project_id !== projectId; const refresh = useCallback(async () => { if (!projectId) return; @@ -59,18 +73,29 @@ export default function ProjectGraylog() { setError(null); try { - const [agentList, creds, subjectList, detectionList] = await Promise.all([ + 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); @@ -92,6 +117,43 @@ export default function ProjectGraylog() { 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; @@ -162,7 +224,7 @@ export default function ProjectGraylog() { Back )} -

Graylog

+

Intégrations

{error && ( @@ -176,8 +238,69 @@ export default function ProjectGraylog() { )} +
+

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

+

Configuration Graylog

- Configurer + Configurer les intégrations )}