aboutsummaryrefslogtreecommitdiffstats
path: root/index.html
diff options
context:
space:
mode:
authorLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-05-03 13:21:56 +0530
committerLibravatarLibravatar Biswa Kalyan Bhuyan <[email protected]> 2025-05-03 13:21:56 +0530
commit2b85ffa5d9997b59223fab4dd527d0b3c0406be4 (patch)
treebd14fca930e1704f962d402be626cebfcd8e995d /index.html
downloadchronos-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.html550
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">&times;</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>&copy; 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>