Commit 929e549a authored by vertighel's avatar vertighel
Browse files

status page update

parent 6f20521c
Loading
Loading
Loading
Loading
Loading
+73 −0
Original line number Diff line number Diff line
@@ -327,3 +327,76 @@
{% macro monitor_val_template() %}
<span class="val-node"></span>
{% endmacro %}

{# --- WIDGET: Telemetry Card with Dynamic Fields --- #}
{% macro widget_telemetry_card(label, fields) %}
{% set safe_id = label | replace(' ', '-') | lower %}
<div class="col-md-6" id="container-{{ safe_id }}">
    <div class="card h-100 border-secondary shadow-sm">
        <div class="card-header bg-dark d-flex justify-content-between align-items-center">
            <span class="text-capitalize fw-bold">{{ label }}</span>
            <small class="text-muted timer">just now</small>
        </div>
        <div class="card-body p-0">
            <!-- Raw JSON pre-renderer -->
            <pre class="p-3 m-0 text-light bg-black d-none" id="data-{{ safe_id }}" style="font-size: 0.75rem; white-space: pre-wrap;"></pre>
            
            <div id="pretty-{{ safe_id }}" class="pretty-container">
                <table class="table table-dark mb-0 table-sm align-middle" style="font-size: 0.9rem;">
                    <thead>
                        <tr class="text-muted small border-bottom border-secondary">
                            <th class="ps-3" style="width: 35%;">parameter</th>
                            <th style="width: 50%;">status</th>
                            <th style="width: 15%;" class="text-center">err</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for field in fields %}
                        <tr class="border-bottom border-secondary border-opacity-25">
                            <td class="ps-3 fw-semibold py-2">{{ field.label }}</td>
                            <td class="py-2">
                                {% if field.subfields %}
                                    <!-- Nested Object: render subtable key-value -->
                                    <table class="table table-sm table-borderless m-0 p-0" style="background: transparent;">
                                        <tbody>
                                            {% for sub in field.subfields %}
                                            <tr>
                                                <td class="text-muted p-0 pe-2 small" style="width: 35%;">{{ sub.label }}</td>
                                                <td class="p-0">
                                                    {% if sub.is_array2 %}
                                                        <div class="d-flex gap-3 font-monospace">
                                                            <span data-status="{{ safe_id }}-{{ field.endpoint }}-{{ sub.key }}-0">N/A</span>
                                                            <span data-status="{{ safe_id }}-{{ field.endpoint }}-{{ sub.key }}-1">N/A</span>
                                                        </div>
                                                    {% else %}
                                                        <span data-status="{{ safe_id }}-{{ field.endpoint }}-{{ sub.key }}">N/A</span>
                                                    {% endif %}
                                                </td>
                                            </tr>
                                            {% endfor %}
                                        </tbody>
                                    </table>
                                {% else %}
                                    <!-- Flat Field -->
                                    {% if field.is_array2 %}
                                        <div class="d-flex gap-3 font-monospace">
                                            <span data-status="{{ safe_id }}-{{ field.endpoint }}-0">N/A</span>
                                            <span data-status="{{ safe_id }}-{{ field.endpoint }}-1">N/A</span>
                                        </div>
                                    {% else %}
                                        <span class="badge bg-secondary" data-status="{{ safe_id }}-{{ field.endpoint }}">N/A</span>
                                    {% endif %}
                                {% endif %}
                            </td>
                            <td class="text-center py-2">
                                <span class="badge bg-success" data-status="{{ safe_id }}-{{ field.endpoint }}-error">OK</span>
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>
{% endmacro %}
+6 −6
Original line number Diff line number Diff line
@@ -21,15 +21,15 @@
    </div>
</div>

<!-- Ordered containers -->
<!-- Dynamic monitor cards populated automatically from WebSocket payload -->
<div id="status-container" class="row g-3">
    {{ w.widget_monitor(label="dome", safe_id="dome") }}
    {{ w.widget_monitor(label="stage", safe_id="stage") }}
    {{ w.widget_monitor(label="camera", safe_id="camera") }}
    {{ w.widget_monitor(label="telescope", safe_id="telescope") }}
    {{ w.widget_monitor("dome", "dome") }}
    {{ w.widget_monitor("stage", "stage") }}
    {{ w.widget_monitor("camera", "camera") }}
    {{ w.widget_monitor("telescope", "telescope") }}
</div>

<!-- Blueprints for dynamic parts -->
<!-- Blueprints for dynamic parts (Cloned by JS, no HTML strings in JS) -->
<template id="table-blueprint">{{ w.monitor_table_template() }}</template>
<template id="row-blueprint">{{ w.monitor_row_template() }}</template>
<template id="subtable-blueprint">{{ w.monitor_subtable_template() }}</template>
+19 −19
Original line number Diff line number Diff line
// status-view.js
// Renders subsystem tables dynamically, splitting 2-element arrays into two lines and objects into structured sub-tables.
// Dynamic telemetry update loop. Clones HTML blueprints and maps keys as labels.

