Correctif Bug activation IA
This commit is contained in:
1 parent
b3f5f80d2d
commit
8d104f2058
3 files changed
+55
-5
No files matched your search
@@ -260,8 +260,17 @@ router.patch('/config', (req, res, next) => {
|
|||||||
body.quotaQuotidien !== undefined ? body.quotaQuotidien : row.quota_quotidien,
|
body.quotaQuotidien !== undefined ? body.quotaQuotidien : row.quota_quotidien,
|
||||||
);
|
);
|
||||||
|
|
||||||
audit(req, { action: 'ai_config_updated', category: 'ai', actorId: req.user.id, details: body });
|
// Desactivation globale (transition actif -> inactif) : on retire aussi
|
||||||
res.json({ ok: true });
|
// l'acces individuel de chaque utilisateur plutot que de le laisser
|
||||||
|
// "vrai" en base mais inoperant, ce qui afficherait un interrupteur
|
||||||
|
// actif mais sans effet dans le panneau Utilisateurs.
|
||||||
|
let usersReset = 0;
|
||||||
|
if (body.actif === false && row.actif) {
|
||||||
|
usersReset = db.prepare('UPDATE users SET ai_enabled = 0 WHERE ai_enabled = 1').run().changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
audit(req, { action: 'ai_config_updated', category: 'ai', actorId: req.user.id, details: { ...body, usersReset } });
|
||||||
|
res.json({ ok: true, usersReset });
|
||||||
} catch (e) { next(e); }
|
} catch (e) { next(e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -356,8 +356,11 @@ function ConfigSection() {
|
|||||||
const save = async (next) => {
|
const save = async (next) => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await api.patch('/admin/ai/config', next);
|
const result = await api.patch('/admin/ai/config', next);
|
||||||
setBanner({ ok: true, msg: 'Configuration enregistrée.' });
|
const msg = result?.usersReset
|
||||||
|
? `Configuration enregistrée. Accès IA retiré à ${result.usersReset} utilisateur${result.usersReset > 1 ? 's' : ''} (Assistant IA désactivé globalement).`
|
||||||
|
: 'Configuration enregistrée.';
|
||||||
|
setBanner({ ok: true, msg });
|
||||||
await load();
|
await load();
|
||||||
} catch (e) { setBanner({ ok: false, msg: e.message }); }
|
} catch (e) { setBanner({ ok: false, msg: e.message }); }
|
||||||
finally { setSaving(false); }
|
finally { setSaving(false); }
|
||||||
|
|||||||
@@ -428,6 +428,11 @@ export default function UsersSection({ currentUserId }) {
|
|||||||
const [openMenu, setOpenMenu] = useState(null);
|
const [openMenu, setOpenMenu] = useState(null);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
// null tant que non charge (voir l'effet ci-dessous) : l'interrupteur
|
||||||
|
// individuel de chaque utilisateur n'a d'effet que si l'IA est activee
|
||||||
|
// globalement ET qu'un fournisseur actif est configure (seule source de
|
||||||
|
// verite : aiAccess.js cote backend).
|
||||||
|
const [aiConfig, setAiConfig] = useState(null);
|
||||||
|
|
||||||
const load = useCallback(async (initial = false) => {
|
const load = useCallback(async (initial = false) => {
|
||||||
if (initial) setInitialLoading(true);
|
if (initial) setInitialLoading(true);
|
||||||
@@ -437,6 +442,16 @@ export default function UsersSection({ currentUserId }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => { load(true); }, [load]);
|
useEffect(() => { load(true); }, [load]);
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
Promise.all([api.get('/admin/ai/config'), api.get('/admin/ai/providers')])
|
||||||
|
.then(([cfg, provs]) => {
|
||||||
|
if (cancelled) return;
|
||||||
|
setAiConfig({ actif: !!cfg.actif, hasActiveProvider: provs.some(p => p.actif) });
|
||||||
|
})
|
||||||
|
.catch(() => { if (!cancelled) setAiConfig({ actif: false, hasActiveProvider: false }); });
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}, []);
|
||||||
useEffect(() => { setPage(1); }, [search, filterStatus, filterRole]);
|
useEffect(() => { setPage(1); }, [search, filterStatus, filterRole]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!openMenu) return;
|
if (!openMenu) return;
|
||||||
@@ -458,6 +473,17 @@ export default function UsersSection({ currentUserId }) {
|
|||||||
const safePage = Math.min(page, totalPages);
|
const safePage = Math.min(page, totalPages);
|
||||||
const paged = filtered.slice((safePage - 1) * pageSize, safePage * pageSize);
|
const paged = filtered.slice((safePage - 1) * pageSize, safePage * pageSize);
|
||||||
|
|
||||||
|
// true tant que aiConfig n'est pas encore charge : le vrai garde-fou reste
|
||||||
|
// cote serveur (getAiAccessStatus), ceci n'est qu'une aide visuelle.
|
||||||
|
const aiAvailable = !aiConfig || (aiConfig.actif && aiConfig.hasActiveProvider);
|
||||||
|
const aiUnavailableReason = !aiConfig
|
||||||
|
? ''
|
||||||
|
: !aiConfig.actif
|
||||||
|
? "L'Assistant IA est desactive globalement. Activez-le dans Assistant IA > Configuration avant d'accorder l'acces a un utilisateur."
|
||||||
|
: !aiConfig.hasActiveProvider
|
||||||
|
? "Aucun fournisseur IA actif n'est configure. Configurez-en un dans Assistant IA > Fournisseurs avant d'accorder l'acces a un utilisateur."
|
||||||
|
: '';
|
||||||
|
|
||||||
// ── Actions ───────────────────────────────────────────────────────────
|
// ── Actions ───────────────────────────────────────────────────────────
|
||||||
const confirm = (title, message, onConfirm, confirmLabel) =>
|
const confirm = (title, message, onConfirm, confirmLabel) =>
|
||||||
setConfirmAction({ title, message, onConfirm, confirmLabel });
|
setConfirmAction({ title, message, onConfirm, confirmLabel });
|
||||||
@@ -639,6 +665,17 @@ export default function UsersSection({ currentUserId }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{aiConfig && !aiAvailable && (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 8, margin: '0 20px 12px',
|
||||||
|
padding: '8px 14px', borderRadius: 8, fontSize: 12.5,
|
||||||
|
background: 'var(--surface-2)', color: 'var(--text-muted)', border: '1px solid var(--border)',
|
||||||
|
}}>
|
||||||
|
<IcoSparkle />
|
||||||
|
{aiUnavailableReason}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Tableau */}
|
{/* Tableau */}
|
||||||
{filtered.length === 0 ? (
|
{filtered.length === 0 ? (
|
||||||
<p style={{ color: 'var(--text-muted)', textAlign: 'center', padding: '40px 20px' }}>
|
<p style={{ color: 'var(--text-muted)', textAlign: 'center', padding: '40px 20px' }}>
|
||||||
@@ -692,7 +729,8 @@ export default function UsersSection({ currentUserId }) {
|
|||||||
<AiToggle
|
<AiToggle
|
||||||
checked={!!u.ai_enabled}
|
checked={!!u.ai_enabled}
|
||||||
onClick={() => toggleAiAccess(u)}
|
onClick={() => toggleAiAccess(u)}
|
||||||
title={u.ai_enabled ? "Désactiver l'accès à l'Assistant IA" : "Activer l'accès à l'Assistant IA"}
|
disabled={!aiAvailable}
|
||||||
|
title={!aiAvailable ? aiUnavailableReason : (u.ai_enabled ? "Désactiver l'accès à l'Assistant IA" : "Activer l'accès à l'Assistant IA")}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Reference in new issue
Block a user