mirror of
https://github.com/DeNNiiInc/Connect-5.git
synced 2026-04-18 00:56:00 +00:00
Add database status bar with connection, latency, and write capability monitoring
This commit is contained in:
129
db-status.js
Normal file
129
db-status.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Database Status Monitor
|
||||||
|
class DatabaseStatusMonitor {
|
||||||
|
constructor() {
|
||||||
|
this.updateInterval = 5000; // 5 seconds
|
||||||
|
this.intervalId = null;
|
||||||
|
this.elements = {
|
||||||
|
connection: document.getElementById('dbConnection'),
|
||||||
|
latency: document.getElementById('dbLatency'),
|
||||||
|
write: document.getElementById('dbWrite'),
|
||||||
|
timestamp: document.getElementById('dbTimestamp')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/db-status');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
this.updateUI(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch database status:', error);
|
||||||
|
this.updateUI({
|
||||||
|
connected: false,
|
||||||
|
latency: 0,
|
||||||
|
writeCapable: false,
|
||||||
|
error: 'Failed to connect to server'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUI(status) {
|
||||||
|
// Update connection status
|
||||||
|
const connectionDot = this.elements.connection.querySelector('.status-dot');
|
||||||
|
const connectionText = this.elements.connection.querySelector('.status-text');
|
||||||
|
|
||||||
|
if (status.connected) {
|
||||||
|
connectionDot.className = 'status-dot status-connected';
|
||||||
|
connectionText.textContent = 'Connected';
|
||||||
|
this.elements.connection.classList.remove('status-error');
|
||||||
|
this.elements.connection.classList.add('status-success');
|
||||||
|
} else {
|
||||||
|
connectionDot.className = 'status-dot status-disconnected';
|
||||||
|
connectionText.textContent = 'Disconnected';
|
||||||
|
this.elements.connection.classList.remove('status-success');
|
||||||
|
this.elements.connection.classList.add('status-error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update latency
|
||||||
|
if (status.connected && status.latency > 0) {
|
||||||
|
this.elements.latency.textContent = `${status.latency} ms`;
|
||||||
|
|
||||||
|
// Color code based on latency
|
||||||
|
if (status.latency < 50) {
|
||||||
|
this.elements.latency.className = 'db-status-value latency-good';
|
||||||
|
} else if (status.latency < 150) {
|
||||||
|
this.elements.latency.className = 'db-status-value latency-ok';
|
||||||
|
} else {
|
||||||
|
this.elements.latency.className = 'db-status-value latency-slow';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.elements.latency.textContent = '-- ms';
|
||||||
|
this.elements.latency.className = 'db-status-value';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update write capability
|
||||||
|
const writeDot = this.elements.write.querySelector('.status-dot');
|
||||||
|
const writeText = this.elements.write.querySelector('.status-text');
|
||||||
|
|
||||||
|
if (status.writeCapable) {
|
||||||
|
writeDot.className = 'status-dot status-connected';
|
||||||
|
writeText.textContent = 'Enabled';
|
||||||
|
this.elements.write.classList.remove('status-error', 'status-warning');
|
||||||
|
this.elements.write.classList.add('status-success');
|
||||||
|
} else if (status.connected) {
|
||||||
|
writeDot.className = 'status-dot status-warning';
|
||||||
|
writeText.textContent = 'Read-Only';
|
||||||
|
this.elements.write.classList.remove('status-error', 'status-success');
|
||||||
|
this.elements.write.classList.add('status-warning');
|
||||||
|
} else {
|
||||||
|
writeDot.className = 'status-dot status-disconnected';
|
||||||
|
writeText.textContent = 'Disabled';
|
||||||
|
this.elements.write.classList.remove('status-success', 'status-warning');
|
||||||
|
this.elements.write.classList.add('status-error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update timestamp
|
||||||
|
const now = new Date();
|
||||||
|
const timeString = now.toLocaleTimeString('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
this.elements.timestamp.textContent = timeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
// Initial check
|
||||||
|
this.checkStatus();
|
||||||
|
|
||||||
|
// Set up periodic checks
|
||||||
|
this.intervalId = setInterval(() => {
|
||||||
|
this.checkStatus();
|
||||||
|
}, this.updateInterval);
|
||||||
|
|
||||||
|
console.log('✅ Database status monitor started');
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.intervalId) {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = null;
|
||||||
|
console.log('⏹️ Database status monitor stopped');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is ready
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
window.dbStatusMonitor = new DatabaseStatusMonitor();
|
||||||
|
window.dbStatusMonitor.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up on page unload
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
if (window.dbStatusMonitor) {
|
||||||
|
window.dbStatusMonitor.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
27
index.html
27
index.html
@@ -156,6 +156,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- Database Status Bar -->
|
||||||
|
<div class="db-status-bar" id="dbStatusBar">
|
||||||
|
<div class="db-status-item">
|
||||||
|
<span class="db-status-label">SQL Connection:</span>
|
||||||
|
<span class="db-status-indicator" id="dbConnection">
|
||||||
|
<span class="status-dot"></span>
|
||||||
|
<span class="status-text">Checking...</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="db-status-item">
|
||||||
|
<span class="db-status-label">Latency:</span>
|
||||||
|
<span class="db-status-value" id="dbLatency">-- ms</span>
|
||||||
|
</div>
|
||||||
|
<div class="db-status-item">
|
||||||
|
<span class="db-status-label">Write Capability:</span>
|
||||||
|
<span class="db-status-indicator" id="dbWrite">
|
||||||
|
<span class="status-dot"></span>
|
||||||
|
<span class="status-text">Checking...</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="db-status-item db-status-timestamp">
|
||||||
|
<span class="db-status-label">Last Check:</span>
|
||||||
|
<span class="db-status-value" id="dbTimestamp">--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<p>© 2025 Beyond Cloud Technology. All rights reserved.</p>
|
<p>© 2025 Beyond Cloud Technology. All rights reserved.</p>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -183,6 +209,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="storage.js"></script>
|
<script src="storage.js"></script>
|
||||||
|
<script src="db-status.js"></script>
|
||||||
<script src="multiplayer.js"></script>
|
<script src="multiplayer.js"></script>
|
||||||
<script src="game.js"></script>
|
<script src="game.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
69
server.js
69
server.js
@@ -27,6 +27,75 @@ app.get('/', (req, res) => {
|
|||||||
res.sendFile(path.join(__dirname, 'index.html'));
|
res.sendFile(path.join(__dirname, 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Database health check endpoint
|
||||||
|
app.get('/api/db-status', async (req, res) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
let status = {
|
||||||
|
connected: false,
|
||||||
|
latency: 0,
|
||||||
|
writeCapable: false,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test connection with a simple query
|
||||||
|
const [result] = await db.pool.query('SELECT 1 as test');
|
||||||
|
const latency = Date.now() - startTime;
|
||||||
|
|
||||||
|
if (result && result[0].test === 1) {
|
||||||
|
status.connected = true;
|
||||||
|
status.latency = latency;
|
||||||
|
|
||||||
|
// Test write capability
|
||||||
|
try {
|
||||||
|
const testTableName = '_health_check_test';
|
||||||
|
|
||||||
|
// Create test table if it doesn't exist
|
||||||
|
await db.pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS ${testTableName} (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
test_value VARCHAR(50),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Try to insert a test record
|
||||||
|
const testValue = `test_${Date.now()}`;
|
||||||
|
await db.pool.query(
|
||||||
|
`INSERT INTO ${testTableName} (test_value) VALUES (?)`,
|
||||||
|
[testValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up old test records (keep only last 10)
|
||||||
|
await db.pool.query(`
|
||||||
|
DELETE FROM ${testTableName}
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT id FROM (
|
||||||
|
SELECT id FROM ${testTableName}
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 10
|
||||||
|
) AS keep_records
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
status.writeCapable = true;
|
||||||
|
} catch (writeError) {
|
||||||
|
console.error('Write test failed:', writeError.message);
|
||||||
|
status.writeCapable = false;
|
||||||
|
status.error = `Write test failed: ${writeError.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Database health check failed:', error.message);
|
||||||
|
status.connected = false;
|
||||||
|
status.latency = Date.now() - startTime;
|
||||||
|
status.error = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(status);
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize game manager
|
// Initialize game manager
|
||||||
const gameManager = new GameManager(io, db);
|
const gameManager = new GameManager(io, db);
|
||||||
|
|
||||||
|
|||||||
166
styles.css
166
styles.css
@@ -528,6 +528,157 @@ body {
|
|||||||
box-shadow: 0 6px 30px rgba(147, 51, 234, 0.6);
|
box-shadow: 0 6px 30px rgba(147, 51, 234, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Database Status Bar */
|
||||||
|
.db-status-bar {
|
||||||
|
background: rgba(20, 24, 39, 0.95);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
margin: 2rem 0 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-bar:hover {
|
||||||
|
border-color: var(--border-medium);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-value {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-indicator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--text-muted);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.status-connected {
|
||||||
|
background: var(--success);
|
||||||
|
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.status-disconnected {
|
||||||
|
background: var(--danger);
|
||||||
|
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
|
||||||
|
animation: pulse-error 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot.status-warning {
|
||||||
|
background: #f59e0b;
|
||||||
|
box-shadow: 0 0 10px rgba(245, 158, 11, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-error {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-indicator.status-success {
|
||||||
|
border-color: rgba(16, 185, 129, 0.3);
|
||||||
|
background: rgba(16, 185, 129, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-indicator.status-error {
|
||||||
|
border-color: rgba(239, 68, 68, 0.3);
|
||||||
|
background: rgba(239, 68, 68, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-indicator.status-warning {
|
||||||
|
border-color: rgba(245, 158, 11, 0.3);
|
||||||
|
background: rgba(245, 158, 11, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Latency color coding */
|
||||||
|
.latency-good {
|
||||||
|
color: var(--success);
|
||||||
|
border-color: rgba(16, 185, 129, 0.3);
|
||||||
|
background: rgba(16, 185, 129, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-ok {
|
||||||
|
color: #f59e0b;
|
||||||
|
border-color: rgba(245, 158, 11, 0.3);
|
||||||
|
background: rgba(245, 158, 11, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.latency-slow {
|
||||||
|
color: var(--danger);
|
||||||
|
border-color: rgba(239, 68, 68, 0.3);
|
||||||
|
background: rgba(239, 68, 68, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-timestamp {
|
||||||
|
margin-left: auto;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
@@ -611,4 +762,19 @@ body {
|
|||||||
.victory-title {
|
.victory-title {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.db-status-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-item {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-status-timestamp {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user