diff options
author | 2025-05-03 13:21:56 +0530 | |
---|---|---|
committer | 2025-05-03 13:21:56 +0530 | |
commit | 2b85ffa5d9997b59223fab4dd527d0b3c0406be4 (patch) | |
tree | bd14fca930e1704f962d402be626cebfcd8e995d /index.html | |
download | chronos-2b85ffa5d9997b59223fab4dd527d0b3c0406be4.tar.gz chronos-2b85ffa5d9997b59223fab4dd527d0b3c0406be4.tar.bz2 chronos-2b85ffa5d9997b59223fab4dd527d0b3c0406be4.zip |
feat: site which monitors the system graph using openbsd snmpd
Diffstat (limited to 'index.html')
-rw-r--r-- | index.html | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/index.html b/index.html new file mode 100644 index 0000000..9da7941 --- /dev/null +++ b/index.html @@ -0,0 +1,550 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"/> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <title>My Hardware Setup</title> + <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans&family=Roboto&family=Shrikhand&display=swap" rel="stylesheet"> + <link href="style.css" rel="stylesheet"/> + <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> + <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> + <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script> + <script src="https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.min.js"></script> + <script src="server-stats.js" defer></script> + <script src="snmp-charts.js" defer></script> + <script src="metrics-access.js" defer></script> +</head> +<body> + <header> + <h1>The Heart of the Operation: My Rig, Server & Laptops</h1> + <h3>Dive deep into the silicon and circuits that power my digital world!</h3> + </header> + + <section class="main-content"> + <div class="container"> + <h2>What Makes it Tick?</h2> + <p>Ever wondered what's inside the box? I have build my setup that help's me do all of my activities smoothly and easily. Below, I'll break down the key components and what they do.</p> + + <img src="ashita-no-joe-joe-yabuki.gif" alt="Animated CPU GIF" class="component-image"> + <div class="component-section"> + <h3><span class="icon">🖧</span> The Server</h3> + <p>This is where all of my websites, applications, files, and projects are hosted. These are accessible 24/7.</p> + <div class="component-item"> + <h4>CPU: Intel Xeon Gold</h4> + <p>The processor that handles all server requests.</p> + </div> + + <div class="component-item"> + <h4>RAM: 8GB</h4> + <p>Memory for the server to run applications.</p> + </div> + + <div class="component-item"> + <h4>Storage: 50GB SSD</h4> + <p>Fast storage for the server's operating system and data.</p> + </div> + + <div class="component-item"> + <h4>Server Status:</h4> + <div id="server-stats"> + <div class="status-container"> + <div id="server-status" class="status-light"> </div> + <span id="uptime">Checking status...</span> + </div> + </div> + </div> + + <div class="component-item"> + <button id="view-metrics-btn" class="button">View Server Metrics</button> + </div> + + <!-- Add SNMP Metrics Section (hidden by default) --> + <div class="component-item" id="metrics-section" style="display: none;"> + <h4>Real-time Metrics:</h4> + <div id="metrics-container"> + <div class="metric-grid"> + <!-- Charts will be dynamically added here based on available metrics --> + </div> + </div> + </div> + </div> + <div class="component-section"> + <h3><span class="icon">💻</span> ThinkPad E14</h3> + <p>My daily driver laptop for work and portability.</p> + <div class="component-item"> + <h4>RAM: 16GB</h4> + <p>Plenty of memory for multitasking.</p> + </div> + <div class="component-item"> + <h4>Storage: 256GB SSD</h4> + <p>Fast storage for quick boot and application loading.</p> + </div> + </div> + + <div class="component-section"> + <h3><span class="icon">💻</span> ThinkPad T480 (Librebooted)</h3> + <p>My privacy-focused laptop, running Libreboot for enhanced security and control.</p> + <div class="component-item"> + <h4>RAM: 16GB</h4> + <p>Memory for smooth operation, even with security-focused software.</p> + </div> + <div class="component-item"> + <h4>Storage: 512GB SSD</h4> + <p>Fast and reliable storage.</p> + </div> + </div> + </div> + </section> + + <section class="cta-section"> + <div class="container"> + <h2>Explore More!</h2> + <p>Want to see these components in action? Check out these links:</p> + <a href="https://surgot.in" class="button">Visit My Website</a> + <a href="https://git.surgot.in" class="button">See My Code</a> + </div> + </section> + + <!-- + <section class="testimonials"> + <div class="container"> + <h2>What People Are Saying (About My Projects, Powered by This Hardware):</h2> + <div class="testimonial"> + <p>"[QUOTE ABOUT YOUR WEBSITE/PROJECT] - It loads so fast!" - [Name/Username]</p> + </div> + <div class="testimonial"> + <p>"[QUOTE ABOUT YOUR YOUTUBE VIDEOS/PROJECT] - The editing is incredible!" - [Name/Username]</p> + </div> + </div> + </section> + --> + + <!-- Password Modal --> + <div id="password-modal" class="modal"> + <div class="modal-content"> + <span class="close-button">×</span> + <h3>Authentication Required</h3> + <p>Please enter the password to view server metrics:</p> + <div class="password-input-container"> + <input type="password" id="metrics-password" placeholder="Enter password"> + <button id="submit-password" class="button">Submit</button> + </div> + <p id="password-error" class="error-message"></p> + </div> + </div> + + <footer> + <p>© Surgot/2025 - Built with passion (and a lot of processing power!)</p> + </footer> + + <!-- Consolidated JavaScript --> + <script> + // --- Server Stats Logic (from server-stats.js) --- + async function getServerStats() { + const statusElement = document.getElementById('server-status'); + const uptimeElement = document.getElementById('uptime'); + + if (!statusElement || !uptimeElement) { + console.error("Server status elements not found in the DOM"); + return; + } + + try { + // Fetch stats directly from the server endpoint + const response = await fetch('/stat/.server-stats.json'); + + if (!response || !response.ok) { + throw new Error(`Could not find server stats file`); + } + + const data = await response.json(); + + let status = "green"; + const load1 = parseFloat(data.load_1min); + const cores = parseInt(data.cores, 10) || 1; + + if (!isNaN(load1)) { + if (load1 > (cores * 0.7)) status = "yellow"; + if (load1 > (cores * 0.9)) status = "red"; + } else { + status = "yellow"; + console.warn("Invalid load value:", data.load_1min); + } + + statusElement.classList.remove('green', 'yellow', 'red'); + statusElement.classList.add(status); + + let tooltipText = "Unknown status"; + if (status === "green") tooltipText = "Server is running normally"; + else if (status === "yellow") tooltipText = "Server is under medium load"; + else if (status === "red") tooltipText = "Server is under heavy load"; + statusElement.title = tooltipText; + + uptimeElement.textContent = `Uptime: ${data.uptime || 'Unknown'} (Load: ${data.load_1min || '?'}, ${data.load_5min || '?'}, ${data.load_15min || '?'})`; + + } catch (error) { + console.error("Error fetching server stats:", error); + statusElement.classList.remove('green', 'yellow', 'red'); + statusElement.classList.add('yellow'); + statusElement.title = "Status check failed - server may still be operational"; + uptimeElement.textContent = 'Status: Available (Stats unavailable)'; + } + } + + // --- Metrics Access Logic (from metrics-access.js) --- + const viewMetricsBtn = document.getElementById('view-metrics-btn'); + const passwordModal = document.getElementById('password-modal'); + const passwordInput = document.getElementById('metrics-password'); + const submitPasswordBtn = document.getElementById('submit-password'); + const closeModalBtn = document.querySelector('.close-button'); + const passwordError = document.getElementById('password-error'); + const metricsSection = document.getElementById('metrics-section'); + const AUTH_SESSION_KEY = 'metrics_authenticated'; + const METRICS_PASSWORD_HASH = "3a7526cc5662c9d46d9458a07ed6608689006a64f095838cefd244fb9da20780"; // Default: "metrics2023" + + function checkAuthentication() { + return sessionStorage.getItem(AUTH_SESSION_KEY) === 'true'; + } + + function setAuthenticated(state) { + sessionStorage.setItem(AUTH_SESSION_KEY, state ? 'true' : 'false'); + } + + function showMetrics() { + if (!metricsSection) return; + metricsSection.style.display = 'block'; + if (typeof updateCharts === 'function') { + setTimeout(updateCharts, 100); + } + if (!document.getElementById('hide-metrics-btn')) { + createHideMetricsButton(); + } + } + + function hideMetrics() { + if (!metricsSection) return; + metricsSection.style.display = 'none'; + if (viewMetricsBtn) { + viewMetricsBtn.textContent = 'View Server Metrics'; + } + const hideBtn = document.getElementById('hide-metrics-btn'); + if (hideBtn) hideBtn.remove(); // Remove hide button when hiding + } + + function createHideMetricsButton() { + const hideBtn = document.createElement('button'); + hideBtn.id = 'hide-metrics-btn'; + hideBtn.className = 'button'; + hideBtn.textContent = 'Hide Server Metrics'; + hideBtn.style.marginLeft = '10px'; + hideBtn.addEventListener('click', hideMetrics); + + if (viewMetricsBtn && viewMetricsBtn.parentNode) { + viewMetricsBtn.parentNode.appendChild(hideBtn); + } + } + + function openPasswordModal() { + if (!passwordModal || !passwordInput) return; + passwordInput.value = ''; + passwordError.textContent = ''; + passwordError.style.opacity = 0; + passwordModal.style.display = 'block'; + setTimeout(() => { + passwordModal.classList.add('show'); + passwordInput.focus(); + }, 10); + } + + function closePasswordModal() { + if (!passwordModal) return; + passwordModal.classList.remove('show'); + setTimeout(() => { + passwordModal.style.display = 'none'; + }, 300); + } + + function validatePassword(password) { + if (typeof CryptoJS === 'undefined') { + console.error("CryptoJS library not loaded!"); + showError("Authentication library error."); + return false; + } + const hash = CryptoJS.SHA256(password).toString(); + return hash === METRICS_PASSWORD_HASH; + } + + function showError(message) { + if (!passwordError || !passwordInput) return; + passwordError.textContent = message; + passwordError.style.opacity = 0; + setTimeout(() => { + passwordError.style.opacity = 1; + }, 10); + passwordInput.classList.add('shake'); + setTimeout(() => { + passwordInput.classList.remove('shake'); + }, 500); + } + + if (viewMetricsBtn) { + viewMetricsBtn.addEventListener('click', function() { + if (checkAuthentication()) { + showMetrics(); + } else { + openPasswordModal(); + } + }); + } + + if (submitPasswordBtn) { + submitPasswordBtn.addEventListener('click', function() { + if (!passwordInput) return; + const password = passwordInput.value; + if (!password) { + showError('Please enter a password'); + return; + } + if (validatePassword(password)) { + setAuthenticated(true); + closePasswordModal(); + showMetrics(); + } else { + passwordInput.value = ''; + showError('Incorrect password. Please try again.'); + } + }); + } + + if (closeModalBtn) { + closeModalBtn.addEventListener('click', closePasswordModal); + } + + window.addEventListener('click', function(event) { + if (event.target === passwordModal) { + closePasswordModal(); + } + }); + + if (passwordInput) { + passwordInput.addEventListener('keyup', function(event) { + if (event.key === 'Enter') { + submitPasswordBtn.click(); + } + }); + } + + // --- SNMP Charts Logic (from snmp-charts.js) --- + const API_ENDPOINT = '/api/metrics'; + const UPDATE_INTERVAL = 10000; + const CHART_HISTORY = 60; + let charts = {}; + let chartContainers = {}; + let chartsInitialized = false; + let updateIntervalId; + let metricsDefinitions = {}; + + const chartColors = { + networkIn: '#88B7B5', networkOut: '#FDCFF3', cpu: '#88B7B5', + memory: '#FDCFF3', system: '#88B7B5', generic: '#88B7B5' + }; + + function formatBytes(bytes, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1024, dm = decimals < 0 ? 0 : decimals, sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } + + function formatTime(centiseconds) { + const totalSeconds = Math.floor(centiseconds / 100); + const days = Math.floor(totalSeconds / 86400), hours = Math.floor((totalSeconds % 86400) / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60), seconds = totalSeconds % 60; + if (days > 0) return `${days}d ${hours}h ${minutes}m`; + if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`; + if (minutes > 0) return `${minutes}m ${seconds}s`; + return `${seconds}s`; + } + + function calculateRates(data) { + if (data.length < 2) return []; + const rates = []; + for (let i = 1; i < data.length; i++) { + const timeDiff = (data[i].timestamp - data[i-1].timestamp) / 1000; + const valueDiff = data[i].value - data[i-1].value; + if (timeDiff < 0.001) continue; + const rate = valueDiff >= 0 ? valueDiff / timeDiff : (4294967295 + valueDiff) / timeDiff; + rates.push({ timestamp: data[i].timestamp, value: rate }); + } + return rates; + } + + function createChartContainers(metrics) { + const metricGrid = document.querySelector('.metric-grid'); + if (!metricGrid) return; + metricGrid.innerHTML = ''; + for (const [metricName, definition] of Object.entries(metrics)) { + const displayName = definition.label || metricName; + const metricCard = document.createElement('div'); + metricCard.className = 'metric-card'; + metricCard.innerHTML = `<h5>${displayName}</h5><div class="chart-container"><canvas id="${metricName}Chart"></canvas></div>`; + metricGrid.appendChild(metricCard); + chartContainers[metricName] = metricCard; + } + } + + function initCharts(metrics) { + if (chartsInitialized) return; + const commonOptions = { + responsive: true, maintainAspectRatio: false, animation: { duration: 300 }, + scales: { + x: { type: 'time', time: { unit: 'minute', tooltipFormat: 'HH:mm:ss' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, ticks: { color: '#E8F1F2', maxTicksLimit: 5 } }, + y: { beginAtZero: true, grid: { color: 'rgba(255, 255, 255, 0.1)' }, ticks: { color: '#E8F1F2' } } + }, + plugins: { + legend: { labels: { color: '#E8F1F2', font: { family: "'IBM Plex Sans', sans-serif" } } }, + tooltip: { mode: 'index', intersect: false, backgroundColor: 'rgba(17, 17, 17, 0.8)', titleColor: '#FDCFF3', bodyColor: '#E8F1F2', borderColor: '#333', borderWidth: 1 } + } + }; + + for (const [metricName, definition] of Object.entries(metrics)) { + const canvas = document.getElementById(`${metricName}Chart`); + if (!canvas) continue; + const ctx = canvas.getContext('2d'); + let chartOptions = { ...commonOptions }; + let datasets = []; + let label = definition.label || metricName; + let color = chartColors.generic; + + if (metricName === 'network_in' || metricName === 'network_out') { + chartOptions.scales.y.title = { display: true, text: 'Bytes/sec', color: '#E8F1F2' }; + chartOptions.scales.y.ticks.callback = value => formatBytes(value, 0); + label = metricName === 'network_in' ? 'In' : 'Out'; + color = metricName === 'network_in' ? chartColors.networkIn : chartColors.networkOut; + } else if (metricName === 'cpu_load') { + chartOptions.scales.y.title = { display: true, text: 'Load/Usage', color: '#E8F1F2' }; + label = 'CPU'; color = chartColors.cpu; + } else if (metricName.includes('memory')) { + chartOptions.scales.y.title = { display: true, text: 'Memory (KB)', color: '#E8F1F2' }; + chartOptions.scales.y.ticks.callback = value => formatBytes(value * 1024, 0); + label = metricName.includes('total') ? 'Total' : metricName.includes('size') ? 'Size' : 'Used'; + color = chartColors.memory; + } else if (metricName === 'system_uptime') { + chartOptions.scales.y.title = { display: true, text: 'Uptime', color: '#E8F1F2' }; + chartOptions.scales.y.ticks.callback = value => formatTime(value); + label = 'Uptime'; color = chartColors.system; + } else if (metricName === 'system_processes') { + chartOptions.scales.y.title = { display: true, text: 'Count', color: '#E8F1F2' }; + label = 'Processes'; color = chartColors.system; + } else { + chartOptions.scales.y.title = { display: true, text: 'Value', color: '#E8F1F2' }; + } + + datasets = [{ + label: label, + borderColor: color, backgroundColor: `${color}33`, borderWidth: 2, + data: [], pointRadius: 0, fill: true + }]; + + charts[metricName] = new Chart(ctx, { type: 'line', data: { datasets }, options: chartOptions }); + } + chartsInitialized = true; + startDataUpdates(); + } + + function startDataUpdates() { + updateCharts(); + if (updateIntervalId) clearInterval(updateIntervalId); + updateIntervalId = setInterval(updateCharts, UPDATE_INTERVAL); + } + + function stopDataUpdates() { + if (updateIntervalId) { + clearInterval(updateIntervalId); + updateIntervalId = null; + } + } + + async function updateCharts() { + if (!chartsInitialized || !document.getElementById('metrics-section').offsetParent) return; + try { + const response = await fetch(API_ENDPOINT); + if (!response.ok) { + console.error('Failed to fetch metrics:', response.statusText); + return; + } + const data = await response.json(); + metricsDefinitions = data.definitions || {}; + for (const [metricName, metricData] of Object.entries(data.metrics)) { + if (!charts[metricName] || !metricData || metricData.length === 0) continue; + let chartData = (metricName === 'network_in' || metricName === 'network_out') ? calculateRates(metricData) : metricData; + if (!chartData || chartData.length === 0) continue; + + chartData.forEach(point => { + if (typeof point.value === 'string') { + const match = point.value.match(/\d+/); + point.value = match ? parseFloat(match[0]) : 0; + } + }); + + charts[metricName].data.datasets[0].data = chartData.map(point => ({ x: point.timestamp, y: point.value })); + charts[metricName].update('none'); + } + } catch (error) { + console.error('Error updating charts:', error); + } + } + + async function initializeMetricsUI() { + try { + const response = await fetch(API_ENDPOINT); + if (!response.ok) { + console.error('Failed to fetch metrics definitions:', response.statusText); + return; + } + const data = await response.json(); + metricsDefinitions = data.definitions || {}; + createChartContainers(metricsDefinitions); + initCharts(metricsDefinitions); + } catch (error) { + console.error('Error initializing metrics UI:', error); + } + } + + function handleMetricsVisibilityChange() { + if (!metricsSection) return; + if (metricsSection.offsetParent !== null) { + if (!chartsInitialized) { + initializeMetricsUI(); + } else { + startDataUpdates(); + } + } else { + stopDataUpdates(); + } + } + + // Initial Load + document.addEventListener('DOMContentLoaded', function() { + // Start server stats check + getServerStats(); + setInterval(getServerStats, 30000); + + // Apply styles to submit button + if (submitPasswordBtn) submitPasswordBtn.classList.add('submit-button'); + + // Check if already authenticated and show metrics if so + if (checkAuthentication()) showMetrics(); + + // Observer for metrics section visibility + if (metricsSection) { + const observer = new MutationObserver(handleMetricsVisibilityChange); + observer.observe(metricsSection, { attributes: true, attributeFilter: ['style'] }); + } + }); + + </script> + +</body> +</html> |