添加初始化页面
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
16
server/db.js
16
server/db.js
@@ -1,6 +1,12 @@
|
|||||||
const mysql = require('mysql2/promise');
|
const mysql = require('mysql2/promise');
|
||||||
|
|
||||||
const pool = mysql.createPool({
|
let pool;
|
||||||
|
|
||||||
|
function initPool() {
|
||||||
|
if (pool) {
|
||||||
|
pool.end().catch(e => console.error('Error closing pool:', e));
|
||||||
|
}
|
||||||
|
pool = mysql.createPool({
|
||||||
host: process.env.MYSQL_HOST || 'localhost',
|
host: process.env.MYSQL_HOST || 'localhost',
|
||||||
port: parseInt(process.env.MYSQL_PORT) || 3306,
|
port: parseInt(process.env.MYSQL_PORT) || 3306,
|
||||||
user: process.env.MYSQL_USER || 'root',
|
user: process.env.MYSQL_USER || 'root',
|
||||||
@@ -10,5 +16,11 @@ const pool = mysql.createPool({
|
|||||||
connectionLimit: 10,
|
connectionLimit: 10,
|
||||||
queueLimit: 0
|
queueLimit: 0
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = pool;
|
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(cors());
|
||||||
app.use(express.json());
|
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')));
|
app.use(express.static(path.join(__dirname, '..', 'public')));
|
||||||
|
|
||||||
// ==================== Prometheus Source CRUD ====================
|
// ==================== Prometheus Source CRUD ====================
|
||||||
|
|||||||
Reference in New Issue
Block a user