92e67d0769
- Stack : Expo Router, Axios, Zustand, React Native Paper (thème sombre), date-fns - Sécurité : secrets dans Android Keystore via expo-secure-store, TLS obligatoire, headers X-N8N-API-KEY + X-App-Token injectés par intercepteur Axios - API : client.ts centralisé + workflows.ts + executions.ts (TypeScript strict) - Store : Zustand appStore avec chargement depuis secure store au démarrage - Hooks : usePolling (générique), useWorkflows, useExecutions - Composants : StatusBadge, WorkflowCard, ExecutionCard, SkeletonLoader - Screens : Dashboard, Workflows, Executions, Logs (détail exécution), Settings - Navigation Expo Router : 4 tabs + stack Logs + écran Setup initial - Docs : INSTALL.md, UPDATE.md, BACKUP.md, HAPROXY.md, SECURITY.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
138 lines
4.1 KiB
TypeScript
138 lines
4.1 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import { useAppStore } from '../store/appStore';
|
|
import {
|
|
fetchExecutions,
|
|
fetchExecutionById,
|
|
deleteExecution,
|
|
Execution,
|
|
ExecutionStatus,
|
|
FetchExecutionsParams,
|
|
} from '../api/executions';
|
|
import { usePolling } from './usePolling';
|
|
|
|
/** Options de filtrage passées au hook à l'initialisation */
|
|
interface UseExecutionsOptions {
|
|
/** Nombre max d'exécutions à charger */
|
|
limit?: number;
|
|
/** Filtre de statut initial */
|
|
statusFilter?: ExecutionStatus;
|
|
/** Restreindre aux exécutions d'un workflow spécifique */
|
|
workflowId?: string;
|
|
}
|
|
|
|
/** Interface des valeurs et actions exposées par le hook */
|
|
interface UseExecutionsResult {
|
|
executions: Execution[];
|
|
isLoading: boolean;
|
|
isRefreshing: boolean;
|
|
/** Exécution chargée en détail (avec logs de nœuds) */
|
|
selectedExecution: Execution | null;
|
|
/** Recharge la liste (pull-to-refresh) */
|
|
refresh: () => Promise<void>;
|
|
/**
|
|
* Charge les logs complets d'une exécution pour l'écran de détail.
|
|
* Peuple selectedExecution.
|
|
*
|
|
* @param id - Identifiant de l'exécution
|
|
*/
|
|
loadExecutionDetail: (id: string) => Promise<void>;
|
|
/**
|
|
* Supprime une exécution de l'historique et la retire de l'état local.
|
|
*
|
|
* @param id - Identifiant de l'exécution à supprimer
|
|
*/
|
|
removeExecution: (id: string) => Promise<void>;
|
|
/**
|
|
* Change le filtre de statut actif et relance le chargement.
|
|
*
|
|
* @param status - Statut cible ou undefined pour tout afficher
|
|
*/
|
|
setStatusFilter: (status: ExecutionStatus | undefined) => void;
|
|
}
|
|
|
|
/**
|
|
* Hook de gestion des exécutions : listing avec filtres, polling, détail et suppression.
|
|
* Sépare clairement la liste (vue Executions) du détail (vue Logs).
|
|
*/
|
|
export const useExecutions = (options: UseExecutionsOptions = {}): UseExecutionsResult => {
|
|
const { preferences, config } = useAppStore();
|
|
const [executions, setExecutions] = useState<Execution[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
const [selectedExecution, setSelectedExecution] = useState<Execution | null>(null);
|
|
const [statusFilter, setStatusFilter] = useState<ExecutionStatus | undefined>(
|
|
options.statusFilter
|
|
);
|
|
|
|
/** Charge la liste avec les filtres courants */
|
|
const loadExecutions = useCallback(async (): Promise<void> => {
|
|
const params: FetchExecutionsParams = {
|
|
limit: options.limit ?? 50,
|
|
...(statusFilter !== undefined && { status: statusFilter }),
|
|
...(options.workflowId && { workflowId: options.workflowId }),
|
|
};
|
|
|
|
try {
|
|
const result = await fetchExecutions(params);
|
|
setExecutions(result.data);
|
|
} catch {
|
|
// Géré par l'intercepteur Axios
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [options.limit, options.workflowId, statusFilter]);
|
|
|
|
/** Polling automatique — désactivé si l'app n'est pas encore configurée */
|
|
usePolling({
|
|
callback: loadExecutions,
|
|
interval: preferences.pollingInterval,
|
|
enabled: config.isConfigured,
|
|
immediate: true,
|
|
});
|
|
|
|
const refresh = useCallback(async (): Promise<void> => {
|
|
setIsRefreshing(true);
|
|
try {
|
|
await loadExecutions();
|
|
} finally {
|
|
setIsRefreshing(false);
|
|
}
|
|
}, [loadExecutions]);
|
|
|
|
/**
|
|
* Charge les logs détaillés d'une exécution.
|
|
* Appelle GET /executions/:id qui inclut le champ `data` avec les nœuds.
|
|
*/
|
|
const loadExecutionDetail = useCallback(async (id: string): Promise<void> => {
|
|
try {
|
|
const detail = await fetchExecutionById(id);
|
|
setSelectedExecution(detail);
|
|
} catch {
|
|
// Géré par l'intercepteur Axios
|
|
}
|
|
}, []);
|
|
|
|
/**
|
|
* Supprime une exécution côté serveur et la retire optimistiquement de l'état local.
|
|
*/
|
|
const removeExecution = useCallback(async (id: string): Promise<void> => {
|
|
try {
|
|
await deleteExecution(id);
|
|
setExecutions((prev) => prev.filter((e) => e.id !== id));
|
|
} catch {
|
|
// Géré par l'intercepteur Axios
|
|
}
|
|
}, []);
|
|
|
|
return {
|
|
executions,
|
|
isLoading,
|
|
isRefreshing,
|
|
selectedExecution,
|
|
refresh,
|
|
loadExecutionDetail,
|
|
removeExecution,
|
|
setStatusFilter,
|
|
};
|
|
};
|