feat(ui): regrouper la configuration Tuleap et Graylog
This commit is contained in:
parent
d300b64603
commit
ee9d00efa4
3 changed files with 129 additions and 6 deletions
|
|
@ -517,9 +517,9 @@ export default function ProjectDashboard() {
|
||||||
to={`/projects/${project.id}/graylog`}
|
to={`/projects/${project.id}/graylog`}
|
||||||
className="rounded-lg border border-gray-200 bg-white p-4 hover:border-gray-300"
|
className="rounded-lg border border-gray-200 bg-white p-4 hover:border-gray-300"
|
||||||
>
|
>
|
||||||
<div className="text-sm font-semibold text-gray-900">Graylog</div>
|
<div className="text-sm font-semibold text-gray-900">Intégrations</div>
|
||||||
<div className="mt-1 text-xs text-gray-500">
|
<div className="mt-1 text-xs text-gray-500">
|
||||||
Configure le polling Graylog et surveille les sujets scorés.
|
Configure Tuleap et Graylog au même endroit.
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import { FormEvent, useCallback, useEffect, useState } from "react";
|
import { FormEvent, useCallback, useEffect, useState } from "react";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
|
getTuleapCredentials,
|
||||||
getGraylogCredentials,
|
getGraylogCredentials,
|
||||||
listAgents,
|
listAgents,
|
||||||
listGraylogDetections,
|
listGraylogDetections,
|
||||||
listGraylogSubjects,
|
listGraylogSubjects,
|
||||||
manualGraylogPoll,
|
manualGraylogPoll,
|
||||||
|
setTuleapCredentials,
|
||||||
setGraylogCredentials,
|
setGraylogCredentials,
|
||||||
|
testTuleapConnection,
|
||||||
testGraylogConnection,
|
testGraylogConnection,
|
||||||
} from "../../lib/api";
|
} from "../../lib/api";
|
||||||
import { getErrorMessage } from "../../lib/errors";
|
import { getErrorMessage } from "../../lib/errors";
|
||||||
|
|
@ -15,6 +18,7 @@ import type {
|
||||||
GraylogCredentialsSafe,
|
GraylogCredentialsSafe,
|
||||||
GraylogDetection,
|
GraylogDetection,
|
||||||
GraylogSubject,
|
GraylogSubject,
|
||||||
|
TuleapCredentialsSafe,
|
||||||
} from "../../lib/types";
|
} from "../../lib/types";
|
||||||
import {
|
import {
|
||||||
backLinkClass,
|
backLinkClass,
|
||||||
|
|
@ -30,10 +34,14 @@ export default function ProjectGraylog() {
|
||||||
const { projectId } = useParams<{ projectId: string }>();
|
const { projectId } = useParams<{ projectId: string }>();
|
||||||
|
|
||||||
const [agents, setAgents] = useState<Agent[]>([]);
|
const [agents, setAgents] = useState<Agent[]>([]);
|
||||||
|
const [tuleapCredentials, setTuleapCredentialsState] = useState<TuleapCredentialsSafe | null>(null);
|
||||||
const [credentials, setCredentials] = useState<GraylogCredentialsSafe | null>(null);
|
const [credentials, setCredentials] = useState<GraylogCredentialsSafe | null>(null);
|
||||||
const [subjects, setSubjects] = useState<GraylogSubject[]>([]);
|
const [subjects, setSubjects] = useState<GraylogSubject[]>([]);
|
||||||
const [detections, setDetections] = useState<GraylogDetection[]>([]);
|
const [detections, setDetections] = useState<GraylogDetection[]>([]);
|
||||||
|
|
||||||
|
const [tuleapUrl, setTuleapUrl] = useState("");
|
||||||
|
const [tuleapUsername, setTuleapUsername] = useState("");
|
||||||
|
const [tuleapPassword, setTuleapPassword] = useState("");
|
||||||
const [baseUrl, setBaseUrl] = useState("");
|
const [baseUrl, setBaseUrl] = useState("");
|
||||||
const [apiToken, setApiToken] = useState("");
|
const [apiToken, setApiToken] = useState("");
|
||||||
const [analystAgentId, setAnalystAgentId] = useState("");
|
const [analystAgentId, setAnalystAgentId] = useState("");
|
||||||
|
|
@ -45,12 +53,18 @@ export default function ProjectGraylog() {
|
||||||
const [scoreThreshold, setScoreThreshold] = useState(70);
|
const [scoreThreshold, setScoreThreshold] = useState(70);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [savingTuleap, setSavingTuleap] = useState(false);
|
||||||
|
const [testingTuleap, setTestingTuleap] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
|
|
||||||
const analysts = agents.filter((agent) => agent.role === "analyst");
|
const analysts = agents.filter((agent) => agent.role === "analyst");
|
||||||
const developers = agents.filter((agent) => agent.role === "developer");
|
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 () => {
|
const refresh = useCallback(async () => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
@ -59,18 +73,29 @@ export default function ProjectGraylog() {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [agentList, creds, subjectList, detectionList] = await Promise.all([
|
const [agentList, tuleapCreds, creds, subjectList, detectionList] = await Promise.all([
|
||||||
listAgents(),
|
listAgents(),
|
||||||
|
getTuleapCredentials(projectId),
|
||||||
getGraylogCredentials(projectId),
|
getGraylogCredentials(projectId),
|
||||||
listGraylogSubjects(projectId),
|
listGraylogSubjects(projectId),
|
||||||
listGraylogDetections(projectId),
|
listGraylogDetections(projectId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setAgents(agentList);
|
setAgents(agentList);
|
||||||
|
setTuleapCredentialsState(tuleapCreds);
|
||||||
setCredentials(creds);
|
setCredentials(creds);
|
||||||
setSubjects(subjectList);
|
setSubjects(subjectList);
|
||||||
setDetections(detectionList);
|
setDetections(detectionList);
|
||||||
|
|
||||||
|
if (tuleapCreds) {
|
||||||
|
setTuleapUrl(tuleapCreds.tuleap_url);
|
||||||
|
setTuleapUsername(tuleapCreds.username);
|
||||||
|
} else {
|
||||||
|
setTuleapUrl("");
|
||||||
|
setTuleapUsername("");
|
||||||
|
}
|
||||||
|
setTuleapPassword("");
|
||||||
|
|
||||||
if (creds) {
|
if (creds) {
|
||||||
setBaseUrl(creds.base_url);
|
setBaseUrl(creds.base_url);
|
||||||
setAnalystAgentId(creds.analyst_agent_id);
|
setAnalystAgentId(creds.analyst_agent_id);
|
||||||
|
|
@ -92,6 +117,43 @@ export default function ProjectGraylog() {
|
||||||
void refresh();
|
void refresh();
|
||||||
}, [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) {
|
async function handleSave(event: FormEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
@ -162,7 +224,7 @@ export default function ProjectGraylog() {
|
||||||
Back
|
Back
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<h2 className={pageTitleClass}>Graylog</h2>
|
<h2 className={pageTitleClass}>Intégrations</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
|
|
@ -176,8 +238,69 @@ export default function ProjectGraylog() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSaveTuleap} className={`space-y-3 ${cardContentClass}`}>
|
||||||
|
<h3 className="text-sm font-semibold text-gray-900">Configuration Tuleap</h3>
|
||||||
|
|
||||||
|
{usingGlobalTuleapFallback && (
|
||||||
|
<div className={noticeClass("warning")}>
|
||||||
|
Aucun credential Tuleap spécifique au projet n'est configuré. Le fallback global est utilisé.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="text-xs text-gray-500">
|
||||||
|
{hasProjectScopedTuleapCredentials
|
||||||
|
? "Credentials Tuleap projet actifs."
|
||||||
|
: "Credentials utilisés pour les trackers Tuleap du projet."}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className={inputClass}
|
||||||
|
type="url"
|
||||||
|
value={tuleapUrl}
|
||||||
|
onChange={(event) => setTuleapUrl(event.target.value)}
|
||||||
|
placeholder="https://tuleap.example.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className={inputClass}
|
||||||
|
type="text"
|
||||||
|
value={tuleapUsername}
|
||||||
|
onChange={(event) => setTuleapUsername(event.target.value)}
|
||||||
|
placeholder="Username Tuleap"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className={inputClass}
|
||||||
|
type="password"
|
||||||
|
value={tuleapPassword}
|
||||||
|
onChange={(event) => setTuleapPassword(event.target.value)}
|
||||||
|
placeholder="Mot de passe Tuleap"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={savingTuleap}
|
||||||
|
className={buttonClass({ variant: "primary" })}
|
||||||
|
>
|
||||||
|
{savingTuleap ? "Sauvegarde..." : "Sauvegarder"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => void handleTestTuleap()}
|
||||||
|
disabled={testingTuleap || !tuleapCredentials}
|
||||||
|
className={buttonClass({ variant: "secondary" })}
|
||||||
|
>
|
||||||
|
{testingTuleap ? "Test..." : "Tester la connexion"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<form onSubmit={handleSave} className={`space-y-3 ${cardContentClass}`}>
|
<form onSubmit={handleSave} className={`space-y-3 ${cardContentClass}`}>
|
||||||
<h3 className="text-sm font-semibold text-gray-900">Configuration</h3>
|
<h3 className="text-sm font-semibold text-gray-900">Configuration Graylog</h3>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export default function ProjectModules() {
|
||||||
to={`/projects/${projectId}/graylog`}
|
to={`/projects/${projectId}/graylog`}
|
||||||
className={`mt-2 ${buttonClass({ variant: "secondary", size: "xs" })}`}
|
className={`mt-2 ${buttonClass({ variant: "secondary", size: "xs" })}`}
|
||||||
>
|
>
|
||||||
Configurer
|
Configurer les intégrations
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue