添加初始化页面

This commit is contained in:
CN-JS-HuiBai
2026-04-04 16:53:41 +08:00
parent e69424dab2
commit c6c3dd2134
4 changed files with 349 additions and 11 deletions

131
public/init.html Normal file
View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统初始化 - 数据可视化展示大屏</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<style>
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}
.init-container {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 40px;
width: 100%;
max-width: 460px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
z-index: 10;
}
.init-header {
text-align: center;
margin-bottom: 28px;
}
.init-header h2 {
margin: 0 0 10px 0;
color: var(--text-main);
font-size: 24px;
}
.init-header p {
margin: 0;
color: var(--text-muted);
font-size: 14px;
}
.form-message {
margin-top: 16px;
padding: 12px;
border-radius: 6px;
display: none;
font-size: 14px;
word-break: break-all;
}
.form-message.success {
display: block;
background: rgba(16, 185, 129, 0.1);
color: #10b981;
border: 1px solid rgba(16, 185, 129, 0.2);
}
.form-message.error {
display: block;
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.2);
}
.actions {
display: flex;
gap: 12px;
margin-top: 24px;
}
.actions .btn {
flex: 1;
justify-content: center;
padding: 10px 0;
}
</style>
</head>
<body>
<!-- Animated Background -->
<div class="bg-grid"></div>
<div class="bg-glow bg-glow-1"></div>
<div class="bg-glow bg-glow-2"></div>
<div class="init-container">
<div class="init-header">
<h2>系统初始化</h2>
<p>请配置您的 MySQL 数据库连接信息以完成首次设置</p>
</div>
<div class="init-form" id="initForm">
<div class="form-row">
<div class="form-group" style="flex: 2;">
<label for="host">数据库地址 (Host)</label>
<input type="text" id="host" value="localhost" autocomplete="off">
</div>
<div class="form-group" style="flex: 1;">
<label for="port">端口 (Port)</label>
<input type="number" id="port" value="3306" autocomplete="off">
</div>
</div>
<div class="form-row">
<div class="form-group form-group-wide">
<label for="user">用户名 (User)</label>
<input type="text" id="user" value="root" autocomplete="off">
</div>
</div>
<div class="form-row">
<div class="form-group form-group-wide">
<label for="password">密码 (Password)</label>
<input type="password" id="password" placeholder="留空则无密码">
</div>
</div>
<div class="form-row">
<div class="form-group form-group-wide">
<label for="database">数据库名 (Database)</label>
<input type="text" id="database" value="display_wall" autocomplete="off">
</div>
</div>
<div class="form-message" id="messageBox"></div>
<div class="actions">
<button class="btn btn-test" id="btnTest">测试连接</button>
<button class="btn btn-add" id="btnInit">初始化数据库</button>
</div>
</div>
</div>
<script src="/js/init.js"></script>
</body>
</html>

79
public/js/init.js Normal file
View File

@@ -0,0 +1,79 @@
document.addEventListener('DOMContentLoaded', () => {
const hostInput = document.getElementById('host');
const portInput = document.getElementById('port');
const userInput = document.getElementById('user');
const passwordInput = document.getElementById('password');
const databaseInput = document.getElementById('database');
const btnTest = document.getElementById('btnTest');
const btnInit = document.getElementById('btnInit');
const messageBox = document.getElementById('messageBox');
function showMessage(msg, isError = false) {
messageBox.textContent = msg;
messageBox.className = 'form-message ' + (isError ? 'error' : 'success');
}
btnTest.addEventListener('click', async () => {
btnTest.disabled = true;
const oldText = btnTest.textContent;
btnTest.textContent = '测试中...';
try {
const res = await fetch('/api/setup/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
host: hostInput.value,
port: portInput.value,
user: userInput.value,
password: passwordInput.value
})
});
const data = await res.json();
if (data.success) {
showMessage('连接成功!可以进行初始化。');
} else {
showMessage('连接失败: ' + (data.error || '未知错误'), true);
}
} catch (err) {
showMessage('请求失败: ' + err.message, true);
} finally {
btnTest.disabled = false;
btnTest.textContent = oldText;
}
});
btnInit.addEventListener('click', async () => {
btnInit.disabled = true;
const oldText = btnInit.textContent;
btnInit.textContent = '初始化中...';
try {
const res = await fetch('/api/setup/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
host: hostInput.value,
port: portInput.value,
user: userInput.value,
password: passwordInput.value,
database: databaseInput.value
})
});
const data = await res.json();
if (data.success) {
showMessage('初始化成功!即将跳转到系统首页面...');
setTimeout(() => {
window.location.href = '/';
}, 1500);
} else {
showMessage('初始化失败: ' + (data.error || '未知错误'), true);
btnInit.disabled = false;
btnInit.textContent = oldText;
}
} catch (err) {
showMessage('请求失败: ' + err.message, true);
btnInit.disabled = false;
btnInit.textContent = oldText;
}
});
});

