Skip to content

Instantly share code, notes, and snippets.

@wd5gnr
Created November 15, 2025 20:05
Show Gist options
  • Select an option

  • Save wd5gnr/6ebe8becf6cec4b83195a8b330c2abd8 to your computer and use it in GitHub Desktop.

Select an option

Save wd5gnr/6ebe8becf6cec4b83195a8b330c2abd8 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- hint: Put this in a folder, edit the IP address and port
variable MOON and just above there, the webcam URL
Then in the same folder run (if you want to use 8085 as the port #):
python3 -m http.server 8085
Then in Orca, set your device tab to http://127.0.0.1:8085/mini-dash.html
Easy to launch this with systemd, in your profile, whatever
-->
<meta charset="UTF-8">
<title>AD5X Enhanced Dashboard</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #1d1d1d;
color: #f0f0f0;
margin: 0;
padding: 12px;
}
h1 { margin: 0 0 12px 0; font-size: 1.3rem; }
.grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 12px; }
.card {
background: #262626;
border-radius: 8px;
padding: 10px 12px;
box-shadow: 0 0 6px rgba(0,0,0,0.5);
}
.card h2 {
margin: 0 0 8px 0;
font-size: 1.05rem;
border-bottom: 1px solid #444;
padding-bottom: 4px;
}
.row { display: flex; justify-content: space-between; padding: 2px 0; }
.label { color: #aaa; }
.value { font-weight: 600; }
.small { font-size: 0.85rem; }
#webcam {
width: 100%;
max-height: 100%;
object-fit: contain;
background: #000;
border-radius: 6px;
}
.bar-container {
background: #333;
border-radius: 4px;
height: 10px;
margin-top: 4px;
overflow: hidden;
}
.bar {
height: 100%;
background: #4aa3ff;
width: 0%;
}
/* Graph styles */
.graph {
width: 100%;
height: 120px;
background: #111;
position: relative;
border-radius: 6px;
margin-top: 8px;
}
.graph svg {
width: 100%;
height: 100%;
}
.axis-label {
fill: #aaa;
font-size: 10px;
}
#console {
background: #111;
border: 1px solid #333;
padding: 6px;
height: 150px;
overflow-y: auto;
font-size: 0.85rem;
white-space: pre-wrap;
border-radius: 6px;
}
</style>
</head>
<body>
<h1>AD5X Enhanced Dashboard</h1>
<div class="grid">
<div>
<div class="card">
<h2>Printer State</h2>
<div class="row"><span class="label">State</span><span class="value" id="state-text">—</span></div>
<div class="row"><span class="label">Flags</span><span class="value small" id="state-flags">—</span></div>
<div class="row"><span class="label">ZMod</span><span class="value small" id="state-zmod">—</span></div>
</div>
<div class="card">
<h2>Temperatures & System</h2>
<div class="row"><span class="label">Nozzle</span><span class="value" id="temp-nozzle">—</span></div>
<div id="noz-graph" class="graph"></div>
<div class="row"><span class="label">Bed</span><span class="value" id="temp-bed">—</span></div>
<div id="bed-graph" class="graph"></div>
<div class="row"><span class="label">CPU Load</span><span class="value small" id="cpu-load-text">—</span></div>
<div id="cpu-graph" class="graph"></div>
<div class="row"><span class="label">Memory Free</span><span class="value small" id="mem-free">—</span></div>
</div>
<div class="card">
<h2>Job</h2>
<div class="row"><span class="label">File</span><span class="value small" id="job-file">—</span></div>
<div class="row"><span class="label">State</span><span class="value" id="job-state">—</span></div>
<div class="row"><span class="label">Progress</span><span class="value" id="job-progress">—</span></div>
<div class="bar-container"><div class="bar" id="job-bar"></div></div>
<div class="row"><span class="label">Elapsed</span><span class="value small" id="job-printtime">—</span></div>
<div class="row"><span class="label">Remaining</span><span class="value small" id="job-remaining">—</span></div>
<div class="row"><span class="label">Layers</span><span class="value small" id="job-layers">—</span></div>
<div class="bar-container"><div class="bar" id="layer-bar"></div></div>
<div class="row"><span class="label">Filament</span><span class="value small" id="job-filament">—</span></div>
<div class="row"><span class="label">Feedrate</span><span class="value small" id="job-feed">—</span></div>
<div class="row"><span class="label">Speed Factor</span><span class="value small" id="job-speedfactor">—</span></div>
</div>
<div class="card">
<h2>Toolhead</h2>
<div class="row"><span class="label">XYZ</span><span class="value small" id="toolhead-pos">—</span></div>
<div class="row"><span class="label">Homed</span><span class="value" id="toolhead-homed">—</span></div>
</div>
<div class="card">
<h2>Messages</h2>
<div id="console"></div>
</div>
</div>
<div class="card">
<h2>Webcam</h2>
<img id="webcam" src="http://192.168.1.70:8080/?action=stream">
</div>
</div>
<script>
const MOON = "http://192.168.1.70:7125";
function $(id){return document.getElementById(id);}
function fmtTime(sec){
if(!sec || sec<=0) return "—";
sec = Math.floor(sec);
const h=Math.floor(sec/3600), m=Math.floor((sec%3600)/60), s=sec%60;
return h>0 ? `${h}h ${m}m ${s}s` : `${m}m ${s}s`;
}
async function fetchJSON(url){
const r=await fetch(url,{cache:"no-store"});
if(!r.ok) throw new Error();
return r.json();
}
/* -------------------- GRAPHS -------------------- */
let nozData=[], bedData=[], cpuData=[];
const graphLimit=60;
function addGraphPoint(arr,v){
arr.push(v);
if(arr.length>graphLimit) arr.shift();
}
function drawGraph(arr, elem, labelMax="°C", labelMin="°C"){
const w = elem.clientWidth;
const h = elem.clientHeight;
if(arr.length<2){ elem.innerHTML=""; return; }
const max = Math.max(...arr);
const min = Math.min(...arr);
const range = Math.max(max-min,1);
const pts = arr.map((v,i)=>[
(i/(arr.length-1))*w,
h - ((v-min)/range)*h
]);
elem.innerHTML = `
<svg>
<text x="4" y="12" class="axis-label">${max.toFixed(0)}${labelMax}</text>
<text x="4" y="${h-4}" class="axis-label">${min.toFixed(0)}${labelMin}</text>
<text x="${w-40}" y="${h-4}" class="axis-label">60s</text>
<polyline fill="none" stroke="#4aa3ff" stroke-width="2"
points="${pts.map(p=>p.join(',')).join(' ')}" />
</svg>`;
}
/* -------------------- STATE -------------------- */
async function updateState() {
try {
// Printer info (basic)
const js = await fetchJSON(`${MOON}/printer/info`);
const r = js.result || {};
// Print stats (from previous update)
const ps = window._latestPrintStats || null;
// STATE text
if (ps && ps.state) {
$("state-text").textContent = ps.state;
} else {
$("state-text").textContent = r.state || "—";
}
// FLAGS
const flags = [];
if (ps) {
if (ps.state === "printing") flags.push("printing");
if (ps.state === "paused") flags.push("paused");
if (ps.state === "ready") flags.push("ready");
}
if (r.klippy_connected !== false) flags.push("connected");
$("state-flags").textContent = flags.length ? flags.join(", ") : "—";
// ZMod version
const js1 = await fetchJSON(`${MOON}/machine/system_info`);
const d1 = js1.result.system_info.distribution;
$("state-zmod").textContent =
d1 ? `${d1.name} (${d1.codename || "-"})` : "—";
} catch (e) {
$("state-text").textContent = "-";
$("state-flags").textContent = "-";
$("state-zmod").textContent = "-";
}
}
/* -------------------- TEMPS & CPU -------------------- */
async function updateTemps(){
try{
const js = await fetchJSON(`${MOON}/printer/objects/query?extruder&heater_bed&system_stats`);
const st = js.result.status;
/* Temps */
const noz=st.extruder.temperature;
const nozT=st.extruder.target;
const bed=st.heater_bed.temperature;
const bedT=st.heater_bed.target;
$("temp-nozzle").textContent = `${noz.toFixed(1)}°C (${nozT}°)`;
$("temp-bed").textContent = `${bed.toFixed(1)}°C (${bedT}°)`;
addGraphPoint(nozData,noz);
addGraphPoint(bedData,bed);
drawGraph(nozData,$("noz-graph"));
drawGraph(bedData,$("bed-graph"));
/* CPU Load */
const load = st.system_stats.sysload || 0;
$("cpu-load-text").textContent = load.toFixed(2);
addGraphPoint(cpuData,load);
drawGraph(cpuData,$("cpu-graph"),"", "");
/* Memory */
const avail = st.system_stats.memavail || 0;
$("mem-free").textContent = (avail/1024).toFixed(0)+" MB";
}catch(e){}
}
/* -------------------- JOB -------------------- */
async function updateJob(){
try{
const js = await fetchJSON(`${MOON}/printer/objects/query?print_stats&display_status&virtual_sdcard&gcode_move&toolhead`);
const st = js.result.status;
const ps = st.print_stats;
window._latestPrintStats=ps;
const ds = st.display_status;
const vsd = st.virtual_sdcard;
const gm = st.gcode_move;
const th = st.toolhead;
const info = ps.info||{};
const fname = ps.filename || (vsd.file_path? vsd.file_path.split("/").pop():"—");
$("job-file").textContent = fname;
$("job-state").textContent = ps.state||"—";
let pct = null;
if(ds.progress!=null) pct = ds.progress*100;
else if(vsd.progress!=null) pct = vsd.progress*100;
$("job-progress").textContent = pct!=null? pct.toFixed(1)+"%":"—";
if(pct!=null) $("job-bar").style.width = pct+"%";
$("job-printtime").textContent = fmtTime(ps.print_duration);
let remaining = null;
if(pct>0) remaining = ps.print_duration*(100/pct - 1);
$("job-remaining").textContent = remaining? fmtTime(remaining):"—";
const cl=info.current_layer, tl=info.total_layer;
if(cl!=null && tl!=null){
$("job-layers").textContent = `${cl} / ${tl}`;
$("layer-bar").style.width = (cl/tl)*100+"%";
}else $("job-layers").textContent="—";
$("job-filament").textContent =
ps.filament_used? ps.filament_used.toFixed(1)+"mm":"—";
$("job-feed").textContent =
gm.speed ? (gm.speed/60).toFixed(1)+" mm/s" : "—";
$("job-speedfactor").textContent =
gm.speed_factor? gm.speed_factor.toFixed(2)+"×":"—";
const pos = th.position||[];
$("toolhead-pos").textContent =
pos.length>=3?`${pos[0].toFixed(2)}, ${pos[1].toFixed(2)}, ${pos[2].toFixed(2)}`:"—";
$("toolhead-homed").textContent = th.homed_axes||"—";
}catch(e){}
}
/* -------------------- CONSOLE -------------------- */
let lastMessage="";
async function updateConsole(){
try{
const js = await fetchJSON(`${MOON}/printer/objects/query?display_status`);
const msg = js.result.status.display_status.message;
if(msg && msg!==lastMessage){
lastMessage=msg;
const c=$("console");
c.textContent+=msg+"\n";
c.scrollTop=c.scrollHeight;
}
}catch(e){}
}
/* -------------------- LOOP -------------------- */
setInterval(updateState, 3000);
setInterval(updateTemps, 1000);
setInterval(updateJob, 1200);
setInterval(updateConsole, 1500);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment