feat: gate live and task actions when modules are disabled

This commit is contained in:
thibaud-lclr 2026-04-15 17:33:20 +02:00
parent 5662f34415
commit 1952a139ae
3 changed files with 62 additions and 6 deletions

View file

@ -1,6 +1,7 @@
use crate::error::AppError; use crate::error::AppError;
use crate::models::agent::Agent; use crate::models::agent::Agent;
use crate::models::agent_task::AgentTask; use crate::models::agent_task::AgentTask;
use crate::models::module::{ProjectModule, MODULE_AGENT_TASK_RUNNER};
use crate::models::project::Project; use crate::models::project::Project;
use crate::AppState; use crate::AppState;
use tauri::State; use tauri::State;
@ -24,6 +25,13 @@ pub fn create_agent_task(
.lock() .lock()
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
let enabled = ProjectModule::is_enabled(&db, &project_id, MODULE_AGENT_TASK_RUNNER)?;
if !enabled {
return Err(AppError::from(
"Le module Agent task runner est désactivé pour ce projet".to_string(),
));
}
Project::get_by_id(&db, &project_id)?; Project::get_by_id(&db, &project_id)?;
Agent::get_by_id(&db, &agent_id)?; Agent::get_by_id(&db, &agent_id)?;
@ -59,6 +67,13 @@ pub fn retry_agent_task(state: State<'_, AppState>, task_id: String) -> Result<(
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?; .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
let task = AgentTask::get_by_id(&db, &task_id)?; let task = AgentTask::get_by_id(&db, &task_id)?;
let enabled = ProjectModule::is_enabled(&db, &task.project_id, MODULE_AGENT_TASK_RUNNER)?;
if !enabled {
return Err(AppError::from(
"Le module Agent task runner est désactivé pour ce projet".to_string(),
));
}
if task.status != "error" && task.status != "cancelled" && task.status != "done" { if task.status != "error" && task.status != "cancelled" && task.status != "done" {
return Err(AppError::from(format!( return Err(AppError::from(format!(
"Impossible de relancer une tâche avec le statut '{}'", "Impossible de relancer une tâche avec le statut '{}'",

View file

@ -5,6 +5,7 @@ import {
createLiveSession, createLiveSession,
listAgents, listAgents,
listLiveMessages, listLiveMessages,
listProjectModules,
listLiveSessions, listLiveSessions,
sendLiveMessage, sendLiveMessage,
} from "../../lib/api"; } from "../../lib/api";
@ -28,6 +29,7 @@ export default function ProjectLiveAgent() {
const [draft, setDraft] = useState(""); const [draft, setDraft] = useState("");
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [creatingSession, setCreatingSession] = useState(false); const [creatingSession, setCreatingSession] = useState(false);
const [moduleEnabled, setModuleEnabled] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const usableAgents = useMemo( const usableAgents = useMemo(
@ -68,8 +70,14 @@ export default function ProjectLiveAgent() {
setError(null); setError(null);
try { try {
const [availableAgents] = await Promise.all([listAgents()]); const [availableAgents, modules] = await Promise.all([
listAgents(),
listProjectModules(projectId),
]);
setAgents(availableAgents); setAgents(availableAgents);
setModuleEnabled(
modules.find((mod) => mod.module_key === "ai_live_chat")?.enabled ?? true
);
const firstAgentId = availableAgents[0]?.id ?? ""; const firstAgentId = availableAgents[0]?.id ?? "";
setSelectedAgentId((current) => current || firstAgentId); setSelectedAgentId((current) => current || firstAgentId);
@ -113,6 +121,10 @@ export default function ProjectLiveAgent() {
async function handleCreateSession(event: FormEvent) { async function handleCreateSession(event: FormEvent) {
event.preventDefault(); event.preventDefault();
if (!projectId || !selectedAgentId) return; if (!projectId || !selectedAgentId) return;
if (!moduleEnabled) {
setError("Le module Live chat agent est désactivé pour ce projet.");
return;
}
setCreatingSession(true); setCreatingSession(true);
setError(null); setError(null);
@ -130,6 +142,10 @@ export default function ProjectLiveAgent() {
async function handleSendMessage(event: FormEvent) { async function handleSendMessage(event: FormEvent) {
event.preventDefault(); event.preventDefault();
if (!selectedSessionId || !draft.trim()) return; if (!selectedSessionId || !draft.trim()) return;
if (!moduleEnabled) {
setError("Le module Live chat agent est désactivé pour ce projet.");
return;
}
const content = draft.trim(); const content = draft.trim();
setDraft(""); setDraft("");
@ -172,6 +188,11 @@ export default function ProjectLiveAgent() {
{error} {error}
</div> </div>
)} )}
{!moduleEnabled && (
<div className="rounded border border-amber-200 bg-amber-50 p-2 text-sm text-amber-700">
Le module est désactivé. La lecture reste possible, mais la création de session et l'envoi de message sont bloqués.
</div>
)}
<form onSubmit={handleCreateSession} className="rounded-lg border border-gray-200 bg-white p-4"> <form onSubmit={handleCreateSession} className="rounded-lg border border-gray-200 bg-white p-4">
<h3 className="mb-3 text-sm font-semibold text-gray-800">Nouvelle session</h3> <h3 className="mb-3 text-sm font-semibold text-gray-800">Nouvelle session</h3>
@ -196,7 +217,7 @@ export default function ProjectLiveAgent() {
/> />
<button <button
type="submit" type="submit"
disabled={creatingSession || !selectedAgentId} disabled={creatingSession || !selectedAgentId || !moduleEnabled}
className="rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50" className="rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
> >
{creatingSession ? "Création..." : "Créer la session"} {creatingSession ? "Création..." : "Créer la session"}
@ -270,7 +291,7 @@ export default function ProjectLiveAgent() {
/> />
<button <button
type="submit" type="submit"
disabled={!selectedSessionId || sending || !draft.trim()} disabled={!selectedSessionId || sending || !draft.trim() || !moduleEnabled}
className="rounded bg-gray-900 px-4 py-2 text-sm text-white hover:bg-black disabled:opacity-50" className="rounded bg-gray-900 px-4 py-2 text-sm text-white hover:bg-black disabled:opacity-50"
> >
{sending ? "Envoi..." : "Envoyer"} {sending ? "Envoi..." : "Envoyer"}

View file

@ -6,6 +6,7 @@ import {
createAgentTask, createAgentTask,
listAgentTasks, listAgentTasks,
listAgents, listAgents,
listProjectModules,
retryAgentTask, retryAgentTask,
} from "../../lib/api"; } from "../../lib/api";
import { getErrorMessage } from "../../lib/errors"; import { getErrorMessage } from "../../lib/errors";
@ -26,6 +27,7 @@ export default function ProjectTasks() {
const [agentId, setAgentId] = useState(""); const [agentId, setAgentId] = useState("");
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [moduleEnabled, setModuleEnabled] = useState(true);
const [creating, setCreating] = useState(false); const [creating, setCreating] = useState(false);
const [workingTaskId, setWorkingTaskId] = useState<string | null>(null); const [workingTaskId, setWorkingTaskId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -40,12 +42,16 @@ export default function ProjectTasks() {
setError(null); setError(null);
try { try {
const [availableAgents, projectTasks] = await Promise.all([ const [availableAgents, projectTasks, modules] = await Promise.all([
listAgents(), listAgents(),
listAgentTasks(projectId), listAgentTasks(projectId),
listProjectModules(projectId),
]); ]);
setAgents(availableAgents); setAgents(availableAgents);
setTasks(projectTasks); setTasks(projectTasks);
setModuleEnabled(
modules.find((mod) => mod.module_key === "agent_task_runner")?.enabled ?? true
);
if (!agentId && availableAgents.length > 0) { if (!agentId && availableAgents.length > 0) {
setAgentId(availableAgents[0].id); setAgentId(availableAgents[0].id);
} }
@ -95,6 +101,10 @@ export default function ProjectTasks() {
async function handleCreateTask(event: FormEvent) { async function handleCreateTask(event: FormEvent) {
event.preventDefault(); event.preventDefault();
if (!projectId || !agentId || !title.trim()) return; if (!projectId || !agentId || !title.trim()) return;
if (!moduleEnabled) {
setError("Le module Agent task runner est désactivé pour ce projet.");
return;
}
setCreating(true); setCreating(true);
setError(null); setError(null);
@ -111,6 +121,11 @@ export default function ProjectTasks() {
} }
async function handleRetry(taskId: string) { async function handleRetry(taskId: string) {
if (!moduleEnabled) {
setError("Le module Agent task runner est désactivé pour ce projet.");
return;
}
setWorkingTaskId(taskId); setWorkingTaskId(taskId);
setError(null); setError(null);
try { try {
@ -145,6 +160,11 @@ export default function ProjectTasks() {
{error} {error}
</div> </div>
)} )}
{!moduleEnabled && (
<div className="rounded border border-amber-200 bg-amber-50 p-2 text-sm text-amber-700">
Le module est désactivé. Les tâches existantes restent visibles, mais la création et la relance sont bloquées.
</div>
)}
<form onSubmit={handleCreateTask} className="rounded-lg border border-gray-200 bg-white p-4"> <form onSubmit={handleCreateTask} className="rounded-lg border border-gray-200 bg-white p-4">
<h3 className="mb-3 text-sm font-semibold text-gray-800">Créer une tâche</h3> <h3 className="mb-3 text-sm font-semibold text-gray-800">Créer une tâche</h3>
@ -171,7 +191,7 @@ export default function ProjectTasks() {
<button <button
type="submit" type="submit"
disabled={creating || !agentId || !title.trim()} disabled={creating || !agentId || !title.trim() || !moduleEnabled}
className="rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50" className="rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
> >
{creating ? "Création..." : "Créer"} {creating ? "Création..." : "Créer"}
@ -223,7 +243,7 @@ export default function ProjectTasks() {
<button <button
type="button" type="button"
onClick={() => void handleRetry(task.id)} onClick={() => void handleRetry(task.id)}
disabled={workingTaskId === task.id} disabled={workingTaskId === task.id || !moduleEnabled}
className="rounded bg-gray-900 px-3 py-1 text-xs text-white hover:bg-black disabled:opacity-50" className="rounded bg-gray-900 px-3 py-1 text-xs text-white hover:bg-black disabled:opacity-50"
> >
Relancer Relancer