View File

@@ -1,14 +1,26 @@
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.MYSQL_HOST || 'localhost',
port: parseInt(process.env.MYSQL_PORT) || 3306,
user: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD || '',
database: process.env.MYSQL_DATABASE || 'display_wall',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
let pool;
module.exports = pool;
function initPool() {
if (pool) {
pool.end().catch(e => console.error('Error closing pool:', e));
}
pool = mysql.createPool({
host: process.env.MYSQL_HOST || 'localhost',
port: parseInt(process.env.MYSQL_PORT) || 3306,
user: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD || '',
database: process.env.MYSQL_DATABASE || 'display_wall',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
}
initPool();
module.exports = {
query: (...args) => pool.query(...args),
initPool
};

View File

@@ -10,6 +10,122 @@ const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
const fs = require('fs');
let isDbInitialized = false;
async function checkDb() {
try {
const [rows] = await db.query("SHOW TABLES LIKE 'prometheus_sources'");
if (rows.length > 0) {
isDbInitialized = true;
} else {
isDbInitialized = false;
}
} catch (err) {
isDbInitialized = false;
}
}
checkDb();
// Setup API Routes
app.post('/api/setup/test', async (req, res) => {
const { host, port, user, password } = req.body;
try {
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection({
host: host || 'localhost',
port: parseInt(port) || 3306,
user: user || 'root',
password: password || ''
});
await connection.ping();
await connection.end();
res.json({ success: true, message: 'Connection successful' });
} catch (err) {
res.status(400).json({ success: false, error: err.message });
}
});
app.post('/api/setup/init', async (req, res) => {
const { host, port, user, password, database } = req.body;
try {
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection({
host: host || 'localhost',
port: parseInt(port) || 3306,
user: user || 'root',
password: password || ''
});
const dbName = database || 'display_wall';
// Create database
await connection.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
await connection.query(`USE \`${dbName}\``);
// Create tables
await connection.query(`
CREATE TABLE IF NOT EXISTS prometheus_sources (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
url VARCHAR(500) NOT NULL,
description TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
await connection.end();
// Save to .env
const envContent = `MYSQL_HOST=${host || 'localhost'}
MYSQL_PORT=${port || '3306'}
MYSQL_USER=${user || 'root'}
MYSQL_PASSWORD=${password || ''}
MYSQL_DATABASE=${dbName}
PORT=${process.env.PORT || 3000}
REFRESH_INTERVAL=${process.env.REFRESH_INTERVAL || 5000}
`;
fs.writeFileSync(path.join(__dirname, '..', '.env'), envContent);
// Update process.env
process.env.MYSQL_HOST = host;
process.env.MYSQL_PORT = port;
process.env.MYSQL_USER = user;
process.env.MYSQL_PASSWORD = password;
process.env.MYSQL_DATABASE = dbName;
// Re-initialize pool
db.initPool();
isDbInitialized = true;
res.json({ success: true, message: 'Initialization complete' });
} catch (err) {
console.error('Initialization error:', err);
res.status(500).json({ success: false, error: err.message });
}
});
// Middleware to protect routes
app.use((req, res, next) => {
if (!isDbInitialized) {
if (req.path.startsWith('/api/setup') || req.path === '/init.html' || req.path.startsWith('/css/') || req.path.startsWith('/js/') || req.path.startsWith('/fonts/')) {
return next();
}
if (req.path.startsWith('/api/')) {
return res.status(503).json({ error: 'Database not initialized', needSetup: true });
}
return res.redirect('/init.html');
}
if (req.path === '/init.html') {
return res.redirect('/');
}
next();
});
app.use(express.static(path.join(__dirname, '..', 'public')));
// ==================== Prometheus Source CRUD ====================