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`}
|
||||
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">
|
||||
Configure le polling Graylog et surveille les sujets scorés.
|
||||
Configure Tuleap et Graylog au même endroit.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<Agent[]>([]);
|
||||
const [tuleapCredentials, setTuleapCredentialsState] = useState<TuleapCredentialsSafe | null>(null);
|
||||
const [credentials, setCredentials] = useState<GraylogCredentialsSafe | null>(null);
|
||||
const [subjects, setSubjects] = useState<GraylogSubject[]>([]);
|
||||
const [detections, setDetections] = useState<GraylogDetection[]>([]);
|
||||
|
||||
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<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(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
|
||||
</Link>
|
||||
)}
|
||||
<h2 className={pageTitleClass}>Graylog</h2>
|
||||
<h2 className={pageTitleClass}>Intégrations</h2>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
|
@ -176,8 +238,69 @@ export default function ProjectGraylog() {
|
|||
</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}`}>
|
||||
<h3 className="text-sm font-semibold text-gray-900">Configuration</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-900">Configuration Graylog</h3>
|
||||
|
||||
<input
|
||||
className={inputClass}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export default function ProjectModules() {
|
|||
to={`/projects/${projectId}/graylog`}
|
||||
className={`mt-2 ${buttonClass({ variant: "secondary", size: "xs" })}`}
|
||||
>
|
||||
Configurer
|
||||
Configurer les intégrations
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue