Redesign UI like Driver Navigator + fix CMD window
UI changes: - Top title bar with logo, app name, and system status LED - Toolbar with 4 icon tabs: Scan, Drivers, Download, System - Scan page with computer SVG illustration, stats, and big SCAN NOW button - Step indicator bar: Scan Devices > Review Drivers > Install Updates - Driver list with per-row progress bars, Download + Install buttons - "Update All Drivers" CTA bar at bottom when outdated drivers found - Windows Update tab with pending/installed sections - System Info tab with resource bars - Footer with About/Help buttons Backend fixes: - Add cmdutil.HiddenCommand() using CREATE_NO_WINDOW (0x08000000) - All PowerShell subprocesses now run without visible console window - Build with -ldflags "-H windowsgui" to hide main CMD window Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
408
ui/app.js
408
ui/app.js
@@ -1,307 +1,266 @@
|
||||
/* ============================================================
|
||||
Driver Booster Pro - Application Logic
|
||||
Driver Navigator Style: tabs, driver rows, action buttons
|
||||
============================================================ */
|
||||
|
||||
const App = {
|
||||
state: {
|
||||
sysInfo: null,
|
||||
drivers: null,
|
||||
updates: null,
|
||||
},
|
||||
var App = {
|
||||
state: { sysInfo:null, drivers:null, updates:null },
|
||||
|
||||
init() {
|
||||
this.setupNavigation();
|
||||
init: function() {
|
||||
this.setupTabs();
|
||||
this.setupFilters();
|
||||
this.refreshSysInfo();
|
||||
// Re-render Lucide icons after DOM mutations
|
||||
this.refreshIcons();
|
||||
},
|
||||
|
||||
refreshIcons() {
|
||||
if (window.lucide) {
|
||||
lucide.createIcons();
|
||||
}
|
||||
refreshIcons: function() {
|
||||
if (window.lucide) lucide.createIcons();
|
||||
},
|
||||
|
||||
// ---- Navigation ----
|
||||
setupNavigation() {
|
||||
document.querySelectorAll('.nav-item[data-page]').forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
var page = item.dataset.page;
|
||||
document.querySelectorAll('.nav-item').forEach(function(n) { n.classList.remove('active'); });
|
||||
item.classList.add('active');
|
||||
document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); });
|
||||
var target = document.getElementById('page-' + page);
|
||||
// ---- Tab Navigation ----
|
||||
setupTabs: function() {
|
||||
var self = this;
|
||||
document.querySelectorAll('.toolbar-tab').forEach(function(tab) {
|
||||
tab.addEventListener('click', function() {
|
||||
var id = tab.dataset.tab;
|
||||
document.querySelectorAll('.toolbar-tab').forEach(function(t){ t.classList.remove('active'); });
|
||||
tab.classList.add('active');
|
||||
document.querySelectorAll('.tab-page').forEach(function(p){ p.classList.remove('active'); });
|
||||
var target = document.getElementById('tab-' + id);
|
||||
if (target) target.classList.add('active');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// ---- Filters ----
|
||||
setupFilters() {
|
||||
// ---- Filter Pills ----
|
||||
setupFilters: function() {
|
||||
var self = this;
|
||||
document.querySelectorAll('.filter-btn').forEach(function(btn) {
|
||||
document.querySelectorAll('.pill').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.filter-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
document.querySelectorAll('.pill').forEach(function(b){ b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
self.filterDrivers(btn.dataset.filter);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
filterDrivers(filter) {
|
||||
document.querySelectorAll('.driver-card').forEach(function(card) {
|
||||
var show = filter === 'all' ||
|
||||
(filter === 'outdated' && card.classList.contains('outdated')) ||
|
||||
(filter === 'error' && card.classList.contains('error')) ||
|
||||
(filter === 'signed' && card.dataset.signed === 'true');
|
||||
card.style.display = show ? '' : 'none';
|
||||
filterDrivers: function(f) {
|
||||
document.querySelectorAll('.drv-row').forEach(function(row) {
|
||||
var show = f === 'all' ||
|
||||
(f === 'outdated' && row.classList.contains('outdated')) ||
|
||||
(f === 'error' && row.classList.contains('error')) ||
|
||||
(f === 'signed' && row.dataset.signed === 'true');
|
||||
row.style.display = show ? '' : 'none';
|
||||
});
|
||||
},
|
||||
|
||||
// ---- Loading ----
|
||||
showLoading(text) {
|
||||
showLoading: function(text) {
|
||||
document.getElementById('loading-text').textContent = text;
|
||||
document.getElementById('loading-overlay').style.display = 'flex';
|
||||
this.refreshIcons();
|
||||
},
|
||||
|
||||
hideLoading() {
|
||||
hideLoading: function() {
|
||||
document.getElementById('loading-overlay').style.display = 'none';
|
||||
},
|
||||
|
||||
// ---- System Info ----
|
||||
async refreshSysInfo() {
|
||||
refreshSysInfo: function() {
|
||||
var self = this;
|
||||
this.showLoading('Collecting system information...');
|
||||
try {
|
||||
var res = await fetch('/api/sysinfo');
|
||||
var info = await res.json();
|
||||
this.state.sysInfo = info;
|
||||
this.renderSysInfo(info);
|
||||
this.updateDashboardResources(info);
|
||||
} catch (e) {
|
||||
console.error('Failed to get sysinfo:', e);
|
||||
}
|
||||
this.hideLoading();
|
||||
fetch('/api/sysinfo').then(function(r){ return r.json(); }).then(function(info) {
|
||||
self.state.sysInfo = info;
|
||||
self.renderSysInfo(info);
|
||||
self.hideLoading();
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
self.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
renderSysInfo(info) {
|
||||
this.setText('sys-name', info.computerName);
|
||||
this.setText('sys-os', info.osName);
|
||||
this.setText('sys-version', info.osVersion);
|
||||
this.setText('sys-build', info.osBuild);
|
||||
this.setText('sys-arch', info.architecture);
|
||||
this.setText('sys-cpu', info.cpuName);
|
||||
this.setText('sys-cores', info.cpuCores);
|
||||
this.setText('sys-ram-total', info.totalRam);
|
||||
this.setText('sys-ram-used', info.usedRam);
|
||||
this.setText('sys-ram-free', info.freeRam);
|
||||
this.setText('sys-ram-pct', (info.ramPercent || 0) + '%');
|
||||
this.setText('sys-disk-total', info.diskTotal);
|
||||
this.setText('sys-disk-used', info.diskUsed);
|
||||
this.setText('sys-disk-free', info.diskFree);
|
||||
this.setText('sys-disk-pct', (info.diskPercent || 0) + '%');
|
||||
renderSysInfo: function(d) {
|
||||
this.set('sys-name', d.computerName);
|
||||
this.set('sys-os', d.osName);
|
||||
this.set('sys-version', d.osVersion);
|
||||
this.set('sys-build', d.osBuild);
|
||||
this.set('sys-arch', d.architecture);
|
||||
this.set('sys-cpu', d.cpuName);
|
||||
this.set('sys-cores', d.cpuCores);
|
||||
this.set('sys-ram-total', d.totalRam);
|
||||
this.set('sys-ram-used', d.usedRam);
|
||||
this.set('sys-ram-free', d.freeRam);
|
||||
this.set('sys-ram-pct', (d.ramPercent||0)+'%');
|
||||
this.set('sys-disk-total', d.diskTotal);
|
||||
this.set('sys-disk-used', d.diskUsed);
|
||||
this.set('sys-disk-free', d.diskFree);
|
||||
this.set('sys-disk-pct', (d.diskPercent||0)+'%');
|
||||
var rb = document.getElementById('sys-ram-bar');
|
||||
var db = document.getElementById('sys-disk-bar');
|
||||
if(rb) rb.style.width = (d.ramPercent||0)+'%';
|
||||
if(db) db.style.width = (d.diskPercent||0)+'%';
|
||||
},
|
||||
|
||||
updateDashboardResources(info) {
|
||||
this.setText('dash-ram-percent', (info.ramPercent || 0) + '%');
|
||||
this.setText('dash-ram-detail', (info.usedRam || '--') + ' / ' + (info.totalRam || '--'));
|
||||
document.getElementById('dash-ram-bar').style.width = (info.ramPercent || 0) + '%';
|
||||
this.setText('dash-disk-detail', (info.diskUsed || '--') + ' / ' + (info.diskTotal || '--'));
|
||||
document.getElementById('dash-disk-bar').style.width = (info.diskPercent || 0) + '%';
|
||||
|
||||
// Warn color for high usage
|
||||
var ramBar = document.getElementById('dash-ram-bar');
|
||||
var diskBar = document.getElementById('dash-disk-bar');
|
||||
if (info.ramPercent > 80) {
|
||||
ramBar.style.background = 'linear-gradient(90deg, #f59e0b, #ef4444)';
|
||||
} else {
|
||||
ramBar.style.background = '';
|
||||
}
|
||||
if (info.diskPercent > 85) {
|
||||
diskBar.style.background = 'linear-gradient(90deg, #f59e0b, #ef4444)';
|
||||
} else {
|
||||
diskBar.style.background = '';
|
||||
}
|
||||
},
|
||||
|
||||
// ---- Drivers ----
|
||||
async scanDrivers() {
|
||||
// ---- Driver Scan ----
|
||||
scanDrivers: function() {
|
||||
var self = this;
|
||||
this.showLoading('Scanning drivers... This may take a moment.');
|
||||
try {
|
||||
var res = await fetch('/api/drivers/scan');
|
||||
var result = await res.json();
|
||||
this.state.drivers = result;
|
||||
this.renderDrivers(result);
|
||||
this.updateDashboardDrivers(result);
|
||||
} catch (e) {
|
||||
console.error('Failed to scan drivers:', e);
|
||||
}
|
||||
this.hideLoading();
|
||||
// Switch to drivers tab
|
||||
document.querySelectorAll('.toolbar-tab').forEach(function(t){ t.classList.remove('active'); });
|
||||
document.querySelector('[data-tab="drivers"]').classList.add('active');
|
||||
document.querySelectorAll('.tab-page').forEach(function(p){ p.classList.remove('active'); });
|
||||
document.getElementById('tab-drivers').classList.add('active');
|
||||
|
||||
// Update steps
|
||||
this.setStep(2);
|
||||
|
||||
fetch('/api/drivers/scan').then(function(r){ return r.json(); }).then(function(result) {
|
||||
self.state.drivers = result;
|
||||
self.renderDrivers(result);
|
||||
self.updateScanStats(result);
|
||||
self.hideLoading();
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
self.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
renderDrivers(result) {
|
||||
document.getElementById('driver-summary').style.display = 'flex';
|
||||
document.getElementById('driver-filters').style.display = 'flex';
|
||||
this.setText('drv-total', result.totalCount);
|
||||
this.setText('drv-outdated', result.outdatedCount);
|
||||
this.setText('drv-errors', result.errorCount);
|
||||
this.setText('drv-time', result.scanTime);
|
||||
updateScanStats: function(r) {
|
||||
this.set('scan-stat-total', r.totalCount);
|
||||
this.set('scan-stat-outdated', r.outdatedCount);
|
||||
this.set('scan-stat-errors', r.errorCount);
|
||||
},
|
||||
|
||||
renderDrivers: function(result) {
|
||||
document.getElementById('driver-result-count').style.display = 'block';
|
||||
document.getElementById('driver-filter-strip').style.display = 'flex';
|
||||
this.set('drv-total-2', result.totalCount);
|
||||
this.set('drv-outdated-2', result.outdatedCount);
|
||||
this.set('drv-time-2', result.scanTime);
|
||||
|
||||
var list = document.getElementById('driver-list');
|
||||
if (!result.drivers || result.drivers.length === 0) {
|
||||
list.innerHTML =
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i data-lucide="search-x"></i></div>' +
|
||||
'<h3>No Drivers Found</h3>' +
|
||||
'<p>No driver information was returned.</p>' +
|
||||
'</div>';
|
||||
list.innerHTML = '<div class="placeholder-msg"><i data-lucide="search-x"></i><p>No drivers found</p></div>';
|
||||
this.refreshIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
list.innerHTML = result.drivers.map(function(d) {
|
||||
var classes = ['driver-card'];
|
||||
if (d.needsUpdate) classes.push('outdated');
|
||||
if (d.status === 'Error' || d.status === 'Degraded') classes.push('error');
|
||||
var cls = ['drv-row'];
|
||||
if (d.needsUpdate) cls.push('outdated');
|
||||
if (d.status === 'Error' || d.status === 'Degraded') cls.push('error');
|
||||
|
||||
var iconName = self.getClassIconName(d.deviceClass);
|
||||
var icon = self.driverIcon(d.deviceClass);
|
||||
var barClass = d.needsUpdate ? 'warn' : (d.status === 'Error' ? 'err' : 'ok');
|
||||
|
||||
var badges = '';
|
||||
if (d.isSigned) {
|
||||
badges += '<span class="badge badge-signed"><i data-lucide="shield-check"></i> Signed</span>';
|
||||
} else {
|
||||
badges += '<span class="badge badge-unsigned"><i data-lucide="shield-x"></i> Unsigned</span>';
|
||||
}
|
||||
if (d.needsUpdate) {
|
||||
badges += '<span class="badge badge-outdated"><i data-lucide="clock"></i> Outdated</span>';
|
||||
} else {
|
||||
badges += '<span class="badge badge-ok"><i data-lucide="circle-check"></i> OK</span>';
|
||||
}
|
||||
var signBadge = d.isSigned
|
||||
? '<span class="drv-badge signed"><i data-lucide="shield-check"></i> Signed</span>'
|
||||
: '<span class="drv-badge unsigned"><i data-lucide="shield-x"></i></span>';
|
||||
|
||||
return '<div class="' + classes.join(' ') + '" data-signed="' + d.isSigned + '">' +
|
||||
'<div class="driver-class-icon"><i data-lucide="' + iconName + '"></i></div>' +
|
||||
'<div class="driver-info">' +
|
||||
'<div class="driver-name">' + self.esc(d.deviceName) + '</div>' +
|
||||
'<div class="driver-meta">' +
|
||||
self.esc(d.manufacturer || 'Unknown') +
|
||||
' <span class="driver-meta-sep">·</span> v' + self.esc(d.driverVersion || '?') +
|
||||
' <span class="driver-meta-sep">·</span> ' + self.esc(d.driverDate) +
|
||||
'</div>' +
|
||||
var actionBtn = d.needsUpdate
|
||||
? '<button class="drv-btn install-btn"><i data-lucide="download"></i> Download</button>' +
|
||||
'<button class="drv-btn"><i data-lucide="play"></i> Install</button>'
|
||||
: '<button class="drv-btn installed-btn"><i data-lucide="circle-check"></i> Up to date</button>';
|
||||
|
||||
return '<div class="' + cls.join(' ') + '" data-signed="' + d.isSigned + '">' +
|
||||
'<div class="drv-icon"><i data-lucide="' + icon + '"></i></div>' +
|
||||
'<div class="drv-info">' +
|
||||
'<div class="drv-name">' + self.esc(d.deviceName) + '</div>' +
|
||||
'<div class="drv-meta">' + self.esc(d.manufacturer||'Unknown') + ' · v' + self.esc(d.driverVersion||'?') + ' · ' + self.esc(d.driverDate) + ' ' + signBadge + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="driver-badges">' + badges + '</div>' +
|
||||
'<div class="drv-progress">' +
|
||||
'<div class="drv-bar"><div class="drv-bar-fill ' + barClass + '"></div></div>' +
|
||||
'</div>' +
|
||||
'<div class="drv-actions">' + actionBtn + '</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
|
||||
// Show CTA if there are outdated drivers
|
||||
var cta = document.getElementById('cta-update-all');
|
||||
if (result.outdatedCount > 0) {
|
||||
cta.style.display = 'flex';
|
||||
this.set('cta-count', result.outdatedCount);
|
||||
} else {
|
||||
cta.style.display = 'none';
|
||||
}
|
||||
|
||||
this.refreshIcons();
|
||||
},
|
||||
|
||||
updateDashboardDrivers(result) {
|
||||
this.setText('dash-driver-count', result.totalCount);
|
||||
this.setText('dash-outdated-count', result.outdatedCount);
|
||||
},
|
||||
|
||||
getClassIconName(cls) {
|
||||
driverIcon: function(cls) {
|
||||
if (!cls) return 'package';
|
||||
var upper = cls.toUpperCase();
|
||||
var map = {
|
||||
'DISPLAY': 'monitor',
|
||||
'MEDIA': 'volume-2',
|
||||
'AUDIO': 'volume-2',
|
||||
'SOUND': 'volume-2',
|
||||
'NET': 'wifi',
|
||||
'NETWORK': 'wifi',
|
||||
'USB': 'usb',
|
||||
'HID': 'mouse',
|
||||
'KEYBOARD': 'keyboard',
|
||||
'DISK': 'hard-drive',
|
||||
'STORAGE': 'hard-drive',
|
||||
'PROCESSOR': 'cpu',
|
||||
'SYSTEM': 'settings',
|
||||
'BLUETOOTH': 'bluetooth',
|
||||
'CAMERA': 'camera',
|
||||
'IMAGE': 'camera',
|
||||
'PRINT': 'printer',
|
||||
'BATTERY': 'battery-charging',
|
||||
'BIOMETRIC': 'fingerprint',
|
||||
'FIRMWARE': 'circuit-board',
|
||||
'SECURITY': 'shield',
|
||||
'SENSOR': 'thermometer',
|
||||
var u = cls.toUpperCase();
|
||||
var m = {
|
||||
'DISPLAY':'monitor','MEDIA':'volume-2','AUDIO':'volume-2','SOUND':'volume-2',
|
||||
'NET':'wifi','NETWORK':'wifi','USB':'usb','HID':'mouse','KEYBOARD':'keyboard',
|
||||
'DISK':'hard-drive','STORAGE':'hard-drive','PROCESSOR':'cpu','SYSTEM':'settings',
|
||||
'BLUETOOTH':'bluetooth','CAMERA':'camera','IMAGE':'camera','PRINT':'printer',
|
||||
'BATTERY':'battery-charging','FIRMWARE':'circuit-board','SECURITY':'shield',
|
||||
'SENSOR':'thermometer'
|
||||
};
|
||||
for (var key in map) {
|
||||
if (upper.indexOf(key) !== -1) return map[key];
|
||||
}
|
||||
for (var k in m) { if (u.indexOf(k) !== -1) return m[k]; }
|
||||
return 'package';
|
||||
},
|
||||
|
||||
// ---- Updates ----
|
||||
async checkUpdates() {
|
||||
// ---- Windows Update ----
|
||||
checkUpdates: function() {
|
||||
var self = this;
|
||||
this.showLoading('Checking for Windows updates...');
|
||||
try {
|
||||
var res = await fetch('/api/updates/check');
|
||||
var result = await res.json();
|
||||
this.state.updates = result;
|
||||
this.renderUpdates(result);
|
||||
this.setText('dash-update-count', result.pendingCount);
|
||||
} catch (e) {
|
||||
console.error('Failed to check updates:', e);
|
||||
}
|
||||
this.hideLoading();
|
||||
// Switch to updates tab
|
||||
document.querySelectorAll('.toolbar-tab').forEach(function(t){ t.classList.remove('active'); });
|
||||
document.querySelector('[data-tab="updates"]').classList.add('active');
|
||||
document.querySelectorAll('.tab-page').forEach(function(p){ p.classList.remove('active'); });
|
||||
document.getElementById('tab-updates').classList.add('active');
|
||||
|
||||
fetch('/api/updates/check').then(function(r){ return r.json(); }).then(function(result) {
|
||||
self.state.updates = result;
|
||||
self.renderUpdates(result);
|
||||
self.hideLoading();
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
self.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
renderUpdates(result) {
|
||||
renderUpdates: function(result) {
|
||||
var list = document.getElementById('update-list');
|
||||
|
||||
if (result.error) {
|
||||
list.innerHTML =
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i data-lucide="alert-circle"></i></div>' +
|
||||
'<h3>Error</h3>' +
|
||||
'<p style="color:var(--danger)">' + this.esc(result.error) + '</p>' +
|
||||
'</div>';
|
||||
list.innerHTML = '<div class="placeholder-msg"><i data-lucide="alert-circle"></i><p style="color:var(--red)">' + this.esc(result.error) + '</p></div>';
|
||||
this.refreshIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.updates || result.updates.length === 0) {
|
||||
list.innerHTML =
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i data-lucide="circle-check"></i></div>' +
|
||||
'<h3>All Up to Date</h3>' +
|
||||
'<p>Your system is fully updated.</p>' +
|
||||
'</div>';
|
||||
list.innerHTML = '<div class="placeholder-msg"><i data-lucide="circle-check"></i><p>Your system is fully up to date!</p></div>';
|
||||
this.refreshIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
var pending = result.updates.filter(function(u) { return !u.isInstalled; });
|
||||
var installed = result.updates.filter(function(u) { return u.isInstalled; });
|
||||
var pending = result.updates.filter(function(u){return !u.isInstalled;});
|
||||
var installed = result.updates.filter(function(u){return u.isInstalled;});
|
||||
var self = this;
|
||||
var html = '';
|
||||
|
||||
if (pending.length > 0) {
|
||||
html += '<div class="update-section-title pending"><i data-lucide="download"></i> Pending Updates (' + pending.length + ')</div>';
|
||||
html += '<div class="upd-section pend"><i data-lucide="download"></i> Pending (' + pending.length + ')</div>';
|
||||
html += pending.map(function(u) {
|
||||
return '<div class="update-card pending">' +
|
||||
'<div class="update-title">' + self.esc(u.title) + '</div>' +
|
||||
'<div class="update-meta">' +
|
||||
(u.kbArticle ? '<span class="update-tag"><i data-lucide="file-text"></i> ' + self.esc(u.kbArticle) + '</span>' : '') +
|
||||
(u.category ? '<span class="update-tag"><i data-lucide="folder"></i> ' + self.esc(u.category) + '</span>' : '') +
|
||||
(u.size ? '<span class="update-tag"><i data-lucide="hard-drive"></i> ' + self.esc(u.size) + '</span>' : '') +
|
||||
(u.severity && u.severity !== 'Unspecified' ? '<span class="update-tag"><i data-lucide="alert-triangle"></i> ' + self.esc(u.severity) + '</span>' : '') +
|
||||
(u.isMandatory ? '<span class="update-tag" style="color:var(--danger)"><i data-lucide="alert-circle"></i> Mandatory</span>' : '') +
|
||||
return '<div class="upd-row pending">' +
|
||||
'<div class="upd-title">' + self.esc(u.title) + '</div>' +
|
||||
'<div class="upd-meta">' +
|
||||
(u.kbArticle ? '<span class="upd-tag"><i data-lucide="file-text"></i> '+self.esc(u.kbArticle)+'</span>' : '') +
|
||||
(u.category ? '<span class="upd-tag"><i data-lucide="folder"></i> '+self.esc(u.category)+'</span>' : '') +
|
||||
(u.size ? '<span class="upd-tag"><i data-lucide="hard-drive"></i> '+self.esc(u.size)+'</span>' : '') +
|
||||
(u.severity && u.severity !== 'Unspecified' ? '<span class="upd-tag"><i data-lucide="alert-triangle"></i> '+self.esc(u.severity)+'</span>' : '') +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
if (installed.length > 0) {
|
||||
html += '<div class="update-section-title installed"><i data-lucide="check-circle"></i> Recently Installed (' + installed.length + ')</div>';
|
||||
html += '<div class="upd-section inst"><i data-lucide="check-circle"></i> Recently Installed (' + installed.length + ')</div>';
|
||||
html += installed.map(function(u) {
|
||||
return '<div class="update-card installed">' +
|
||||
'<div class="update-title">' + self.esc(u.title) + '</div>' +
|
||||
'</div>';
|
||||
return '<div class="upd-row done"><div class="upd-title">' + self.esc(u.title) + '</div></div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
@@ -309,21 +268,28 @@ const App = {
|
||||
this.refreshIcons();
|
||||
},
|
||||
|
||||
// ---- Utility ----
|
||||
setText(id, value) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.textContent = (value != null && value !== '') ? value : '--';
|
||||
// ---- Step bar ----
|
||||
setStep: function(n) {
|
||||
for (var i = 1; i <= 3; i++) {
|
||||
var el = document.getElementById('step-' + i);
|
||||
if (!el) continue;
|
||||
el.classList.remove('active', 'done');
|
||||
if (i < n) el.classList.add('done');
|
||||
if (i === n) el.classList.add('active');
|
||||
}
|
||||
},
|
||||
|
||||
esc(str) {
|
||||
if (!str) return '';
|
||||
var div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
// ---- Helpers ----
|
||||
set: function(id, val) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) el.textContent = (val != null && val !== '') ? val : '--';
|
||||
},
|
||||
esc: function(s) {
|
||||
if (!s) return '';
|
||||
var d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
// Boot
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
App.init();
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() { App.init(); });
|
||||
|
||||
Reference in New Issue
Block a user