nomyo-router/static/index.html
alpha-nerd-nomyo 2f09dbe22c
Add files via upload
adding dashboard copy link
adding copy get route for dashboard
2025-09-04 10:39:10 +02:00

195 lines
No EOL
5.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>NOMYO Router Dashboard</title>
<style>
body{font-family:Arial,Helvetica,sans-serif;background:#f7f7f7;color:#333;padding:20px;}
h1{margin-top:0;}
table{border-collapse:collapse;width:100%;margin-bottom:20px;}
th,td{border:1px solid #ddd;padding:8px;}
th{background:#333;color:#fff;}
tr:nth-child(even){background:#f2f2f2;}
.endpoint{font-weight:bold;}
.model{font-family:monospace;}
.loading{color:#999;}
/* NEW STYLES */
.tables-wrapper{
display:flex;
gap:1rem;
margin-top:1rem;
}
.table-container{
width:50%;
}
/* Ensure the heading aligns nicely inside each container */
.table-container h2{
margin:0 0 0.5rem 0;
}
/* ---- NEW STYLES FOR PORTRAIT (height > width) ---- */
@media (orientation: portrait) {
/* Stack the two tables vertically */
.tables-wrapper {
flex-direction: column; /* instead of the default row */
}
.table-container {
width: 100%; /* full width when stacked */
}
/* Put the “Running Models” table first */
.table-container:nth-child(2) { /* the PS table is the 2nd child */
order: -1;
}
/* Keep the other table after it (default order 0) */
.table-container:nth-child(1) {
order: 0;
}
}
/* Add a tiny statusstyle section */
.status-ok { color: #006400; font-weight: bold; } /* dark green */
.status-error{ color: #8B0000; font-weight: bold; } /* dark red */
.copy-link {
font-size:0.9em;
margin-left:0.5em;
color:#0066cc;
cursor:pointer;
text-decoration:underline;
}
.copy-link:hover { text-decoration:none; }
</style>
</head>
<body>
<a href="https://www.nomyo.ai" target="_blank"><img src="./static/228394408.png" width="100px" height="100px"></a><h1>Router Dashboard</h1>
<div class="tables-wrapper">
<div class="table-container">
<h2><span id="tags-count"></span> Available Models (Tags)</h2>
<table id="tags-table">
<thead><tr><th>Model</th><th>Digest</th></tr></thead>
<tbody id="tags-body">
<tr><td colspan="2" class="loading">Loading…</td></tr>
</tbody>
</table>
</div>
<div class="table-container">
<h2>Running Models (PS)</h2>
<table id="ps-table">
<thead>
<tr>
<th>Model</th>
<th>Params</th>
<th>Quant</th>
<th>Ctx</th>
<th>Digest</th>
</tr>
</thead>
<tbody id="ps-body">
<tr><td colspan="2" class="loading">Loading…</td></tr>
</tbody>
</table>
</div>
</div>
<h2>Configured Endpoints</h2>
<table id="endpoints-table">
<thead>
<tr>
<th>Endpoint</th>
<th>Status</th>
<th>Version</th>
</tr>
</thead>
<tbody id="endpoints-body">
<tr><td colspan="3" class="loading">Loading…</td></tr>
</tbody>
</table>
<script>
async function fetchJSON(url){
const resp = await fetch(url);
if(!resp.ok){ throw new Error(`Failed ${url}: ${resp.status}`); }
return await resp.json();
}
async function loadEndpoints(){
try{
const data = await fetchJSON('/api/config');
const body = document.getElementById('endpoints-body');
// Map each endpoint object to a table row
body.innerHTML = data.endpoints.map(e => {
const statusClass = e.status === 'ok' ? 'status-ok' : 'status-error';
const version = e.version || 'N/A';
return `
<tr>
<td class="endpoint">${e.url}</td>
<td class="status ${statusClass}">${e.status}</td>
<td class="version">${version}</td>
</tr>`;
}).join('');
}catch(e){
console.error(e);
const body = document.getElementById('endpoints-body');
body.innerHTML = `<tr><td colspan="3" class="loading">Failed to load endpoints</td></tr>`;
}
}
async function loadTags(){
try{
const data = await fetchJSON('/api/tags');
const body = document.getElementById('tags-body');
body.innerHTML = data.models.map(m => {
// Build the model cell
let modelCell = `${m.id || m.name}`;
// Add the copy link *only if a digest exists*
if (m.digest) {
modelCell += `
<a href="#" class="copy-link" data-source="${m.name}">
copy
</a>`;
}
return `
<tr>
<td class="model">${modelCell}</td>
<td>${m.digest || ''}</td>
</tr>`;
}).join(''); const countSpan = document.getElementById('tags-count');
countSpan.textContent = `${data.models.length}`;
// Attach copylink handlers
document.querySelectorAll('.copy-link').forEach(link => {
link.addEventListener('click', async (e) => {
e.preventDefault();
const source = link.dataset.source;
const dest = prompt(`Enter destination for ${source}:`);
if (!dest) return; // cancel if empty
try{
const resp = await fetch(`/api/copy?source=${encodeURIComponent(source)}&destination=${encodeURIComponent(dest)}`);
if (!resp.ok) throw new Error(`Copy failed: ${resp.status}`);
alert(`Copied ${source} to ${dest} successfully.`);
}catch(err){
console.error(err);
alert(`Error copying ${source} to ${dest}: ${err}`);
}
});
});
}catch(e){ console.error(e); }
}
async function loadPS(){
try{
const data = await fetchJSON('/api/ps');
const body = document.getElementById('ps-body');
body.innerHTML = data.models.map(m=>`<tr><td class="model">${m.name}</td><td>${m.details.parameter_size}</td><td>${m.details.quantization_level}</td><td>${m.context_length}</td><td>${m.digest}</td></tr>`).join('');
}catch(e){ console.error(e); }
}
window.addEventListener('load', ()=>{
loadEndpoints();
loadTags();
loadPS();
});
</script>
</body>
</html>