620 lines
22 KiB
HTML
620 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>MY Network Monitor - Distributed Protocol v2.0</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
|
||
color: #00ff41;
|
||
font-family: 'Courier New', 'Lucida Console', monospace;
|
||
min-height: 100vh;
|
||
overflow-x: auto;
|
||
animation: backgroundPulse 10s ease-in-out infinite alternate;
|
||
}
|
||
|
||
@keyframes backgroundPulse {
|
||
0% { background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%); }
|
||
100% { background: linear-gradient(135deg, #0f0f0f 0%, #1f1f3e 50%, #1b274e 100%); }
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
border: 2px solid #00ff41;
|
||
border-radius: 10px;
|
||
background: rgba(0, 255, 65, 0.05);
|
||
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 2.5em;
|
||
text-shadow: 0 0 10px #00ff41;
|
||
animation: glow 2s ease-in-out infinite alternate;
|
||
}
|
||
|
||
@keyframes glow {
|
||
from { text-shadow: 0 0 10px #00ff41, 0 0 20px #00ff41; }
|
||
to { text-shadow: 0 0 20px #00ff41, 0 0 30px #00ff41, 0 0 40px #00ff41; }
|
||
}
|
||
|
||
.status-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.status-card {
|
||
background: rgba(0, 0, 0, 0.7);
|
||
border: 1px solid #00ff41;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 0 15px rgba(0, 255, 65, 0.2);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.status-card:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 5px 25px rgba(0, 255, 65, 0.4);
|
||
}
|
||
|
||
.status-card h3 {
|
||
color: #00ff41;
|
||
margin-bottom: 15px;
|
||
font-size: 1.2em;
|
||
text-transform: uppercase;
|
||
border-bottom: 1px solid #00ff41;
|
||
padding-bottom: 5px;
|
||
}
|
||
|
||
.status-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin: 10px 0;
|
||
padding: 5px 0;
|
||
}
|
||
|
||
.status-value {
|
||
color: #ffffff;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status-online { color: #00ff00; }
|
||
.status-offline { color: #ff0000; }
|
||
.status-warning { color: #ffff00; }
|
||
|
||
.ascii-display {
|
||
background: rgba(0, 0, 0, 0.9);
|
||
border: 2px solid #00ff41;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 11px;
|
||
line-height: 1.1;
|
||
white-space: pre;
|
||
overflow-x: auto;
|
||
box-shadow: inset 0 0 20px rgba(0, 255, 65, 0.1);
|
||
}
|
||
|
||
.control-panel {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin: 20px 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn {
|
||
background: linear-gradient(45deg, #00ff41, #00cc33);
|
||
color: #000;
|
||
border: none;
|
||
padding: 12px 24px;
|
||
font-family: inherit;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
border-radius: 5px;
|
||
text-transform: uppercase;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);
|
||
}
|
||
|
||
.btn:hover {
|
||
background: linear-gradient(45deg, #00cc33, #00ff41);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(0, 255, 65, 0.5);
|
||
}
|
||
|
||
.btn:active {
|
||
transform: translateY(1px);
|
||
}
|
||
|
||
.network-topology {
|
||
background: rgba(0, 0, 0, 0.8);
|
||
border: 1px solid #00ff41;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
min-height: 300px;
|
||
}
|
||
|
||
.node {
|
||
display: inline-block;
|
||
background: rgba(0, 255, 65, 0.1);
|
||
border: 2px solid #00ff41;
|
||
border-radius: 50%;
|
||
width: 80px;
|
||
height: 80px;
|
||
line-height: 76px;
|
||
text-align: center;
|
||
margin: 10px;
|
||
position: relative;
|
||
animation: pulse 3s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
}
|
||
|
||
.node.this-node {
|
||
background: rgba(0, 255, 65, 0.3);
|
||
animation: strongPulse 2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes strongPulse {
|
||
0%, 100% { box-shadow: 0 0 10px #00ff41; }
|
||
50% { box-shadow: 0 0 30px #00ff41, 0 0 40px #00ff41; }
|
||
}
|
||
|
||
.logs-panel {
|
||
background: rgba(0, 0, 0, 0.9);
|
||
border: 1px solid #00ff41;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
margin: 20px 0;
|
||
height: 200px;
|
||
overflow-y: auto;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.log-entry {
|
||
margin: 5px 0;
|
||
padding: 2px 0;
|
||
}
|
||
|
||
.log-timestamp {
|
||
color: #888;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.log-info { color: #00ff41; }
|
||
.log-warning { color: #ffff00; }
|
||
.log-error { color: #ff0000; }
|
||
|
||
.footer {
|
||
text-align: center;
|
||
margin-top: 40px;
|
||
padding: 20px;
|
||
border-top: 1px solid #00ff41;
|
||
color: #888;
|
||
}
|
||
|
||
.loading {
|
||
display: inline-block;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.metrics-bar {
|
||
background: rgba(0, 0, 0, 0.5);
|
||
height: 20px;
|
||
border-radius: 10px;
|
||
margin: 10px 0;
|
||
overflow: hidden;
|
||
border: 1px solid #00ff41;
|
||
}
|
||
|
||
.metrics-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #00ff41, #00cc33);
|
||
transition: width 0.5s ease;
|
||
}
|
||
|
||
/* Responsive design */
|
||
@media (max-width: 768px) {
|
||
.status-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 1.8em;
|
||
}
|
||
|
||
.ascii-display {
|
||
font-size: 9px;
|
||
}
|
||
|
||
.control-panel {
|
||
justify-content: center;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>MY NETWORK MONITOR</h1>
|
||
<p>Distributed Content Protocol v2.0</p>
|
||
<p id="last-update">Last Update: <span id="timestamp">Loading...</span></p>
|
||
</div>
|
||
|
||
<div class="control-panel">
|
||
<button class="btn" onclick="refreshData()">
|
||
<span id="refresh-icon">🔄</span> REFRESH
|
||
</button>
|
||
<button class="btn" onclick="toggleASCII()">
|
||
📊 ASCII VIEW
|
||
</button>
|
||
<button class="btn" onclick="startSync()">
|
||
⚡ START SYNC
|
||
</button>
|
||
<button class="btn" onclick="showLogs()">
|
||
📋 LOGS
|
||
</button>
|
||
</div>
|
||
|
||
<div class="status-grid">
|
||
<div class="status-card">
|
||
<h3>🖥️ Node Status</h3>
|
||
<div class="status-item">
|
||
<span>Node ID:</span>
|
||
<span class="status-value" id="node-id">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Status:</span>
|
||
<span class="status-value" id="node-status">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Uptime:</span>
|
||
<span class="status-value" id="node-uptime">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Version:</span>
|
||
<span class="status-value" id="node-version">Loading...</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status-card">
|
||
<h3>🌐 Network Status</h3>
|
||
<div class="status-item">
|
||
<span>Connected Peers:</span>
|
||
<span class="status-value" id="peer-count">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Known Nodes:</span>
|
||
<span class="status-value" id="known-nodes">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Network Health:</span>
|
||
<span class="status-value" id="network-health">Loading...</span>
|
||
</div>
|
||
<div class="metrics-bar">
|
||
<div class="metrics-fill" id="network-bar" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status-card">
|
||
<h3>⚡ Sync Engine</h3>
|
||
<div class="status-item">
|
||
<span>Sync Status:</span>
|
||
<span class="status-value" id="sync-status">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Active Syncs:</span>
|
||
<span class="status-value" id="active-syncs">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Queue Size:</span>
|
||
<span class="status-value" id="queue-size">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Workers:</span>
|
||
<span class="status-value" id="workers-count">Loading...</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status-card">
|
||
<h3>📊 Content Stats</h3>
|
||
<div class="status-item">
|
||
<span>Total Items:</span>
|
||
<span class="status-value" id="content-items">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Total Size:</span>
|
||
<span class="status-value" id="content-size">Loading...</span>
|
||
</div>
|
||
<div class="status-item">
|
||
<span>Replicated:</span>
|
||
<span class="status-value" id="replicated-count">Loading...</span>
|
||
</div>
|
||
<div class="metrics-bar">
|
||
<div class="metrics-fill" id="storage-bar" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="ascii-container" class="ascii-display" style="display: none;">
|
||
<div id="ascii-content">Loading ASCII status...</div>
|
||
</div>
|
||
|
||
<div class="network-topology" id="topology-view">
|
||
<h3>🔗 Network Topology</h3>
|
||
<div id="topology-nodes">
|
||
<div class="node this-node">THIS<br>NODE</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="logs-panel" id="logs-panel" style="display: none;">
|
||
<h3>📋 System Logs</h3>
|
||
<div id="logs-content">
|
||
<div class="log-entry log-info">
|
||
<span class="log-timestamp">{{ monitoring_data.timestamp[:19] if monitoring_data.timestamp else 'N/A' }}</span>
|
||
<span>[INFO] MY Network Monitor initialized</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<p>MY Network Protocol - Decentralized Content Distribution System</p>
|
||
<p>Real-time monitoring dashboard with live updates every 15 seconds</p>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let asciiVisible = false;
|
||
let logsVisible = false;
|
||
let updateInterval;
|
||
|
||
// Инициализация данных
|
||
let monitoringData = {};
|
||
|
||
function setMonitoringData(data) {
|
||
monitoringData = data;
|
||
}
|
||
|
||
function initializeData() {
|
||
if (monitoringData.status === 'online') {
|
||
updateNodeInfo(monitoringData.node_info);
|
||
updatePeersInfo(monitoringData.peers_info);
|
||
updateSyncStatus(monitoringData.sync_status);
|
||
} else {
|
||
showOfflineStatus();
|
||
}
|
||
updateTimestamp();
|
||
}
|
||
|
||
function updateNodeInfo(nodeInfo) {
|
||
document.getElementById('node-id').textContent =
|
||
nodeInfo.node_id ? nodeInfo.node_id.substring(0, 16) + '...' : 'Unknown';
|
||
|
||
const statusElement = document.getElementById('node-status');
|
||
statusElement.textContent = nodeInfo.status ? nodeInfo.status.toUpperCase() : 'UNKNOWN';
|
||
statusElement.className = 'status-value ' +
|
||
(nodeInfo.status === 'running' ? 'status-online' : 'status-offline');
|
||
|
||
const uptime = nodeInfo.uptime || 0;
|
||
const hours = Math.floor(uptime / 3600);
|
||
const minutes = Math.floor((uptime % 3600) / 60);
|
||
document.getElementById('node-uptime').textContent = `${hours}h ${minutes}m`;
|
||
|
||
document.getElementById('node-version').textContent = nodeInfo.version || 'MY Network 2.0';
|
||
}
|
||
|
||
function updatePeersInfo(peersInfo) {
|
||
const peerCount = peersInfo.peer_count || 0;
|
||
document.getElementById('peer-count').textContent = peerCount;
|
||
document.getElementById('known-nodes').textContent = peersInfo.peers ? peersInfo.peers.length : 0;
|
||
|
||
const healthElement = document.getElementById('network-health');
|
||
const health = peerCount > 0 ? 'CONNECTED' : 'ISOLATED';
|
||
healthElement.textContent = health;
|
||
healthElement.className = 'status-value ' +
|
||
(peerCount > 0 ? 'status-online' : 'status-warning');
|
||
|
||
// Обновить индикатор сети
|
||
const networkBar = document.getElementById('network-bar');
|
||
const healthPercent = Math.min(peerCount * 20, 100);
|
||
networkBar.style.width = healthPercent + '%';
|
||
|
||
// Обновить топологию
|
||
updateTopology(peersInfo.peers || []);
|
||
}
|
||
|
||
function updateSyncStatus(syncStatus) {
|
||
const isRunning = syncStatus.is_running || false;
|
||
const statusElement = document.getElementById('sync-status');
|
||
statusElement.textContent = isRunning ? 'RUNNING' : 'STOPPED';
|
||
statusElement.className = 'status-value ' +
|
||
(isRunning ? 'status-online' : 'status-offline');
|
||
|
||
document.getElementById('active-syncs').textContent = syncStatus.active_syncs || 0;
|
||
document.getElementById('queue-size').textContent = syncStatus.queue_size || 0;
|
||
document.getElementById('workers-count').textContent = syncStatus.workers_count || 0;
|
||
}
|
||
|
||
function updateTopology(peers) {
|
||
const topologyNodes = document.getElementById('topology-nodes');
|
||
|
||
// Очистить существующие ноды (кроме центральной)
|
||
const existingNodes = topologyNodes.querySelectorAll('.node:not(.this-node)');
|
||
existingNodes.forEach(node => node.remove());
|
||
|
||
// Добавить ноды пиров
|
||
peers.slice(0, 8).forEach((peer, index) => {
|
||
const node = document.createElement('div');
|
||
node.className = 'node';
|
||
node.innerHTML = peer.node_id ? peer.node_id.substring(0, 4).toUpperCase() : 'PEER';
|
||
node.style.animationDelay = (index * 0.2) + 's';
|
||
topologyNodes.appendChild(node);
|
||
});
|
||
}
|
||
|
||
function showOfflineStatus() {
|
||
document.getElementById('node-status').textContent = 'OFFLINE';
|
||
document.getElementById('node-status').className = 'status-value status-offline';
|
||
document.getElementById('network-health').textContent = 'DISCONNECTED';
|
||
document.getElementById('network-health').className = 'status-value status-offline';
|
||
document.getElementById('sync-status').textContent = 'STOPPED';
|
||
document.getElementById('sync-status').className = 'status-value status-offline';
|
||
}
|
||
|
||
function updateTimestamp() {
|
||
const now = new Date();
|
||
document.getElementById('timestamp').textContent =
|
||
now.toISOString().substring(0, 19).replace('T', ' ') + ' UTC';
|
||
}
|
||
|
||
async function refreshData() {
|
||
const refreshIcon = document.getElementById('refresh-icon');
|
||
refreshIcon.className = 'loading';
|
||
refreshIcon.textContent = '⏳';
|
||
|
||
try {
|
||
const response = await fetch('/api/my/monitor/live');
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
updateNodeInfo(data.data.node_info);
|
||
updatePeersInfo({
|
||
peer_count: data.data.network_stats.connected_peers,
|
||
peers: data.data.peers
|
||
});
|
||
updateSyncStatus(data.data.sync_status);
|
||
updateTimestamp();
|
||
|
||
addLogEntry('info', 'Data refreshed successfully');
|
||
}
|
||
} catch (error) {
|
||
addLogEntry('error', 'Failed to refresh data: ' + error.message);
|
||
} finally {
|
||
refreshIcon.className = '';
|
||
refreshIcon.textContent = '🔄';
|
||
}
|
||
}
|
||
|
||
async function toggleASCII() {
|
||
asciiVisible = !asciiVisible;
|
||
const container = document.getElementById('ascii-container');
|
||
|
||
if (asciiVisible) {
|
||
container.style.display = 'block';
|
||
try {
|
||
const response = await fetch('/api/my/monitor/ascii');
|
||
const data = await response.json();
|
||
document.getElementById('ascii-content').textContent = data.ascii;
|
||
} catch (error) {
|
||
document.getElementById('ascii-content').textContent = 'Error loading ASCII view';
|
||
}
|
||
} else {
|
||
container.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
async function startSync() {
|
||
try {
|
||
const response = await fetch('/api/my/sync/start', { method: 'POST' });
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
addLogEntry('info', 'Network sync started');
|
||
setTimeout(refreshData, 1000); // Обновить через секунду
|
||
} else {
|
||
addLogEntry('error', 'Failed to start sync');
|
||
}
|
||
} catch (error) {
|
||
addLogEntry('error', 'Sync request failed: ' + error.message);
|
||
}
|
||
}
|
||
|
||
function showLogs() {
|
||
logsVisible = !logsVisible;
|
||
const logsPanel = document.getElementById('logs-panel');
|
||
logsPanel.style.display = logsVisible ? 'block' : 'none';
|
||
}
|
||
|
||
function addLogEntry(level, message) {
|
||
const logsContent = document.getElementById('logs-content');
|
||
const logEntry = document.createElement('div');
|
||
logEntry.className = `log-entry log-${level}`;
|
||
|
||
const timestamp = new Date().toISOString().substring(11, 19);
|
||
logEntry.innerHTML = `
|
||
<span class="log-timestamp">${timestamp}</span>
|
||
<span>[${level.toUpperCase()}] ${message}</span>
|
||
`;
|
||
|
||
logsContent.appendChild(logEntry);
|
||
logsContent.scrollTop = logsContent.scrollHeight;
|
||
|
||
// Ограничить количество логов
|
||
if (logsContent.children.length > 50) {
|
||
logsContent.removeChild(logsContent.firstChild);
|
||
}
|
||
}
|
||
|
||
// Автоматическое обновление каждые 15 секунд
|
||
function startAutoUpdate() {
|
||
updateInterval = setInterval(refreshData, 15000);
|
||
}
|
||
|
||
function stopAutoUpdate() {
|
||
if (updateInterval) {
|
||
clearInterval(updateInterval);
|
||
}
|
||
}
|
||
|
||
// Инициализация при загрузке страницы
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Получить данные из data-атрибута
|
||
const dataElement = document.getElementById('monitoring-data');
|
||
if (dataElement) {
|
||
try {
|
||
const data = JSON.parse(dataElement.textContent);
|
||
setMonitoringData(data);
|
||
} catch (e) {
|
||
console.error('Error parsing monitoring data:', e);
|
||
setMonitoringData({status: 'offline', error: 'Data parsing failed'});
|
||
}
|
||
}
|
||
initializeData();
|
||
startAutoUpdate();
|
||
addLogEntry('info', 'MY Network Monitor loaded');
|
||
});
|
||
|
||
// Остановить обновления при уходе со страницы
|
||
window.addEventListener('beforeunload', stopAutoUpdate);
|
||
</script>
|
||
|
||
<!-- Данные мониторинга -->
|
||
<script type="application/json" id="monitoring-data">{{ monitoring_data | tojson }}</script>
|
||
</body>
|
||
</html> |