document.addEventListener('DOMContentLoaded', () => {
    const tableBlueprint = document.getElementById('table-blueprint');
@@ -10,7 +10,7 @@ document.addEventListener('DOMContentLoaded', () => {
    const globalRawCheckbox = document.getElementById('global-raw');
    const globalForceCheckbox = document.getElementById('global-force');

    // Central state cache to remember previous telemetry values for change detection
    // Central state cache to remember previous telemetry values for change-detection pulsing
    const previousState = {};


@@ -72,18 +72,18 @@ document.addEventListener('DOMContentLoaded', () => {

        const showRaw = globalRawCheckbox ? globalRawCheckbox.checked : false;

        // Initialize state cache for the subsystem if missing
        if (!previousState[subsystem]) {
            previousState[subsystem] = {};
        }

        // Render raw JSON display
        if (dataPre) {
            dataPre.textContent = JSON.stringify(data, null, 4);
            dataPre.classList.toggle('d-none', !showRaw);
        }

        // 1. One-time table DOM assembly (builds the structure once)
        // Initialize state cache for the subsystem if missing
        if (!previousState[subsystem]) {
            previousState[subsystem] = {};
        }

        // 1. One-time table DOM assembly (only runs on first websocket message for this subsystem)
        if (prettyDiv && prettyDiv.children.length === 0 && tableBlueprint && rowBlueprint) {
            prettyDiv.classList.toggle('d-none', showRaw);

@@ -92,7 +92,9 @@ document.addEventListener('DOMContentLoaded', () => {

            Object.entries(data).forEach(([deviceKey, val]) => {
                const rowClone = rowBlueprint.content.cloneNode(true);
                rowClone.querySelector('.device-name').textContent = deviceKey.replace('_', ' ');
                
                // NO EXPLICIT LABELS: Automatically use the JSON key as label, replacing underscores
                rowClone.querySelector('.device-name').textContent = deviceKey.replace(/_/g, ' ');

                const statusTd = rowClone.querySelector('.device-status');
                const errTd = rowClone.querySelector('.device-err');
@@ -112,7 +114,8 @@ document.addEventListener('DOMContentLoaded', () => {

                        Object.entries(responseValue).forEach(([k, v]) => {
                            const subrowClone = subrowBlueprint.content.cloneNode(true);
                            subrowClone.querySelector('.key-cell').textContent = k.replace('_', ' ');
                            // Use sub-key as label
                            subrowClone.querySelector('.key-cell').textContent = k.replace(/_/g, ' ');
                            
                            // Bind nested status attribute
                            const valCell = subrowClone.querySelector('.val-cell');
@@ -149,13 +152,13 @@ document.addEventListener('DOMContentLoaded', () => {
            let finalValue = 'N/A';

            // Special case: update error badge cell
            if (subProperty === 'error') {
            if (statusKey.endsWith('-error')) {
                const errors = endpointData.error;
                const hasErrors = errors && errors.length > 0;
                const displayValue = hasErrors ? 'ERR' : 'OK';

                const prevValue = el.textContent;
                const hasChanged = prevValue !== '' && prevValue !== 'N/A' && prevValue !== displayValue;
                const hasChanged = prevValue !== '' && prevValue !== displayValue;

                if (hasChanged) {
                    el.classList.add('pulse-update');
@@ -164,11 +167,8 @@ document.addEventListener('DOMContentLoaded', () => {

                el.textContent = displayValue;
                el.className = `badge ${hasErrors ? 'bg-danger' : 'bg-success'}`;
                if (hasErrors) {
                    el.setAttribute('title', errors.join(', '));
                } else {
                    el.removeAttribute('title');
                }
                if (hasErrors) el.setAttribute('title', errors.join(', '));
                else el.removeAttribute('title');
                return;
            }

@@ -189,12 +189,12 @@ document.addEventListener('DOMContentLoaded', () => {
                finalValue = 'N/A';
            }

            // Dynamic layout generation: if value is a 2-element array, split on two lines
            // Dynamic layout: if value is a 2-element array, show side-by-side with a gap
            let displayHTML = '';
            const isArray2 = Array.isArray(finalValue) && finalValue.length === 2;

            if (isArray2) {
                displayHTML = `<div class="small fw-bold">${finalValue[0]}</div><div class="small fw-bold">${finalValue[1]}</div>`;
                displayHTML = `<div class="d-flex gap-3 font-monospace"><span>${finalValue[0]}</span><span>${finalValue[1]}</span></div>`;
            } else {
                const displayValue = typeof finalValue === 'object' ? JSON.stringify(finalValue) : finalValue;
                displayHTML = displayValue.toString();