mirror of
https://github.com/DeNNiiInc/Connect-5.git
synced 2026-04-17 22:46: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>
|
||||
</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">
|
||||
<p>© 2025 Beyond Cloud Technology. All rights reserved.</p>
|
||||
</footer>
|
||||
@@ -183,6 +209,7 @@
|
||||
</div>
|
||||
|
||||
<script src="storage.js"></script>
|
||||
<script src="db-status.js"></script>
|
||||
<script src="multiplayer.js"></script>
|
||||
<script src="game.js"></script>
|
||||
<script>
|
||||
|
||||
69
server.js
69
server.js
@@ -27,6 +27,75 @@ app.get('/', (req, res) => {
|
||||
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
|
||||
const gameManager = new GameManager(io, db);
|
||||
|
||||
|
||||
168
styles.css
168
styles.css
@@ -528,6 +528,157 @@ body {
|
||||
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 {
|
||||
margin-top: 2rem;
|
||||
@@ -544,7 +695,7 @@ body {
|
||||
grid-template-columns: repeat(25, 22px);
|
||||
grid-template-rows: repeat(25, 22px);
|
||||
}
|
||||
|
||||
|
||||
.board[data-size="50"] {
|
||||
grid-template-columns: repeat(50, 15px);
|
||||
grid-template-rows: repeat(50, 15px);
|
||||
@@ -611,4 +762,19 @@ body {
|
||||
.victory-title {
|
||||
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