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::models::agent::Agent;
use crate::models::agent_task::AgentTask;
use crate::models::module::{ProjectModule, MODULE_AGENT_TASK_RUNNER};
use crate::models::project::Project;
use crate::AppState;
use tauri::State;
@ -24,6 +25,13 @@ pub fn create_agent_task(
.lock()
.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)?;
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)))?;
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" {
return Err(AppError::from(format!(
"Impossible de relancer une tâche avec le statut '{}'",

View file

@ -5,6 +5,7 @@ import {
createLiveSession,
listAgents,
listLiveMessages,
listProjectModules,
listLiveSessions,
sendLiveMessage,
} from "../../lib/api";
@ -28,6 +29,7 @@ export default function ProjectLiveAgent() {
const [draft, setDraft] = useState("");
const [sending, setSending] = useState(false);
const [creatingSession, setCreatingSession] = useState(false);
const [moduleEnabled, setModuleEnabled] = useState(true);
const [error, setError] = useState<string | null>(null);
const usableAgents = useMemo(
@ -68,8 +70,14 @@ export default function ProjectLiveAgent() {
setError(null);
try {
const [availableAgents] = await Promise.all([listAgents()]);
const [availableAgents, modules] = await Promise.all([
listAgents(),
listProjectModules(projectId),
]);
setAgents(availableAgents);
setModuleEnabled(
modules.find((mod) => mod.module_key === "ai_live_chat")?.enabled ?? true
);
const firstAgentId = availableAgents[0]?.id ?? "";
setSelectedAgentId((current) => current || firstAgentId);
@ -113,6 +121,10 @@ export default function ProjectLiveAgent() {
async function handleCreateSession(event: FormEvent) {
event.preventDefault();
if (!projectId || !selectedAgentId) return;
if (!moduleEnabled) {
setError("Le module Live chat agent est désactivé pour ce projet.");
return;
}
setCreatingSession(true);
setError(null);
@ -130,6 +142,10 @@ export default function ProjectLiveAgent() {
async function handleSendMessage(event: FormEvent) {
event.preventDefault();
if (!selectedSessionId || !draft.trim()) return;
if (!moduleEnabled) {
setError("Le module Live chat agent est désactivé pour ce projet.");
return;
}
const content = draft.trim();
setDraft("");
@ -172,6 +188,11 @@ export default function ProjectLiveAgent() {
{error}
</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">
<h3 className="mb-3 text-sm font-semibold text-gray-800">Nouvelle session</h3>
@ -196,7 +217,7 @@ export default function ProjectLiveAgent() {
/>
<button
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"
>
{creatingSession ? "Création..." : "Créer la session"}
@ -270,7 +291,7 @@ export default function ProjectLiveAgent() {
/>
<button
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"
>
{sending ? "Envoi..." : "Envoyer"}

View file

@ -6,6 +6,7 @@ import {
createAgentTask,
listAgentTasks,
listAgents,
listProjectModules,
retryAgentTask,
} from "../../lib/api";
import { getErrorMessage } from "../../lib/errors";
@ -26,6 +27,7 @@ export default function ProjectTasks() {
const [agentId, setAgentId] = useState("");
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [moduleEnabled, setModuleEnabled] = useState(true);
const [creating, setCreating] = useState(false);
const [workingTaskId, setWorkingTaskId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
@ -40,12 +42,16 @@ export default function ProjectTasks() {
setError(null);
try {
const [availableAgents, projectTasks] = await Promise.all([
const [availableAgents, projectTasks, modules] = await Promise.all([
listAgents(),
listAgentTasks(projectId),
listProjectModules(projectId),
]);
setAgents(availableAgents);
setTasks(projectTasks);
setModuleEnabled(
modules.find((mod) => mod.module_key === "agent_task_runner")?.enabled ?? true
);
if (!agentId && availableAgents.length > 0) {
setAgentId(availableAgents[0].id);
}
@ -95,6 +101,10 @@ export default function ProjectTasks() {
async function handleCreateTask(event: FormEvent) {
event.preventDefault();
if (!projectId || !agentId || !title.trim()) return;
if (!moduleEnabled) {
setError("Le module Agent task runner est désactivé pour ce projet.");
return;
}
setCreating(true);
setError(null);
@ -111,6 +121,11 @@ export default function ProjectTasks() {
}
async function handleRetry(taskId: string) {
if (!moduleEnabled) {
setError("Le module Agent task runner est désactivé pour ce projet.");
return;
}
setWorkingTaskId(taskId);
setError(null);
try {
@ -145,6 +160,11 @@ export default function ProjectTasks() {
{error}
</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">
<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
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"
>
{creating ? "Création..." : "Créer"}
@ -223,7 +243,7 @@ export default function ProjectTasks() {
<button
type="button"
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"
>
Relancer