uploader-bot/app/templates/my_network_monitor.html

620 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>