添加初始化页面
This commit is contained in:
131
public/init.html
Normal file
131
public/init.html
Normal 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
79
public/js/init.js
Normal 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;
|
||||
}
|
||||
});
|
||||
});
|
||||
34
server/db.js
34
server/db.js
@@ -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
|
||||
};
|
||||
|
||||
116
server/index.js
116
server/index.js
@@ -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 ====================
|
||||
|
||||
Reference in New Issue
Block a user