Files
n8n-workflows/static/mobile-app.html
dopeuni444 ae8cf6dc5b Add comprehensive analysis and documentation files
Added multiple markdown reports summarizing repository status, integration landscape, workflow analysis, and executive summaries. Introduced new Python modules for performance testing, enhanced API, and community features. Updated search_categories.json and added new templates and static files for mobile and communication interfaces.
2025-09-29 05:10:12 +04:00

406 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>N8N Workflows - Mobile App</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.app-container {
max-width: 100%;
margin: 0 auto;
background: white;
min-height: 100vh;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
text-align: center;
position: sticky;
top: 0;
z-index: 100;
}
.header h1 {
font-size: 24px;
margin-bottom: 5px;
}
.header p {
opacity: 0.9;
font-size: 14px;
}
.search-container {
padding: 20px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
}
.search-box {
width: 100%;
padding: 15px;
border: 2px solid #e9ecef;
border-radius: 25px;
font-size: 16px;
outline: none;
transition: all 0.3s ease;
}
.search-box:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.filters {
display: flex;
gap: 10px;
margin-top: 15px;
flex-wrap: wrap;
}
.filter-btn {
padding: 8px 16px;
border: 1px solid #ddd;
background: white;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.filter-btn.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
padding: 20px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 15px;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.stat-number {
font-size: 28px;
font-weight: bold;
color: #667eea;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
color: #666;
}
.workflows-list {
padding: 0 20px 20px;
}
.workflow-card {
background: white;
margin-bottom: 15px;
border-radius: 15px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.workflow-card:hover {
transform: translateY(-2px);
}
.workflow-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
color: #333;
}
.workflow-description {
color: #666;
font-size: 14px;
line-height: 1.5;
margin-bottom: 15px;
}
.workflow-meta {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 15px;
}
.meta-tag {
background: #f8f9fa;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
color: #666;
}
.workflow-actions {
display: flex;
gap: 10px;
}
.action-btn {
padding: 8px 16px;
border: none;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-secondary {
background: #f8f9fa;
color: #666;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.error {
background: #fee;
color: #c33;
padding: 15px;
border-radius: 10px;
margin: 20px;
text-align: center;
}
.fab {
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 60px;
background: #667eea;
color: white;
border: none;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
}
.fab:hover {
transform: scale(1.1);
}
@media (max-width: 480px) {
.stats-grid {
grid-template-columns: 1fr;
}
.filters {
justify-content: center;
}
}
</style>
</head>
<body>
<div class="app-container">
<div class="header">
<h1>🚀 N8N Workflows</h1>
<p>Mobile Automation Platform</p>
</div>
<div class="search-container">
<input type="text" class="search-box" placeholder="Search workflows..." id="searchInput">
<div class="filters">
<button class="filter-btn active" data-trigger="all">All</button>
<button class="filter-btn" data-trigger="Webhook">Webhook</button>
<button class="filter-btn" data-trigger="Scheduled">Scheduled</button>
<button class="filter-btn" data-trigger="Manual">Manual</button>
<button class="filter-btn" data-trigger="Complex">Complex</button>
</div>
</div>
<div class="stats-grid" id="statsGrid">
<div class="stat-card">
<div class="stat-number" id="totalWorkflows">-</div>
<div class="stat-label">Total Workflows</div>
</div>
<div class="stat-card">
<div class="stat-number" id="activeWorkflows">-</div>
<div class="stat-label">Active</div>
</div>
<div class="stat-card">
<div class="stat-number" id="integrations">-</div>
<div class="stat-label">Integrations</div>
</div>
<div class="stat-card">
<div class="stat-number" id="nodes">-</div>
<div class="stat-label">Total Nodes</div>
</div>
</div>
<div class="workflows-list" id="workflowsList">
<div class="loading">Loading workflows...</div>
</div>
</div>
<button class="fab" onclick="refreshData()">🔄</button>
<script>
let currentFilters = {
trigger: 'all',
complexity: 'all',
active_only: false
};
let allWorkflows = [];
async function loadStats() {
try {
const response = await fetch('/api/stats');
const stats = await response.json();
document.getElementById('totalWorkflows').textContent = stats.total.toLocaleString();
document.getElementById('activeWorkflows').textContent = stats.active.toLocaleString();
document.getElementById('integrations').textContent = stats.unique_integrations.toLocaleString();
document.getElementById('nodes').textContent = stats.total_nodes.toLocaleString();
} catch (error) {
console.error('Error loading stats:', error);
}
}
async function loadWorkflows() {
try {
const params = new URLSearchParams({
limit: '20',
trigger: currentFilters.trigger,
complexity: currentFilters.complexity,
active_only: currentFilters.active_only
});
const response = await fetch(`/api/workflows?${params}`);
const data = await response.json();
allWorkflows = data.workflows;
displayWorkflows(allWorkflows);
} catch (error) {
console.error('Error loading workflows:', error);
document.getElementById('workflowsList').innerHTML =
'<div class="error">Failed to load workflows. Please try again.</div>';
}
}
function displayWorkflows(workflows) {
const container = document.getElementById('workflowsList');
if (workflows.length === 0) {
container.innerHTML = '<div class="loading">No workflows found</div>';
return;
}
container.innerHTML = workflows.map(workflow => `
<div class="workflow-card">
<div class="workflow-title">${workflow.name}</div>
<div class="workflow-description">${workflow.description}</div>
<div class="workflow-meta">
<span class="meta-tag">${workflow.trigger_type}</span>
<span class="meta-tag">${workflow.complexity}</span>
<span class="meta-tag">${workflow.node_count} nodes</span>
${workflow.active ? '<span class="meta-tag" style="background: #d4edda; color: #155724;">Active</span>' : ''}
</div>
<div class="workflow-actions">
<button class="action-btn btn-primary" onclick="viewWorkflow('${workflow.filename}')">View</button>
<button class="action-btn btn-secondary" onclick="downloadWorkflow('${workflow.filename}')">Download</button>
</div>
</div>
`).join('');
}
function filterWorkflows() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
let filtered = allWorkflows.filter(workflow => {
const matchesSearch = !searchTerm ||
workflow.name.toLowerCase().includes(searchTerm) ||
workflow.description.toLowerCase().includes(searchTerm) ||
workflow.integrations.some(integration =>
integration.toLowerCase().includes(searchTerm)
);
const matchesTrigger = currentFilters.trigger === 'all' ||
workflow.trigger_type === currentFilters.trigger;
const matchesComplexity = currentFilters.complexity === 'all' ||
workflow.complexity === currentFilters.complexity;
const matchesActive = !currentFilters.active_only || workflow.active;
return matchesSearch && matchesTrigger && matchesComplexity && matchesActive;
});
displayWorkflows(filtered);
}
function viewWorkflow(filename) {
window.open(`/api/workflows/${filename}`, '_blank');
}
function downloadWorkflow(filename) {
window.open(`/api/workflows/${filename}/download`, '_blank');
}
function refreshData() {
loadStats();
loadWorkflows();
}
// Event listeners
document.getElementById('searchInput').addEventListener('input', filterWorkflows);
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilters.trigger = btn.dataset.trigger;
filterWorkflows();
});
});
// Initialize app
loadStats();
loadWorkflows();
</script>
</body>
</html>