<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ShardMaster Orchestrator</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body class="bg-slate-900 text-slate-100 font-sans">
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
const App = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [activeOp, setActiveOp] = useState(null);
// REEMPLAZA ESTA URL CON LA DE TU WORKER ORQUESTRADOR
const WORKER_URL = "https://worker-orchestrator.rodrimm3006.workers.dev/";
const runOperation = async (op) => {
setLoading(true);
setActiveOp(op);
try {
const response = await fetch(WORKER_URL, {
method: 'POST',
body: JSON.stringify({ operation: op, params: {} })
});
const result = await response.json();
setData(result);
} catch (err) {
alert("Error conectando con el Orquestador");
}
setLoading(false);
};
return (
<div class="max-w-6xl mx-auto p-6">
<header class="flex justify-between items-center mb-10 border-b border-slate-700 pb-6">
<div>
<h1 class="text-3xl font-bold bg-gradient-to-r from-blue-400 to-emerald-400 bg-clip-text text-transparent">
<i class="fas fa-network-wired mr-3"></i>D1 Orchestrator MVP
</h1>
<p class="text-slate-400 mt-1">Gestión de base de datos distribuida en el Edge</p>
</div>
<div class="flex gap-3">
<span class="px-3 py-1 bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 rounded-full text-sm">
<i class="fas fa-circle text-[10px] mr-2 animate-pulse"></i> Shard A: Online
</span>
<span class="px-3 py-1 bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 rounded-full text-sm">
<i class="fas fa-circle text-[10px] mr-2 animate-pulse"></i> Shard B: Online
</span>
</div>
</header>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<button onClick={() => runOperation('COUNT_ALL')} class="p-6 bg-slate-800 rounded-xl border border-slate-700 hover:border-blue-500 transition-all text-left group">
<i class="fas fa-database text-blue-400 text-2xl mb-4"></i>
<h3 class="font-bold text-lg">Global Count</h3>
<p class="text-slate-400 text-sm">Agrega el conteo total de todos los Shards en tiempo real.</p>
</button>
<button onClick={() => runOperation('GLOBAL_TOP_10')} class="p-6 bg-slate-800 rounded-xl border border-slate-700 hover:border-emerald-500 transition-all text-left group">
<i class="fas fa-trophy text-emerald-400 text-2xl mb-4"></i>
<h3 class="font-bold text-lg">Top Leaders</h3>
<p class="text-slate-400 text-sm">Merge-Sort distribuido para obtener el Top 10 global.</p>
</button>
<button onClick={() => runOperation('VIRTUAL_JOIN')} class="p-6 bg-slate-800 rounded-xl border border-slate-700 hover:border-purple-500 transition-all text-left group">
<i class="fas fa-link text-purple-400 text-2xl mb-4"></i>
<h3 class="font-bold text-lg">Virtual Join</h3>
<p class="text-slate-400 text-sm">Cruce de tablas entre Shards físicamente aislados.</p>
</button>
</div>
{loading && (
<div class="flex justify-center my-20">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
)}
{data && !loading && (
<div class="bg-slate-800 rounded-2xl border border-slate-700 overflow-hidden animate-fadeIn">
<div class="p-4 bg-slate-700/50 flex justify-between items-center">
<span class="text-sm font-mono text-slate-300">Resultado: {activeOp}</span>
<span class="text-xs bg-slate-900 px-2 py-1 rounded text-blue-400">Latencia: {data.duration || 'N/A'}</span>
</div>
<div class="p-6">
{activeOp === 'COUNT_ALL' ? (
<div class="text-center py-10">
<div class="text-6xl font-black text-white mb-2">{data.total_rows.toLocaleString()}</div>
<div class="text-slate-400 uppercase tracking-widest text-sm">Registros totales en el cluster</div>
</div>
) : (
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead>
<tr class="text-slate-500 border-b border-slate-700">
<th class="pb-3 font-medium">ID / Nombre</th>
<th class="pb-3 font-medium">Región</th>
<th class="pb-3 font-medium text-right">Detalles / Logs</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-700">
{(data.data || []).map((item, idx) => (
<tr key={idx} class="hover:bg-white/5 transition-colors">
<td class="py-4">
<div class="font-bold">{item.name}</div>
<div class="text-xs text-slate-500">ID: {item.id}</div>
</td>
<td class="py-4">
<span class="px-2 py-1 bg-slate-900 rounded text-xs border border-slate-700 italic">
{item.region}
</span>
</td>
<td class="py-4 text-right">
{item.score && <span class="text-emerald-400 font-mono">{item.score.toLocaleString()} pts</span>}
{item.activity_history && item.activity_history.map((log, lidx) => (
<div key={lidx} class="text-[10px] text-purple-300 italic">{log.action}</div>
))}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
)}
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>