node metrics
This commit is contained in:
@@ -44,12 +44,15 @@
|
||||
.empty { color:var(--dim); font-style:italic; }
|
||||
.pill { display:inline-block; padding:0 7px; border-radius:9px;
|
||||
border:1px solid var(--border); font-size:11px; }
|
||||
input, button { font:inherit; color:var(--fg); background:var(--bg);
|
||||
input, button, select { font:inherit; color:var(--fg); background:var(--bg);
|
||||
border:1px solid var(--border); border-radius:6px; padding:5px 8px; }
|
||||
input { width:100%; margin-bottom:6px; }
|
||||
button { cursor:pointer; color:var(--accent); }
|
||||
button:hover { border-color:var(--accent); }
|
||||
button.small { font-size:11px; padding:1px 7px; }
|
||||
dialog { color:var(--fg); background:var(--panel); border:1px solid var(--border); border-radius:8px; min-width:min(420px,calc(100vw - 32px)); }
|
||||
dialog::backdrop { background:rgba(0,0,0,.55); }
|
||||
.placement-dialog-actions { display:flex; justify-content:flex-end; gap:8px; margin-top:12px; }
|
||||
.form-row { display:flex; gap:8px; }
|
||||
.form-row button { white-space:nowrap; }
|
||||
.error-msg { color:var(--bad); font-size:12px; min-height:16px; }
|
||||
@@ -324,6 +327,14 @@
|
||||
<div id="testing-log" class="console empty">no test output yet</div>
|
||||
</section>
|
||||
</main>
|
||||
<dialog id="model-placement-dialog">
|
||||
<form method="dialog">
|
||||
<div id="model-placement-dialog-title"></div>
|
||||
<label for="model-placement-node">Node</label>
|
||||
<select id="model-placement-node"></select>
|
||||
<div class="placement-dialog-actions"><button value="cancel">Cancel</button><button value="confirm">Confirm</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
<script>
|
||||
"use strict";
|
||||
const $ = id => document.getElementById(id);
|
||||
@@ -1801,16 +1812,16 @@ async function requestSelectedModelLoad() {
|
||||
$("chat-status").textContent = `load queued on ${short(assignment.node_id || "node")} for layers ${assignment.shard_start}-${assignment.shard_end}`;
|
||||
}
|
||||
|
||||
async function requestAdminModelLoad(model) {
|
||||
const result = await apiCall("/v1/models/load", "POST", { model, force: true });
|
||||
async function requestAdminModelLoad(model, nodeId) {
|
||||
const result = await apiCall("/v1/models/load", "POST", { model, node_id: nodeId, force: true });
|
||||
if (!result.ok) return showAdminModelPlacementStatus(result.data.error || "model load request failed", true);
|
||||
const assignment = result.data.assignment || {};
|
||||
showAdminModelPlacementStatus(`Load queued on ${short(assignment.node_id || "node")} for ${model}.`);
|
||||
await refreshActiveTab(true);
|
||||
}
|
||||
|
||||
async function releaseAdminModel(model) {
|
||||
const result = await apiCall("/v1/models/release", "POST", { model });
|
||||
async function releaseAdminModel(model, nodeId) {
|
||||
const result = await apiCall("/v1/models/release", "POST", { model, node_id: nodeId });
|
||||
if (!result.ok) return showAdminModelPlacementStatus(result.data.error || "model release request failed", true);
|
||||
showAdminModelPlacementStatus(`Release queued for ${result.data.released || 0} node(s) serving ${model}.`);
|
||||
await refreshActiveTab(true);
|
||||
@@ -1836,6 +1847,9 @@ function renderAdminNodePool(map) {
|
||||
html += table(["node", "assignment", "state / slots", "RAM", "GPU / VRAM", "model drive"], nodes.map(node => {
|
||||
const hw = node.hardware_profile || {};
|
||||
const cap = node.capacity || {};
|
||||
// The network map keeps reported resource capacity under `capacity`.
|
||||
node.ram_bytes = cap.ram_bytes ?? node.ram_bytes;
|
||||
node.vram_bytes = cap.vram_bytes ?? node.vram_bytes;
|
||||
const disk = hw.model_drive_free_bytes ?? hw.model_path_free_bytes ?? hw.disk_free_bytes;
|
||||
const gpu = hw.gpu_name || (hw.cuda_available ? "CUDA GPU" : "CPU only");
|
||||
return [nodeDisplayCell(node), esc(node.hf_repo || node.model || "unassigned"),
|
||||
@@ -1866,10 +1880,26 @@ function renderAdminModelPlacement(models, map) {
|
||||
$("admin-model-placement").addEventListener("click", event => {
|
||||
const load = event.target.closest("[data-admin-model-load]");
|
||||
const release = event.target.closest("[data-admin-model-release]");
|
||||
if (load) void requestAdminModelLoad(load.dataset.adminModelLoad);
|
||||
if (release) void releaseAdminModel(release.dataset.adminModelRelease);
|
||||
if (load) void chooseModelPlacementNode("load", load.dataset.adminModelLoad);
|
||||
if (release) void chooseModelPlacementNode("release", release.dataset.adminModelRelease);
|
||||
});
|
||||
|
||||
function chooseModelPlacementNode(action, model) {
|
||||
const dialog = $("model-placement-dialog");
|
||||
const select = $("model-placement-node");
|
||||
const aliases = new Set([model].filter(Boolean));
|
||||
const nodes = (lastNetworkMap?.nodes || []).filter(node => action === "load" || aliases.has(node.model) || aliases.has(node.hf_repo));
|
||||
if (!nodes.length) return showAdminModelPlacementStatus(`No node can ${action} ${model}.`, true);
|
||||
$("model-placement-dialog-title").textContent = `${action === "load" ? "Load" : "Release"} ${model} on a node`;
|
||||
select.innerHTML = nodes.map(node => `<option value="${esc(node.node_id)}">${esc(short(node.friendly_name || node.node_id, 20))} — ${esc(node.hf_repo || node.model || "unassigned")}</option>`).join("");
|
||||
dialog.onclose = () => {
|
||||
if (dialog.returnValue !== "confirm") return;
|
||||
if (action === "load") void requestAdminModelLoad(model, select.value);
|
||||
else void releaseAdminModel(model, select.value);
|
||||
};
|
||||
dialog.showModal();
|
||||
}
|
||||
|
||||
function chatAuthToken() {
|
||||
if (accountApiKeys.length) return accountApiKeys[0];
|
||||
return null;
|
||||
@@ -2512,6 +2542,7 @@ async function fetchAdminTab() {
|
||||
if (isAdmin) fetches.push(apiCall("/v1/admin/accounts"));
|
||||
const results = await Promise.all(fetches);
|
||||
const [raft, consoleData, summary, wallets, models, map, adminResp] = results;
|
||||
if (map) lastNetworkMap = map;
|
||||
renderIfChanged("hive", raft, renderHive);
|
||||
renderIfChanged("console", consoleData, renderConsole);
|
||||
renderIfChanged("billing-summary", summary, data => renderBilling(data));
|
||||
|
||||
Reference in New Issue
Block a user