mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-18 20:56:43 +02:00
Paramètres : clé d'API FF Nancy (en-tête X-API-Key)
L'API api.ffnancy.fr exige désormais une clé (401 « Clé d'API requise »). - Nouveau champ « Clé d'API » dans Paramètres → API FF Nancy, stocké dans data/.db-config.json (ffApiKey), jamais renvoyé au navigateur ; vide = conserver la clé enregistrée. Repli sur la variable FF_API_KEY. - Tous les appels à l'API FF envoient l'en-tête X-API-Key (ff_get, proxy commandes-auto, publicités, /api/diag, test de connexion). - Le test de connexion signale explicitement une clé manquante ou refusée. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KdY28Lq8Q8xX4RZsAvWRQ7
This commit is contained in:
11 files changed
+167
-59
No files matched your search
@@ -292,22 +292,28 @@ function SectionFfApi() {
|
||||
const [savedUrl, setSavedUrl] = useState<string | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testedUrl, setTestedUrl] = useState<string | null>(null);
|
||||
// Toujours vide au chargement : la clé enregistrée ne quitte pas le serveur.
|
||||
const [cle, setCle] = useState("");
|
||||
const [cleEnregistree, setCleEnregistree] = useState(false);
|
||||
const [showCle, setShowCle] = useState(false);
|
||||
|
||||
// Charge l'URL enregistrée pour la préremplir (vide = valeur par défaut).
|
||||
// Charge l'URL enregistrée pour la préremplir (vide = valeur par défaut)
|
||||
// et l'indication qu'une clé est enregistrée.
|
||||
useEffect(() => {
|
||||
loadSavedConfig()
|
||||
.then((cfg) => {
|
||||
setSavedUrl(cfg.ffApiBaseUrl || null);
|
||||
if (cfg.ffApiBaseUrl) setUrl(cfg.ffApiBaseUrl);
|
||||
setCleEnregistree(cfg.ffApiKeyEnregistree);
|
||||
})
|
||||
.catch(() => { /* réglage optionnel ; l'échec de lecture est signalé par la page */ });
|
||||
}, []);
|
||||
|
||||
const runTest = useCallback((candidate?: string) => {
|
||||
const runTest = useCallback((candidate?: string, cleCandidate?: string) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setStatus(null);
|
||||
testFfApiConnection(candidate)
|
||||
testFfApiConnection(candidate, cleCandidate)
|
||||
.then((res) => {
|
||||
setTestedUrl(res.url);
|
||||
if (res.success) setStatus(res.status as FfSyncStatus);
|
||||
@@ -322,13 +328,17 @@ function SectionFfApi() {
|
||||
const save = async () => {
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await saveFfApiSettings(url);
|
||||
const res = await saveFfApiSettings(url, cle || undefined);
|
||||
if (!res.success) {
|
||||
toast.erreur(res.error ?? "Enregistrement impossible.");
|
||||
return;
|
||||
}
|
||||
setSavedUrl(url.trim() || null);
|
||||
toast.succes(url.trim() ? "Adresse de l'API FF enregistrée." : "L'adresse par défaut de l'API FF sera utilisée.");
|
||||
if (cle) {
|
||||
setCle("");
|
||||
setCleEnregistree(true);
|
||||
}
|
||||
toast.succes(url.trim() ? "Réglages de l'API FF enregistrés." : "Réglages de l'API FF enregistrés (adresse par défaut).");
|
||||
runTest(url);
|
||||
} catch (e) {
|
||||
toast.erreur(`Enregistrement impossible : ${messageErreur(e)}`);
|
||||
@@ -361,13 +371,36 @@ function SectionFfApi() {
|
||||
className="w-full font-mono"
|
||||
/>
|
||||
</Champ>
|
||||
<Champ
|
||||
id="ff-cle"
|
||||
label="Clé d'API"
|
||||
aide={cleEnregistree ? "Laissez vide pour conserver la clé enregistrée." : "Clé fournie par l'administrateur de l'API FF Nancy, envoyée à chaque appel."}
|
||||
>
|
||||
<div className="relative">
|
||||
<Input id="ff-cle" type={showCle ? "text" : "password"} placeholder={placeholderMotDePasse(cleEnregistree)} value={cle}
|
||||
onChange={(e) => setCle(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") void save(); }}
|
||||
className="w-full pr-10 font-mono" autoComplete="new-password" />
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={() => setShowCle((v) => !v)}
|
||||
aria-label={showCle ? "Masquer la clé" : "Afficher la clé"}
|
||||
title={showCle ? "Masquer la clé" : "Afficher la clé"}
|
||||
className="absolute right-0.5 top-1/2 -translate-y-1/2 text-[var(--text-muted)]"
|
||||
>
|
||||
{showCle ? <EyeOff /> : <Eye />}
|
||||
</Button>
|
||||
</div>
|
||||
</Champ>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 border-t border-[var(--border)] pt-4">
|
||||
<Button onClick={save} disabled={saving}>
|
||||
{saving ? <Loader2 className="animate-spin" /> : <Save />}
|
||||
Enregistrer
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => runTest(url)} disabled={loading}>
|
||||
<Button variant="outline" onClick={() => runTest(url, cle || undefined)} disabled={loading}>
|
||||
{loading ? <Loader2 className="animate-spin" /> : <RefreshCw />}
|
||||
Tester la connexion
|
||||
</Button>
|
||||
|
||||
Reference in new issue
Block a user