<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gestionnaire de Workflows - MCP Server</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">MCP n8n Server</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">Accueil</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/validator">Validateur</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/templates">Templates</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/docs">Documentation</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/nextjs-integration">NextJS Integration</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/workflow-manager">Gestionnaire de Workflows</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/n8n-api">API n8n</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<h1 class="mb-4">Gestionnaire de Workflows</h1>
<p class="lead">Gérez vos workflows n8n facilement depuis cette interface.</p>
<div class="row mb-4">
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" id="tagFilter" placeholder="Filtrer par tags (séparés par des virgules)">
<button class="btn btn-outline-secondary" type="button" id="filterButton">Filtrer</button>
<button class="btn btn-outline-secondary" type="button" id="clearFilterButton">Effacer</button>
</div>
</div>
<div class="col-md-6 text-end">
<button class="btn btn-primary" id="createWorkflowBtn" data-bs-toggle="modal" data-bs-target="#createWorkflowModal">
<i class="bi bi-plus-circle"></i> Créer un workflow
</button>
<button class="btn btn-success" id="importWorkflowBtn" data-bs-toggle="modal" data-bs-target="#importWorkflowModal">
<i class="bi bi-upload"></i> Importer
</button>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Liste des workflows</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Nom</th>
<th>Tags</th>
<th>Statut</th>
<th>Dernière mise à jour</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="workflowsTable">
<tr>
<td colspan="5" class="text-center">Chargement des workflows...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal pour créer un workflow -->
<div class="modal fade" id="createWorkflowModal" tabindex="-1" aria-labelledby="createWorkflowModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createWorkflowModalLabel">Créer un nouveau workflow</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="createWorkflowForm">
<div class="mb-3">
<label for="workflowName" class="form-label">Nom du workflow</label>
<input type="text" class="form-control" id="workflowName" required>
</div>
<div class="mb-3">
<label for="workflowTags" class="form-label">Tags (séparés par des virgules)</label>
<input type="text" class="form-control" id="workflowTags">
</div>
<div class="mb-3">
<label for="workflowData" class="form-label">Données du workflow (JSON)</label>
<textarea class="form-control" id="workflowData" rows="10" required></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id="submitCreateWorkflow">Créer</button>
</div>
</div>
</div>
</div>
<!-- Modal pour importer un workflow -->
<div class="modal fade" id="importWorkflowModal" tabindex="-1" aria-labelledby="importWorkflowModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="importWorkflowModalLabel">Importer un workflow</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="importWorkflowForm" enctype="multipart/form-data">
<div class="mb-3">
<label for="workflowFile" class="form-label">Fichier de workflow (JSON)</label>
<input type="file" class="form-control" id="workflowFile" accept=".json" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id="submitImportWorkflow">Importer</button>
</div>
</div>
</div>
</div>
<!-- Modal pour afficher les détails d'un workflow -->
<div class="modal fade" id="viewWorkflowModal" tabindex="-1" aria-labelledby="viewWorkflowModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="viewWorkflowModalLabel">Détails du workflow</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<pre id="workflowDetails" class="bg-light p-3 rounded" style="max-height: 500px; overflow-y: auto;"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
<footer class="bg-light py-3 mt-5">
<div class="container text-center">
<p class="mb-0">MCP n8n Server © 2023</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Charger la liste des workflows
loadWorkflows();
// Gestionnaire d'événements pour le bouton de filtrage
document.getElementById('filterButton').addEventListener('click', function() {
loadWorkflows(document.getElementById('tagFilter').value);
});
// Gestionnaire d'événements pour le bouton d'effacement du filtre
document.getElementById('clearFilterButton').addEventListener('click', function() {
document.getElementById('tagFilter').value = '';
loadWorkflows();
});
// Gestionnaire d'événements pour le formulaire de création de workflow
document.getElementById('submitCreateWorkflow').addEventListener('click', function() {
const name = document.getElementById('workflowName').value;
const tagsInput = document.getElementById('workflowTags').value;
const tags = tagsInput ? tagsInput.split(',').map(tag => tag.trim()) : [];
let workflowDataInput = document.getElementById('workflowData').value;
try {
// Convertir les données en objet JSON
const workflowData = JSON.parse(workflowDataInput);
// Ajouter le nom et les tags au workflow
workflowData.name = name;
workflowData.tags = tags;
// Envoyer la requête pour créer le workflow
fetch('/api/workflow-manager/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(workflowData)
})
.then(response => response.json())
.then(result => {
if (result.success) {
alert('Workflow créé avec succès !');
// Fermer le modal et recharger la liste des workflows
const modal = bootstrap.Modal.getInstance(document.getElementById('createWorkflowModal'));
modal.hide();
loadWorkflows();
} else {
alert(`Erreur lors de la création du workflow: ${result.error}`);
}
})
.catch(error => {
console.error('Erreur lors de la création du workflow:', error);
alert(`Erreur: ${error.message}`);
});
} catch (error) {
alert('Les données du workflow doivent être au format JSON valide');
}
});
// Gestionnaire d'événements pour le formulaire d'importation de workflow
document.getElementById('submitImportWorkflow').addEventListener('click', function() {
const fileInput = document.getElementById('workflowFile');
if (fileInput.files.length === 0) {
alert('Veuillez sélectionner un fichier');
return;
}
const formData = new FormData();
formData.append('workflow', fileInput.files[0]);
// Envoyer la requête pour importer le workflow
fetch('/api/workflow-manager/import', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => {
if (result.success) {
alert('Workflow importé avec succès !');
// Fermer le modal et recharger la liste des workflows
const modal = bootstrap.Modal.getInstance(document.getElementById('importWorkflowModal'));
modal.hide();
loadWorkflows();
} else {
alert(`Erreur lors de l'importation du workflow: ${result.error}`);
}
})
.catch(error => {
console.error('Erreur lors de l\'importation du workflow:', error);
alert(`Erreur: ${error.message}`);
});
});
});
// Fonction pour charger la liste des workflows
function loadWorkflows(tags = '') {
let url = '/api/workflow-manager/list';
if (tags) {
url += `?tags=${encodeURIComponent(tags)}`;
}
fetch(url)
.then(response => response.json())
.then(data => {
if (data.success) {
const workflowsTable = document.getElementById('workflowsTable');
if (data.workflows.length === 0) {
workflowsTable.innerHTML = '<tr><td colspan="5" class="text-center">Aucun workflow trouvé</td></tr>';
return;
}
workflowsTable.innerHTML = '';
data.workflows.forEach(workflow => {
const row = document.createElement('tr');
// Formater la date
const updatedAt = new Date(workflow.updatedAt).toLocaleString();
// Formater les tags
const tagsHtml = workflow.tags.map(tag => `<span class="badge bg-info me-1">${tag}</span>`).join('');
row.innerHTML = `
<td>${workflow.name}</td>
<td>${tagsHtml}</td>
<td><span class="badge ${workflow.active ? 'bg-success' : 'bg-secondary'}">${workflow.active ? 'Actif' : 'Inactif'}</span></td>
<td>${updatedAt}</td>
<td>
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-info view-workflow" data-id="${workflow.id}">
<i class="bi bi-eye"></i>
</button>
<button type="button" class="btn btn-sm btn-primary export-workflow" data-id="${workflow.id}">
<i class="bi bi-download"></i>
</button>
<button type="button" class="btn btn-sm btn-danger delete-workflow" data-id="${workflow.id}" data-name="${workflow.name}">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
`;
workflowsTable.appendChild(row);
});
// Ajouter des écouteurs d'événements pour les boutons d'action
addActionButtonListeners();
} else {
console.error('Erreur lors du chargement des workflows:', data.error);
document.getElementById('workflowsTable').innerHTML = `<tr><td colspan="5" class="text-center text-danger">Erreur: ${data.error}</td></tr>`;
}
})
.catch(error => {
console.error('Erreur lors du chargement des workflows:', error);
document.getElementById('workflowsTable').innerHTML = `<tr><td colspan="5" class="text-center text-danger">Erreur: ${error.message}</td></tr>`;
});
}
// Fonction pour ajouter des écouteurs d'événements aux boutons d'action
function addActionButtonListeners() {
// Boutons pour voir les détails d'un workflow
document.querySelectorAll('.view-workflow').forEach(button => {
button.addEventListener('click', function() {
const workflowId = this.dataset.id;
fetch(`/api/workflow-manager/get/${workflowId}`)
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('workflowDetails').textContent = JSON.stringify(data.workflow, null, 2);
const modal = new bootstrap.Modal(document.getElementById('viewWorkflowModal'));
modal.show();
} else {
alert(`Erreur: ${data.error}`);
}
})
.catch(error => {
console.error('Erreur lors de la récupération des détails du workflow:', error);
alert(`Erreur: ${error.message}`);
});
});
});
// Boutons pour exporter un workflow
document.querySelectorAll('.export-workflow').forEach(button => {
button.addEventListener('click', function() {
const workflowId = this.dataset.id;
window.location.href = `/api/workflow-manager/export/${workflowId}`;
});
});
// Boutons pour supprimer un workflow
document.querySelectorAll('.delete-workflow').forEach(button => {
button.addEventListener('click', function() {
const workflowId = this.dataset.id;
const workflowName = this.dataset.name;
if (confirm(`Êtes-vous sûr de vouloir supprimer le workflow "${workflowName}" ?`)) {
fetch(`/api/workflow-manager/delete/${workflowId}`, {
method: 'DELETE'
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Workflow supprimé avec succès !');
loadWorkflows();
} else {
alert(`Erreur: ${data.error}`);
}
})
.catch(error => {
console.error('Erreur lors de la suppression du workflow:', error);
alert(`Erreur: ${error.message}`);
});
}
});
});
}
</script>
</body>
</html>