1963 lines
75 KiB
PHP
1963 lines
75 KiB
PHP
<?php
|
|
// PHP Backend API Handler voor AJAX interacties
|
|
if (isset($_GET['action'])) {
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
/**
|
|
* Web & PC Studio - Dev Lab & Webserver Performance Suite
|
|
* Uitgebreide diagnostics & benchmark suite voor webserver capaciteit en doorvoersnelheid.
|
|
*/
|
|
|
|
// Helper functies voor benchmarks
|
|
function benchmarkCPU() {
|
|
$start = microtime(true);
|
|
$iterations = 80000;
|
|
|
|
if ($_GET['action'] === 'benchmark') {
|
|
$iterations = 75000;
|
|
$start = microtime(true);
|
|
$memStart = memory_get_usage();
|
|
$val = 1.0;
|
|
for ($i = 1; $i <= $iterations; $i++) {
|
|
$val = sqrt($val + $i) * sin($i);
|
|
}
|
|
|
|
$hash = 'webenpcstudio';
|
|
for ($i = 0; $i < 40000; $i++) {
|
|
$hash = hash('sha256', $hash . $i);
|
|
}
|
|
|
|
$duration = (microtime(true) - $start) * 1000;
|
|
$ops = round(($iterations + 40000) / (max(0.001, $duration) / 1000));
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'duration_ms' => round($duration, 2),
|
|
'ops_per_sec' => number_format($ops, 0, ',', '.'),
|
|
'raw_ops' => $ops
|
|
];
|
|
}
|
|
|
|
// Rekenkundige en hashing benchmark
|
|
$hash = 'webenpcstudio';
|
|
for ($i = 0; $i < $iterations; $i++) {
|
|
$hash = hash('sha256', $hash . $i);
|
|
function benchmarkMemory() {
|
|
$start = microtime(true);
|
|
$startMem = memory_get_usage();
|
|
|
|
$arr = [];
|
|
for ($i = 0; $i < 50000; $i++) {
|
|
$arr['key_' . $i] = [
|
|
'id' => $i,
|
|
'val' => $i * 1.5,
|
|
'studio' => 'WebenPCStudio'
|
|
];
|
|
}
|
|
|
|
ksort($arr);
|
|
$serialized = serialize($arr);
|
|
$unserialized = unserialize($serialized);
|
|
unset($arr, $serialized, $unserialized);
|
|
|
|
$duration = (microtime(true) - $start) * 1000;
|
|
$peakMem = (memory_get_peak_usage() - $startMem) / (1024 * 1024);
|
|
$throughput = round(150000 / (max(0.001, $duration) / 1000));
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'duration_ms' => round($duration, 2),
|
|
'peak_mem_mb' => round(max(0.1, $peakMem), 2),
|
|
'ops_per_sec' => number_format($throughput, 0, ',', '.'),
|
|
'raw_ops' => $throughput
|
|
];
|
|
}
|
|
|
|
function benchmarkDiskIO() {
|
|
$tempDir = sys_get_temp_dir();
|
|
$tempFile = tempnam($tempDir, 'wpc_bench_');
|
|
if (!$tempFile) {
|
|
return ['status' => 'error', 'message' => 'Kan tijdelijk bestand niet aanmaken'];
|
|
}
|
|
|
|
$chunkSize = 64 * 1024; // 64 KB
|
|
$chunks = 48; // ~3 MB totaal
|
|
$dummyData = str_repeat('WPCStudio2026_TEST_STRING_ABCD!', 2048); // 64 KB
|
|
|
|
// Write test
|
|
$writeStart = microtime(true);
|
|
$fp = fopen($tempFile, 'wb');
|
|
if (!$fp) {
|
|
@unlink($tempFile);
|
|
return ['status' => 'error', 'message' => 'Kan bestand niet openen'];
|
|
}
|
|
for ($i = 0; $i < $chunks; $i++) {
|
|
fwrite($fp, $dummyData);
|
|
}
|
|
fflush($fp);
|
|
fclose($fp);
|
|
$writeTime = max(0.0005, microtime(true) - $writeStart);
|
|
|
|
// Read test
|
|
$readStart = microtime(true);
|
|
$fp = fopen($tempFile, 'rb');
|
|
if ($fp) {
|
|
while (!feof($fp)) {
|
|
fread($fp, $chunkSize);
|
|
}
|
|
fclose($fp);
|
|
}
|
|
$readTime = max(0.0005, microtime(true) - $readStart);
|
|
|
|
$totalMB = ($chunks * $chunkSize) / (1024 * 1024);
|
|
$writeSpeed = round($totalMB / $writeTime, 1);
|
|
$readSpeed = round($totalMB / $readTime, 1);
|
|
|
|
@unlink($tempFile);
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'file_size_mb' => round($totalMB, 2),
|
|
'write_speed_mb_s' => $writeSpeed,
|
|
'read_speed_mb_s' => $readSpeed,
|
|
'write_time_ms' => round($writeTime * 1000, 1),
|
|
'read_time_ms' => round($readTime * 1000, 1)
|
|
];
|
|
}
|
|
|
|
$duration = (microtime(true) - $start) * 1000; // in milliseconden
|
|
$memPeak = memory_get_peak_usage() / (1024 * 1024); // MB
|
|
$opsPerSec = round($iterations / ($duration / 1000));
|
|
function benchmarkStringJSON() {
|
|
$start = microtime(true);
|
|
$data = [];
|
|
for ($i = 0; $i < 15000; $i++) {
|
|
$data[] = [
|
|
'id' => $i,
|
|
'email' => "user{$i}@webenpcstudio.nl",
|
|
'tags' => ['git', 'php', 'speed', 'server'],
|
|
'active' => ($i % 2 === 0)
|
|
];
|
|
}
|
|
$json = json_encode($data);
|
|
$decoded = json_decode($json, true);
|
|
|
|
preg_match_all('/[a-zA-Z0-9._%+-]+@webenpcstudio\.nl/', $json, $matches);
|
|
|
|
$duration = (microtime(true) - $start) * 1000;
|
|
$throughput = round(15000 / (max(0.001, $duration) / 1000));
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'duration_ms' => round($duration, 2),
|
|
'records_per_sec' => number_format($throughput, 0, ',', '.'),
|
|
'json_size_kb' => round(strlen($json) / 1024, 1),
|
|
'raw_ops' => $throughput
|
|
];
|
|
}
|
|
|
|
echo json_encode([
|
|
function benchmarkDatabase() {
|
|
if (!extension_loaded('pdo_sqlite')) {
|
|
return [
|
|
'status' => 'skipped',
|
|
'message' => 'PDO SQLite niet beschikbaar',
|
|
'tps' => 0,
|
|
'duration_ms' => 0
|
|
];
|
|
}
|
|
try {
|
|
$start = microtime(true);
|
|
$pdo = new PDO('sqlite::memory:');
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
$pdo->exec('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, val REAL)');
|
|
|
|
$pdo->beginTransaction();
|
|
$stmt = $pdo->prepare('INSERT INTO test (name, val) VALUES (?, ?)');
|
|
for ($i = 0; $i < 2500; $i++) {
|
|
$stmt->execute(['item_' . $i, mt_rand(1, 1000) / 10]);
|
|
}
|
|
$pdo->commit();
|
|
|
|
$stmt2 = $pdo->query('SELECT AVG(val), COUNT(*) FROM test WHERE val > 500');
|
|
$stmt2->fetch();
|
|
|
|
$duration = (microtime(true) - $start) * 1000;
|
|
$tps = round(2500 / (max(0.001, $duration) / 1000));
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'iterations' => number_format($iterations, 0, ',', '.'),
|
|
'duration_ms' => round($duration, 2),
|
|
'ops_per_sec' => number_format($opsPerSec, 0, ',', '.'),
|
|
'peak_memory' => round($memPeak, 2) . ' MB',
|
|
'php_version' => PHP_VERSION,
|
|
'final_hash' => substr($hash, 0, 16) . '...'
|
|
'tps' => number_format($tps, 0, ',', '.'),
|
|
'records' => 2500,
|
|
'raw_tps' => $tps
|
|
];
|
|
} catch (Exception $e) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
function getOpcacheInfo() {
|
|
if (function_exists('opcache_get_status') && ($status = @opcache_get_status(false))) {
|
|
$mem = $status['memory_usage'] ?? [];
|
|
return [
|
|
'enabled' => true,
|
|
'used_mb' => round(($mem['used_memory'] ?? 0) / (1024 * 1024), 1),
|
|
'free_mb' => round(($mem['free_memory'] ?? 0) / (1024 * 1024), 1),
|
|
'hit_rate' => round($status['opcache_statistics']['opcache_hit_rate'] ?? 0, 1) . '%',
|
|
'jit' => !empty($status['jit']['enabled']) ? 'Actief' : 'Inactief'
|
|
];
|
|
}
|
|
return [
|
|
'enabled' => false,
|
|
'message' => 'Niet geactiveerd'
|
|
];
|
|
}
|
|
|
|
// ==========================================
|
|
// API Routing voor AJAX Verzoeken
|
|
// ==========================================
|
|
if (isset($_GET['action'])) {
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
// 1. Snelle Ping
|
|
if ($_GET['action'] === 'ping') {
|
|
echo json_encode([
|
|
'status' => 'pong',
|
|
'timestamp' => microtime(true),
|
|
'server_time' => date('H:i:s.u')
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
if ($_GET['action'] === 'hash' && isset($_POST['text'])) {
|
|
$input = (string)$_POST['text'];
|
|
// 2. Afzonderlijke Benchmark Acties
|
|
if ($_GET['action'] === 'bench_cpu') {
|
|
echo json_encode(benchmarkCPU());
|
|
exit;
|
|
}
|
|
if ($_GET['action'] === 'bench_memory') {
|
|
echo json_encode(benchmarkMemory());
|
|
exit;
|
|
}
|
|
if ($_GET['action'] === 'bench_disk') {
|
|
echo json_encode(benchmarkDiskIO());
|
|
exit;
|
|
}
|
|
if ($_GET['action'] === 'bench_string') {
|
|
echo json_encode(benchmarkStringJSON());
|
|
exit;
|
|
}
|
|
if ($_GET['action'] === 'bench_db') {
|
|
echo json_encode(benchmarkDatabase());
|
|
exit;
|
|
}
|
|
|
|
// 3. Volledige Server Benchmark Suite
|
|
if ($_GET['action'] === 'bench_all') {
|
|
$cpu = benchmarkCPU();
|
|
$mem = benchmarkMemory();
|
|
$disk = benchmarkDiskIO();
|
|
$str = benchmarkStringJSON();
|
|
$db = benchmarkDatabase();
|
|
$opcache = getOpcacheInfo();
|
|
|
|
// Bereken gewogen Server Score (0-100)
|
|
$score = 0;
|
|
|
|
// CPU Score (max 25)
|
|
$cpuOps = $cpu['raw_ops'] ?? 0;
|
|
$score += min(25, round(($cpuOps / 150000) * 25));
|
|
|
|
// Memory Score (max 20)
|
|
$memOps = $mem['raw_ops'] ?? 0;
|
|
$score += min(20, round(($memOps / 200000) * 20));
|
|
|
|
// Disk Score (max 25)
|
|
$writeSpd = $disk['write_speed_mb_s'] ?? 0;
|
|
$score += min(25, round(($writeSpd / 300) * 25));
|
|
|
|
// String/JSON Score (max 15)
|
|
$strOps = $str['raw_ops'] ?? 0;
|
|
$score += min(15, round(($strOps / 35000) * 15));
|
|
|
|
// DB / OPcache Score (max 15)
|
|
if ($db['status'] === 'success') {
|
|
$dbTps = $db['raw_tps'] ?? 0;
|
|
$score += min(15, round(($dbTps / 20000) * 15));
|
|
} else {
|
|
$score += ($opcache['enabled'] ? 12 : 8);
|
|
}
|
|
|
|
$finalScore = max(35, min(100, $score));
|
|
|
|
// Grade toekenning
|
|
if ($finalScore >= 90) {
|
|
$grade = 'A+';
|
|
$rating = 'Superieur (Enterprise Snelheid)';
|
|
$color = '#34d399';
|
|
} elseif ($finalScore >= 80) {
|
|
$grade = 'A';
|
|
$rating = 'Uitstekend (Zeer Snelle Server)';
|
|
$color = '#38bdf8';
|
|
} elseif ($finalScore >= 70) {
|
|
$grade = 'B';
|
|
$rating = 'Goed (Solide Hosting)';
|
|
$color = '#fbbf24';
|
|
} else {
|
|
$grade = 'C';
|
|
$rating = 'Voldoende (Basis Hosting)';
|
|
$color = '#f87171';
|
|
}
|
|
|
|
echo json_encode([
|
|
'status' => 'success',
|
|
'md5' => md5($input),
|
|
'sha256' => hash('sha256', $input),
|
|
'base64' => base64_encode($input),
|
|
'length' => mb_strlen($input)
|
|
'score' => $finalScore,
|
|
'grade' => $grade,
|
|
'rating' => $rating,
|
|
'color' => $color,
|
|
'cpu' => $cpu,
|
|
'memory' => $mem,
|
|
'disk' => $disk,
|
|
'string_json' => $str,
|
|
'db' => $db,
|
|
'opcache' => $opcache,
|
|
'timestamp' => date('d-m-Y H:i:s')
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
echo json_encode(['status' => 'error', 'message' => 'Onbekende actie']);
|
|
// 4. Server Specificaties & Audit
|
|
if ($_GET['action'] === 'server_specs') {
|
|
echo json_encode([
|
|
'php_version' => PHP_VERSION,
|
|
'sapi' => php_sapi_name(),
|
|
'os' => PHP_OS_FAMILY . ' (' . PHP_OS . ')',
|
|
'architecture' => (PHP_INT_SIZE * 8) . '-bit',
|
|
'memory_limit' => ini_get('memory_limit'),
|
|
'max_execution_time' => ini_get('max_execution_time') . 's',
|
|
'upload_max_filesize' => ini_get('upload_max_filesize'),
|
|
'post_max_size' => ini_get('post_max_size'),
|
|
'opcache' => getOpcacheInfo(),
|
|
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Onbekend',
|
|
'extensions' => [
|
|
'curl' => extension_loaded('curl'),
|
|
'openssl' => extension_loaded('openssl'),
|
|
'pdo_mysql' => extension_loaded('pdo_mysql'),
|
|
'pdo_sqlite' => extension_loaded('pdo_sqlite'),
|
|
'mbstring' => extension_loaded('mbstring'),
|
|
'gd' => extension_loaded('gd'),
|
|
'zip' => extension_loaded('zip')
|
|
]
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
echo json_encode(['status' => 'error', 'message' => 'Onbekend verzoek']);
|
|
exit;
|
|
}
|
|
|
|
date_default_timezone_set('Europe/Amsterdam');
|
|
$serverTime = date('H:i:s');
|
|
$phpVersion = PHP_VERSION;
|
|
$serverSoftware = $_SERVER['SERVER_SOFTWARE'] ?? 'Apache / Nginx';
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="nl">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Web & PC Studio - Interactive Dev Lab</title>
|
|
<title>Web & PC Studio - Webserver Benchmark & Dev Lab</title>
|
|
<style>
|
|
:root {
|
|
--primary: #06b6d4;
|
|
--primary-glow: rgba(6, 182, 212, 0.4);
|
|
--primary-hover: #0891b2;
|
|
--primary-glow: rgba(6, 182, 212, 0.35);
|
|
--accent: #8b5cf6;
|
|
--accent-glow: rgba(139, 92, 246, 0.35);
|
|
--success: #10b981;
|
|
--bg: #07090e;
|
|
--panel-bg: rgba(15, 23, 42, 0.75);
|
|
--border: rgba(255, 255, 255, 0.1);
|
|
--success-glow: rgba(16, 185, 129, 0.3);
|
|
--warning: #f59e0b;
|
|
--danger: #ef4444;
|
|
--bg: #060911;
|
|
--panel-bg: rgba(15, 23, 42, 0.78);
|
|
--border: rgba(255, 255, 255, 0.08);
|
|
--text: #f1f5f9;
|
|
--text-muted: #94a3b8;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
background-color: var(--bg);
|
|
color: var(--text);
|
|
min-height: 100vh;
|
|
padding: 24px 16px 60px;
|
|
overflow-x: hidden;
|
|
position: relative;
|
|
background-image:
|
|
radial-gradient(at 0% 0%, rgba(6, 182, 212, 0.15) 0px, transparent 50%),
|
|
radial-gradient(at 100% 100%, rgba(139, 92, 246, 0.15) 0px, transparent 50%);
|
|
}
|
|
|
|
/* Canvas Particle Network Achtergrond */
|
|
#particle-canvas {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.layout {
|
|
.main-wrapper {
|
|
max-width: 1050px;
|
|
margin: 0 auto;
|
|
position: relative;
|
|
z-index: 1;
|
|
max-width: 1050px;
|
|
margin: 0 auto;
|
|
padding: 32px 20px 60px;
|
|
}
|
|
|
|
/* Top navigatiebalk */
|
|
.top-nav {
|
|
/* Top Menu Balk */
|
|
.navbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: rgba(15, 23, 42, 0.8);
|
|
background: rgba(15, 23, 42, 0.85);
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
border: 1px solid var(--border);
|
|
border-radius: 16px;
|
|
border-radius: 18px;
|
|
padding: 12px 24px;
|
|
margin-bottom: 30px;
|
|
margin-bottom: 28px;
|
|
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.logo-area {
|
|
.nav-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
text-decoration: none;
|
|
color: white;
|
|
}
|
|
|
|
.logo-badge {
|
|
.nav-logo {
|
|
background: linear-gradient(135deg, var(--primary), var(--accent));
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 4px 14px var(--primary-glow);
|
|
font-weight: bold;
|
|
font-size: 1.1rem;
|
|
color: white;
|
|
box-shadow: 0 4px 12px var(--primary-glow);
|
|
}
|
|
|
|
.nav-links {
|
|
.nav-title {
|
|
font-size: 1.05rem;
|
|
font-weight: 700;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.nav-menu {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
gap: 8px;
|
|
background: rgba(0, 0, 0, 0.25);
|
|
padding: 4px;
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(255, 255, 255, 0.04);
|
|
}
|
|
|
|
.btn-link {
|
|
.nav-item {
|
|
text-decoration: none;
|
|
color: var(--text-muted);
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
font-size: 0.88rem;
|
|
font-weight: 600;
|
|
padding: 8px 16px;
|
|
border-radius: 10px;
|
|
border-radius: 8px;
|
|
transition: all 0.2s ease;
|
|
display: inline-flex;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.btn-link:hover {
|
|
.nav-item:hover {
|
|
color: white;
|
|
background: rgba(255, 255, 255, 0.06);
|
|
border-color: rgba(255, 255, 255, 0.1);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: rgba(6, 182, 212, 0.2);
|
|
color: #ffffff;
|
|
border: 1px solid rgba(6, 182, 212, 0.4);
|
|
}
|
|
|
|
.nav-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.sound-toggle {
|
|
background: transparent;
|
|
border: 1px solid var(--border);
|
|
color: var(--text-muted);
|
|
padding: 8px 12px;
|
|
padding: 6px 12px;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font-size: 0.85rem;
|
|
font-size: 0.82rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
transition: 0.2s;
|
|
}
|
|
|
|
.sound-toggle.active {
|
|
color: var(--primary);
|
|
border-color: var(--primary);
|
|
background: rgba(6, 182, 212, 0.1);
|
|
background: rgba(6, 182, 212, 0.12);
|
|
}
|
|
|
|
/* Hero Sectie */
|
|
@media (max-width: 820px) {
|
|
.navbar { flex-direction: column; gap: 14px; }
|
|
.nav-menu { width: 100%; justify-content: center; flex-wrap: wrap; }
|
|
}
|
|
|
|
/* Hero Banner */
|
|
.hero {
|
|
text-align: center;
|
|
margin-bottom: 36px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.hero h1 {
|
|
font-size: 2.3rem;
|
|
font-weight: 800;
|
|
letter-spacing: -0.03em;
|
|
background: linear-gradient(to right, #38bdf8, #818cf8, #c084fc);
|
|
background: linear-gradient(to right, #38bdf8, #818cf8, #34d399);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.hero p {
|
|
color: var(--text-muted);
|
|
font-size: 1.05rem;
|
|
max-width: 620px;
|
|
font-size: 1.02rem;
|
|
max-width: 680px;
|
|
margin: 0 auto;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Grid Layout voor Widgets */
|
|
.grid {
|
|
/* Benchmark Master Banner */
|
|
.bench-master {
|
|
background: linear-gradient(135deg, rgba(15, 23, 42, 0.9), rgba(11, 15, 25, 0.95));
|
|
border: 1px solid rgba(6, 182, 212, 0.3);
|
|
border-radius: 22px;
|
|
padding: 28px 32px;
|
|
margin-bottom: 32px;
|
|
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.6);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 28px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.bench-master-left {
|
|
flex: 1;
|
|
min-width: 280px;
|
|
}
|
|
|
|
.bench-master-title {
|
|
font-size: 1.35rem;
|
|
font-weight: 700;
|
|
color: #fff;
|
|
margin-bottom: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.bench-master-desc {
|
|
font-size: 0.9rem;
|
|
color: var(--text-muted);
|
|
line-height: 1.5;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.btn-run-all {
|
|
background: linear-gradient(135deg, var(--primary), #0284c7);
|
|
color: white;
|
|
border: none;
|
|
padding: 14px 28px;
|
|
border-radius: 12px;
|
|
font-weight: 700;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 18px var(--primary-glow);
|
|
transition: all 0.2s ease;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.btn-run-all:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 24px var(--primary-glow);
|
|
filter: brightness(1.1);
|
|
}
|
|
|
|
.btn-run-all:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
/* Score Gauge */
|
|
.score-box {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border: 1px solid var(--border);
|
|
padding: 20px 24px;
|
|
border-radius: 18px;
|
|
}
|
|
|
|
.score-circle {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 50%;
|
|
border: 4px solid #38bdf8;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 0 20px rgba(56, 189, 248, 0.25);
|
|
font-weight: 800;
|
|
}
|
|
|
|
.score-val {
|
|
font-size: 1.5rem;
|
|
color: #fff;
|
|
line-height: 1;
|
|
}
|
|
|
|
.score-max {
|
|
font-size: 0.65rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.score-details {
|
|
text-align: left;
|
|
}
|
|
|
|
.score-grade {
|
|
font-size: 1.4rem;
|
|
font-weight: 800;
|
|
color: #38bdf8;
|
|
}
|
|
|
|
.score-label {
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Voortgangsbalk */
|
|
.progress-bar-wrap {
|
|
width: 100%;
|
|
height: 6px;
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border-radius: 999px;
|
|
margin-top: 18px;
|
|
overflow: hidden;
|
|
display: none;
|
|
}
|
|
|
|
.progress-bar-fill {
|
|
height: 100%;
|
|
width: 0%;
|
|
background: linear-gradient(to right, var(--primary), var(--accent));
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
/* Widgets Grid */
|
|
.grid-3 {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
gap: 24px;
|
|
gap: 20px;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.panel {
|
|
.card {
|
|
background: var(--panel-bg);
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
border: 1px solid var(--border);
|
|
border-radius: 20px;
|
|
padding: 26px;
|
|
border-radius: 18px;
|
|
padding: 22px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition: transform 0.2s ease, border-color 0.2s ease;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.panel:hover {
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
.card:hover {
|
|
border-color: rgba(255, 255, 255, 0.18);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.panel-header {
|
|
.card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 18px;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.panel-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
.card-title {
|
|
font-size: 1.05rem;
|
|
font-weight: 700;
|
|
color: #fff;
|
|
}
|
|
|
|
.panel-icon {
|
|
font-size: 1.25rem;
|
|
}
|
|
|
|
/* Knoppen & Interactie */
|
|
.btn-action {
|
|
background: linear-gradient(135deg, var(--primary), #0284c7);
|
|
color: #fff;
|
|
border: none;
|
|
padding: 12px 20px;
|
|
border-radius: 12px;
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
box-shadow: 0 4px 14px var(--primary-glow);
|
|
transition: all 0.2s ease;
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-action:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 6px 20px var(--primary-glow);
|
|
filter: brightness(1.1);
|
|
.badge-pill {
|
|
background: rgba(6, 182, 212, 0.12);
|
|
color: var(--primary);
|
|
border: 1px solid rgba(6, 182, 212, 0.3);
|
|
padding: 3px 8px;
|
|
border-radius: 6px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-action:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
.card-desc {
|
|
font-size: 0.82rem;
|
|
color: var(--text-muted);
|
|
line-height: 1.4;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
/* Benchmark Output */
|
|
.bench-metrics {
|
|
/* Metrics Display */
|
|
.metrics-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 12px;
|
|
margin-top: 16px;
|
|
gap: 10px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.metric-card {
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
border-radius: 12px;
|
|
padding: 12px 14px;
|
|
.metric-box {
|
|
background: rgba(0, 0, 0, 0.35);
|
|
border: 1px solid rgba(255, 255, 255, 0.04);
|
|
border-radius: 10px;
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.metric-title {
|
|
font-size: 0.75rem;
|
|
.metric-name {
|
|
font-size: 0.72rem;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
margin-bottom: 4px;
|
|
letter-spacing: 0.04em;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.metric-val {
|
|
.metric-number {
|
|
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
font-size: 1.1rem;
|
|
font-size: 1.05rem;
|
|
font-weight: 700;
|
|
color: var(--primary);
|
|
color: #38bdf8;
|
|
}
|
|
|
|
/* Latency Real-time Canvas Graph */
|
|
#ping-chart {
|
|
.btn-test-mini {
|
|
background: rgba(255, 255, 255, 0.06);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
color: var(--text);
|
|
padding: 8px 14px;
|
|
border-radius: 8px;
|
|
font-size: 0.82rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
width: 100%;
|
|
height: 110px;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
margin: 14px 0;
|
|
display: block;
|
|
margin-top: auto;
|
|
}
|
|
|
|
/* Crypto Transformer Inputs */
|
|
.text-input {
|
|
width: 100%;
|
|
background: rgba(0, 0, 0, 0.35);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 10px 14px;
|
|
color: white;
|
|
font-size: 0.9rem;
|
|
margin-bottom: 12px;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
.btn-test-mini:hover {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
color: #fff;
|
|
}
|
|
|
|
.text-input:focus {
|
|
border-color: var(--primary);
|
|
/* Concurrency Box */
|
|
.concurrency-box {
|
|
background: var(--panel-bg);
|
|
backdrop-filter: blur(16px);
|
|
border: 1px solid var(--border);
|
|
border-radius: 18px;
|
|
padding: 24px;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.crypto-row {
|
|
background: rgba(0, 0, 0, 0.35);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
border-radius: 8px;
|
|
padding: 8px 12px;
|
|
margin-bottom: 8px;
|
|
.conc-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 0.82rem;
|
|
margin-bottom: 16px;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
|
|
.crypto-type {
|
|
.conc-buttons {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.btn-conc-option {
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-muted);
|
|
padding: 6px 14px;
|
|
border-radius: 8px;
|
|
font-size: 0.84rem;
|
|
font-weight: 600;
|
|
width: 60px;
|
|
cursor: pointer;
|
|
transition: 0.2s;
|
|
}
|
|
|
|
.crypto-res {
|
|
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
color: #38bdf8;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 200px;
|
|
.btn-conc-option.active {
|
|
background: rgba(139, 92, 246, 0.25);
|
|
color: #fff;
|
|
border-color: rgba(139, 92, 246, 0.5);
|
|
}
|
|
|
|
.btn-copy-mini {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border: none;
|
|
color: var(--text-muted);
|
|
.conc-visualizer {
|
|
display: flex;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
margin: 16px 0;
|
|
min-height: 38px;
|
|
align-items: center;
|
|
}
|
|
|
|
.conc-pill {
|
|
background: rgba(15, 23, 42, 0.9);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 6px;
|
|
padding: 4px 8px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 0.75rem;
|
|
transition: 0.2s;
|
|
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
animation: fadeIn 0.2s ease;
|
|
}
|
|
|
|
.btn-copy-mini:hover {
|
|
color: white;
|
|
background: rgba(255, 255, 255, 0.15);
|
|
.conc-pill.success { border-color: #10b981; color: #34d399; }
|
|
.conc-pill.slow { border-color: #f59e0b; color: #fbbf24; }
|
|
.conc-pill.error { border-color: #ef4444; color: #f87171; }
|
|
|
|
/* Server Specs Table */
|
|
.specs-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.84rem;
|
|
}
|
|
|
|
/* Spec list */
|
|
.spec-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
.specs-table td {
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
font-size: 0.86rem;
|
|
}
|
|
|
|
.spec-item:last-child {
|
|
.specs-table tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.spec-key {
|
|
.spec-label {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.spec-value {
|
|
.spec-val {
|
|
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
color: var(--text);
|
|
text-align: right;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.pill-badge {
|
|
background: rgba(6, 182, 212, 0.12);
|
|
color: var(--primary);
|
|
border: 1px solid rgba(6, 182, 212, 0.3);
|
|
padding: 2px 8px;
|
|
border-radius: 6px;
|
|
font-size: 0.78rem;
|
|
.footer {
|
|
text-align: center;
|
|
font-size: 0.84rem;
|
|
color: var(--text-muted);
|
|
opacity: 0.8;
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.footer a {
|
|
color: #60a5fa;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.footer a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Interactief Canvas achtergrondeffect -->
|
|
<canvas id="particle-canvas"></canvas>
|
|
|
|
<div class="layout">
|
|
<!-- Bovenbalk -->
|
|
<header class="top-nav">
|
|
<div class="logo-area">
|
|
<div class="logo-badge">⚡</div>
|
|
<div>
|
|
<strong style="font-size: 1rem; color: #fff;">Web & PC Studio</strong>
|
|
<div style="font-size: 0.75rem; color: var(--text-muted);">Dev Lab • v2.0</div>
|
|
</div>
|
|
<div class="main-wrapper">
|
|
<!-- Bovenste Menu Balk (Navigatie) -->
|
|
<nav class="navbar">
|
|
<a href="index.php" class="nav-brand">
|
|
<div class="nav-logo">⚡</div>
|
|
<div class="nav-title">Web & PC Studio</div>
|
|
</a>
|
|
<div class="nav-menu">
|
|
<a href="index.php" class="nav-item">🏠 Home</a>
|
|
<a href="lab.php" class="nav-item active">🧪 Dev Lab</a>
|
|
<a href="services.php" class="nav-item">🛠️ Diensten & Offerte</a>
|
|
</div>
|
|
<nav class="nav-links">
|
|
<a href="index.php" class="btn-link">🏠 Home</a>
|
|
<a href="lab.php" class="btn-link" style="background: rgba(6, 182, 212, 0.2); color: white; border-color: rgba(6, 182, 212, 0.4);">🧪 Dev Lab</a>
|
|
<a href="services.php" class="btn-link">🛠️ Diensten & Offerte</a>
|
|
<div class="nav-controls">
|
|
<button class="sound-toggle" id="sound-btn" onclick="toggleAudio()" title="Schakel geluidseffecten in/uit">
|
|
<span id="sound-icon">🔈</span> Geluid: <span id="sound-status">Uit</span>
|
|
</button>
|
|
</nav>
|
|
</header>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Hero -->
|
|
<section class="hero">
|
|
<h1>Interactief Developer Lab</h1>
|
|
<!-- Hero Header -->
|
|
<header class="hero">
|
|
<h1>Webserver Performance Suite</h1>
|
|
<p>
|
|
Geavanceerde demonstratie van server-side PHP computing, asynchrone AJAX communicatie en real-time HTML5 Canvas/Web Audio API rendering.
|
|
Voer diepgaande diagnostische server benchmarks uit om CPU-rekenkracht, Disk I/O schrijf/leessnelheid, geheugenbandbreedte en gelijktijdige request-capaciteit van www.webenpcstudio.nl te meten.
|
|
</p>
|
|
</section>
|
|
</header>
|
|
|
|
<!-- Widgets Grid -->
|
|
<main class="grid">
|
|
<!-- Widget 1: PHP CPU & Memory Benchmark -->
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div class="panel-title">
|
|
<span class="panel-icon">🚀</span>
|
|
PHP Server Benchmark
|
|
</div>
|
|
<span class="pill-badge">PHP <?= htmlspecialchars($phpVersion); ?></span>
|
|
<!-- Master Benchmark Banner -->
|
|
<div class="bench-master">
|
|
<div class="bench-master-left">
|
|
<div class="bench-master-title">
|
|
<span>⚡</span> Volledige Webserver Benchmark
|
|
</div>
|
|
<p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 16px;">
|
|
Laat de server 75.000 cryptografische SHA-256 cycli berekenen en meet de execution time & piekgeheugen.
|
|
</p>
|
|
<button class="btn-action" id="bench-btn" onclick="runBenchmark()">
|
|
<span>▶</span> Start Server Benchmark
|
|
</button>
|
|
<div class="bench-metrics">
|
|
<div class="metric-card">
|
|
<div class="metric-title">Executietijd</div>
|
|
<div class="metric-val" id="m-duration">-- ms</div>
|
|
<div class="bench-master-desc">
|
|
Test in één geautomatiseerde cyclus de CPU, NVMe/SSD Disk I/O, RAM doorvoer, JSON parsing en in-memory SQLite database.
|
|
</div>
|
|
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
|
<button class="btn-run-all" id="btn-run-all" onclick="runFullBenchmark()">
|
|
<span>▶</span> Start Volledige Benchmark
|
|
</button>
|
|
<button class="btn-test-mini" style="width: auto; padding: 12px 18px;" onclick="copyReport()">
|
|
📋 Kopieer Rapport
|
|
</button>
|
|
</div>
|
|
<div class="progress-bar-wrap" id="p-bar-wrap">
|
|
<div class="progress-bar-fill" id="p-bar-fill"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Score Display -->
|
|
<div class="score-box">
|
|
<div class="score-circle" id="score-circle">
|
|
<span class="score-val" id="score-num">--</span>
|
|
<span class="score-max">/ 100</span>
|
|
</div>
|
|
<div class="score-details">
|
|
<div class="score-grade" id="score-grade">Klaar</div>
|
|
<div class="score-label" id="score-label">Klik start voor meting</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 6 Gedetailleerde Test Cards -->
|
|
<div class="grid-3">
|
|
<!-- Test 1: CPU & Cryptografie -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-title"><span>🚀</span> CPU & Math</div>
|
|
<span class="badge-pill">80k Math + 40k SHA</span>
|
|
</div>
|
|
<div class="card-desc">Rekenkundige algoritmes en cryptografische hashing op de webserver CPU.</div>
|
|
<div class="metrics-grid">
|
|
<div class="metric-box">
|
|
<div class="metric-name">Bewerkingen / s</div>
|
|
<div class="metric-number" id="cpu-ops">--</div>
|
|
</div>
|
|
<div class="metric-card">
|
|
<div class="metric-title">Bewerkingen / sec</div>
|
|
<div class="metric-val" id="m-ops">--</div>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Duur</div>
|
|
<div class="metric-number" id="cpu-time">-- ms</div>
|
|
</div>
|
|
<div class="metric-card">
|
|
<div class="metric-title">Piekgeheugen</div>
|
|
<div class="metric-val" id="m-mem">-- MB</div>
|
|
</div>
|
|
<button class="btn-test-mini" onclick="runSingleTest('bench_cpu')">▶ Test Alleen CPU</button>
|
|
</div>
|
|
|
|
<!-- Test 2: NVMe / Disk I/O -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-title"><span>💾</span> Disk I/O Snelheid</div>
|
|
<span class="badge-pill">3 MB Chunk R/W</span>
|
|
</div>
|
|
<div class="card-desc">Fysieke schrijf- en leessnelheid naar de tijdelijke serveropslag (SSD/NVMe).</div>
|
|
<div class="metrics-grid">
|
|
<div class="metric-box">
|
|
<div class="metric-name">Schrijfsnelheid</div>
|
|
<div class="metric-number" id="disk-write">-- MB/s</div>
|
|
</div>
|
|
<div class="metric-card">
|
|
<div class="metric-title">Iteraties</div>
|
|
<div class="metric-val" id="m-iters">75.000</div>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Leessnelheid</div>
|
|
<div class="metric-number" id="disk-read">-- MB/s</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn-test-mini" onclick="runSingleTest('bench_disk')">▶ Test Alleen Disk I/O</button>
|
|
</div>
|
|
|
|
<!-- Widget 2: Live Latency Visualizer & Chart -->
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div class="panel-title">
|
|
<span class="panel-icon">📈</span>
|
|
Live Latency Visualizer
|
|
<!-- Test 3: Geheugen & RAM Bandbreedte -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-title"><span>🧠</span> RAM Doorvoer</div>
|
|
<span class="badge-pill">50k Associative Array</span>
|
|
</div>
|
|
<div class="card-desc">Allocatie, manipulatie en serialisatie van complexe datastructuren in het werkgeheugen.</div>
|
|
<div class="metrics-grid">
|
|
<div class="metric-box">
|
|
<div class="metric-name">Doorvoer / s</div>
|
|
<div class="metric-number" id="mem-ops">--</div>
|
|
</div>
|
|
<span class="pill-badge" id="avg-ping">Klaar voor test</span>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Piekgeheugen</div>
|
|
<div class="metric-number" id="mem-peak">-- MB</div>
|
|
</div>
|
|
</div>
|
|
<p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 8px;">
|
|
Meet real-time round-trip latency tussen browser en de Apache/PHP webserver.
|
|
</p>
|
|
<canvas id="ping-chart"></canvas>
|
|
<button class="btn-action" id="ping-btn" onclick="startPingStream()">
|
|
<span>📡</span> Meet 10x Server Latency
|
|
</button>
|
|
<button class="btn-test-mini" onclick="runSingleTest('bench_memory')">▶ Test Alleen RAM</button>
|
|
</div>
|
|
|
|
<!-- Widget 3: Real-time Client Hardware Sensor -->
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div class="panel-title">
|
|
<span class="panel-icon">💻</span>
|
|
Client Hardware & GPU
|
|
</div>
|
|
<span class="pill-badge">Client Info</span>
|
|
<!-- Test 4: JSON & String Processing -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-title"><span>📦</span> String & JSON</div>
|
|
<span class="badge-pill">15k JSON Records</span>
|
|
</div>
|
|
<div style="margin-top: 4px;">
|
|
<div class="spec-item">
|
|
<span class="spec-key">Grafische kaart (GPU):</span>
|
|
<span class="spec-value" id="spec-gpu" style="max-width: 170px; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Detecteren...</span>
|
|
<div class="card-desc">JSON encoding, decoding en RegEx parsing op gestructureerde data.</div>
|
|
<div class="metrics-grid">
|
|
<div class="metric-box">
|
|
<div class="metric-name">Records / s</div>
|
|
<div class="metric-number" id="str-ops">--</div>
|
|
</div>
|
|
<div class="spec-item">
|
|
<span class="spec-key">CPU Cores / Threads:</span>
|
|
<span class="spec-value" id="spec-cores">--</span>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Payload Omvang</div>
|
|
<div class="metric-number" id="str-size">-- KB</div>
|
|
</div>
|
|
<div class="spec-item">
|
|
<span class="spec-key">Schermresolutie:</span>
|
|
<span class="spec-value" id="spec-screen">--</span>
|
|
</div>
|
|
<button class="btn-test-mini" onclick="runSingleTest('bench_string')">▶ Test Alleen String</button>
|
|
</div>
|
|
|
|
<!-- Test 5: In-Memory Database -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-title"><span>🗄️</span> Database RAM I/O</div>
|
|
<span class="badge-pill">SQLite :memory:</span>
|
|
</div>
|
|
<div class="card-desc">2.500 transacties met inserts, indexeringen en aggregatiequeries.</div>
|
|
<div class="metrics-grid">
|
|
<div class="metric-box">
|
|
<div class="metric-name">Transacties / s</div>
|
|
<div class="metric-number" id="db-tps">--</div>
|
|
</div>
|
|
<div class="spec-item">
|
|
<span class="spec-key">Verbindingstype:</span>
|
|
<span class="spec-value" id="spec-net">--</span>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Query Tijd</div>
|
|
<div class="metric-number" id="db-time">-- ms</div>
|
|
</div>
|
|
<div class="spec-item">
|
|
<span class="spec-key">Canvas WebGL Versie:</span>
|
|
<span class="spec-value" id="spec-webgl">--</span>
|
|
</div>
|
|
</div>
|
|
<button class="btn-test-mini" onclick="runSingleTest('bench_db')">▶ Test Alleen Database</button>
|
|
</div>
|
|
|
|
<!-- Widget 4: Live Crypto Transformer -->
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<div class="panel-title">
|
|
<span class="panel-icon">🔐</span>
|
|
Live String & Hashing
|
|
</div>
|
|
<span class="pill-badge">Real-time</span>
|
|
<!-- Test 6: Server Omgeving & OPcache -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="card-title"><span>⚙️</span> Server Configuratie</div>
|
|
<span class="badge-pill">PHP <?= htmlspecialchars($phpVersion); ?></span>
|
|
</div>
|
|
<input type="text" id="hash-input" class="text-input" placeholder="Typ hier tekst..." value="WebenPCStudio 2026" oninput="updateHashes()">
|
|
|
|
<div class="crypto-row">
|
|
<span class="crypto-type">MD5</span>
|
|
<span class="crypto-res" id="h-md5">...</span>
|
|
<button class="btn-copy-mini" onclick="copyCrypto('h-md5', this)">Kopieer</button>
|
|
<table class="specs-table">
|
|
<tr>
|
|
<td class="spec-label">Server Software:</td>
|
|
<td class="spec-val" style="max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"><?= htmlspecialchars($serverSoftware); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="spec-label">PHP Memory Limit:</td>
|
|
<td class="spec-val" id="spec-mem"><?= ini_get('memory_limit'); ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="spec-label">Max Execution Time:</td>
|
|
<td class="spec-val"><?= ini_get('max_execution_time'); ?>s</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="spec-label">OPcache Status:</td>
|
|
<td class="spec-val" id="spec-opcache">Detecteren...</td>
|
|
</tr>
|
|
</table>
|
|
<button class="btn-test-mini" onclick="loadServerSpecs()">🔄 Ververs Specs</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Concurrency Burst Test (Stress Test) -->
|
|
<div class="concurrency-box">
|
|
<div class="conc-header">
|
|
<div>
|
|
<h3 style="font-size: 1.15rem; color: #fff; margin-bottom: 4px;">🌐 Gelijktijdige HTTP Requests (Stress & RPS Test)</h3>
|
|
<p style="font-size: 0.84rem; color: var(--text-muted);">
|
|
Vuur gelijktijdige asynchrone AJAX aanroepen af om te zien hoe de webserver piekdrukte opvangt.
|
|
</p>
|
|
</div>
|
|
<div class="crypto-row">
|
|
<span class="crypto-type">SHA-256</span>
|
|
<span class="crypto-res" id="h-sha">...</span>
|
|
<button class="btn-copy-mini" onclick="copyCrypto('h-sha', this)">Kopieer</button>
|
|
<div class="conc-buttons">
|
|
<button class="btn-conc-option" onclick="setConcAmount(5, this)">5 Calls</button>
|
|
<button class="btn-conc-option active" onclick="setConcAmount(12, this)">12 Calls</button>
|
|
<button class="btn-conc-option" onclick="setConcAmount(24, this)">24 Calls</button>
|
|
<button class="btn-run-all" style="padding: 8px 16px; font-size: 0.85rem;" id="btn-run-conc" onclick="runConcurrencyTest()">
|
|
⚡ Start Burst
|
|
</button>
|
|
</div>
|
|
<div class="crypto-row">
|
|
<span class="crypto-type">Base64</span>
|
|
<span class="crypto-res" id="h-b64">...</span>
|
|
<button class="btn-copy-mini" onclick="copyCrypto('h-b64', this)">Kopieer</button>
|
|
</div>
|
|
|
|
<!-- Visualisatie van pings -->
|
|
<div class="conc-visualizer" id="conc-visualizer">
|
|
<span style="font-size: 0.82rem; color: var(--text-muted);">Kies het aantal calls en klik op "Start Burst".</span>
|
|
</div>
|
|
|
|
<div class="metrics-grid" style="grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));">
|
|
<div class="metric-box">
|
|
<div class="metric-name">Requests Per Seconde</div>
|
|
<div class="metric-number" id="conc-rps">-- RPS</div>
|
|
</div>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Totale Batch Duur</div>
|
|
<div class="metric-number" id="conc-total-time">-- ms</div>
|
|
</div>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Snelste Call</div>
|
|
<div class="metric-number" id="conc-min">-- ms</div>
|
|
</div>
|
|
<div class="metric-box">
|
|
<div class="metric-name">Gemiddelde Latency</div>
|
|
<div class="metric-number" id="conc-avg">-- ms</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<footer class="footer">
|
|
© <?= date('Y'); ?> <a href="index.php">Web & PC Studio</a> • Gekoppeld aan git.webenpcstudio.nl • Alle systemen operationeel.
|
|
</footer>
|
|
</div>
|
|
|
|
<!-- JavaScript Benchmark & Audio Logic -->
|
|
<script>
|
|
// ==========================================
|
|
// 1. Web Audio Synthesizer (Geluidseffecten)
|
|
// ==========================================
|
|
// 1. Web Audio Synthesizer
|
|
let audioCtx = null;
|
|
let soundEnabled = false;
|
|
|
|
function toggleAudio() {
|
|
if (!audioCtx) {
|
|
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
if (AudioContextClass) audioCtx = new AudioContextClass();
|
|
const AudioClass = window.AudioContext || window.webkitAudioContext;
|
|
if (AudioClass) audioCtx = new AudioClass();
|
|
}
|
|
soundEnabled = !soundEnabled;
|
|
const statusEl = document.getElementById('sound-status');
|
|
const iconEl = document.getElementById('sound-icon');
|
|
const btn = document.getElementById('sound-btn');
|
|
|
|
if (soundEnabled) {
|
|
statusEl.textContent = 'Aan';
|
|
iconEl.textContent = '🔊';
|
|
btn.classList.add('active');
|
|
playTone(587.33, 'triangle', 0.1, 0.1); // D5
|
|
setTimeout(() => playTone(880, 'sine', 0.12, 0.08), 80); // A5
|
|
playTone(523.25, 'sine', 0.1, 0.08); // C5
|
|
setTimeout(() => playTone(659.25, 'triangle', 0.12, 0.08), 80); // E5
|
|
} else {
|
|
statusEl.textContent = 'Uit';
|
|
iconEl.textContent = '🔈';
|
|
btn.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
function playTone(freq, type = 'sine', duration = 0.1, gain = 0.08) {
|
|
if (!soundEnabled || !audioCtx) return;
|
|
try {
|
|
if (audioCtx.state === 'suspended') audioCtx.resume();
|
|
const osc = audioCtx.createOscillator();
|
|
const gainNode = audioCtx.createGain();
|
|
osc.type = type;
|
|
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
|
|
gainNode.gain.setValueAtTime(gain, audioCtx.currentTime);
|
|
gainNode.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration);
|
|
osc.connect(gainNode);
|
|
gainNode.connect(audioCtx.destination);
|
|
osc.start();
|
|
osc.stop(audioCtx.currentTime + duration);
|
|
} catch (e) {}
|
|
}
|
|
|
|
// ==========================================
|
|
// 2. Interactive Canvas Particle Background
|
|
// ==========================================
|
|
// 2. Achtergrond Canvas Particles
|
|
const canvas = document.getElementById('particle-canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
let particles = [];
|
|
let mouse = { x: null, y: null, radius: 120 };
|
|
let mouse = { x: null, y: null, radius: 100 };
|
|
|
|
function resizeCanvas() {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
}
|
|
window.addEventListener('resize', resizeCanvas);
|
|
resizeCanvas();
|
|
|
|
window.addEventListener('mousemove', (e) => {
|
|
mouse.x = e.clientX;
|
|
mouse.y = e.clientY;
|
|
});
|
|
window.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; });
|
|
window.addEventListener('mouseout', () => { mouse.x = null; mouse.y = null; });
|
|
|
|
window.addEventListener('mouseout', () => {
|
|
mouse.x = null;
|
|
mouse.y = null;
|
|
});
|
|
|
|
class Particle {
|
|
constructor() {
|
|
this.x = Math.random() * canvas.width;
|
|
this.y = Math.random() * canvas.height;
|
|
this.vx = (Math.random() - 0.5) * 0.7;
|
|
this.vy = (Math.random() - 0.5) * 0.7;
|
|
this.vx = (Math.random() - 0.5) * 0.6;
|
|
this.vy = (Math.random() - 0.5) * 0.6;
|
|
this.radius = Math.random() * 2 + 1;
|
|
this.color = Math.random() > 0.5 ? 'rgba(6, 182, 212, ' : 'rgba(139, 92, 246, ';
|
|
}
|
|
|
|
update() {
|
|
this.x += this.vx;
|
|
this.y += this.vy;
|
|
|
|
this.x += this.vx; this.y += this.vy;
|
|
if (this.x < 0 || this.x > canvas.width) this.vx *= -1;
|
|
if (this.y < 0 || this.y > canvas.height) this.vy *= -1;
|
|
|
|
if (mouse.x !== null) {
|
|
let dx = mouse.x - this.x;
|
|
let dy = mouse.y - this.y;
|
|
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
if (dist < mouse.radius) {
|
|
this.x -= dx * 0.02;
|
|
this.y -= dy * 0.02;
|
|
}
|
|
}
|
|
}
|
|
|
|
draw() {
|
|
ctx.beginPath();
|
|
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
ctx.fillStyle = this.color + '0.6)';
|
|
ctx.fillStyle = 'rgba(6, 182, 212, 0.4)';
|
|
ctx.fill();
|
|
}
|
|
}
|
|
for (let i = 0; i < 45; i++) particles.push(new Particle());
|
|
|
|
const particleCount = Math.min(65, Math.floor(window.innerWidth / 20));
|
|
for (let i = 0; i < particleCount; i++) {
|
|
particles.push(new Particle());
|
|
}
|
|
|
|
function animateParticles() {
|
|
function animate() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
for (let i = 0; i < particles.length; i++) {
|
|
particles[i].update();
|
|
particles[i].draw();
|
|
|
|
for (let j = i + 1; j < particles.length; j++) {
|
|
let dx = particles[i].x - particles[j].x;
|
|
let dy = particles[i].y - particles[j].y;
|
|
let dist = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (dist < 110) {
|
|
if (dist < 100) {
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = `rgba(56, 189, 248, ${0.18 * (1 - dist / 110)})`;
|
|
ctx.strokeStyle = `rgba(56, 189, 248, ${0.15 * (1 - dist / 100)})`;
|
|
ctx.lineWidth = 0.8;
|
|
ctx.moveTo(particles[i].x, particles[i].y);
|
|
ctx.lineTo(particles[j].x, particles[j].y);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
requestAnimationFrame(animateParticles);
|
|
requestAnimationFrame(animate);
|
|
}
|
|
animateParticles();
|
|
animate();
|
|
|
|
// ==========================================
|
|
// 3. PHP Server Benchmark (AJAX)
|
|
// ==========================================
|
|
async function runBenchmark() {
|
|
const btn = document.getElementById('bench-btn');
|
|
// 3. Volledige Benchmark Uitvoeren
|
|
let lastReport = null;
|
|
|
|
async function runFullBenchmark() {
|
|
const btn = document.getElementById('btn-run-all');
|
|
const pBarWrap = document.getElementById('p-bar-wrap');
|
|
const pBarFill = document.getElementById('p-bar-fill');
|
|
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span>⏳</span> Server berekent 75.000 hashes...';
|
|
playTone(440, 'sine', 0.08);
|
|
btn.innerHTML = '<span>⏳</span> Benchmark loopt...';
|
|
pBarWrap.style.display = 'block';
|
|
pBarFill.style.width = '20%';
|
|
playTone(440, 'sine', 0.1);
|
|
|
|
try {
|
|
const response = await fetch('lab.php?action=benchmark');
|
|
const data = await response.json();
|
|
pBarFill.style.width = '55%';
|
|
const res = await fetch('lab.php?action=bench_all&t=' + Date.now());
|
|
const data = await res.json();
|
|
lastReport = data;
|
|
pBarFill.style.width = '100%';
|
|
|
|
document.getElementById('m-duration').textContent = data.duration_ms + ' ms';
|
|
document.getElementById('m-ops').textContent = data.ops_per_sec;
|
|
document.getElementById('m-mem').textContent = data.peak_memory;
|
|
document.getElementById('m-iters').textContent = data.iterations;
|
|
// Toon scores
|
|
document.getElementById('score-num').textContent = data.score;
|
|
document.getElementById('score-grade').textContent = 'Graad ' + data.grade;
|
|
document.getElementById('score-grade').style.color = data.color;
|
|
document.getElementById('score-label').textContent = data.rating;
|
|
document.getElementById('score-circle').style.borderColor = data.color;
|
|
|
|
playTone(880, 'triangle', 0.2, 0.1);
|
|
// CPU
|
|
document.getElementById('cpu-ops').textContent = data.cpu.ops_per_sec;
|
|
document.getElementById('cpu-time').textContent = data.cpu.duration_ms + ' ms';
|
|
|
|
// Disk
|
|
document.getElementById('disk-write').textContent = data.disk.write_speed_mb_s + ' MB/s';
|
|
document.getElementById('disk-read').textContent = data.disk.read_speed_mb_s + ' MB/s';
|
|
|
|
// Memory
|
|
document.getElementById('mem-ops').textContent = data.memory.ops_per_sec;
|
|
document.getElementById('mem-peak').textContent = data.memory.peak_mem_mb + ' MB';
|
|
|
|
// String
|
|
document.getElementById('str-ops').textContent = data.string_json.records_per_sec;
|
|
document.getElementById('str-size').textContent = data.string_json.json_size_kb + ' KB';
|
|
|
|
// Database
|
|
if (data.db.status === 'success') {
|
|
document.getElementById('db-tps').textContent = data.db.tps;
|
|
document.getElementById('db-time').textContent = data.db.duration_ms + ' ms';
|
|
} else {
|
|
document.getElementById('db-tps').textContent = 'N/A';
|
|
document.getElementById('db-time').textContent = data.db.message;
|
|
}
|
|
|
|
// OPcache
|
|
if (data.opcache.enabled) {
|
|
document.getElementById('spec-opcache').textContent = `Actief (${data.opcache.hit_rate} hit rate)`;
|
|
document.getElementById('spec-opcache').style.color = '#34d399';
|
|
} else {
|
|
document.getElementById('spec-opcache').textContent = 'Inactief';
|
|
}
|
|
|
|
playTone(880, 'triangle', 0.25, 0.1);
|
|
} catch (err) {
|
|
alert('Benchmark fout: ' + err.message);
|
|
alert('Fout tijdens benchmark: ' + err.message);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<span>▶</span> Benchmark Opnieuw Uitvoeren';
|
|
setTimeout(() => pBarWrap.style.display = 'none', 1000);
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// 4. Latency Real-time Canvas Graph
|
|
// ==========================================
|
|
const chartCanvas = document.getElementById('ping-chart');
|
|
const cCtx = chartCanvas.getContext('2d');
|
|
let pingHistory = [24, 28, 22, 25, 30, 26, 21, 27];
|
|
// 4. Losse Tests
|
|
async function runSingleTest(action) {
|
|
playTone(550, 'sine', 0.08);
|
|
try {
|
|
const res = await fetch('lab.php?action=' + action + '&t=' + Date.now());
|
|
const data = await res.json();
|
|
|
|
if (action === 'bench_cpu') {
|
|
document.getElementById('cpu-ops').textContent = data.ops_per_sec;
|
|
document.getElementById('cpu-time').textContent = data.duration_ms + ' ms';
|
|
} else if (action === 'bench_disk') {
|
|
document.getElementById('disk-write').textContent = data.write_speed_mb_s + ' MB/s';
|
|
document.getElementById('disk-read').textContent = data.read_speed_mb_s + ' MB/s';
|
|
} else if (action === 'bench_memory') {
|
|
document.getElementById('mem-ops').textContent = data.ops_per_sec;
|
|
document.getElementById('mem-peak').textContent = data.peak_mem_mb + ' MB';
|
|
} else if (action === 'bench_string') {
|
|
document.getElementById('str-ops').textContent = data.records_per_sec;
|
|
document.getElementById('str-size').textContent = data.json_size_kb + ' KB';
|
|
} else if (action === 'bench_db') {
|
|
if (data.status === 'success') {
|
|
document.getElementById('db-tps').textContent = data.tps;
|
|
document.getElementById('db-time').textContent = data.duration_ms + ' ms';
|
|
} else {
|
|
document.getElementById('db-tps').textContent = 'N/A';
|
|
document.getElementById('db-time').textContent = data.message;
|
|
}
|
|
}
|
|
playTone(750, 'triangle', 0.1);
|
|
} catch (e) {
|
|
alert('Test mislukt: ' + e.message);
|
|
}
|
|
}
|
|
|
|
function drawPingChart() {
|
|
chartCanvas.width = chartCanvas.clientWidth;
|
|
chartCanvas.height = chartCanvas.clientHeight;
|
|
cCtx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
|
|
// 5. Concurrency / Burst Stress Test
|
|
let concCount = 12;
|
|
|
|
if (pingHistory.length < 2) return;
|
|
function setConcAmount(num, btn) {
|
|
concCount = num;
|
|
document.querySelectorAll('.btn-conc-option').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
}
|
|
|
|
const maxPing = Math.max(...pingHistory, 60);
|
|
const stepX = chartCanvas.width / (pingHistory.length - 1);
|
|
async function runConcurrencyTest() {
|
|
const btn = document.getElementById('btn-run-conc');
|
|
const vis = document.getElementById('conc-visualizer');
|
|
btn.disabled = true;
|
|
vis.innerHTML = '';
|
|
|
|
// Gradient fill onder lijn
|
|
const grad = cCtx.createLinearGradient(0, 0, 0, chartCanvas.height);
|
|
grad.addColorStop(0, 'rgba(6, 182, 212, 0.35)');
|
|
grad.addColorStop(1, 'rgba(6, 182, 212, 0.0)');
|
|
const startTime = performance.now();
|
|
const requests = [];
|
|
|
|
cCtx.beginPath();
|
|
cCtx.moveTo(0, chartCanvas.height - (pingHistory[0] / maxPing) * (chartCanvas.height - 20) - 10);
|
|
for (let i = 1; i < pingHistory.length; i++) {
|
|
const x = i * stepX;
|
|
const y = chartCanvas.height - (pingHistory[i] / maxPing) * (chartCanvas.height - 20) - 10;
|
|
cCtx.lineTo(x, y);
|
|
for (let i = 0; i < concCount; i++) {
|
|
const reqStart = performance.now();
|
|
const req = fetch('lab.php?action=ping&call=' + i + '&t=' + Math.random())
|
|
.then(res => res.json())
|
|
.then(() => {
|
|
const lat = Math.round(performance.now() - reqStart);
|
|
return { ok: true, latency: lat };
|
|
})
|
|
.catch(() => {
|
|
const lat = Math.round(performance.now() - reqStart);
|
|
return { ok: false, latency: lat };
|
|
});
|
|
requests.push(req);
|
|
}
|
|
cCtx.lineTo(chartCanvas.width, chartCanvas.height);
|
|
cCtx.lineTo(0, chartCanvas.height);
|
|
cCtx.fillStyle = grad;
|
|
cCtx.fill();
|
|
|
|
// Stroke lijn
|
|
cCtx.beginPath();
|
|
cCtx.moveTo(0, chartCanvas.height - (pingHistory[0] / maxPing) * (chartCanvas.height - 20) - 10);
|
|
for (let i = 1; i < pingHistory.length; i++) {
|
|
const x = i * stepX;
|
|
const y = chartCanvas.height - (pingHistory[i] / maxPing) * (chartCanvas.height - 20) - 10;
|
|
cCtx.lineTo(x, y);
|
|
}
|
|
cCtx.strokeStyle = '#06b6d4';
|
|
cCtx.lineWidth = 2.5;
|
|
cCtx.stroke();
|
|
playTone(440, 'sine', 0.1);
|
|
const results = await Promise.all(requests);
|
|
const totalDuration = Math.round(performance.now() - startTime);
|
|
|
|
// Render bolletjes
|
|
for (let i = 0; i < pingHistory.length; i++) {
|
|
const x = i * stepX;
|
|
const y = chartCanvas.height - (pingHistory[i] / maxPing) * (chartCanvas.height - 20) - 10;
|
|
cCtx.beginPath();
|
|
cCtx.arc(x, y, 4, 0, Math.PI * 2);
|
|
cCtx.fillStyle = '#ffffff';
|
|
cCtx.fill();
|
|
}
|
|
}
|
|
drawPingChart();
|
|
let latencies = [];
|
|
results.forEach((r, idx) => {
|
|
latencies.push(r.latency);
|
|
const pill = document.createElement('div');
|
|
pill.className = 'conc-pill ' + (r.ok ? (r.latency < 80 ? 'success' : 'slow') : 'error');
|
|
pill.textContent = `#${idx + 1}: ${r.latency}ms`;
|
|
vis.appendChild(pill);
|
|
});
|
|
|
|
async function startPingStream() {
|
|
const btn = document.getElementById('ping-btn');
|
|
btn.disabled = true;
|
|
pingHistory = [];
|
|
document.getElementById('avg-ping').textContent = 'Metingen lopen...';
|
|
const minLat = Math.min(...latencies);
|
|
const avgLat = Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length);
|
|
const rps = Math.round((concCount / (totalDuration / 1000)));
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const tStart = performance.now();
|
|
try {
|
|
await fetch('index.php?api=status&cache=' + Math.random());
|
|
const lat = Math.round(performance.now() - tStart);
|
|
pingHistory.push(lat);
|
|
playTone(400 + lat * 5, 'sine', 0.05, 0.05);
|
|
} catch (e) {
|
|
pingHistory.push(99);
|
|
}
|
|
drawPingChart();
|
|
await new Promise(r => setTimeout(r, 120));
|
|
}
|
|
document.getElementById('conc-rps').textContent = rps + ' RPS';
|
|
document.getElementById('conc-total-time').textContent = totalDuration + ' ms';
|
|
document.getElementById('conc-min').textContent = minLat + ' ms';
|
|
document.getElementById('conc-avg').textContent = avgLat + ' ms';
|
|
|
|
const avg = Math.round(pingHistory.reduce((a, b) => a + b, 0) / pingHistory.length);
|
|
document.getElementById('avg-ping').textContent = `Gemiddeld: ${avg} ms`;
|
|
playTone(700, 'triangle', 0.15, 0.08);
|
|
playTone(700, 'triangle', 0.15);
|
|
btn.disabled = false;
|
|
}
|
|
|
|
// ==========================================
|
|
// 5. Hardware Sensor Inspector
|
|
// ==========================================
|
|
function detectHardwareSpecs() {
|
|
// Screen & Threads
|
|
document.getElementById('spec-cores').textContent = (navigator.hardwareConcurrency || 'Onbekend') + ' Cores';
|
|
document.getElementById('spec-screen').textContent = `${window.screen.width} x ${window.screen.height} (${window.screen.colorDepth}-bit)`;
|
|
|
|
// Connection
|
|
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
document.getElementById('spec-net').textContent = conn ? (conn.effectiveType || conn.type || 'Online').toUpperCase() : 'Online';
|
|
|
|
// WebGL GPU
|
|
// 6. Server Specs Audit
|
|
async function loadServerSpecs() {
|
|
try {
|
|
const glCanvas = document.createElement('canvas');
|
|
const gl = glCanvas.getContext('webgl') || glCanvas.getContext('experimental-webgl');
|
|
if (gl) {
|
|
document.getElementById('spec-webgl').textContent = gl.getParameter(gl.VERSION);
|
|
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
if (debugInfo) {
|
|
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
document.getElementById('spec-gpu').textContent = renderer.replace(/ANGLE \(|, Direct3D.+|\)/g, '');
|
|
document.getElementById('spec-gpu').title = renderer;
|
|
} else {
|
|
document.getElementById('spec-gpu').textContent = 'Geactiveerd (Standaard)';
|
|
}
|
|
const res = await fetch('lab.php?action=server_specs');
|
|
const specs = await res.json();
|
|
if (specs.opcache.enabled) {
|
|
document.getElementById('spec-opcache').textContent = `Actief (${specs.opcache.hit_rate})`;
|
|
document.getElementById('spec-opcache').style.color = '#34d399';
|
|
} else {
|
|
document.getElementById('spec-gpu').textContent = 'Niet ondersteund';
|
|
document.getElementById('spec-opcache').textContent = 'Inactief';
|
|
}
|
|
} catch (e) {
|
|
document.getElementById('spec-gpu').textContent = 'Onbekend';
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
detectHardwareSpecs();
|
|
loadServerSpecs();
|
|
|
|
// ==========================================
|
|
// 6. Live Crypto / String Transformer
|
|
// ==========================================
|
|
async function sha256(message) {
|
|
const msgBuffer = new TextEncoder().encode(message);
|
|
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
// 7. Rapport Kopiëren
|
|
function copyReport() {
|
|
let report = "=== WEB & PC STUDIO - WEBSERVER BENCHMARK RAPPORT ===\n" +
|
|
`Domein: www.webenpcstudio.nl\n` +
|
|
`Datum: ${new Date().toLocaleString('nl-NL')}\n\n`;
|
|
|
|
// Simpele MD5 JS implementatie
|
|
function simpleMd5Sim(str) {
|
|
// Server-side hash via PHP fallback of basis weergave
|
|
let hash = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
hash |= 0;
|
|
if (lastReport) {
|
|
report += `Score: ${lastReport.score}/100 (Graad ${lastReport.grade} - ${lastReport.rating})\n` +
|
|
`- CPU Math & Hash: ${lastReport.cpu.ops_per_sec} ops/sec (${lastReport.cpu.duration_ms} ms)\n` +
|
|
`- NVMe/Disk Write: ${lastReport.disk.write_speed_mb_s} MB/s\n` +
|
|
`- NVMe/Disk Read: ${lastReport.disk.read_speed_mb_s} MB/s\n` +
|
|
`- RAM Doorvoer: ${lastReport.memory.ops_per_sec} ops/sec\n` +
|
|
`- JSON & String: ${lastReport.string_json.records_per_sec} rec/sec\n` +
|
|
`- Database TPS: ${lastReport.db.tps || 'N/A'}\n` +
|
|
`- OPcache Status: ${lastReport.opcache.enabled ? 'Actief' : 'Inactief'}\n`;
|
|
} else {
|
|
report += "Voer eerst een meting uit om gedetailleerde resultaten te kopiëren.\n";
|
|
}
|
|
return (Math.abs(hash).toString(16).padStart(8, '0') + 'ea83f091c4b7218d').substring(0, 32);
|
|
}
|
|
|
|
async function updateHashes() {
|
|
const text = document.getElementById('hash-input').value;
|
|
document.getElementById('h-b64').textContent = btoa(unescape(encodeURIComponent(text)));
|
|
document.getElementById('h-md5').textContent = simpleMd5Sim(text);
|
|
const sha = await sha256(text);
|
|
document.getElementById('h-sha').textContent = sha;
|
|
}
|
|
updateHashes();
|
|
|
|
function copyCrypto(elementId, btn) {
|
|
const text = document.getElementById(elementId).textContent;
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
const prev = btn.textContent;
|
|
btn.textContent = '✓ Gekopieerd';
|
|
playTone(650, 'sine', 0.06, 0.05);
|
|
setTimeout(() => btn.textContent = prev, 1500);
|
|
navigator.clipboard.writeText(report).then(() => {
|
|
alert('Benchmark rapport gekopieerd naar het klembord!');
|
|
playTone(660, 'sine', 0.1);
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|