Dashboard visual fixes round 3: CPU bars, donut, vertical bars, Docker text
- Fix CPU bars: specificity override (#displaybox table.dashboard) for thin 10px color-coded bars (green/orange/red) filling full card width - Fix CPU chart: force full-width display on #cpu_chart td and canvas - Fix Docker text: force white color on all #docker_view spans and links - Fix System donut: remove saturate/brightness filter, thinner ring (78%), fully opaque center background - Add vertical pill bars for Array/Cache/Unassigned disk utilization with JS module to convert horizontal bars to 28x50px vertical pills with % text inside, MutationObserver for dynamic updates - Add legend color JS module to fix invisible System card legend circles with inline !important colors matching recolored pie segments Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
247
sidebar.js
247
sidebar.js
@@ -38,10 +38,35 @@
|
||||
menu.appendChild(btn);
|
||||
}
|
||||
|
||||
// --- Lock button icon toggle (locked vs unlocked) ---
|
||||
initLockButton();
|
||||
|
||||
// --- Search overlay (delayed to wait for search plugin init) ---
|
||||
setTimeout(initSearchOverlay, 800);
|
||||
}
|
||||
|
||||
function initLockButton() {
|
||||
var lockItem = document.querySelector('.nav-item.LockButton');
|
||||
if (!lockItem) return;
|
||||
var lockLink = lockItem.querySelector('a');
|
||||
if (!lockLink) return;
|
||||
|
||||
// Set initial state based on title
|
||||
function updateLockState() {
|
||||
var title = lockLink.getAttribute('title') || '';
|
||||
if (title.indexOf('Unlock') === 0) {
|
||||
lockItem.classList.add('is-locked');
|
||||
} else {
|
||||
lockItem.classList.remove('is-locked');
|
||||
}
|
||||
}
|
||||
updateLockState();
|
||||
|
||||
// Watch for title attribute changes (Unraid toggles this on click)
|
||||
var observer = new MutationObserver(function() { updateLockState(); });
|
||||
observer.observe(lockLink, { attributes: true, attributeFilter: ['title'] });
|
||||
}
|
||||
|
||||
function initSearchOverlay() {
|
||||
if (!window.jQuery) return;
|
||||
|
||||
@@ -319,12 +344,27 @@
|
||||
observer.observe(target, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
// Replace Tailscale icon on Dashboard and Settings pages
|
||||
function replaceTailscaleIcon() {
|
||||
var TAILSCALE_SVG = CDN_SVG + '/tailscale-light.svg';
|
||||
// Dashboard: img with alt="Tailscale" inside a card header
|
||||
var imgs = document.querySelectorAll('img[alt="Tailscale"], img[src*="tailscale" i]');
|
||||
imgs.forEach(function(img) {
|
||||
if (img.src !== TAILSCALE_SVG && img.getAttribute('src') !== TAILSCALE_SVG) {
|
||||
img.src = TAILSCALE_SVG;
|
||||
img.removeAttribute('onerror');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
function initIcons() {
|
||||
// Initial replacement
|
||||
setTimeout(replaceIcons, 500);
|
||||
setTimeout(replaceTailscaleIcon, 500);
|
||||
// Second pass after FolderView finishes its DOM work
|
||||
setTimeout(replaceIcons, 2000);
|
||||
setTimeout(replaceTailscaleIcon, 2000);
|
||||
// Start watching for future changes
|
||||
startObserver();
|
||||
}
|
||||
@@ -335,3 +375,210 @@
|
||||
initIcons();
|
||||
}
|
||||
})();
|
||||
|
||||
// =============================================================
|
||||
// Pie Chart Recoloring — vibrant theme colors for System card
|
||||
// =============================================================
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Map Unraid's muted conic-gradient colors → vibrant theme colors
|
||||
var COLOR_MAP = [
|
||||
// gray-blue used portions → theme accent
|
||||
[/rgb\(96,\s*110,\s*127\)/g, 'rgb(126, 184, 218)'],
|
||||
// near-black unused → subtle light
|
||||
[/rgb\(35,\s*37,\s*35\)/g, 'rgba(255, 255, 255, 0.08)'],
|
||||
// muted orange → vibrant orange
|
||||
[/rgb\(215,\s*126,\s*13\)/g, 'rgb(245, 166, 35)'],
|
||||
// muted yellow → vibrant yellow
|
||||
[/rgb\(212,\s*172,\s*13\)/g, 'rgb(241, 196, 15)'],
|
||||
// muted red → vibrant red-coral
|
||||
[/rgb\(205,\s*92,\s*92\)/g, 'rgb(231, 76, 60)'],
|
||||
];
|
||||
|
||||
function recolorPie(pie) {
|
||||
var style = pie.getAttribute('style');
|
||||
if (!style || style.indexOf('conic-gradient') === -1) return;
|
||||
|
||||
var newStyle = style;
|
||||
for (var i = 0; i < COLOR_MAP.length; i++) {
|
||||
newStyle = newStyle.replace(COLOR_MAP[i][0], COLOR_MAP[i][1]);
|
||||
}
|
||||
|
||||
if (newStyle !== style) {
|
||||
pie.setAttribute('style', newStyle);
|
||||
}
|
||||
}
|
||||
|
||||
function recolorAll() {
|
||||
var pies = document.querySelectorAll('div.pie');
|
||||
pies.forEach(recolorPie);
|
||||
}
|
||||
|
||||
function startPieObserver() {
|
||||
var pies = document.querySelectorAll('div.pie');
|
||||
if (pies.length === 0) return;
|
||||
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
if (mutations[i].attributeName === 'style') {
|
||||
recolorPie(mutations[i].target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pies.forEach(function(pie) {
|
||||
recolorPie(pie);
|
||||
observer.observe(pie, { attributes: true, attributeFilter: ['style'] });
|
||||
});
|
||||
}
|
||||
|
||||
function initPieCharts() {
|
||||
setTimeout(recolorAll, 1000);
|
||||
setTimeout(startPieObserver, 2000);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initPieCharts);
|
||||
} else {
|
||||
initPieCharts();
|
||||
}
|
||||
})();
|
||||
|
||||
// =============================================================
|
||||
// Vertical Pill Bars — Array/Cache/Unassigned disk utilization
|
||||
// =============================================================
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function convertBar(bar) {
|
||||
// Skip bars with no data or already converted
|
||||
if (bar.classList.contains('none') || bar.classList.contains('vertical-bar')) return;
|
||||
|
||||
var fill = bar.querySelector('span:first-child');
|
||||
if (!fill) return;
|
||||
|
||||
// Read width (e.g. "83%") and convert to height
|
||||
var w = fill.style.width;
|
||||
if (w) {
|
||||
fill.style.height = w;
|
||||
fill.style.width = '100%';
|
||||
}
|
||||
|
||||
// Copy % text from sibling .load span into bar's last span
|
||||
var parent = bar.parentElement;
|
||||
if (parent) {
|
||||
var loadSpan = parent.querySelector('.load');
|
||||
var textSpan = bar.querySelector('span:last-child');
|
||||
if (loadSpan && textSpan && textSpan !== fill) {
|
||||
textSpan.textContent = loadSpan.textContent.trim();
|
||||
}
|
||||
}
|
||||
|
||||
bar.classList.add('vertical-bar');
|
||||
}
|
||||
|
||||
function convertAllBars(container) {
|
||||
var bars = container.querySelectorAll('.usage-disk');
|
||||
bars.forEach(convertBar);
|
||||
}
|
||||
|
||||
function initVerticalBars() {
|
||||
var tbodyIds = ['array_list', 'pool_list0', 'devs_list'];
|
||||
var targets = [];
|
||||
|
||||
tbodyIds.forEach(function(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
targets.push(el);
|
||||
convertAllBars(el);
|
||||
}
|
||||
});
|
||||
|
||||
if (targets.length === 0) return;
|
||||
|
||||
// Watch for style changes (Unraid updates bar widths) and childList (dynamic adds)
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(m) {
|
||||
var tbody = m.target.closest ? m.target.closest('tbody') : null;
|
||||
if (!tbody) tbody = m.target;
|
||||
// Reconvert: remove vertical-bar class from affected bars, then reconvert
|
||||
var bars = tbody.querySelectorAll('.usage-disk');
|
||||
bars.forEach(function(bar) {
|
||||
if (bar.classList.contains('vertical-bar')) {
|
||||
bar.classList.remove('vertical-bar');
|
||||
}
|
||||
convertBar(bar);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
targets.forEach(function(el) {
|
||||
observer.observe(el, { attributes: true, attributeFilter: ['style'], subtree: true, childList: true });
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
setTimeout(initVerticalBars, 1500);
|
||||
// Second pass for late-loading content
|
||||
setTimeout(initVerticalBars, 3000);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
|
||||
// =============================================================
|
||||
// System Card Legend Colors — fix invisible legend circles
|
||||
// =============================================================
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Legend circle colors matching recolored pie segments (in DOM order)
|
||||
var LEGEND_COLORS = [
|
||||
'rgb(126, 184, 218)', // System — accent blue
|
||||
'rgb(245, 166, 35)', // VM — orange
|
||||
'rgb(241, 196, 15)', // ZFS cache — yellow
|
||||
'rgb(231, 76, 60)', // Docker — red
|
||||
'rgba(255, 255, 255, 0.3)' // Free — subtle white
|
||||
];
|
||||
|
||||
function colorLegend() {
|
||||
var dynamic = document.getElementById('dynamic');
|
||||
if (!dynamic) return;
|
||||
|
||||
var circles = dynamic.querySelectorAll('i.fa-circle');
|
||||
circles.forEach(function(circle, i) {
|
||||
if (i < LEGEND_COLORS.length) {
|
||||
circle.style.setProperty('color', LEGEND_COLORS[i], 'important');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initLegendColors() {
|
||||
var dynamic = document.getElementById('dynamic');
|
||||
if (!dynamic) return;
|
||||
|
||||
colorLegend();
|
||||
|
||||
// Re-apply when Unraid re-renders the system card
|
||||
var observer = new MutationObserver(function() {
|
||||
colorLegend();
|
||||
});
|
||||
observer.observe(dynamic, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
function init() {
|
||||
setTimeout(initLegendColors, 2000);
|
||||
setTimeout(colorLegend, 4000);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user