tahamajs/IE / Projects /cli /templates /simulator.html
tahamajs's picture
download
raw
19.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MioBook Sandbox Dev Simulator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=JetBrains+Mono&display=swap" rel="stylesheet">
<style>
body { font-family: 'Outfit', sans-serif; }
code, pre { font-family: 'JetBrains Mono', monospace; }
</style>
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen flex flex-col">
<header class="border-b border-slate-800 bg-slate-900/50 backdrop-blur-md px-6 py-4 flex justify-between items-center sticky top-0 z-10">
<div class="flex items-center gap-3">
<span class="text-2xl">⚙️</span>
<div>
<h1 class="text-lg font-bold tracking-tight bg-gradient-to-r from-cyan-400 to-purple-500 bg-clip-text text-transparent" id="tool-title-header">Sandbox Dev Simulator</h1>
<p class="text-xs text-slate-400" id="tool-desc-header">Simulate local widgets and components</p>
</div>
</div>
<div class="flex items-center gap-3">
<button onclick="triggerDeploy()" class="bg-purple-600 hover:bg-purple-700 text-white px-3 py-1.5 rounded font-semibold text-sm transition">🚀 Deploy</button>
<span class="text-xs bg-slate-800 text-slate-400 px-2 py-1 rounded font-mono border border-slate-700" id="tool-category-header">Dev Mode</span>
<button onclick="reloadIframe()" class="bg-cyan-500 hover:bg-cyan-600 text-slate-950 px-3 py-1.5 rounded font-semibold text-sm transition">🔄 Reload</button>
</div>
</header>
<main class="flex-1 flex overflow-hidden">
<!-- Left panel: Iframe -->
<div class="flex-1 p-6 flex flex-col gap-4 border-r border-slate-800">
<div class="flex justify-between items-center">
<h2 class="text-sm font-semibold uppercase tracking-wider text-slate-400">Sandbox Preview</h2>
<div class="flex items-center gap-2">
<span class="text-xs text-slate-500">CORS Proxy is active at /proxy</span>
<span id="sandbox-status" class="text-xs px-2.5 py-0.5 rounded-full bg-yellow-500/20 text-yellow-400 border border-yellow-500/30">loading</span>
</div>
</div>
<div class="flex-1 bg-slate-900 border border-slate-800 rounded-xl overflow-hidden shadow-2xl relative min-h-[500px]">
<iframe id="sandbox-iframe" src="/tool.html" class="w-full h-full border-none bg-white"></iframe>
</div>
</div>
<!-- Right panel: Controller & Console -->
<div class="w-[450px] p-6 flex flex-col gap-6 overflow-y-auto bg-slate-900/30 border-l border-slate-800">
<!-- Input Section -->
<div class="flex flex-col gap-3">
<div class="flex justify-between items-center">
<h2 class="text-sm font-semibold uppercase tracking-wider text-slate-400">1. Initialize Inputs</h2>
<div class="flex gap-1.5">
<button onclick="toggleInputView('form')" id="btn-tab-form" class="text-[10px] px-2 py-1 rounded bg-slate-800 text-cyan-400 font-semibold border border-slate-700">Form</button>
<button onclick="toggleInputView('json')" id="btn-tab-json" class="text-[10px] px-2 py-1 rounded bg-slate-900 text-slate-400">JSON</button>
<button onclick="toggleInputView('terminal')" id="btn-tab-terminal" class="text-[10px] px-2 py-1 rounded bg-slate-900 text-slate-400">Terminal</button>
</div>
</div>
<!-- Form view -->
<div id="input-view-form" class="bg-slate-900 border border-slate-800 rounded-xl p-4 flex flex-col gap-4">
<form id="dynamic-form" class="flex flex-col gap-3">
<div class="text-xs text-slate-500" id="form-fallback-msg">No parameters defined in tool.json. Use JSON tab.</div>
</form>
<div class="flex items-center gap-2 mt-2">
<input type="checkbox" id="auto-send-cb" checked class="rounded border-slate-800 bg-slate-950 text-cyan-500 focus:ring-0">
<label for="auto-send-cb" class="text-xs text-slate-400">Auto-send on change</label>
</div>
<button onclick="sendInit()" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 rounded-lg font-semibold text-sm transition shadow-lg shadow-purple-600/20 mt-1">🚀 Send Init Payload</button>
</div>
<!-- JSON view (hidden by default) -->
<div id="input-view-json" class="bg-slate-900 border border-slate-800 rounded-xl p-4 flex flex-col gap-3 hidden">
<label class="text-xs text-slate-400">Mock Data Payload (JSON)</label>
<textarea id="mock-payload" class="w-full h-36 bg-slate-950 border border-slate-800 rounded-lg p-3 text-xs font-mono text-cyan-400 focus:outline-none focus:border-cyan-500" placeholder='{"items": []}'>{
"title": "Monthly Target Report",
"data": [12, 19, 3, 5, 2, 3]
}</textarea>
<button onclick="sendInit()" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 rounded-lg font-semibold text-sm transition shadow-lg shadow-purple-600/20">🚀 Send Init Payload</button>
</div>
<!-- Terminal view (hidden by default) -->
<div id="input-view-terminal" class="bg-slate-900 border border-slate-800 rounded-xl p-4 flex flex-col gap-3 hidden">
<div class="flex gap-2">
<input type="text" id="terminal-input" onkeydown="handleTerminalKey(event)" class="flex-1 bg-slate-950 border border-slate-800 rounded px-2.5 py-1.5 text-xs text-green-400 font-mono focus:outline-none focus:border-green-500" placeholder="Run shell command...">
<button onclick="runTerminalCommand()" class="bg-green-600 hover:bg-green-700 text-slate-950 font-bold px-3 py-1.5 rounded text-xs transition">Run</button>
</div>
<div id="terminal-output" class="bg-slate-950 border border-slate-800 rounded-lg p-3 h-48 font-mono text-[10px] text-green-400 overflow-y-auto whitespace-pre-wrap">
[Terminal ready] Type a command above and press Enter.
</div>
</div>
</div>
<!-- Log Console -->
<div class="flex-1 flex flex-col gap-3 min-h-[300px]">
<div class="flex justify-between items-center">
<h2 class="text-sm font-semibold uppercase tracking-wider text-slate-400">2. Sandbox Message Logs</h2>
<button onclick="clearLogs()" class="text-xs text-slate-500 hover:text-slate-300">Clear</button>
</div>
<div id="logs-container" class="flex-1 bg-slate-950 border border-slate-800 rounded-xl p-4 overflow-y-auto flex flex-col gap-3 text-xs font-mono">
<div class="text-slate-500">[Simulator] Waiting for sandbox ready message...</div>
</div>
</div>
</div>
</main>
<!-- Modal for Deploy Result -->
<div id="deploy-modal" class="fixed inset-0 bg-black/75 backdrop-blur-sm z-50 flex items-center justify-center hidden">
<div class="bg-slate-900 border border-slate-800 rounded-2xl w-[600px] max-h-[80vh] flex flex-col p-6 shadow-2xl">
<div class="flex justify-between items-center border-b border-slate-800 pb-3 mb-4">
<h3 class="text-lg font-bold text-slate-100">🚀 Deployment Output Logs</h3>
<button onclick="closeDeployModal()" class="text-slate-500 hover:text-slate-300">✕</button>
</div>
<div class="flex-1 overflow-y-auto bg-slate-950 p-4 rounded-xl border border-slate-800 font-mono text-xs text-slate-300 whitespace-pre-wrap min-h-[200px]" id="deploy-output">
Deploying... Please wait...
</div>
<div class="flex justify-end mt-4">
<button onclick="closeDeployModal()" class="bg-slate-800 hover:bg-slate-700 text-slate-200 px-4 py-2 rounded-lg font-semibold text-sm transition">Close</button>
</div>
</div>
</div>
<script>
const iframe = document.getElementById('sandbox-iframe');
const statusBadge = document.getElementById('sandbox-status');
const logsContainer = document.getElementById('logs-container');
let toolMetadata = null;
function log(type, message, data = null) {
const time = new Date().toLocaleTimeString();
const div = document.createElement('div');
div.className = 'border-l-2 pl-3 py-1 flex flex-col gap-1 ';
if (type === 'Simulator') {
div.className += 'border-cyan-500 text-cyan-400 bg-cyan-500/5';
} else if (type === 'ready') {
div.className += 'border-green-500 text-green-400 bg-green-500/5';
} else if (type === 'submit') {
div.className += 'border-purple-500 text-purple-400 bg-purple-500/5';
} else if (type.startsWith('console-')) {
const level = type.split('-')[1];
if (level === 'warn') {
div.className += 'border-yellow-500 text-yellow-400 bg-yellow-500/5';
div.innerHTML = '<span class="font-bold">[' + time + '] ⚠️ [WARN]:</span> <span class="whitespace-pre-wrap">' + message + '</span>';
logsContainer.appendChild(div);
logsContainer.scrollTop = logsContainer.scrollHeight;
return;
} else if (level === 'error') {
div.className += 'border-red-500 text-red-400 bg-red-500/5';
div.innerHTML = '<span class="font-bold">[' + time + '] ❌ [ERROR]:</span> <span class="whitespace-pre-wrap">' + message + '</span>';
logsContainer.appendChild(div);
logsContainer.scrollTop = logsContainer.scrollHeight;
return;
} else {
div.className += 'border-slate-500 text-slate-300 bg-slate-500/5';
div.innerHTML = '<span class="font-bold">[' + time + '] 💬 [LOG]:</span> <span class="whitespace-pre-wrap">' + message + '</span>';
logsContainer.appendChild(div);
logsContainer.scrollTop = logsContainer.scrollHeight;
return;
}
} else {
div.className += 'border-yellow-500 text-yellow-400 bg-yellow-500/5';
}
let dataHtml = '';
if (data) {
dataHtml = '<pre class="mt-1 text-[10px] bg-black/30 p-2 rounded overflow-x-auto text-slate-300">' + JSON.stringify(data, null, 2) + '</pre>';
}
div.innerHTML = '<span class="font-bold">[' + time + '] ' + type.toUpperCase() + ':</span> <span>' + message + '</span>' + dataHtml;
logsContainer.appendChild(div);
logsContainer.scrollTop = logsContainer.scrollHeight;
}
window.addEventListener('message', (event) => {
const msg = event.data;
if (!msg || !msg.type) return;
if (msg.type === 'ready') {
statusBadge.textContent = 'ready';
statusBadge.className = 'text-xs px-2.5 py-0.5 rounded-full bg-green-500/20 text-green-400 border border-green-500/30';
log('ready', 'Sandbox loaded and is ready.');
if (document.getElementById('auto-send-cb').checked) {
sendInit();
}
} else if (msg.type === 'submit' || msg.type === 'result') {
log(msg.type, 'Received payload from sandbox:', msg.data);
} else if (msg.type === 'console') {
const argsStr = msg.args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ');
log('console-' + msg.level, argsStr);
} else {
log(msg.type, 'Unknown message received', msg);
}
});
function toggleInputView(mode) {
const formView = document.getElementById('input-view-form');
const jsonView = document.getElementById('input-view-json');
const termView = document.getElementById('input-view-terminal');
const btnForm = document.getElementById('btn-tab-form');
const btnJson = document.getElementById('btn-tab-json');
const btnTerm = document.getElementById('btn-tab-terminal');
formView.classList.add('hidden');
jsonView.classList.add('hidden');
termView.classList.add('hidden');
btnForm.className = 'text-[10px] px-2 py-1 rounded bg-slate-900 text-slate-400';
btnJson.className = 'text-[10px] px-2 py-1 rounded bg-slate-900 text-slate-400';
btnTerm.className = 'text-[10px] px-2 py-1 rounded bg-slate-900 text-slate-400';
if (mode === 'form') {
formView.classList.remove('hidden');
btnForm.className = 'text-[10px] px-2 py-1 rounded bg-slate-800 text-cyan-400 font-semibold border border-slate-700';
} else if (mode === 'json') {
jsonView.classList.remove('hidden');
btnJson.className = 'text-[10px] px-2 py-1 rounded bg-slate-800 text-cyan-400 font-semibold border border-slate-700';
} else {
termView.classList.remove('hidden');
btnTerm.className = 'text-[10px] px-2 py-1 rounded bg-slate-800 text-cyan-400 font-semibold border border-slate-700';
}
}
function sendInit() {
try {
let payload;
if (!document.getElementById('input-view-form').classList.contains('hidden')) {
payload = getFormPayload();
} else {
const payloadStr = document.getElementById('mock-payload').value;
payload = JSON.parse(payloadStr);
}
iframe.contentWindow.postMessage({
type: 'init',
data: payload
}, '*');
log('Simulator', 'Sent INIT payload to sandbox.', payload);
} catch(e) {
alert('Error: ' + e.message);
}
}
function getFormPayload() {
if (!toolMetadata || !toolMetadata.parameters) return {};
const payload = {};
toolMetadata.parameters.forEach(p => {
const el = document.getElementById('param-' + p.name);
if (!el) return;
if (p.type === 'boolean') {
payload[p.name] = el.checked;
} else if (p.type === 'number') {
payload[p.name] = el.value === '' ? null : Number(el.value);
} else {
payload[p.name] = el.value;
}
});
return payload;
}
function onFormChange() {
const payload = getFormPayload();
document.getElementById('mock-payload').value = JSON.stringify(payload, null, 2);
if (document.getElementById('auto-send-cb').checked) {
iframe.contentWindow.postMessage({
type: 'init',
data: payload
}, '*');
}
}
function reloadIframe() {
iframe.src = iframe.src;
statusBadge.textContent = 'loading';
statusBadge.className = 'text-xs px-2.5 py-0.5 rounded-full bg-yellow-500/20 text-yellow-400 border border-yellow-500/30';
log('Simulator', 'Iframe reloaded.');
}
function clearLogs() {
logsContainer.innerHTML = '<div class="text-slate-500">[Simulator] Logs cleared.</div>';
}
function triggerDeploy() {
const modal = document.getElementById('deploy-modal');
const output = document.getElementById('deploy-output');
modal.classList.remove('hidden');
output.textContent = 'Deploying tool using local SDK deploy logic... Please wait...';
fetch('/api/deploy', { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
output.innerHTML = '<span class="text-green-400 font-bold">✔ SUCCESS</span>\n\n' + data.output;
} else {
output.innerHTML = '<span class="text-red-400 font-bold">✘ ERROR</span>\n\n' + data.output;
}
})
.catch(err => {
output.innerHTML = '<span class="text-red-400 font-bold">✘ NETWORK ERROR:</span> ' + err.message;
});
}
function closeDeployModal() {
document.getElementById('deploy-modal').classList.add('hidden');
}
function runTerminalCommand() {
const input = document.getElementById('terminal-input');
const output = document.getElementById('terminal-output');
const cmd = input.value.trim();
if (!cmd) return;
output.textContent = '$ ' + cmd + '\nRunning...';
fetch('/api/terminal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command: cmd })
})
.then(r => r.json())
.then(data => {
output.textContent = '$ ' + cmd + '\n' + data.output;
output.scrollTop = output.scrollHeight;
})
.catch(err => {
output.textContent = '$ ' + cmd + '\nNetwork Error: ' + err.message;
});
}
function handleTerminalKey(e) {
if (e.key === 'Enter') {
runTerminalCommand();
}
}
fetch('/tool.json')
.then(r => r.json())
.then(data => {
toolMetadata = data;
document.getElementById('tool-title-header').textContent = data.name || 'Sandbox Dev Simulator';
document.getElementById('tool-desc-header').textContent = data.description || 'Simulate local widgets and components';
document.getElementById('tool-category-header').textContent = (data.category || 'dev').toUpperCase();
if (data.parameters && data.parameters.length > 0) {
const form = document.getElementById('dynamic-form');
form.innerHTML = '';
data.parameters.forEach(p => {
const row = document.createElement('div');
row.className = 'flex flex-col gap-1';
const label = document.createElement('label');
label.className = 'text-xs text-slate-400 font-semibold';
label.textContent = p.name + (p.required ? ' *' : '');
let input;
if (p.type === 'boolean') {
row.className = 'flex items-center justify-between py-1 border-b border-slate-800';
input = document.createElement('input');
input.type = 'checkbox';
input.className = 'rounded border-slate-800 bg-slate-950 text-cyan-500 focus:ring-0';
} else if (p.type === 'number') {
input = document.createElement('input');
input.type = 'number';
input.className = 'w-full bg-slate-950 border border-slate-800 rounded px-3 py-1.5 text-xs text-cyan-400 focus:outline-none focus:border-cyan-500';
} else {
input = document.createElement('input');
input.type = 'text';
input.className = 'w-full bg-slate-950 border border-slate-800 rounded px-3 py-1.5 text-xs text-cyan-400 focus:outline-none focus:border-cyan-500';
}
input.id = 'param-' + p.name;
input.onchange = onFormChange;
input.oninput = onFormChange;
if (p.type === 'boolean') {
row.appendChild(label);
row.appendChild(input);
} else {
row.appendChild(label);
row.appendChild(input);
if (p.description) {
const desc = document.createElement('span');
desc.className = 'text-[10px] text-slate-500';
desc.textContent = p.description;
row.appendChild(desc);
}
}
form.appendChild(row);
});
onFormChange();
}
})
.catch(e => {
console.log('No tool.json found or failed to load:', e);
});
const devSource = new EventSource('/live-reload');
devSource.onmessage = function(e) {
if (e.data === 'reload') {
console.log('[Dev Simulator] Reload event received, reloading iframe...');
reloadIframe();
}
};
</script>
</body>
</html>

Xet Storage Details

Size:
19.5 kB
·
Xet hash:
991ce4200c635c8f6a8b00916f18916649048c8b2d1a1fe72f0c6d84c92cde40

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.