drstoneworkui / index.html
taleslemonade's picture
undefined - Initial Deployment
03ae5fe verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Liquid Glass UI for n8n Workflows</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.glass-effect {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
.liquid-animation {
position: relative;
overflow: hidden;
}
.liquid-animation::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
45deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent
);
transform: rotate(45deg);
animation: liquidFlow 6s linear infinite;
}
@keyframes liquidFlow {
0% {
transform: rotate(45deg) translate(-30%, -30%);
}
100% {
transform: rotate(45deg) translate(30%, 30%);
}
}
.node-item {
transition: all 0.3s ease;
}
.node-item:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.json-viewer {
max-height: 400px;
overflow-y: auto;
}
/* Custom scrollbar */
.json-viewer::-webkit-scrollbar {
width: 8px;
}
.json-viewer::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
.json-viewer::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 10px;
}
.json-viewer::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-800 text-white p-4 md:p-8">
<div class="max-w-6xl mx-auto">
<header class="flex flex-col md:flex-row justify-between items-center mb-8 gap-4">
<div class="glass-effect liquid-animation p-6 w-full md:w-auto">
<h1 class="text-3xl md:text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-cyan-400 to-blue-500">
<i class="fas fa-code-branch mr-3"></i>n8n Workflow UI
</h1>
<p class="text-sm opacity-80 mt-2">Liquid Glass Interface for Webhook Integration</p>
</div>
<div class="glass-effect p-4 w-full md:w-auto flex items-center gap-4">
<div class="flex-1">
<label class="block text-sm font-medium mb-1">Webhook URL</label>
<div class="flex">
<input type="text" id="webhookUrl" placeholder="https://your-n8n-instance.com/webhook/path"
class="bg-black bg-opacity-30 text-white px-4 py-2 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1">
<button id="testWebhook" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-r-md transition">
<i class="fas fa-bolt mr-2"></i>Test
</button>
</div>
</div>
</div>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Workflow Configuration -->
<div class="glass-effect p-6 lg:col-span-1">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-cog mr-2 text-blue-400"></i> Workflow Setup
</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Workflow Name</label>
<input type="text" id="workflowName" placeholder="My Awesome Workflow"
class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">HTTP Method</label>
<select id="httpMethod" class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="POST">POST</option>
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Authentication</label>
<select id="authMethod" class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="none">None</option>
<option value="basic">Basic Auth</option>
<option value="bearer">Bearer Token</option>
<option value="apiKey">API Key</option>
</select>
</div>
<div id="authFields" class="hidden space-y-2">
<input type="text" id="authUsername" placeholder="Username"
class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<input type="password" id="authPassword" placeholder="Password/Token"
class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button id="saveConfig" class="w-full bg-green-600 hover:bg-green-700 px-4 py-2 rounded-md transition flex items-center justify-center">
<i class="fas fa-save mr-2"></i> Save Configuration
</button>
</div>
<div class="mt-6 pt-4 border-t border-white border-opacity-20">
<h3 class="text-sm font-medium mb-2">Recent Workflows</h3>
<div id="recentWorkflows" class="space-y-2">
<!-- Dynamically populated -->
</div>
</div>
</div>
<!-- Workflow Visualization -->
<div class="glass-effect p-6 lg:col-span-2">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold flex items-center">
<i class="fas fa-project-diagram mr-2 text-purple-400"></i> Workflow Visualization
</h2>
<div class="flex space-x-2">
<button id="addNode" class="bg-indigo-600 hover:bg-indigo-700 px-3 py-1 rounded-md text-sm transition">
<i class="fas fa-plus mr-1"></i> Add Node
</button>
<button id="exportWorkflow" class="bg-purple-600 hover:bg-purple-700 px-3 py-1 rounded-md text-sm transition">
<i class="fas fa-file-export mr-1"></i> Export
</button>
</div>
</div>
<div id="workflowCanvas" class="min-h-64 bg-black bg-opacity-20 rounded-md p-4">
<div class="text-center py-8 text-white text-opacity-50">
<i class="fas fa-magic text-4xl mb-2"></i>
<p>Add your first node to start building your workflow</p>
</div>
</div>
<div class="mt-6">
<h3 class="text-sm font-medium mb-2 flex items-center">
<i class="fas fa-cubes mr-2"></i> Available Nodes
</h3>
<div id="nodeLibrary" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
<!-- Dynamically populated -->
</div>
</div>
</div>
</div>
<!-- JSON Viewer Modal -->
<div id="jsonModal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 hidden">
<div class="glass-effect w-full max-w-3xl max-h-screen overflow-auto m-4 p-6 rounded-xl">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold">JSON Viewer</h3>
<button id="closeModal" class="text-white hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div id="jsonViewer" class="json-viewer bg-black bg-opacity-30 p-4 rounded-md font-mono text-sm overflow-auto"></div>
<div class="mt-4 flex justify-end space-x-2">
<button id="copyJson" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-md transition">
<i class="fas fa-copy mr-2"></i> Copy JSON
</button>
<button id="sendToWebhook" class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-md transition">
<i class="fas fa-paper-plane mr-2"></i> Send to Webhook
</button>
</div>
</div>
</div>
</div>
<footer class="text-center text-xs opacity-70 mt-12 mb-4">
den by olu dara@stoneweb
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Sample node types for the library
const nodeTypes = [
{ name: 'Webhook', icon: 'fa-link', color: 'bg-purple-500' },
{ name: 'HTTP Request', icon: 'fa-globe', color: 'bg-blue-500' },
{ name: 'Function', icon: 'fa-code', color: 'bg-yellow-500' },
{ name: 'IF Condition', icon: 'fa-question', color: 'bg-green-500' },
{ name: 'Delay', icon: 'fa-clock', color: 'bg-indigo-500' },
{ name: 'Spreadsheet', icon: 'fa-table', color: 'bg-emerald-500' },
{ name: 'Email', icon: 'fa-envelope', color: 'bg-red-500' },
{ name: 'Telegram', icon: 'fa-paper-plane', color: 'bg-cyan-500' },
{ name: 'Slack', icon: 'fa-slack', color: 'bg-pink-500' },
{ name: 'Discord', icon: 'fa-discord', color: 'bg-indigo-500' },
{ name: 'Google Sheets', icon: 'fa-google', color: 'bg-green-500' },
{ name: 'MySQL', icon: 'fa-database', color: 'bg-orange-500' }
];
// Sample recent workflows
const recentWorkflows = [
{ name: 'Customer Onboarding', lastUsed: '2 hours ago' },
{ name: 'API Monitoring', lastUsed: 'Yesterday' },
{ name: 'Data Sync', lastUsed: '3 days ago' }
];
// Current workflow data
let workflowData = {
name: '',
nodes: [],
connections: [],
webhookUrl: '',
httpMethod: 'POST',
authMethod: 'none'
};
// DOM elements
const webhookUrlInput = document.getElementById('webhookUrl');
const testWebhookBtn = document.getElementById('testWebhook');
const workflowNameInput = document.getElementById('workflowName');
const httpMethodSelect = document.getElementById('httpMethod');
const authMethodSelect = document.getElementById('authMethod');
const authFields = document.getElementById('authFields');
const saveConfigBtn = document.getElementById('saveConfig');
const recentWorkflowsList = document.getElementById('recentWorkflows');
const addNodeBtn = document.getElementById('addNode');
const exportWorkflowBtn = document.getElementById('exportWorkflow');
const workflowCanvas = document.getElementById('workflowCanvas');
const nodeLibrary = document.getElementById('nodeLibrary');
const jsonModal = document.getElementById('jsonModal');
const jsonViewer = document.getElementById('jsonViewer');
const closeModalBtn = document.getElementById('closeModal');
const copyJsonBtn = document.getElementById('copyJson');
const sendToWebhookBtn = document.getElementById('sendToWebhook');
// Initialize the UI
function initUI() {
// Populate node library
nodeLibrary.innerHTML = '';
nodeTypes.forEach(nodeType => {
const nodeElement = document.createElement('div');
nodeElement.className = `node-item glass-effect p-3 rounded-md cursor-pointer flex flex-col items-center ${nodeType.color} bg-opacity-20 hover:bg-opacity-30`;
nodeElement.innerHTML = `
<i class="fas ${nodeType.icon} text-2xl mb-1 ${nodeType.color}"></i>
<span class="text-sm">${nodeType.name}</span>
`;
nodeElement.addEventListener('click', () => addNodeToCanvas(nodeType));
nodeLibrary.appendChild(nodeElement);
});
// Populate recent workflows
recentWorkflowsList.innerHTML = '';
recentWorkflows.forEach(workflow => {
const workflowElement = document.createElement('div');
workflowElement.className = 'glass-effect p-2 rounded-md cursor-pointer hover:bg-opacity-30';
workflowElement.innerHTML = `
<div class="font-medium">${workflow.name}</div>
<div class="text-xs opacity-70">${workflow.lastUsed}</div>
`;
recentWorkflowsList.appendChild(workflowElement);
});
// Show/hide auth fields based on selection
authMethodSelect.addEventListener('change', function() {
if (this.value === 'none') {
authFields.classList.add('hidden');
} else {
authFields.classList.remove('hidden');
}
});
// Modal controls
closeModalBtn.addEventListener('click', () => {
jsonModal.classList.add('hidden');
});
copyJsonBtn.addEventListener('click', () => {
navigator.clipboard.writeText(jsonViewer.textContent)
.then(() => {
const originalText = copyJsonBtn.innerHTML;
copyJsonBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!';
setTimeout(() => {
copyJsonBtn.innerHTML = originalText;
}, 2000);
});
});
sendToWebhookBtn.addEventListener('click', () => {
sendToWebhook();
});
// Test webhook button
testWebhookBtn.addEventListener('click', () => {
if (!webhookUrlInput.value) {
alert('Please enter a webhook URL');
return;
}
testWebhookBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Testing...';
// Simulate API call
setTimeout(() => {
testWebhookBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Success!';
setTimeout(() => {
testWebhookBtn.innerHTML = '<i class="fas fa-bolt mr-2"></i> Test';
}, 2000);
}, 1500);
});
// Save configuration
saveConfigBtn.addEventListener('click', () => {
workflowData.name = workflowNameInput.value;
workflowData.httpMethod = httpMethodSelect.value;
workflowData.authMethod = authMethodSelect.value;
workflowData.webhookUrl = webhookUrlInput.value;
alert('Configuration saved successfully!');
});
// Add node button
addNodeBtn.addEventListener('click', () => {
// Show node library if empty canvas
if (workflowCanvas.querySelector('.text-center')) {
workflowCanvas.innerHTML = '';
nodeLibrary.classList.remove('hidden');
} else {
nodeLibrary.classList.toggle('hidden');
}
});
// Export workflow
exportWorkflowBtn.addEventListener('click', () => {
if (workflowData.nodes.length === 0) {
alert('No nodes to export!');
return;
}
// Generate sample JSON (in a real app, this would be the actual workflow data)
const sampleWorkflow = {
name: workflowData.name || 'Unnamed Workflow',
nodes: workflowData.nodes.map(node => ({
id: node.id,
name: node.name,
type: node.type,
parameters: {}
})),
connections: workflowData.connections,
webhook: {
url: workflowData.webhookUrl,
method: workflowData.httpMethod,
auth: workflowData.authMethod !== 'none' ? {
type: workflowData.authMethod,
credentials: {
username: document.getElementById('authUsername').value,
password: document.getElementById('authPassword').value
}
} : null
},
timestamp: new Date().toISOString()
};
jsonViewer.textContent = JSON.stringify(sampleWorkflow, null, 2);
jsonModal.classList.remove('hidden');
});
}
// Add a node to the canvas
function addNodeToCanvas(nodeType) {
const nodeId = 'node-' + Date.now();
const nodeElement = document.createElement('div');
nodeElement.id = nodeId;
nodeElement.className = `node-item glass-effect p-4 rounded-lg mb-4 cursor-move relative ${nodeType.color} bg-opacity-20 hover:bg-opacity-30`;
nodeElement.innerHTML = `
<div class="flex items-center mb-2">
<i class="fas ${nodeType.icon} mr-2 ${nodeType.color}"></i>
<h3 class="font-medium">${nodeType.name}</h3>
<button class="ml-auto text-white text-opacity-50 hover:text-opacity-100 node-delete">
<i class="fas fa-times"></i>
</button>
</div>
<div class="text-xs opacity-70 mb-2">Click to configure</div>
<div class="flex justify-between text-xs">
<div class="node-input bg-black bg-opacity-30 px-2 py-1 rounded">Input</div>
<div class="node-output bg-black bg-opacity-30 px-2 py-1 rounded">Output</div>
</div>
`;
// Remove the placeholder if it exists
if (workflowCanvas.querySelector('.text-center')) {
workflowCanvas.innerHTML = '';
}
workflowCanvas.appendChild(nodeElement);
// Add to workflow data
workflowData.nodes.push({
id: nodeId,
name: nodeType.name,
type: nodeType.name.toLowerCase().replace(' ', '-')
});
// Make draggable
makeDraggable(nodeElement);
// Add delete functionality
const deleteBtn = nodeElement.querySelector('.node-delete');
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (confirm('Delete this node?')) {
nodeElement.remove();
workflowData.nodes = workflowData.nodes.filter(n => n.id !== nodeId);
// If no nodes left, show placeholder
if (workflowCanvas.children.length === 0) {
workflowCanvas.innerHTML = `
<div class="text-center py-8 text-white text-opacity-50">
<i class="fas fa-magic text-4xl mb-2"></i>
<p>Add your first node to start building your workflow</p>
</div>
`;
}
}
});
// Node click for configuration
nodeElement.addEventListener('click', () => {
openNodeConfig(nodeId, nodeType);
});
}
// Make nodes draggable
function makeDraggable(element) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// Get the mouse cursor position at startup
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// Calculate the new cursor position
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// Set the element's new position
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
element.style.position = 'absolute';
}
function closeDragElement() {
// Stop moving when mouse button is released
document.onmouseup = null;
document.onmousemove = null;
}
}
// Open node configuration
function openNodeConfig(nodeId, nodeType) {
const node = workflowData.nodes.find(n => n.id === nodeId);
// Create modal content based on node type
let modalContent = `
<div class="glass-effect p-6 rounded-xl max-w-md mx-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold flex items-center">
<i class="fas ${nodeType.icon} mr-2 ${nodeType.color}"></i>
${nodeType.name} Configuration
</h3>
<button id="closeNodeConfig" class="text-white hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
`;
// Add different fields based on node type
if (nodeType.name === 'Webhook') {
modalContent += `
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Webhook Path</label>
<input type="text" value="/webhook/${nodeId.split('-')[1]}"
class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">HTTP Method</label>
<select class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="POST" selected>POST</option>
<option value="GET">GET</option>
</select>
</div>
</div>
`;
} else if (nodeType.name === 'HTTP Request') {
modalContent += `
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">URL</label>
<input type="text" placeholder="https://api.example.com/endpoint"
class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Method</label>
<select class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="GET">GET</option>
<option value="POST" selected>POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-1">Headers</label>
<textarea placeholder="Content-Type: application/json\nAuthorization: Bearer token"
class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 h-24"></textarea>
</div>
</div>
`;
} else {
modalContent += `
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Configuration</label>
<textarea placeholder="Enter configuration for ${nodeType.name} node..."
class="w-full bg-black bg-opacity-30 text-white px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 h-32"></textarea>
</div>
</div>
`;
}
modalContent += `
<div class="mt-6 flex justify-end space-x-2">
<button id="cancelNodeConfig" class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-md transition">
Cancel
</button>
<button id="saveNodeConfig" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-md transition">
<i class="fas fa-save mr-2"></i> Save
</button>
</div>
</div>
`;
// Create and show modal
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50';
modal.innerHTML = modalContent;
document.body.appendChild(modal);
// Close modal
const closeBtn = modal.querySelector('#closeNodeConfig');
const cancelBtn = modal.querySelector('#cancelNodeConfig');
const closeModal = () => {
document.body.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
// Save configuration
const saveBtn = modal.querySelector('#saveNodeConfig');
saveBtn.addEventListener('click', () => {
// In a real app, save the configuration to workflowData
alert('Node configuration saved!');
closeModal();
});
}
// Send JSON to webhook
function sendToWebhook() {
if (!webhookUrlInput.value) {
alert('Please configure a webhook URL first');
return;
}
sendToWebhookBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Sending...';
// Simulate API call
setTimeout(() => {
sendToWebhookBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Sent!';
setTimeout(() => {
sendToWebhookBtn.innerHTML = '<i class="fas fa-paper-plane mr-2"></i> Send to Webhook';
jsonModal.classList.add('hidden');
}, 1500);
}, 2000);
}
// Initialize the app
initUI();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=taleslemonade/drstoneworkui" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>