{ "html": "
\n
\n Data Lake\n \n 0 vars\n connecting...\n
\n
\n \n \n \n \n \n \n \n \n \n
VariableTypeValue
\n
No variables match the filter.
\n
\n
", "css": "#w0-wrap { box-sizing: border-box; background: #09111a; color: #d8eeff; font-family: 'Courier New', monospace; font-size: 12px; height: 100%; display: flex; flex-direction: column; overflow: hidden; }\n#w0-wrap *, #w0-wrap *::before, #w0-wrap *::after { box-sizing: border-box; margin: 0; padding: 0; }\n#header { background: #162336; border-bottom: 1px solid #1e3050; padding: 5px 8px; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }\n#header .title { font-size: 11px; font-weight: bold; color: #00c8f0; white-space: nowrap; }\n#search { flex: 1; background: #09111a; border: 1px solid #1e3050; border-radius: 3px; color: #d8eeff; font-family: 'Courier New', monospace; font-size: 11px; padding: 2px 6px; outline: none; }\n#search:focus { border-color: #00c8f0; }\n#count { font-size: 10px; color: #6a9bbf; white-space: nowrap; }\n#status { font-size: 10px; color: #ffb830; white-space: nowrap; }\n#status.ok { color: #00e09a; }\n#status.err { color: #ff3a5a; }\n#table-wrap { flex: 1; overflow-y: auto; overflow-x: hidden; }\n#table-wrap::-webkit-scrollbar { width: 5px; }\n#table-wrap::-webkit-scrollbar-track { background: #09111a; }\n#table-wrap::-webkit-scrollbar-thumb { background: #1e3050; border-radius: 2px; }\ntable { width: 100%; border-collapse: collapse; }\nthead th { position: sticky; top: 0; background: #162336; border-bottom: 1px solid #1e3050; color: #6a9bbf; font-size: 10px; font-weight: normal; padding: 3px 6px; text-align: left; text-transform: uppercase; letter-spacing: 0.05em; z-index: 1; }\nthead th:nth-child(1) { width: 55%; }\nthead th:nth-child(2) { width: 12%; }\nthead th:nth-child(3) { width: 33%; }\ntbody tr { border-bottom: 1px solid #1e3050; }\ntbody tr:hover { background: #162336; }\ntbody tr.flash td { color: #00e09a; }\ntd { padding: 3px 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\ntd:nth-child(1) { color: #00c8f0; font-size: 11px; }\ntd:nth-child(2) { color: #6a9bbf; font-size: 10px; }\ntd:nth-child(3) { color: #d8eeff; font-size: 11px; }\n#empty { padding: 20px; text-align: center; color: #6a9bbf; font-size: 11px; }", "js": "// State\nvar vars = {};\nvar filter = '';\n\n// DOM refs\nvar tbody = document.getElementById('tbody');\nvar countEl = document.getElementById('count');\nvar statusEl = document.getElementById('status');\nvar searchEl = document.getElementById('search');\nvar emptyEl = document.getElementById('empty');\n\n// Format a value for display in the table\nfunction fmt(v) {\n if (v === null || v === undefined) return '--';\n if (typeof v === 'number') return Number.isInteger(v) ? String(v) : v.toFixed(4);\n if (typeof v === 'boolean') return v ? 'true' : 'false';\n if (typeof v === 'object') return JSON.stringify(v).slice(0, 60);\n return String(v).slice(0, 80);\n}\n\n// Re-render the table rows based on current vars and filter\nfunction render() {\n var keys = Object.keys(vars).sort();\n var filtered = keys.filter(function(k) {\n return !filter || k.toLowerCase().indexOf(filter) !== -1;\n });\n\n // Show or hide the empty message\n emptyEl.style.display = filtered.length === 0 ? '' : 'none';\n countEl.textContent = filtered.length + ' / ' + keys.length + ' vars';\n\n // Build a map of existing rows so we can update in place\n var existingRows = {};\n var rowEls = tbody.querySelectorAll('tr[data-key]');\n for (var i = 0; i < rowEls.length; i++) {\n existingRows[rowEls[i].dataset.key] = rowEls[i];\n }\n\n // Remove rows that are no longer in the filtered set\n Object.keys(existingRows).forEach(function(k) {\n if (filtered.indexOf(k) === -1) {\n existingRows[k].remove();\n delete existingRows[k];\n }\n });\n\n // Add or update rows for each filtered variable\n filtered.forEach(function(key) {\n var d = vars[key];\n var row = existingRows[key];\n\n if (!row) {\n // Create a new row if it does not exist yet\n row = document.createElement('tr');\n row.dataset.key = key;\n row.innerHTML = '' + key + '';\n tbody.appendChild(row);\n }\n\n var cells = row.cells;\n var newType = d.type || '--';\n var newValue = fmt(d.value);\n\n // Flash the row green briefly when the value changes\n if (cells[2].textContent !== newValue) {\n row.classList.add('flash');\n setTimeout(function() { row.classList.remove('flash'); }, 400);\n }\n\n cells[1].textContent = newType;\n cells[2].textContent = newValue;\n });\n}\n\n// Poll the Cockpit data lake every 500ms\nfunction poll() {\n try {\n // Bail out gracefully if the Cockpit API is not yet available\n if (typeof window.cockpit === 'undefined' ||\n typeof window.cockpit.getAllDataLakeVariablesInfo !== 'function') {\n statusEl.className = 'err';\n statusEl.textContent = 'API not ready';\n return;\n }\n\n var info = window.cockpit.getAllDataLakeVariablesInfo();\n\n if (!info || Object.keys(info).length === 0) {\n statusEl.className = '';\n statusEl.textContent = 'no data';\n return;\n }\n\n // Merge new values into the local vars store\n Object.keys(info).forEach(function(key) {\n var meta = info[key];\n vars[key] = {\n type: (meta && meta.type) ? meta.type : typeof (meta && meta.value),\n value: (meta && meta.value !== undefined) ? meta.value : meta\n };\n });\n\n statusEl.className = 'ok';\n statusEl.textContent = 'live';\n render();\n\n } catch(err) {\n statusEl.className = 'err';\n statusEl.textContent = 'error';\n console.error('[DataLake]', err);\n }\n}\n\n// Filter input handler — re-render immediately on keystroke\nsearchEl.addEventListener('input', function() {\n filter = searchEl.value.trim().toLowerCase();\n render();\n});\n\n// Start polling\nsetTimeout(poll, 300);\nsetInterval(poll, 500);" }