Initial commit: Driver Booster Pro - Go + WebView2 desktop app
Windows system utility with driver scanning, Windows Update integration, and system info collection. Beautiful dark-themed UI via embedded WebView2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
252
ui/app.js
Normal file
252
ui/app.js
Normal file
@@ -0,0 +1,252 @@
|
||||
const App = {
|
||||
state: {
|
||||
sysInfo: null,
|
||||
drivers: null,
|
||||
updates: null,
|
||||
},
|
||||
|
||||
init() {
|
||||
this.setupNavigation();
|
||||
this.setupFilters();
|
||||
this.refreshSysInfo();
|
||||
},
|
||||
|
||||
setupNavigation() {
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const page = item.dataset.page;
|
||||
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||||
document.getElementById('page-' + page).classList.add('active');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setupFilters() {
|
||||
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
this.filterDrivers(btn.dataset.filter);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
filterDrivers(filter) {
|
||||
document.querySelectorAll('.driver-card').forEach(card => {
|
||||
const 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';
|
||||
});
|
||||
},
|
||||
|
||||
showLoading(text) {
|
||||
document.getElementById('loading-text').textContent = text;
|
||||
document.getElementById('loading-overlay').style.display = 'flex';
|
||||
},
|
||||
|
||||
hideLoading() {
|
||||
document.getElementById('loading-overlay').style.display = 'none';
|
||||
},
|
||||
|
||||
// System Info
|
||||
async refreshSysInfo() {
|
||||
this.showLoading('Collecting system information...');
|
||||
try {
|
||||
const res = await fetch('/api/sysinfo');
|
||||
const 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();
|
||||
},
|
||||
|
||||
renderSysInfo(info) {
|
||||
document.getElementById('sys-name').textContent = info.computerName || '--';
|
||||
document.getElementById('sys-os').textContent = info.osName || '--';
|
||||
document.getElementById('sys-version').textContent = info.osVersion || '--';
|
||||
document.getElementById('sys-build').textContent = info.osBuild || '--';
|
||||
document.getElementById('sys-arch').textContent = info.architecture || '--';
|
||||
document.getElementById('sys-cpu').textContent = info.cpuName || '--';
|
||||
document.getElementById('sys-cores').textContent = info.cpuCores || '--';
|
||||
document.getElementById('sys-ram-total').textContent = info.totalRam || '--';
|
||||
document.getElementById('sys-ram-used').textContent = info.usedRam || '--';
|
||||
document.getElementById('sys-ram-free').textContent = info.freeRam || '--';
|
||||
document.getElementById('sys-ram-pct').textContent = (info.ramPercent || 0) + '%';
|
||||
document.getElementById('sys-disk-total').textContent = info.diskTotal || '--';
|
||||
document.getElementById('sys-disk-used').textContent = info.diskUsed || '--';
|
||||
document.getElementById('sys-disk-free').textContent = info.diskFree || '--';
|
||||
document.getElementById('sys-disk-pct').textContent = (info.diskPercent || 0) + '%';
|
||||
},
|
||||
|
||||
updateDashboardResources(info) {
|
||||
document.getElementById('dash-ram-percent').textContent = (info.ramPercent || 0) + '%';
|
||||
document.getElementById('dash-ram-detail').textContent = (info.usedRam || '--') + ' / ' + (info.totalRam || '--');
|
||||
document.getElementById('dash-ram-bar').style.width = (info.ramPercent || 0) + '%';
|
||||
document.getElementById('dash-disk-detail').textContent = (info.diskUsed || '--') + ' / ' + (info.diskTotal || '--');
|
||||
document.getElementById('dash-disk-bar').style.width = (info.diskPercent || 0) + '%';
|
||||
|
||||
if (info.ramPercent > 80) {
|
||||
document.getElementById('dash-ram-bar').style.background = 'linear-gradient(90deg, #f59e0b, #ef4444)';
|
||||
}
|
||||
if (info.diskPercent > 85) {
|
||||
document.getElementById('dash-disk-bar').style.background = 'linear-gradient(90deg, #f59e0b, #ef4444)';
|
||||
}
|
||||
},
|
||||
|
||||
// Drivers
|
||||
async scanDrivers() {
|
||||
this.showLoading('Scanning drivers... This may take a moment.');
|
||||
try {
|
||||
const res = await fetch('/api/drivers/scan');
|
||||
const 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();
|
||||
},
|
||||
|
||||
renderDrivers(result) {
|
||||
document.getElementById('driver-summary').style.display = 'flex';
|
||||
document.getElementById('driver-filters').style.display = 'flex';
|
||||
document.getElementById('drv-total').textContent = result.totalCount;
|
||||
document.getElementById('drv-outdated').textContent = result.outdatedCount;
|
||||
document.getElementById('drv-errors').textContent = result.errorCount;
|
||||
document.getElementById('drv-time').textContent = result.scanTime;
|
||||
|
||||
const list = document.getElementById('driver-list');
|
||||
if (!result.drivers || result.drivers.length === 0) {
|
||||
list.innerHTML = '<div class="empty-state"><p>No drivers found.</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = result.drivers.map(d => {
|
||||
const classes = ['driver-card'];
|
||||
if (d.needsUpdate) classes.push('outdated');
|
||||
if (d.status === 'Error' || d.status === 'Degraded') classes.push('error');
|
||||
|
||||
const icon = this.getClassIcon(d.deviceClass);
|
||||
const badges = [];
|
||||
if (d.isSigned) badges.push('<span class="badge badge-signed">Signed</span>');
|
||||
else badges.push('<span class="badge badge-unsigned">Unsigned</span>');
|
||||
if (d.needsUpdate) badges.push('<span class="badge badge-outdated">Outdated</span>');
|
||||
else badges.push('<span class="badge badge-ok">OK</span>');
|
||||
|
||||
return '<div class="' + classes.join(' ') + '" data-signed="' + d.isSigned + '">' +
|
||||
'<div class="driver-class-icon">' + icon + '</div>' +
|
||||
'<div class="driver-info">' +
|
||||
'<div class="driver-name">' + this.esc(d.deviceName) + '</div>' +
|
||||
'<div class="driver-meta">' + this.esc(d.manufacturer || 'Unknown') + ' • v' + this.esc(d.driverVersion || '?') + ' • ' + this.esc(d.driverDate) + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="driver-badges">' + badges.join('') + '</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
},
|
||||
|
||||
updateDashboardDrivers(result) {
|
||||
document.getElementById('dash-driver-count').textContent = result.totalCount;
|
||||
document.getElementById('dash-outdated-count').textContent = result.outdatedCount;
|
||||
},
|
||||
|
||||
getClassIcon(cls) {
|
||||
const icons = {
|
||||
'DISPLAY': '\uD83D\uDDA5',
|
||||
'MEDIA': '\uD83D\uDD0A',
|
||||
'NET': '\uD83C\uDF10',
|
||||
'USB': '\uD83D\uDD0C',
|
||||
'HIDCLASS': '\uD83D\uDDB1',
|
||||
'KEYBOARD': '\u2328',
|
||||
'DISKDRIVE': '\uD83D\uDCBE',
|
||||
'PROCESSOR': '\u26A1',
|
||||
'SYSTEM': '\u2699',
|
||||
'BLUETOOTH': '\uD83D\uDCE1',
|
||||
'CAMERA': '\uD83D\uDCF7',
|
||||
'PRINTER': '\uD83D\uDDA8',
|
||||
};
|
||||
if (!cls) return '\uD83D\uDCE6';
|
||||
var upper = cls.toUpperCase();
|
||||
for (var key in icons) {
|
||||
if (upper.indexOf(key) !== -1) return icons[key];
|
||||
}
|
||||
return '\uD83D\uDCE6';
|
||||
},
|
||||
|
||||
// Updates
|
||||
async checkUpdates() {
|
||||
this.showLoading('Checking for Windows updates...');
|
||||
try {
|
||||
const res = await fetch('/api/updates/check');
|
||||
const result = await res.json();
|
||||
this.state.updates = result;
|
||||
this.renderUpdates(result);
|
||||
document.getElementById('dash-update-count').textContent = result.pendingCount;
|
||||
} catch (e) {
|
||||
console.error('Failed to check updates:', e);
|
||||
}
|
||||
this.hideLoading();
|
||||
},
|
||||
|
||||
renderUpdates(result) {
|
||||
const list = document.getElementById('update-list');
|
||||
|
||||
if (result.error) {
|
||||
list.innerHTML = '<div class="empty-state"><p style="color:var(--danger)">' + this.esc(result.error) + '</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.updates || result.updates.length === 0) {
|
||||
list.innerHTML = '<div class="empty-state"><p>Your system is up to date!</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const pending = result.updates.filter(u => !u.isInstalled);
|
||||
const installed = result.updates.filter(u => u.isInstalled);
|
||||
|
||||
let html = '';
|
||||
|
||||
if (pending.length > 0) {
|
||||
html += '<h3 style="margin:8px 0;font-size:14px;color:var(--info)">Pending Updates (' + pending.length + ')</h3>';
|
||||
html += pending.map(u =>
|
||||
'<div class="update-card pending">' +
|
||||
'<div class="update-title">' + this.esc(u.title) + '</div>' +
|
||||
'<div class="update-meta">' +
|
||||
(u.kbArticle ? '<span>' + this.esc(u.kbArticle) + '</span>' : '') +
|
||||
(u.category ? '<span>' + this.esc(u.category) + '</span>' : '') +
|
||||
(u.size ? '<span>' + this.esc(u.size) + '</span>' : '') +
|
||||
(u.severity && u.severity !== 'Unspecified' ? '<span>' + this.esc(u.severity) + '</span>' : '') +
|
||||
(u.isMandatory ? '<span style="color:var(--danger)">Mandatory</span>' : '') +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
).join('');
|
||||
}
|
||||
|
||||
if (installed.length > 0) {
|
||||
html += '<h3 style="margin:16px 0 8px;font-size:14px;color:var(--success)">Recently Installed (' + installed.length + ')</h3>';
|
||||
html += installed.map(u =>
|
||||
'<div class="update-card installed">' +
|
||||
'<div class="update-title">' + this.esc(u.title) + '</div>' +
|
||||
'</div>'
|
||||
).join('');
|
||||
}
|
||||
|
||||
list.innerHTML = html;
|
||||
},
|
||||
|
||||
esc(str) {
|
||||
if (!str) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
},
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() { App.init(); });
|
||||
243
ui/index.html
Normal file
243
ui/index.html
Normal file
@@ -0,0 +1,243 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Driver Booster Pro</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="logo">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<rect width="32" height="32" rx="8" fill="url(#logo-grad)"/>
|
||||
<path d="M8 16L14 22L24 10" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<defs><linearGradient id="logo-grad" x1="0" y1="0" x2="32" y2="32"><stop stop-color="#6366f1"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs>
|
||||
</svg>
|
||||
<span class="logo-text">Driver Booster</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav-items">
|
||||
<li class="nav-item active" data-page="dashboard">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
||||
<span>Dashboard</span>
|
||||
</li>
|
||||
<li class="nav-item" data-page="drivers">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
<span>Drivers</span>
|
||||
</li>
|
||||
<li class="nav-item" data-page="updates">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 11-6.219-8.56"/><polyline points="21 3 21 9 15 9"/></svg>
|
||||
<span>Windows Update</span>
|
||||
</li>
|
||||
<li class="nav-item" data-page="system">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||||
<span>System Info</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<div class="version-badge">v1.0.0</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="content">
|
||||
<!-- Dashboard Page -->
|
||||
<div class="page active" id="page-dashboard">
|
||||
<div class="page-header">
|
||||
<h1>Dashboard</h1>
|
||||
<p class="subtitle">System health overview</p>
|
||||
</div>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon drivers-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<span class="stat-value" id="dash-driver-count">--</span>
|
||||
<span class="stat-label">Total Drivers</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon outdated-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<span class="stat-value" id="dash-outdated-count">--</span>
|
||||
<span class="stat-label">Outdated Drivers</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon updates-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 11-6.219-8.56"/><polyline points="21 3 21 9 15 9"/></svg>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<span class="stat-value" id="dash-update-count">--</span>
|
||||
<span class="stat-label">Pending Updates</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon ram-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="1" x2="9" y2="4"/><line x1="15" y1="1" x2="15" y2="4"/><line x1="9" y1="20" x2="9" y2="23"/><line x1="15" y1="20" x2="15" y2="23"/></svg>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<span class="stat-value" id="dash-ram-percent">--</span>
|
||||
<span class="stat-label">RAM Usage</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-panels">
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<h3>Quick Actions</h3>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<button class="action-btn primary" onclick="App.scanDrivers()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
Scan Drivers
|
||||
</button>
|
||||
<button class="action-btn secondary" onclick="App.checkUpdates()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 11-6.219-8.56"/><polyline points="21 3 21 9 15 9"/></svg>
|
||||
Check Updates
|
||||
</button>
|
||||
<button class="action-btn accent" onclick="App.refreshSysInfo()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
|
||||
Refresh System
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<h3>System Resources</h3>
|
||||
</div>
|
||||
<div class="resource-bars">
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">
|
||||
<span>RAM</span>
|
||||
<span id="dash-ram-detail">-- / --</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill ram-fill" id="dash-ram-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-item">
|
||||
<div class="resource-label">
|
||||
<span>Disk (C:)</span>
|
||||
<span id="dash-disk-detail">-- / --</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill disk-fill" id="dash-disk-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drivers Page -->
|
||||
<div class="page" id="page-drivers">
|
||||
<div class="page-header">
|
||||
<h1>Driver Manager</h1>
|
||||
<div class="page-actions">
|
||||
<button class="action-btn primary" onclick="App.scanDrivers()">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
Scan Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="driver-summary" id="driver-summary" style="display:none">
|
||||
<div class="summary-item"><strong id="drv-total">0</strong> Total</div>
|
||||
<div class="summary-item warning"><strong id="drv-outdated">0</strong> Outdated</div>
|
||||
<div class="summary-item error"><strong id="drv-errors">0</strong> Errors</div>
|
||||
<div class="summary-item muted">Scanned in <span id="drv-time">--</span></div>
|
||||
</div>
|
||||
<div class="filter-bar" id="driver-filters" style="display:none">
|
||||
<button class="filter-btn active" data-filter="all">All</button>
|
||||
<button class="filter-btn" data-filter="outdated">Outdated</button>
|
||||
<button class="filter-btn" data-filter="error">Errors</button>
|
||||
<button class="filter-btn" data-filter="signed">Signed</button>
|
||||
</div>
|
||||
<div id="driver-list" class="driver-list">
|
||||
<div class="empty-state">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.3"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
<p>Click <strong>Scan Now</strong> to detect installed drivers</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Updates Page -->
|
||||
<div class="page" id="page-updates">
|
||||
<div class="page-header">
|
||||
<h1>Windows Update</h1>
|
||||
<div class="page-actions">
|
||||
<button class="action-btn primary" onclick="App.checkUpdates()">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 11-6.219-8.56"/><polyline points="21 3 21 9 15 9"/></svg>
|
||||
Check Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="update-list" class="update-list">
|
||||
<div class="empty-state">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.3"><path d="M21 12a9 9 0 11-6.219-8.56"/><polyline points="21 3 21 9 15 9"/></svg>
|
||||
<p>Click <strong>Check Now</strong> to search for Windows updates</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Info Page -->
|
||||
<div class="page" id="page-system">
|
||||
<div class="page-header">
|
||||
<h1>System Information</h1>
|
||||
<div class="page-actions">
|
||||
<button class="action-btn primary" onclick="App.refreshSysInfo()">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sysinfo-grid" id="sysinfo-grid">
|
||||
<div class="info-card">
|
||||
<h4>Computer</h4>
|
||||
<div class="info-row"><span class="info-key">Name</span><span class="info-val" id="sys-name">--</span></div>
|
||||
<div class="info-row"><span class="info-key">OS</span><span class="info-val" id="sys-os">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Version</span><span class="info-val" id="sys-version">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Build</span><span class="info-val" id="sys-build">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Architecture</span><span class="info-val" id="sys-arch">--</span></div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h4>Processor</h4>
|
||||
<div class="info-row"><span class="info-key">CPU</span><span class="info-val" id="sys-cpu">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Cores</span><span class="info-val" id="sys-cores">--</span></div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h4>Memory</h4>
|
||||
<div class="info-row"><span class="info-key">Total</span><span class="info-val" id="sys-ram-total">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Used</span><span class="info-val" id="sys-ram-used">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Free</span><span class="info-val" id="sys-ram-free">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Usage</span><span class="info-val" id="sys-ram-pct">--</span></div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h4>Storage (C:)</h4>
|
||||
<div class="info-row"><span class="info-key">Total</span><span class="info-val" id="sys-disk-total">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Used</span><span class="info-val" id="sys-disk-used">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Free</span><span class="info-val" id="sys-disk-free">--</span></div>
|
||||
<div class="info-row"><span class="info-key">Usage</span><span class="info-val" id="sys-disk-pct">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div class="loading-overlay" id="loading-overlay" style="display:none">
|
||||
<div class="spinner"></div>
|
||||
<p id="loading-text">Scanning...</p>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
424
ui/style.css
Normal file
424
ui/style.css
Normal file
@@ -0,0 +1,424 @@
|
||||
:root {
|
||||
--bg-primary: #0f0f1a;
|
||||
--bg-secondary: #1a1a2e;
|
||||
--bg-card: #16213e;
|
||||
--bg-hover: #1f2b4d;
|
||||
--border: #2a2a4a;
|
||||
--text-primary: #e8e8f0;
|
||||
--text-secondary: #9ca3af;
|
||||
--text-muted: #6b7280;
|
||||
--accent-primary: #6366f1;
|
||||
--accent-secondary: #8b5cf6;
|
||||
--accent-glow: rgba(99, 102, 241, 0.15);
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--info: #3b82f6;
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--sidebar-width: 240px;
|
||||
--transition: 0.2s ease;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.app { display: flex; height: 100vh; }
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-header { padding: 20px; border-bottom: 1px solid var(--border); }
|
||||
|
||||
.logo { display: flex; align-items: center; gap: 10px; }
|
||||
|
||||
.logo-text {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.nav-items { list-style: none; padding: 12px; flex: 1; }
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
transition: all var(--transition);
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nav-item:hover { background: var(--bg-hover); color: var(--text-primary); }
|
||||
|
||||
.nav-item.active {
|
||||
background: var(--accent-glow);
|
||||
color: var(--accent-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-item.active svg { stroke: var(--accent-primary); }
|
||||
|
||||
.sidebar-footer { padding: 16px 20px; border-top: 1px solid var(--border); }
|
||||
|
||||
.version-badge {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-primary);
|
||||
padding: 4px 10px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.content::-webkit-scrollbar { width: 6px; }
|
||||
.content::-webkit-scrollbar-track { background: transparent; }
|
||||
.content::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||||
|
||||
.page { display: none; animation: fadeIn 0.3s ease; }
|
||||
.page.active { display: block; }
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.page-header h1 { font-size: 24px; font-weight: 700; letter-spacing: -0.5px; }
|
||||
|
||||
.subtitle { color: var(--text-secondary); font-size: 14px; margin-top: 4px; }
|
||||
|
||||
/* Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 20px var(--accent-glow);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drivers-icon { background: rgba(99, 102, 241, 0.15); color: var(--accent-primary); }
|
||||
.outdated-icon { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
|
||||
.updates-icon { background: rgba(59, 130, 246, 0.15); color: var(--info); }
|
||||
.ram-icon { background: rgba(16, 185, 129, 0.15); color: var(--success); }
|
||||
|
||||
.stat-value { font-size: 28px; font-weight: 700; display: block; line-height: 1.1; }
|
||||
.stat-label { font-size: 12px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
|
||||
/* Panels */
|
||||
.dashboard-panels { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
|
||||
.panel {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header { padding: 16px 20px; border-bottom: 1px solid var(--border); }
|
||||
|
||||
.panel-header h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Quick Actions */
|
||||
.quick-actions { padding: 20px; display: flex; flex-direction: column; gap: 10px; }
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.primary:hover {
|
||||
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
color: var(--info);
|
||||
border: 1px solid rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.secondary:hover { background: rgba(59, 130, 246, 0.25); }
|
||||
|
||||
.action-btn.accent {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
color: var(--success);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.accent:hover { background: rgba(16, 185, 129, 0.25); }
|
||||
|
||||
.action-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; }
|
||||
|
||||
/* Resource Bars */
|
||||
.resource-bars { padding: 20px; display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
.resource-label { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 6px; }
|
||||
.resource-label span:first-child { font-weight: 600; }
|
||||
.resource-label span:last-child { color: var(--text-secondary); }
|
||||
|
||||
.progress-bar { height: 8px; background: var(--bg-primary); border-radius: 4px; overflow: hidden; }
|
||||
|
||||
.progress-fill { height: 100%; border-radius: 4px; transition: width 0.8s ease; }
|
||||
.ram-fill { background: linear-gradient(90deg, var(--success), #34d399); }
|
||||
.disk-fill { background: linear-gradient(90deg, var(--info), #60a5fa); }
|
||||
|
||||
/* Driver List */
|
||||
.driver-summary {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-bottom: 16px;
|
||||
padding: 14px 20px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.summary-item { font-size: 13px; color: var(--text-secondary); }
|
||||
.summary-item strong { color: var(--text-primary); font-size: 16px; margin-right: 4px; }
|
||||
.summary-item.warning strong { color: var(--warning); }
|
||||
.summary-item.error strong { color: var(--danger); }
|
||||
|
||||
.filter-bar { display: flex; gap: 8px; margin-bottom: 16px; }
|
||||
|
||||
.filter-btn {
|
||||
padding: 6px 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 20px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.filter-btn:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
|
||||
|
||||
.filter-btn.active {
|
||||
background: var(--accent-primary);
|
||||
border-color: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.driver-list, .update-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
|
||||
.driver-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.driver-card:hover { border-color: var(--accent-primary); background: var(--bg-hover); }
|
||||
.driver-card.outdated { border-left: 3px solid var(--warning); }
|
||||
.driver-card.error { border-left: 3px solid var(--danger); }
|
||||
|
||||
.driver-class-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--accent-primary);
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.driver-info { flex: 1; min-width: 0; }
|
||||
|
||||
.driver-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.driver-meta { font-size: 12px; color: var(--text-secondary); margin-top: 2px; }
|
||||
.driver-badges { display: flex; gap: 6px; flex-shrink: 0; }
|
||||
|
||||
.badge { font-size: 11px; padding: 3px 10px; border-radius: 20px; font-weight: 600; }
|
||||
.badge-signed { background: rgba(16, 185, 129, 0.15); color: var(--success); }
|
||||
.badge-unsigned { background: rgba(239, 68, 68, 0.15); color: var(--danger); }
|
||||
.badge-outdated { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
|
||||
.badge-ok { background: rgba(16, 185, 129, 0.15); color: var(--success); }
|
||||
|
||||
/* Update Cards */
|
||||
.update-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 16px 20px;
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.update-card:hover { border-color: var(--accent-primary); }
|
||||
.update-card.installed { opacity: 0.6; }
|
||||
.update-card.pending { border-left: 3px solid var(--info); }
|
||||
|
||||
.update-title { font-weight: 600; font-size: 14px; margin-bottom: 4px; }
|
||||
.update-meta { font-size: 12px; color: var(--text-secondary); display: flex; gap: 16px; flex-wrap: wrap; }
|
||||
|
||||
/* System Info Grid */
|
||||
.sysinfo-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
|
||||
|
||||
.info-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.info-card h4 {
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--accent-primary);
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid rgba(42, 42, 74, 0.5);
|
||||
}
|
||||
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-key { color: var(--text-secondary); }
|
||||
|
||||
.info-val {
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
max-width: 60%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); }
|
||||
.empty-state p { margin-top: 16px; font-size: 14px; }
|
||||
|
||||
/* Loading Overlay */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 15, 26, 0.85);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border);
|
||||
border-top-color: var(--accent-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.loading-overlay p { margin-top: 16px; color: var(--text-secondary); font-size: 14px; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.dashboard-panels { grid-template-columns: 1fr; }
|
||||
.sysinfo-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
Reference in New Issue
Block a user