添加valkey support

This commit is contained in:
CN-JS-HuiBai
2026-04-04 19:33:22 +08:00
parent 96dd30a343
commit f75e755f3d
6 changed files with 184 additions and 16 deletions

View File

@@ -1,13 +1,10 @@
# MySQL Configuration
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=your_password
MYSQL_DATABASE=display_wall
# Server Configuration
HOST=0.0.0.0 HOST=0.0.0.0
PORT=3000 PORT=3000
# Data refresh interval (ms)
REFRESH_INTERVAL=5000 REFRESH_INTERVAL=5000
# Valkey/Redis Cache Configuration
VALKEY_HOST=localhost
VALKEY_PORT=6379
VALKEY_PASSWORD=
VALKEY_DB=dashboard
VALKEY_TTL=30

View File

@@ -67,9 +67,9 @@ RestartSec=10
StandardOutput=syslog StandardOutput=syslog
StandardError=syslog StandardError=syslog
SyslogIdentifier=data-wall SyslogIdentifier=data-wall
# Pass environment via .env file injection for flexibility
EnvironmentFile=-$PROJECT_DIR/.env
Environment=NODE_ENV=production Environment=NODE_ENV=production
Environment=PORT=3000
# If you have specific env vars in .env, they will be loaded by the app's dotenv.config()
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

102
package-lock.json generated
View File

@@ -12,9 +12,16 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.0", "dotenv": "^16.4.0",
"express": "^4.21.0", "express": "^4.21.0",
"ioredis": "^5.10.1",
"mysql2": "^3.11.0" "mysql2": "^3.11.0"
} }
}, },
"node_modules/@ioredis/commands": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
"integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==",
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "25.5.2", "version": "25.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz",
@@ -132,6 +139,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -606,6 +622,53 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/ioredis": {
"version": "5.10.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
"integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "1.5.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ioredis/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/ioredis/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -621,6 +684,18 @@
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/long": { "node_modules/long": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
@@ -885,6 +960,27 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -1049,6 +1145,12 @@
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1" "url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
} }
}, },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",

View File

@@ -13,6 +13,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.0", "dotenv": "^16.4.0",
"express": "^4.21.0", "express": "^4.21.0",
"ioredis": "^5.10.1",
"mysql2": "^3.11.0" "mysql2": "^3.11.0"
} }
} }

59
server/cache.js Normal file
View File

@@ -0,0 +1,59 @@
const Redis = require('ioredis');
const host = process.env.VALKEY_HOST || 'localhost';
const port = parseInt(process.env.VALKEY_PORT) || 6379;
const password = process.env.VALKEY_PASSWORD || undefined;
const db = parseInt(process.env.VALKEY_DB) || 0;
const ttl = parseInt(process.env.VALKEY_TTL) || 30;
let redis = null;
try {
redis = new Redis({
host,
port,
password,
db,
lazyConnect: true,
maxRetriesPerRequest: 1
});
redis.on('error', (err) => {
// Fail silently after one retry, we just won't cache
console.warn('[Cache] Valkey connection failed, caching disabled:', err.message);
});
} catch (err) {
console.warn('[Cache] Valkey init failed:', err.message);
}
const cache = {
async get(key) {
if (!redis) return null;
try {
const data = await redis.get(key);
return data ? JSON.parse(data) : null;
} catch (e) {
return null;
}
},
async set(key, value, customTtl = ttl) {
if (!redis) return;
try {
await redis.set(key, JSON.stringify(value), 'EX', customTtl);
} catch (e) {
// ignore
}
},
async del(key) {
if (!redis) return;
try {
await redis.del(key);
} catch (e) {
// ignore
}
}
};
module.exports = cache;

View File

@@ -4,6 +4,7 @@ const cors = require('cors');
const path = require('path'); const path = require('path');
const db = require('./db'); const db = require('./db');
const prometheusService = require('./prometheus-service'); const prometheusService = require('./prometheus-service');
const cache = require('./cache');
const checkAndFixDatabase = require('./db-integrity-check'); const checkAndFixDatabase = require('./db-integrity-check');
const app = express(); const app = express();
@@ -462,12 +463,20 @@ app.get('/api/metrics/overview', async (req, res) => {
}); });
} }
const allMetrics = await Promise.all(sources.map(source => const allMetrics = await Promise.all(sources.map(async (source) => {
prometheusService.getOverviewMetrics(source.url, source.name).catch(err => { const cacheKey = `source_metrics:${source.url}:${source.name}`;
const cached = await cache.get(cacheKey);
if (cached) return cached;
try {
const metrics = await prometheusService.getOverviewMetrics(source.url, source.name);
await cache.set(cacheKey, metrics, 15); // Cache for 15s
return metrics;
} catch (err) {
console.error(`Error fetching metrics from ${source.name}:`, err.message); console.error(`Error fetching metrics from ${source.name}:`, err.message);
return null; return null;
}) }
)); }));
const validMetrics = allMetrics.filter(m => m !== null); const validMetrics = allMetrics.filter(m => m !== null);