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() {
)}
+
+