1
28
frontend/config.js
Executable file
@@ -0,0 +1,28 @@
|
||||
// Frontend configuration (can be overridden by defining window.BenchConfig before loading this file)
|
||||
(function() {
|
||||
window.BenchConfig = window.BenchConfig || {};
|
||||
|
||||
const origin = window.location.origin;
|
||||
const protocol = window.location.protocol;
|
||||
const hostname = window.location.hostname;
|
||||
|
||||
if (!window.BenchConfig.frontendBaseUrl) {
|
||||
window.BenchConfig.frontendBaseUrl = origin;
|
||||
}
|
||||
|
||||
if (!window.BenchConfig.backendApiUrl) {
|
||||
window.BenchConfig.backendApiUrl = `${protocol}//${hostname}:8007/api`;
|
||||
}
|
||||
|
||||
if (!window.BenchConfig.benchScriptPath) {
|
||||
window.BenchConfig.benchScriptPath = '/scripts/bench.sh';
|
||||
}
|
||||
|
||||
if (!window.BenchConfig.apiTokenPlaceholder) {
|
||||
window.BenchConfig.apiTokenPlaceholder = 'YOUR_TOKEN';
|
||||
}
|
||||
|
||||
if (!window.BenchConfig.iperfServer) {
|
||||
window.BenchConfig.iperfServer = '10.0.1.97';
|
||||
}
|
||||
})();
|
||||
13
frontend/css/components.css
Normal file → Executable file
@@ -475,6 +475,9 @@
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.75rem;
|
||||
border: 1px solid var(--bg-tertiary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.tag-primary {
|
||||
@@ -483,6 +486,16 @@
|
||||
border-color: var(--color-info);
|
||||
}
|
||||
|
||||
.tag .remove-tag {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Alert Component */
|
||||
.alert {
|
||||
padding: var(--spacing-md);
|
||||
|
||||
265
frontend/css/main.css
Normal file → Executable file
@@ -16,6 +16,7 @@
|
||||
--color-info: #66d9ef;
|
||||
--color-purple: #ae81ff;
|
||||
--color-yellow: #e6db74;
|
||||
--border-color: #444444;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 0.25rem;
|
||||
@@ -28,6 +29,12 @@
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
|
||||
/* Icon sizing (customisable) */
|
||||
--section-icon-size: 32px;
|
||||
--button-icon-size: 24px;
|
||||
--icon-btn-size: 42px;
|
||||
--icon-btn-icon-size: 26px;
|
||||
}
|
||||
|
||||
/* Reset & Base */
|
||||
@@ -198,6 +205,264 @@ td {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Device sections */
|
||||
.device-section {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--bg-tertiary);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.device-section .section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
border-bottom: 1px solid var(--bg-tertiary);
|
||||
padding-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.device-section .section-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
color: var(--color-info);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.section-icon-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: var(--section-icon-size);
|
||||
height: var(--section-icon-size);
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 0 2px rgba(0,0,0,0.4));
|
||||
}
|
||||
|
||||
.section-title {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--bg-tertiary);
|
||||
border-radius: 50%;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.icon-btn img {
|
||||
width: var(--button-icon-size);
|
||||
height: var(--button-icon-size);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.icon-btn:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: var(--color-info);
|
||||
}
|
||||
|
||||
.icon-btn.danger {
|
||||
border-color: var(--color-danger);
|
||||
}
|
||||
|
||||
.icon-btn.success {
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
.doc-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.doc-actions .icon-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.device-preamble {
|
||||
border: 1px solid var(--bg-tertiary);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-md);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.preamble-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.preamble-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.preamble-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.preamble-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.header-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.header-value {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.header-meta {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.header-stat {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.usage-pill {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.usage-pill.ok {
|
||||
background: rgba(166, 226, 46, 0.2);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.usage-pill.medium {
|
||||
background: rgba(253, 151, 31, 0.2);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.usage-pill.high {
|
||||
background: rgba(249, 38, 114, 0.2);
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.usage-pill.muted {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.inline-form,
|
||||
.links-form,
|
||||
.tag-form {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.inline-form input,
|
||||
.links-form input,
|
||||
.tag-form input {
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.link-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* Device list actions */
|
||||
.device-list-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device-list-delete {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-danger);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.2rem;
|
||||
transition: transform 0.2s ease;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.device-list-delete:hover {
|
||||
transform: scale(1.2);
|
||||
filter: brightness(1.3);
|
||||
}
|
||||
|
||||
/* Markdown blocks */
|
||||
.markdown-block {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--bg-tertiary);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--spacing-sm);
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown-block code {
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 0 0.25rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
10
frontend/device_detail.html
Normal file → Executable file
@@ -6,6 +6,8 @@
|
||||
<title>Device Detail - Linux BenchTools</title>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/components.css">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon/icons8-devices-3d-fluency-32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon/icons8-devices-3d-fluency-16.png">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
@@ -32,12 +34,15 @@
|
||||
<div id="deviceContent" style="display: none;">
|
||||
<!-- Device Header -->
|
||||
<div class="card">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; gap: 1rem;">
|
||||
<div>
|
||||
<h2 id="deviceHostname" style="color: var(--color-success); margin-bottom: 0.5rem;">--</h2>
|
||||
<p id="deviceDescription" style="color: var(--text-secondary);">--</p>
|
||||
</div>
|
||||
<div id="globalScoreContainer"></div>
|
||||
<div style="display: flex; gap: 0.75rem; align-items: flex-start;">
|
||||
<div id="globalScoreContainer"></div>
|
||||
<button id="deleteDeviceBtn" class="btn btn-danger btn-sm">🗑️ Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="deviceMeta" style="margin-top: 1rem; display: flex; gap: 1.5rem; flex-wrap: wrap;"></div>
|
||||
@@ -214,6 +219,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="config.js"></script>
|
||||
<script src="js/utils.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/device_detail.js"></script>
|
||||
|
||||
19
frontend/devices.html
Normal file → Executable file
@@ -6,6 +6,8 @@
|
||||
<title>Devices - Linux BenchTools</title>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/components.css">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon/icons8-devices-3d-fluency-32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon/icons8-devices-3d-fluency-16.png">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Compact Header -->
|
||||
@@ -58,7 +60,24 @@
|
||||
<p>© 2025 Linux BenchTools - Self-hosted benchmarking tool</p>
|
||||
</footer>
|
||||
|
||||
<!-- Modal for Benchmark Details -->
|
||||
<div id="benchmarkModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">Détails du Benchmark</h3>
|
||||
<button class="modal-close">×</button>
|
||||
</div>
|
||||
<div class="modal-body" id="benchmarkModalBody">
|
||||
<div class="loading">Chargement...</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="BenchUtils.closeModal('benchmarkModal')">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="config.js"></script>
|
||||
<script src="js/utils.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/devices.js"></script>
|
||||
|
||||
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-16.png
Executable file
|
After Width: | Height: | Size: 832 B |
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-32.png
Executable file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-57.png
Executable file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-60.png
Executable file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-70.png
Executable file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-72.png
Executable file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-76.png
Executable file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
frontend/icons/favicon/icons8-devices-3d-fluency-96.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
frontend/icons/icons8-benchmark-64.png
Executable file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
frontend/icons/icons8-bios-94.png
Executable file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
frontend/icons/icons8-check-mark-48.png
Executable file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/icons/icons8-close-48.png
Executable file
|
After Width: | Height: | Size: 908 B |
BIN
frontend/icons/icons8-debian-48.png
Executable file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
frontend/icons/icons8-delete-48.png
Executable file
|
After Width: | Height: | Size: 965 B |
BIN
frontend/icons/icons8-done-48.png
Executable file
|
After Width: | Height: | Size: 776 B |
BIN
frontend/icons/icons8-edit-pencil-48.png
Executable file
|
After Width: | Height: | Size: 526 B |
BIN
frontend/icons/icons8-ethernet-on-94.png
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
frontend/icons/icons8-gpu-64.png
Executable file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
frontend/icons/icons8-hardware-64.png
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
frontend/icons/icons8-hdd-94.png
Executable file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
frontend/icons/icons8-laptop-50.png
Executable file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
frontend/icons/icons8-memory-slot-94.png
Executable file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
frontend/icons/icons8-motherboard-94.png
Executable file
|
After Width: | Height: | Size: 13 KiB |
BIN
frontend/icons/icons8-network-cable-94.png
Executable file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
frontend/icons/icons8-operating-system-64.png
Executable file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
frontend/icons/icons8-pcie-48.png
Executable file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
frontend/icons/icons8-picture-48.png
Executable file
|
After Width: | Height: | Size: 936 B |
BIN
frontend/icons/icons8-processor-64.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
frontend/icons/icons8-processor-94.png
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
frontend/icons/icons8-ram-64.png
Executable file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
frontend/icons/icons8-save-48.png
Executable file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
frontend/icons/icons8-server-94.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
frontend/icons/icons8-setting-48.png
Executable file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
frontend/icons/icons8-shared-folder-94.png
Executable file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
frontend/icons/icons8-ssd-94.png
Executable file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
frontend/icons/icons8-usb-memory-stick-94.png
Executable file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
frontend/icons/icons8-windows-11-48.png
Executable file
|
After Width: | Height: | Size: 192 B |
BIN
frontend/icons/icons8-workstation-94.png
Executable file
|
After Width: | Height: | Size: 13 KiB |
5
frontend/index.html
Normal file → Executable file
@@ -6,6 +6,8 @@
|
||||
<title>Linux BenchTools - Dashboard</title>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/components.css">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon/icons8-devices-3d-fluency-32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon/icons8-devices-3d-fluency-16.png">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
@@ -77,7 +79,7 @@
|
||||
</p>
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" onclick="copyBenchCommand()">Copier</button>
|
||||
<code id="benchCommand">curl -s http://VOTRE_SERVEUR/scripts/bench.sh | bash -s -- --server http://VOTRE_SERVEUR:8007/api/benchmark --token YOUR_TOKEN</code>
|
||||
<code id="benchCommand">Chargement...</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -99,6 +101,7 @@
|
||||
</footer>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="config.js"></script>
|
||||
<script src="js/utils.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/dashboard.js"></script>
|
||||
|
||||
11
frontend/js/api.js
Normal file → Executable file
@@ -1,6 +1,7 @@
|
||||
// Linux BenchTools - API Client
|
||||
|
||||
const API_BASE_URL = window.location.protocol + '//' + window.location.hostname + ':8007/api';
|
||||
const API_BASE_URL = (window.BenchConfig && window.BenchConfig.backendApiUrl)
|
||||
|| `${window.location.protocol}//${window.location.hostname}:8007/api`;
|
||||
|
||||
class BenchAPI {
|
||||
constructor(baseURL = API_BASE_URL) {
|
||||
@@ -143,6 +144,14 @@ class BenchAPI {
|
||||
return this.get(`/benchmarks/${benchmarkId}`);
|
||||
}
|
||||
|
||||
// Update benchmark fields
|
||||
async updateBenchmark(benchmarkId, data) {
|
||||
return this.request(`/benchmarks/${benchmarkId}`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
// Get all benchmarks
|
||||
async getAllBenchmarks(params = {}) {
|
||||
return this.get('/benchmarks', params);
|
||||
|
||||
45
frontend/js/dashboard.js
Normal file → Executable file
@@ -6,6 +6,23 @@ const api = window.BenchAPI;
|
||||
// Global state
|
||||
let allDevices = [];
|
||||
let isLoading = false;
|
||||
let apiToken = null;
|
||||
let iperfServer = null;
|
||||
|
||||
// Load backend config (API token, etc.)
|
||||
async function loadBackendConfig() {
|
||||
try {
|
||||
const response = await fetch(`${window.BenchConfig.backendApiUrl}/config`);
|
||||
if (response.ok) {
|
||||
const config = await response.json();
|
||||
apiToken = config.api_token;
|
||||
iperfServer = config.iperf_server || '10.0.1.97';
|
||||
updateBenchCommandDisplay();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load backend config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load dashboard data
|
||||
async function loadDashboard() {
|
||||
@@ -83,7 +100,7 @@ async function loadStats() {
|
||||
}
|
||||
});
|
||||
|
||||
const avgScore = scoreCount > 0 ? Math.round(scoreSum / scoreCount) : 0;
|
||||
const avgScore = scoreCount > 0 ? Math.ceil(scoreSum / scoreCount) : 0;
|
||||
|
||||
// Update UI
|
||||
document.getElementById('totalDevices').textContent = totalDevices;
|
||||
@@ -221,6 +238,26 @@ function createDeviceRow(device, rank) {
|
||||
`;
|
||||
}
|
||||
|
||||
function buildBenchCommand() {
|
||||
const cfg = window.BenchConfig || {};
|
||||
const frontendBase = (cfg.frontendBaseUrl || window.location.origin).replace(/\/$/, '');
|
||||
const scriptPath = cfg.benchScriptPath || '/scripts/bench.sh';
|
||||
const backendBase = (cfg.backendApiUrl || `${window.location.protocol}//${window.location.hostname}:8007/api`).replace(/\/$/, '');
|
||||
const token = apiToken || 'LOADING...';
|
||||
const iperf = iperfServer || '10.0.1.97';
|
||||
|
||||
// Extract backend URL without /api suffix
|
||||
const backendUrl = backendBase.replace(/\/api$/, '');
|
||||
|
||||
return `curl -fsSL ${frontendBase}${scriptPath} | sudo bash -s -- --server ${backendUrl} --token "${token}" --iperf-server ${iperf}`;
|
||||
}
|
||||
|
||||
function updateBenchCommandDisplay() {
|
||||
const element = document.getElementById('benchCommand');
|
||||
if (!element) return;
|
||||
element.textContent = buildBenchCommand();
|
||||
}
|
||||
|
||||
// Copy bench command to clipboard
|
||||
async function copyBenchCommand() {
|
||||
const command = document.getElementById('benchCommand').textContent;
|
||||
@@ -282,7 +319,11 @@ function refreshDashboard() {
|
||||
}
|
||||
|
||||
// Initialize dashboard on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Load backend config first to get API token
|
||||
await loadBackendConfig();
|
||||
|
||||
// Then load dashboard data
|
||||
loadDashboard();
|
||||
|
||||
// Setup search input listener
|
||||
|
||||
152
frontend/js/device_detail.js
Normal file → Executable file
@@ -1,6 +1,6 @@
|
||||
// Linux BenchTools - Device Detail Logic
|
||||
|
||||
const { formatDate, formatRelativeTime, formatFileSize, createScoreBadge, getScoreBadgeText, escapeHtml, showError, showEmptyState, formatTags, initTabs, openModal, showToast, formatHardwareInfo } = window.BenchUtils;
|
||||
const { formatDate, formatRelativeTime, formatFileSize, createScoreBadge, getScoreBadgeText, escapeHtml, showError, showEmptyState, formatTags, initTabs, openModal, showToast, formatHardwareInfo, formatDuration, formatStorage } = window.BenchUtils;
|
||||
const api = window.BenchAPI;
|
||||
|
||||
let currentDeviceId = null;
|
||||
@@ -46,6 +46,11 @@ async function loadDeviceDetail() {
|
||||
await loadDocuments();
|
||||
await loadLinks();
|
||||
|
||||
const deleteBtn = document.getElementById('deleteDeviceBtn');
|
||||
if (deleteBtn) {
|
||||
deleteBtn.addEventListener('click', handleDeleteDevice);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load device:', error);
|
||||
document.getElementById('loadingState').innerHTML =
|
||||
@@ -300,7 +305,7 @@ function renderStorageDetails() {
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.75rem; font-size: 0.9rem;">
|
||||
${disk.capacity_gb ? `
|
||||
<div>
|
||||
<strong>Capacité:</strong> ${disk.capacity_gb} GB
|
||||
<strong>Capacité:</strong> ${formatStorage(disk.capacity_gb)}
|
||||
</div>
|
||||
` : ''}
|
||||
${disk.type ? `
|
||||
@@ -331,7 +336,114 @@ function renderStorageDetails() {
|
||||
console.error('Failed to parse storage devices:', e);
|
||||
html = '<p style="color: var(--text-danger);">Erreur lors du parsing des données de stockage</p>';
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
// Parse partitions
|
||||
if (snapshot.partitions_json) {
|
||||
try {
|
||||
const partitions = typeof snapshot.partitions_json === 'string'
|
||||
? JSON.parse(snapshot.partitions_json)
|
||||
: snapshot.partitions_json;
|
||||
|
||||
if (Array.isArray(partitions) && partitions.length > 0) {
|
||||
html += `
|
||||
<div style="margin-top: 1.5rem;">
|
||||
<h4 style="margin-bottom: 0.75rem; color: var(--text-secondary);">Partitions et volumes</h4>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="text-align: left; border-bottom: 1px solid var(--border-color); color: var(--text-secondary);">
|
||||
<th style="padding: 0.5rem;">Partition</th>
|
||||
<th style="padding: 0.5rem;">Montage</th>
|
||||
<th style="padding: 0.5rem;">Type</th>
|
||||
<th style="padding: 0.5rem;">Utilisé</th>
|
||||
<th style="padding: 0.5rem;">Libre</th>
|
||||
<th style="padding: 0.5rem;">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${partitions.map(part => {
|
||||
const used = typeof part.used_gb === 'number' ? formatStorage(part.used_gb) : 'N/A';
|
||||
const free = typeof part.free_gb === 'number'
|
||||
? formatStorage(part.free_gb)
|
||||
: (typeof part.total_gb === 'number' && typeof part.used_gb === 'number'
|
||||
? formatStorage(part.total_gb - part.used_gb)
|
||||
: 'N/A');
|
||||
const total = typeof part.total_gb === 'number' ? formatStorage(part.total_gb) : 'N/A';
|
||||
|
||||
return `
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 0.5rem; font-weight: 600;">${escapeHtml(part.name || 'N/A')}</td>
|
||||
<td style="padding: 0.5rem;">${part.mount_point ? escapeHtml(part.mount_point) : '<span style="color: var(--text-muted);">Non monté</span>'}</td>
|
||||
<td style="padding: 0.5rem;">${part.fs_type ? escapeHtml(part.fs_type) : 'N/A'}</td>
|
||||
<td style="padding: 0.5rem;">${used}</td>
|
||||
<td style="padding: 0.5rem;">${free}</td>
|
||||
<td style="padding: 0.5rem;">${total}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse partitions:', error);
|
||||
html += '<p style="color: var(--color-danger); margin-top: 1rem;">Erreur lors de la lecture des partitions</p>';
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshot.network_shares_json) {
|
||||
try {
|
||||
const shares = typeof snapshot.network_shares_json === 'string'
|
||||
? JSON.parse(snapshot.network_shares_json)
|
||||
: snapshot.network_shares_json;
|
||||
|
||||
if (Array.isArray(shares) && shares.length > 0) {
|
||||
html += `
|
||||
<div style="margin-top: 1.5rem;">
|
||||
<h4 style="margin-bottom: 0.75rem; color: var(--text-secondary);">Partages réseau montés</h4>
|
||||
<div style="overflow-x: auto;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="text-align: left; border-bottom: 1px solid var(--border-color); color: var(--text-secondary);">
|
||||
<th style="padding: 0.5rem;">Source</th>
|
||||
<th style="padding: 0.5rem;">Montage</th>
|
||||
<th style="padding: 0.5rem;">Protocole</th>
|
||||
<th style="padding: 0.5rem;">Type</th>
|
||||
<th style="padding: 0.5rem;">Utilisé</th>
|
||||
<th style="padding: 0.5rem;">Libre</th>
|
||||
<th style="padding: 0.5rem;">Total</th>
|
||||
<th style="padding: 0.5rem;">Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${shares.map(share => `
|
||||
<tr style="border-bottom: 1px solid var(--border-color);">
|
||||
<td style="padding: 0.5rem;">${escapeHtml(share.source || 'N/A')}</td>
|
||||
<td style="padding: 0.5rem;">${escapeHtml(share.mount_point || 'N/A')}</td>
|
||||
<td style="padding: 0.5rem;">${escapeHtml(share.protocol || share.fs_type || 'N/A')}</td>
|
||||
<td style="padding: 0.5rem;">${share.fs_type ? escapeHtml(share.fs_type) : 'N/A'}</td>
|
||||
<td style="padding: 0.5rem;">${typeof share.used_gb === 'number' ? formatStorage(share.used_gb) : 'N/A'}</td>
|
||||
<td style="padding: 0.5rem;">${typeof share.free_gb === 'number' ? formatStorage(share.free_gb) : 'N/A'}</td>
|
||||
<td style="padding: 0.5rem;">${typeof share.total_gb === 'number' ? formatStorage(share.total_gb) : 'N/A'}</td>
|
||||
<td style="padding: 0.5rem; max-width: 200px;">${share.options ? escapeHtml(share.options) : '<span style="color: var(--text-muted);">N/A</span>'}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse network shares:', error);
|
||||
html += '<p style="color: var(--color-danger); margin-top: 1rem;">Erreur lors de la lecture des partages réseau</p>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!html) {
|
||||
html = '<p style="color: var(--text-muted);">Aucune information de stockage disponible</p>';
|
||||
}
|
||||
|
||||
@@ -407,11 +519,25 @@ function renderOSDetails() {
|
||||
return;
|
||||
}
|
||||
|
||||
const displayServer = snapshot.display_server || snapshot.session_type || 'N/A';
|
||||
const resolution = snapshot.screen_resolution || 'N/A';
|
||||
const lastBoot = snapshot.last_boot_time || 'N/A';
|
||||
const uptime = snapshot.uptime_seconds != null ? formatDuration(snapshot.uptime_seconds) : 'N/A';
|
||||
const battery = snapshot.battery_percentage != null
|
||||
? `${snapshot.battery_percentage}%${snapshot.battery_status ? ` (${snapshot.battery_status})` : ''}`
|
||||
: 'N/A';
|
||||
|
||||
const items = [
|
||||
{ label: 'Nom', value: snapshot.os_name || 'N/A' },
|
||||
{ label: 'Version', value: snapshot.os_version || 'N/A' },
|
||||
{ label: 'Kernel', value: snapshot.kernel_version || 'N/A' },
|
||||
{ label: 'Architecture', value: snapshot.architecture || 'N/A' },
|
||||
{ label: 'Environnement', value: snapshot.desktop_environment || 'N/A' },
|
||||
{ label: 'Session', value: displayServer },
|
||||
{ label: 'Résolution écran', value: resolution },
|
||||
{ label: 'Dernier boot', value: lastBoot },
|
||||
{ label: 'Uptime', value: uptime },
|
||||
{ label: 'Batterie', value: battery },
|
||||
{ label: 'Virtualisation', value: snapshot.virtualization_type || 'none' }
|
||||
];
|
||||
|
||||
@@ -427,6 +553,26 @@ function renderOSDetails() {
|
||||
`;
|
||||
}
|
||||
|
||||
async function handleDeleteDevice() {
|
||||
if (!currentDevice) return;
|
||||
|
||||
const confirmed = confirm(`Voulez-vous vraiment supprimer le device "${currentDevice.hostname}" ? Cette action est définitive.`);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteDevice(currentDevice.id);
|
||||
showToast('Device supprimé avec succès', 'success');
|
||||
setTimeout(() => {
|
||||
window.location.href = 'devices.html';
|
||||
}, 800);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete device:', error);
|
||||
showToast(error.message || 'Impossible de supprimer le device', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Render Benchmark Results
|
||||
function renderBenchmarkResults() {
|
||||
const bench = currentDevice.last_benchmark;
|
||||
|
||||
1812
frontend/js/devices.js
Normal file → Executable file
104
frontend/js/settings.js
Normal file → Executable file
@@ -3,14 +3,103 @@
|
||||
const { copyToClipboard, showToast, escapeHtml } = window.BenchUtils;
|
||||
|
||||
let tokenVisible = false;
|
||||
const API_TOKEN = 'YOUR_API_TOKEN_HERE'; // Will be replaced by actual token or fetched from backend
|
||||
let API_TOKEN = 'LOADING...';
|
||||
|
||||
// Load backend config (API token, etc.)
|
||||
async function loadBackendConfig() {
|
||||
try {
|
||||
const backendApiUrl = window.BenchConfig?.backendApiUrl || `${window.location.protocol}//${window.location.hostname}:8007/api`;
|
||||
const response = await fetch(`${backendApiUrl}/config`);
|
||||
if (response.ok) {
|
||||
const config = await response.json();
|
||||
API_TOKEN = config.api_token;
|
||||
document.getElementById('apiToken').value = API_TOKEN;
|
||||
generateBenchCommand();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load backend config:', error);
|
||||
API_TOKEN = 'ERROR_LOADING_TOKEN';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize settings page
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
loadDisplayPreferences();
|
||||
loadSettings();
|
||||
generateBenchCommand();
|
||||
await loadBackendConfig();
|
||||
});
|
||||
|
||||
// Load display preferences
|
||||
function loadDisplayPreferences() {
|
||||
const memoryUnit = localStorage.getItem('displayPref_memoryUnit') || 'GB';
|
||||
const storageUnit = localStorage.getItem('displayPref_storageUnit') || 'GB';
|
||||
const cacheUnit = localStorage.getItem('displayPref_cacheUnit') || 'KB';
|
||||
const temperatureUnit = localStorage.getItem('displayPref_temperatureUnit') || 'C';
|
||||
const sectionIconSize = localStorage.getItem('displayPref_sectionIconSize') || '32';
|
||||
const buttonIconSize = localStorage.getItem('displayPref_buttonIconSize') || '24';
|
||||
|
||||
document.getElementById('memoryUnit').value = memoryUnit;
|
||||
document.getElementById('storageUnit').value = storageUnit;
|
||||
document.getElementById('cacheUnit').value = cacheUnit;
|
||||
document.getElementById('temperatureUnit').value = temperatureUnit;
|
||||
document.getElementById('sectionIconSize').value = sectionIconSize;
|
||||
document.getElementById('buttonIconSize').value = buttonIconSize;
|
||||
|
||||
// Apply icon sizes
|
||||
applyIconSizes(sectionIconSize, buttonIconSize);
|
||||
}
|
||||
|
||||
// Apply icon sizes dynamically
|
||||
function applyIconSizes(sectionIconSize, buttonIconSize) {
|
||||
document.documentElement.style.setProperty('--section-icon-size', `${sectionIconSize}px`);
|
||||
document.documentElement.style.setProperty('--button-icon-size', `${buttonIconSize}px`);
|
||||
}
|
||||
|
||||
// Save display preferences
|
||||
function saveDisplayPreferences() {
|
||||
const memoryUnit = document.getElementById('memoryUnit').value;
|
||||
const storageUnit = document.getElementById('storageUnit').value;
|
||||
const cacheUnit = document.getElementById('cacheUnit').value;
|
||||
const temperatureUnit = document.getElementById('temperatureUnit').value;
|
||||
const sectionIconSize = document.getElementById('sectionIconSize').value;
|
||||
const buttonIconSize = document.getElementById('buttonIconSize').value;
|
||||
|
||||
localStorage.setItem('displayPref_memoryUnit', memoryUnit);
|
||||
localStorage.setItem('displayPref_storageUnit', storageUnit);
|
||||
localStorage.setItem('displayPref_cacheUnit', cacheUnit);
|
||||
localStorage.setItem('displayPref_temperatureUnit', temperatureUnit);
|
||||
localStorage.setItem('displayPref_sectionIconSize', sectionIconSize);
|
||||
localStorage.setItem('displayPref_buttonIconSize', buttonIconSize);
|
||||
|
||||
// Apply icon sizes immediately
|
||||
applyIconSizes(sectionIconSize, buttonIconSize);
|
||||
|
||||
showToast('Préférences enregistrées ! Rechargez la page pour voir les changements.', 'success');
|
||||
}
|
||||
|
||||
// Reset display preferences to defaults
|
||||
function resetDisplayPreferences() {
|
||||
localStorage.removeItem('displayPref_memoryUnit');
|
||||
localStorage.removeItem('displayPref_storageUnit');
|
||||
localStorage.removeItem('displayPref_cacheUnit');
|
||||
localStorage.removeItem('displayPref_temperatureUnit');
|
||||
localStorage.removeItem('displayPref_sectionIconSize');
|
||||
localStorage.removeItem('displayPref_buttonIconSize');
|
||||
|
||||
// Reset form to defaults
|
||||
document.getElementById('memoryUnit').value = 'GB';
|
||||
document.getElementById('storageUnit').value = 'GB';
|
||||
document.getElementById('cacheUnit').value = 'KB';
|
||||
document.getElementById('temperatureUnit').value = 'C';
|
||||
document.getElementById('sectionIconSize').value = '32';
|
||||
document.getElementById('buttonIconSize').value = '24';
|
||||
|
||||
// Apply default icon sizes
|
||||
applyIconSizes('32', '24');
|
||||
|
||||
showToast('Préférences réinitialisées aux valeurs par défaut', 'success');
|
||||
}
|
||||
|
||||
// Load settings
|
||||
function loadSettings() {
|
||||
// In a real scenario, these would be fetched from backend or localStorage
|
||||
@@ -22,8 +111,7 @@ function loadSettings() {
|
||||
document.getElementById('iperfServer').value = savedIperfServer;
|
||||
document.getElementById('benchMode').value = savedBenchMode;
|
||||
|
||||
// Set API token (in production, this should be fetched securely)
|
||||
document.getElementById('apiToken').value = API_TOKEN;
|
||||
// API token will be loaded asynchronously from backend
|
||||
|
||||
// Add event listeners for auto-generation
|
||||
document.getElementById('backendUrl').addEventListener('input', () => {
|
||||
@@ -74,8 +162,8 @@ function generateBenchCommand() {
|
||||
const scriptUrl = `${backendUrl.replace(':8007', ':8087')}/scripts/bench.sh`;
|
||||
|
||||
// Build command parts
|
||||
let command = `curl -s ${scriptUrl} | bash -s -- \\
|
||||
--server ${backendUrl}/api/benchmark \\
|
||||
let command = `curl -s ${scriptUrl} | sudo bash -s -- \\
|
||||
--server ${backendUrl} \\
|
||||
--token "${API_TOKEN}"`;
|
||||
|
||||
if (iperfServer) {
|
||||
@@ -143,3 +231,5 @@ window.generateBenchCommand = generateBenchCommand;
|
||||
window.copyGeneratedCommand = copyGeneratedCommand;
|
||||
window.toggleTokenVisibility = toggleTokenVisibility;
|
||||
window.copyToken = copyToken;
|
||||
window.saveDisplayPreferences = saveDisplayPreferences;
|
||||
window.resetDisplayPreferences = resetDisplayPreferences;
|
||||
|
||||
127
frontend/js/utils.js
Normal file → Executable file
@@ -40,6 +40,26 @@ function formatFileSize(bytes) {
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Format duration (seconds) into human readable form
|
||||
function formatDuration(seconds) {
|
||||
if (seconds === null || seconds === undefined) return 'N/A';
|
||||
const totalSeconds = Number(seconds);
|
||||
if (!Number.isFinite(totalSeconds) || totalSeconds < 0) return 'N/A';
|
||||
|
||||
const days = Math.floor(totalSeconds / 86400);
|
||||
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const secs = Math.floor(totalSeconds % 60);
|
||||
|
||||
const parts = [];
|
||||
if (days > 0) parts.push(`${days} j`);
|
||||
if (hours > 0) parts.push(`${hours} h`);
|
||||
if (minutes > 0) parts.push(`${minutes} min`);
|
||||
if (parts.length === 0) parts.push(`${secs} s`);
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
// Get score badge class based on value
|
||||
function getScoreBadgeClass(score) {
|
||||
if (score === null || score === undefined) return 'score-badge';
|
||||
@@ -51,7 +71,9 @@ function getScoreBadgeClass(score) {
|
||||
// Get score badge text
|
||||
function getScoreBadgeText(score) {
|
||||
if (score === null || score === undefined) return '--';
|
||||
return Math.round(score);
|
||||
const numeric = Number(score);
|
||||
if (!Number.isFinite(numeric)) return '--';
|
||||
return Math.ceil(numeric);
|
||||
}
|
||||
|
||||
// Create score badge HTML
|
||||
@@ -299,8 +321,21 @@ function closeModal(modalId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize modal close buttons
|
||||
// Apply icon size preferences from localStorage
|
||||
function applyIconSizePreferences() {
|
||||
const sectionIconSize = localStorage.getItem('displayPref_sectionIconSize') || '32';
|
||||
const buttonIconSize = localStorage.getItem('displayPref_buttonIconSize') || '24';
|
||||
|
||||
document.documentElement.style.setProperty('--section-icon-size', `${sectionIconSize}px`);
|
||||
document.documentElement.style.setProperty('--button-icon-size', `${buttonIconSize}px`);
|
||||
}
|
||||
|
||||
// Initialize modal close buttons and apply icon preferences
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Apply icon size preferences on all pages
|
||||
applyIconSizePreferences();
|
||||
|
||||
// Initialize modal close buttons
|
||||
document.querySelectorAll('.modal').forEach(modal => {
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
@@ -318,10 +353,90 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// Export functions for use in other files
|
||||
// Unit conversion functions
|
||||
function getDisplayPreferences() {
|
||||
return {
|
||||
memoryUnit: localStorage.getItem('displayPref_memoryUnit') || 'GB',
|
||||
storageUnit: localStorage.getItem('displayPref_storageUnit') || 'GB',
|
||||
cacheUnit: localStorage.getItem('displayPref_cacheUnit') || 'KB',
|
||||
temperatureUnit: localStorage.getItem('displayPref_temperatureUnit') || 'C'
|
||||
};
|
||||
}
|
||||
|
||||
// Convert memory from MB to preferred unit
|
||||
function formatMemory(mb, forceUnit = null) {
|
||||
if (!mb || mb === 0) return '0 MB';
|
||||
|
||||
const prefs = getDisplayPreferences();
|
||||
const unit = forceUnit || prefs.memoryUnit;
|
||||
|
||||
if (unit === 'GB') {
|
||||
return (mb / 1024).toFixed(2) + ' GB';
|
||||
}
|
||||
return mb.toFixed(0) + ' MB';
|
||||
}
|
||||
|
||||
// Convert storage from GB to preferred unit
|
||||
function formatStorage(gb, forceUnit = null) {
|
||||
if (!gb || gb === 0) return '0 GB';
|
||||
|
||||
const prefs = getDisplayPreferences();
|
||||
const unit = forceUnit || prefs.storageUnit;
|
||||
|
||||
if (unit === 'TB') {
|
||||
return (gb / 1024).toFixed(2) + ' TB';
|
||||
} else if (unit === 'MB') {
|
||||
return (gb * 1024).toFixed(0) + ' MB';
|
||||
}
|
||||
return gb.toFixed(2) + ' GB';
|
||||
}
|
||||
|
||||
// Convert cache from KB to preferred unit
|
||||
function formatCache(kb, forceUnit = null) {
|
||||
if (!kb || kb === 0) return '0 KB';
|
||||
|
||||
const prefs = getDisplayPreferences();
|
||||
const unit = forceUnit || prefs.cacheUnit;
|
||||
|
||||
if (unit === 'MB') {
|
||||
return (kb / 1024).toFixed(2) + ' MB';
|
||||
}
|
||||
return kb.toFixed(0) + ' KB';
|
||||
}
|
||||
|
||||
// Convert temperature to preferred unit
|
||||
function formatTemperature(celsius, forceUnit = null) {
|
||||
if (celsius === null || celsius === undefined) return 'N/A';
|
||||
|
||||
const prefs = getDisplayPreferences();
|
||||
const unit = forceUnit || prefs.temperatureUnit;
|
||||
|
||||
if (unit === 'F') {
|
||||
const fahrenheit = (celsius * 9/5) + 32;
|
||||
return fahrenheit.toFixed(1) + '°F';
|
||||
}
|
||||
return celsius.toFixed(1) + '°C';
|
||||
}
|
||||
|
||||
function renderMarkdown(text) {
|
||||
if (!text || !text.trim()) {
|
||||
return '<div class="markdown-block" style="color: var(--text-secondary);">Aucune note disponible</div>';
|
||||
}
|
||||
|
||||
let html = escapeHtml(text);
|
||||
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
||||
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
html = html.replace(/\n/g, '<br>');
|
||||
|
||||
return `<div class="markdown-block">${html}</div>`;
|
||||
}
|
||||
|
||||
window.BenchUtils = {
|
||||
formatDate,
|
||||
formatRelativeTime,
|
||||
formatFileSize,
|
||||
formatDuration,
|
||||
getScoreBadgeClass,
|
||||
getScoreBadgeText,
|
||||
createScoreBadge,
|
||||
@@ -340,5 +455,11 @@ window.BenchUtils = {
|
||||
formatHardwareInfo,
|
||||
initTabs,
|
||||
openModal,
|
||||
closeModal
|
||||
closeModal,
|
||||
getDisplayPreferences,
|
||||
formatMemory,
|
||||
formatStorage,
|
||||
formatCache,
|
||||
formatTemperature,
|
||||
renderMarkdown
|
||||
};
|
||||
|
||||
75
frontend/settings.html
Normal file → Executable file
@@ -6,6 +6,8 @@
|
||||
<title>Settings - Linux BenchTools</title>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/components.css">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon/icons8-devices-3d-fluency-32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon/icons8-devices-3d-fluency-16.png">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
@@ -25,6 +27,78 @@
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container">
|
||||
<!-- Display Preferences -->
|
||||
<div class="card">
|
||||
<div class="card-header">🎨 Préférences d'affichage</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info" style="margin-bottom: 1.5rem;">
|
||||
Configurez les unités d'affichage pour les valeurs matérielles
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Unité de mémoire (RAM)</label>
|
||||
<select id="memoryUnit" class="form-control">
|
||||
<option value="MB">Mégaoctets (MB)</option>
|
||||
<option value="GB" selected>Gigaoctets (GB)</option>
|
||||
</select>
|
||||
<small style="color: var(--text-muted);">Affiche la RAM en MB ou GB dans les sections matérielles</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Unité de stockage (Disques)</label>
|
||||
<select id="storageUnit" class="form-control">
|
||||
<option value="MB">Mégaoctets (MB)</option>
|
||||
<option value="GB" selected>Gigaoctets (GB)</option>
|
||||
<option value="TB">Téraoctets (TB)</option>
|
||||
</select>
|
||||
<small style="color: var(--text-muted);">Affiche la capacité des disques en MB, GB ou TB</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Unité de cache CPU</label>
|
||||
<select id="cacheUnit" class="form-control">
|
||||
<option value="KB" selected>Kilooctets (KB)</option>
|
||||
<option value="MB">Mégaoctets (MB)</option>
|
||||
</select>
|
||||
<small style="color: var(--text-muted);">Affiche les caches L1/L2/L3 en KB ou MB</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Unité de température</label>
|
||||
<select id="temperatureUnit" class="form-control">
|
||||
<option value="C" selected>Celsius (°C)</option>
|
||||
<option value="F">Fahrenheit (°F)</option>
|
||||
</select>
|
||||
<small style="color: var(--text-muted);">Affiche la température des composants en Celsius ou Fahrenheit</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Taille des icônes de section</label>
|
||||
<select id="sectionIconSize" class="form-control">
|
||||
<option value="24">Petite (24px)</option>
|
||||
<option value="28">Moyenne (28px)</option>
|
||||
<option value="32" selected>Grande (32px)</option>
|
||||
<option value="36">Très grande (36px)</option>
|
||||
</select>
|
||||
<small style="color: var(--text-muted);">Taille des icônes dans les titres de sections</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Taille des icônes de bouton</label>
|
||||
<select id="buttonIconSize" class="form-control">
|
||||
<option value="18">Petite (18px)</option>
|
||||
<option value="22">Moyenne (22px)</option>
|
||||
<option value="24" selected>Grande (24px)</option>
|
||||
<option value="28">Très grande (28px)</option>
|
||||
</select>
|
||||
<small style="color: var(--text-muted);">Taille des icônes dans les boutons d'action</small>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="saveDisplayPreferences()">💾 Enregistrer les préférences</button>
|
||||
<button class="btn btn-secondary" onclick="resetDisplayPreferences()" style="margin-left: 0.5rem;">🔄 Réinitialiser</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bench Script Configuration -->
|
||||
<div class="card">
|
||||
<div class="card-header">⚡ Configuration Benchmark Script</div>
|
||||
@@ -185,6 +259,7 @@
|
||||
</footer>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="config.js"></script>
|
||||
<script src="js/utils.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/settings.js"></script>
|
||||
|
||||