Initial commit: ToNav Personal Navigation Page
- Flask + SQLite 个人导航页系统 - 前台导航页(分类Tab、卡片展示) - 管理后台(服务管理、分类管理、健康检测) - 响应式设计 - Systemd 服务配置
This commit is contained in:
2
utils/__init__.py
Normal file
2
utils/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Utils package"""
|
||||
BIN
utils/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
utils/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/auth.cpython-313.pyc
Normal file
BIN
utils/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/database.cpython-313.pyc
Normal file
BIN
utils/__pycache__/database.cpython-313.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/health_check.cpython-313.pyc
Normal file
BIN
utils/__pycache__/health_check.cpython-313.pyc
Normal file
Binary file not shown.
38
utils/auth.py
Normal file
38
utils/auth.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""认证工具"""
|
||||
|
||||
import hashlib
|
||||
import sqlite3
|
||||
from config import Config
|
||||
|
||||
def hash_password(password):
|
||||
"""密码哈希(SHA256,简单版)"""
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
def verify_password(password, stored_hash):
|
||||
"""验证密码"""
|
||||
return hash_password(password) == stored_hash
|
||||
|
||||
def authenticate(username, password):
|
||||
"""用户认证"""
|
||||
conn = sqlite3.connect(Config.DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT id, username, password_hash FROM users
|
||||
WHERE username = ?
|
||||
''', (username,))
|
||||
|
||||
user = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if user and verify_password(password, user[2]):
|
||||
return {
|
||||
'id': user[0],
|
||||
'username': user[1]
|
||||
}
|
||||
return None
|
||||
|
||||
def is_logged_in(session):
|
||||
"""检查是否已登录"""
|
||||
return 'user_id' in session
|
||||
112
utils/database.py
Normal file
112
utils/database.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""数据库初始化脚本"""
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
from config import Config
|
||||
|
||||
def init_database():
|
||||
"""初始化数据库表"""
|
||||
db_path = Config.DATABASE_PATH
|
||||
|
||||
# 如果数据库已存在,先删除(可选,开发环境)
|
||||
if os.path.exists(db_path):
|
||||
print(f"数据库已存在: {db_path}")
|
||||
return
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 创建 services 表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS services (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
url VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
icon VARCHAR(50),
|
||||
category VARCHAR(50) DEFAULT '默认',
|
||||
is_enabled INTEGER DEFAULT 1,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
health_check_url VARCHAR(500),
|
||||
health_check_enabled INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建 categories 表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(50) NOT NULL UNIQUE,
|
||||
sort_order INTEGER DEFAULT 0
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建 users 表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建索引
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_services_category ON services(category)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_services_enabled ON services(is_enabled)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_services_sort ON services(sort_order DESC)')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print(f"数据库初始化完成: {db_path}")
|
||||
|
||||
def insert_initial_data():
|
||||
"""插入初始数据"""
|
||||
import hashlib
|
||||
|
||||
conn = sqlite3.connect(Config.DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 创建默认管理员账号 (admin / admin123)
|
||||
# 使用 SHA256 简单哈希(生产环境建议用 bcrypt)
|
||||
password_hash = hashlib.sha256('admin123'.encode()).hexdigest()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO users (username, password_hash)
|
||||
VALUES (?, ?)
|
||||
''', ('admin', password_hash))
|
||||
|
||||
# 创建默认分类
|
||||
categories = [('内网服务', 1), ('开发工具', 2), ('测试环境', 3)]
|
||||
for name, sort_order in categories:
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO categories (name, sort_order)
|
||||
VALUES (?, ?)
|
||||
''', (name, sort_order))
|
||||
|
||||
# 创建默认服务
|
||||
services = [
|
||||
('违禁品查获排行榜', 'http://127.0.0.1:9517', '实时数据统计 · 自动刷新', '📊', '内网服务', 1, 100, 'http://127.0.0.1:9517/api/rankings', 1),
|
||||
('短信接收端-Python', 'http://127.0.0.1:9518', 'HTTP接口 + Web管理', '📱', '内网服务', 1, 90, None, 0),
|
||||
('短信接收端-Go', 'http://127.0.0.1:28001', '高性能版本 · 端口28001', '🔧', '内网服务', 1, 80, 'http://127.0.0.1:28001/', 1),
|
||||
]
|
||||
|
||||
for service in services:
|
||||
cursor.execute('''
|
||||
INSERT INTO services (name, url, description, icon, category, is_enabled, sort_order, health_check_url, health_check_enabled)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', service)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("初始数据插入完成")
|
||||
print("默认管理员账号: admin / admin123")
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_database()
|
||||
insert_initial_data()
|
||||
111
utils/health_check.py
Normal file
111
utils/health_check.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""健康检查工具"""
|
||||
|
||||
import requests
|
||||
import sqlite3
|
||||
import threading
|
||||
import time
|
||||
from config import Config
|
||||
|
||||
def check_service_health(service_id):
|
||||
"""检查单个服务健康状态"""
|
||||
conn = sqlite3.connect(Config.DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT name, url, health_check_url, health_check_enabled
|
||||
FROM services
|
||||
WHERE id = ?
|
||||
''', (service_id,))
|
||||
|
||||
service = cursor.fetchone()
|
||||
if not service:
|
||||
conn.close()
|
||||
return
|
||||
|
||||
name, url, check_url, check_enabled = service
|
||||
check_url = check_url or url # 如果没有单独配置,使用主url
|
||||
|
||||
status = 'offline'
|
||||
status_code = None
|
||||
error = None
|
||||
|
||||
if check_enabled:
|
||||
try:
|
||||
response = requests.get(check_url, timeout=Config.HEALTH_CHECK_TIMEOUT)
|
||||
status_code = response.status_code
|
||||
if response.status_code < 500:
|
||||
status = 'online'
|
||||
except requests.exceptions.Timeout:
|
||||
error = 'timeout'
|
||||
except requests.exceptions.ConnectionError:
|
||||
error = 'connection_error'
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
|
||||
# 这里可以添加状态记录表,暂时只检查
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
'id': service_id,
|
||||
'name': name,
|
||||
'status': status,
|
||||
'status_code': status_code,
|
||||
'error': error
|
||||
}
|
||||
|
||||
def check_all_services():
|
||||
"""检查所有启用健康检测的服务"""
|
||||
conn = sqlite3.connect(Config.DATABASE_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT id FROM services
|
||||
WHERE is_enabled = 1 AND health_check_enabled = 1
|
||||
ORDER BY sort_order DESC
|
||||
''')
|
||||
|
||||
services = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
results = []
|
||||
for (service_id,) in services:
|
||||
result = check_service_health(service_id)
|
||||
if result:
|
||||
results.append(result)
|
||||
|
||||
return results
|
||||
|
||||
class HealthCheckWorker:
|
||||
"""后台健康检查工作线程"""
|
||||
|
||||
def __init__(self):
|
||||
self.running = False
|
||||
self.thread = None
|
||||
|
||||
def start(self):
|
||||
"""启动后台检查"""
|
||||
if not self.running:
|
||||
self.running = True
|
||||
self.thread = threading.Thread(target=self._run, daemon=True)
|
||||
self.thread.start()
|
||||
print("健康检查线程已启动")
|
||||
|
||||
def stop(self):
|
||||
"""停止后台检查"""
|
||||
self.running = False
|
||||
if self.thread:
|
||||
self.thread.join()
|
||||
print("健康检查线程已停止")
|
||||
|
||||
def _run(self):
|
||||
"""检查循环"""
|
||||
while self.running:
|
||||
results = check_all_services()
|
||||
# 记录日志
|
||||
for result in results:
|
||||
print(f"[HealthCheck] {result['name']}: {result['status']}")
|
||||
time.sleep(Config.HEALTH_CHECK_INTERVAL)
|
||||
|
||||
# 全局工作线程
|
||||
health_worker = HealthCheckWorker()
|
||||
Reference in New Issue
Block a user