feat: 顶部添加连接状态指示器,掉线时禁止编辑和新增操作

This commit is contained in:
2026-04-20 18:10:37 +08:00
parent 94efc524c6
commit ca7dc10e92
10 changed files with 14541 additions and 73 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,17 @@
import sqlite3
import json
from datetime import datetime
import shutil
import os
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any
from contextlib import contextmanager
from .config import DATABASE_URL, TODO_STATUS, PRIORITY_LEVELS
# 备份目录
BACKUP_DIR = os.path.join(os.path.dirname(DATABASE_URL), 'backups')
class Database:
"""SQLite数据库管理"""
@@ -80,6 +85,28 @@ class Database:
)
""")
# 邮箱表
cursor.execute("""
CREATE TABLE IF NOT EXISTS emails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT,
created_at TEXT NOT NULL
)
""")
# 邮件发送记录表
cursor.execute("""
CREATE TABLE IF NOT EXISTS email_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_id INTEGER NOT NULL,
email TEXT NOT NULL,
sent_at TEXT NOT NULL,
success INTEGER DEFAULT 1,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
)
""")
# 创建索引
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_type ON items(type)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_items_status ON items(status)")
@@ -177,6 +204,41 @@ class Database:
return items
def count_items(self, type: str = None, status: str = None, tag: str = None,
keyword: str = None) -> int:
"""计算符合条件的条目总数"""
with self.get_conn() as conn:
cursor = conn.cursor()
query = "SELECT COUNT(DISTINCT i.id) as count FROM items i"
params = []
conditions = []
# 标签过滤需要JOIN
if tag:
query += " JOIN item_tags it ON i.id = it.item_id JOIN tags t ON it.tag_id = t.id"
conditions.append("t.name = ?")
params.append(tag)
if type:
conditions.append("i.type = ?")
params.append(type)
if status:
conditions.append("i.status = ?")
params.append(status)
if keyword:
conditions.append("(i.title LIKE ? OR i.content LIKE ? OR i.note LIKE ?)")
keyword_pattern = f"%{keyword}%"
params.extend([keyword_pattern, keyword_pattern, keyword_pattern])
if conditions:
query += " WHERE " + " AND ".join(conditions)
cursor.execute(query, params)
return cursor.fetchone()['count']
def update_item(self, item_id: int, **kwargs) -> bool:
"""更新条目"""
allowed_fields = ['type', 'title', 'content', 'url', 'source', 'status', 'priority', 'due_date', 'note']
@@ -244,6 +306,18 @@ class Database:
""")
return [dict(row) for row in cursor.fetchall()]
def update_tag(self, tag_id: int, name: str) -> bool:
"""更新标签名称"""
with self.get_conn() as conn:
cursor = conn.cursor()
# 检查名称是否已存在(排除自己)
cursor.execute("SELECT id FROM tags WHERE name = ? AND id != ?", (name, tag_id))
if cursor.fetchone():
return False # 名称已存在
cursor.execute("UPDATE tags SET name = ? WHERE id = ?", (name, tag_id))
conn.commit()
return cursor.rowcount > 0
def delete_tag(self, tag_id: int = None, name: str = None) -> bool:
"""删除标签"""
with self.get_conn() as conn:
@@ -290,6 +364,90 @@ class Database:
""", (item_id,))
return [row['name'] for row in cursor.fetchall()]
# ============ Email 操作 ============
def create_email(self, email: str, name: str = None) -> int:
"""创建邮箱"""
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
try:
cursor.execute("INSERT INTO emails (email, name, created_at) VALUES (?, ?, ?)",
(email, name, now))
conn.commit()
return cursor.lastrowid
except sqlite3.IntegrityError:
# 邮箱已存在返回已有ID
cursor.execute("SELECT id FROM emails WHERE email = ?", (email,))
return cursor.fetchone()['id']
def list_emails(self) -> List[Dict[str, Any]]:
"""列出所有邮箱"""
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM emails ORDER BY created_at DESC")
return [dict(row) for row in cursor.fetchall()]
def get_email(self, email_id: int) -> Optional[Dict[str, Any]]:
"""获取单个邮箱"""
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM emails WHERE id = ?", (email_id,))
row = cursor.fetchone()
return dict(row) if row else None
def update_email(self, email_id: int, email: str = None, name: str = None) -> bool:
"""更新邮箱"""
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
if email:
# 检查邮箱是否已存在(排除自己)
cursor.execute("SELECT id FROM emails WHERE email = ? AND id != ?", (email, email_id))
if cursor.fetchone():
return False # 邎箱已存在
if email and name:
cursor.execute("UPDATE emails SET email = ?, name = ? WHERE id = ?", (email, name, email_id))
elif email:
cursor.execute("UPDATE emails SET email = ? WHERE id = ?", (email, email_id))
elif name:
cursor.execute("UPDATE emails SET name = ? WHERE id = ?", (name, email_id))
conn.commit()
return cursor.rowcount > 0
def delete_email(self, email_id: int) -> bool:
"""删除邮箱"""
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM emails WHERE id = ?", (email_id,))
conn.commit()
return cursor.rowcount > 0
# ============ 邮件日志 操作 ============
def log_email_send(self, item_id: int, email: str, success: bool = True) -> int:
"""记录邮件发送"""
now = datetime.now().isoformat()
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("""
INSERT INTO email_logs (item_id, email, sent_at, success)
VALUES (?, ?, ?, ?)
""", (item_id, email, now, 1 if success else 0))
conn.commit()
return cursor.lastrowid
def get_email_logs(self, item_id: int) -> List[Dict[str, Any]]:
"""获取收藏的邮件发送记录"""
with self.get_conn() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM email_logs
WHERE item_id = ?
ORDER BY sent_at DESC
""", (item_id,))
return [dict(row) for row in cursor.fetchall()]
def stats(self) -> Dict[str, Any]:
"""获取统计信息"""
self._ensure_init()
@@ -316,6 +474,252 @@ class Database:
return stats
# ============ 提醒相关 ============
def get_reminders(self) -> Dict[str, Any]:
"""获取提醒信息:即将到期和已过期的待办"""
self._ensure_init()
with self.get_conn() as conn:
cursor = conn.cursor()
now = datetime.now()
reminders = {
'overdue': [], # 已过期
'due_today': [], # 今天到期
'due_soon': [] # 24小时内到期不含今天
}
# 查询未完成的待办(有截止日期的)
cursor.execute("""
SELECT * FROM items
WHERE type = 'todo'
AND status != 'completed'
AND due_date IS NOT NULL
AND due_date != ''
ORDER BY due_date ASC
""")
for row in cursor.fetchall():
item = dict(row)
item['tags'] = self._get_item_tags(conn, item['id'])
try:
due_date_str = item['due_date']
# 支持多种日期格式
if 'T' in due_date_str:
# ISO 格式2026-04-16T14:30
due_date = datetime.strptime(due_date_str[:16], '%Y-%m-%dT%H:%M')
elif len(due_date_str) == 10:
# 只有日期2026-04-16视为当天 23:59:59
due_date = datetime.strptime(due_date_str, '%Y-%m-%d').replace(hour=23, minute=59, second=59)
else:
# 其他格式,尝试解析
due_date = datetime.strptime(due_date_str.split('.')[0], '%Y-%m-%dT%H:%M:%S')
# 计算距离到期的时间
time_left = due_date - now
if time_left.total_seconds() < 0:
# 已过期
days_overdue = abs(int(time_left.total_seconds() / 86400))
item['days_overdue'] = days_overdue
reminders['overdue'].append(item)
elif time_left.total_seconds() < 86400: # 24小时内
# 判断是今天还是明天
if due_date.date() == now.date():
reminders['due_today'].append(item)
else:
reminders['due_soon'].append(item)
elif due_date.date() == now.date():
# 今天到期超过24小时的情况比如现在凌晨截止时间是晚上
reminders['due_today'].append(item)
except (ValueError, AttributeError) as e:
# 日期格式错误,跳过
continue
# 统计总数
reminders['total'] = len(reminders['overdue']) + len(reminders['due_today']) + len(reminders['due_soon'])
return reminders
# ============ 备份操作 ============
def create_backup(self, manual: bool = False) -> Dict[str, Any]:
"""创建数据库备份"""
import os
# 确保备份目录存在
os.makedirs(BACKUP_DIR, exist_ok=True)
now = datetime.now()
backup_name = now.strftime('%Y-%m-%d_%H%M%S')
if manual:
backup_name += '_manual'
backup_path = os.path.join(BACKUP_DIR, f'{backup_name}.db')
# 复制数据库文件
shutil.copy2(self.db_path, backup_path)
# 获取备份信息
backup_info = {
'name': backup_name,
'path': backup_path,
'size': os.path.getsize(backup_path),
'created_at': now.isoformat(),
'manual': manual,
'is_first_of_month': now.day == 1
}
# 保存备份元数据
self._save_backup_meta(backup_info)
# 清理旧备份
self._cleanup_old_backups()
return backup_info
def list_backups(self) -> List[Dict[str, Any]]:
"""列出所有备份"""
import os
if not os.path.exists(BACKUP_DIR):
return []
# 读取备份元数据
meta_path = os.path.join(BACKUP_DIR, 'backup_meta.json')
if os.path.exists(meta_path):
with open(meta_path, 'r') as f:
backups = json.load(f)
else:
# 从文件重建元数据
backups = []
for f in os.listdir(BACKUP_DIR):
if f.endswith('.db'):
path = os.path.join(BACKUP_DIR, f)
backups.append({
'name': f.replace('.db', ''),
'path': path,
'size': os.path.getsize(path),
'created_at': datetime.fromtimestamp(os.path.getmtime(path)).isoformat(),
'manual': '_manual' in f,
'is_first_of_month': self._is_first_of_month_filename(f)
})
# 按时间倒序排列
backups.sort(key=lambda x: x['created_at'], reverse=True)
return backups
def restore_backup(self, backup_name: str) -> bool:
"""恢复备份"""
import os
backup_path = os.path.join(BACKUP_DIR, f'{backup_name}.db')
if not os.path.exists(backup_path):
return False
# 先备份当前数据库(以防万一)
current_backup = self.db_path + '.before_restore'
shutil.copy2(self.db_path, current_backup)
# 恢复备份
shutil.copy2(backup_path, self.db_path)
return True
def delete_backup(self, backup_name: str) -> bool:
"""删除备份"""
import os
backup_path = os.path.join(BACKUP_DIR, f'{backup_name}.db')
if not os.path.exists(backup_path):
return False
os.remove(backup_path)
# 更新元数据
self._remove_backup_meta(backup_name)
return True
def _save_backup_meta(self, backup_info: Dict[str, Any]):
"""保存备份元数据"""
import os
meta_path = os.path.join(BACKUP_DIR, 'backup_meta.json')
# 读取现有元数据
backups = []
if os.path.exists(meta_path):
with open(meta_path, 'r') as f:
backups = json.load(f)
# 添加新备份
backups.append(backup_info)
# 保存
with open(meta_path, 'w') as f:
json.dump(backups, f, indent=2)
def _remove_backup_meta(self, backup_name: str):
"""从元数据中删除备份"""
import os
meta_path = os.path.join(BACKUP_DIR, 'backup_meta.json')
if not os.path.exists(meta_path):
return
with open(meta_path, 'r') as f:
backups = json.load(f)
backups = [b for b in backups if b['name'] != backup_name]
with open(meta_path, 'w') as f:
json.dump(backups, f, indent=2)
def _is_first_of_month_filename(self, filename: str) -> bool:
"""判断是否是每月第一天的备份"""
# 格式2026-04-01_040000.db 或 2026-05-01_...
try:
date_part = filename.split('_')[0]
day = int(date_part.split('-')[2])
return day == 1
except:
return False
def _cleanup_old_backups(self):
"""清理旧备份保留30天 + 每月第一天"""
import os
backups = self.list_backups()
now = datetime.now()
keep_paths = []
for backup in backups:
backup_date = datetime.fromisoformat(backup['created_at'])
days_old = (now - backup_date).days
# 保留条件:
# 1. 手动备份永久保留最多10个
# 2. 30天内的备份
# 3. 每月第一天的备份
if backup['manual']:
# 手动备份保留但最多10个
manual_backups = [b for b in backups if b['manual']]
if manual_backups.index(backup) < 10:
keep_paths.append(backup['path'])
else:
self.delete_backup(backup['name'])
elif days_old <= 30:
keep_paths.append(backup['path'])
elif backup['is_first_of_month']:
# 每月第一天永久保留
keep_paths.append(backup['path'])
else:
# 删除
self.delete_backup(backup['name'])
# 全局数据库实例
db = Database()

0
data/xian_favor.db Normal file
View File

402
logs/api.log Normal file
View File

@@ -0,0 +1,402 @@
* Serving Flask app 'xian_favor.api'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:19014
* Running on http://192.168.2.17:19014
Press CTRL+C to quit
127.0.0.1 - - [19/Apr/2026 10:06:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:04] "GET /api/items?limit=2&sort_by=updated_at&sort_order=desc HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:07:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:07:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:06] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:08:15] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:08:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:08:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:33] "GET /api/health HTTP/1.1" 404 -
192.168.2.8 - - [19/Apr/2026 10:09:33] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:09:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:09:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:10:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:11:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:12:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:06] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:13:16] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:13:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:13:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:33] "GET /api/health HTTP/1.1" 404 -
192.168.2.8 - - [19/Apr/2026 10:14:33] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:14:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:38] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:14:44] "GET / HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:14:44] "GET /api/stats HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:14:44] "GET /api/reminders HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:14:44] "GET /api/tags HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:14:44] "GET /api/items?limit=20&offset=0 HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:14:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:14:58] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:01] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:08] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:16] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:15:16] "GET /api/items?limit=20&offset=0&sort_by=updated_at&sort_order=desc HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:15:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:18] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:15:21] "GET /api/items?limit=20&offset=0&sort_order=desc HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:15:24] "GET /api/items?limit=20&offset=0&sort_by=created_at&sort_order=desc HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:15:26] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:15:27] "GET /api/items?limit=20&offset=0&sort_by=updated_at&sort_order=desc HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:15:28] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:31] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:15:34] "GET /api/items?limit=20&offset=0&sort_order=desc HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:15:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:15:56] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:15:56] "GET /api/items/59 HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:16:00] "GET /api/items/59 HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:16:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:29] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:32] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:16:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:17:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:36] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:46] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:18:56] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:06] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:16] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:17] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:26] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:33] "GET /api/health HTTP/1.1" 404 -
192.168.2.8 - - [19/Apr/2026 10:19:33] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:19:36] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:19:44] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:19:47] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:19:58] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:08] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:19] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:29] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:39] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:49] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:20:59] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:09] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:19] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:29] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:39] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:21:49] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:22:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:23:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:33] "GET /api/health HTTP/1.1" 404 -
192.168.2.8 - - [19/Apr/2026 10:24:37] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:24:40] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:24:44] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:24:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:24:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:25:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:26:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:27:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:28:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:33] "GET /api/health HTTP/1.1" 404 -
192.168.2.8 - - [19/Apr/2026 10:29:37] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:29:40] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:29:44] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:29:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:29:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:30:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:31:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:32:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:33:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:33] "GET /api/health HTTP/1.1" 404 -
192.168.2.8 - - [19/Apr/2026 10:34:37] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:34:40] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:34:44] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:34:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:34:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:35:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:36:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:37:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:38:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:33] "GET /api/health HTTP/1.1" 404 -
192.168.2.8 - - [19/Apr/2026 10:39:37] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:39:40] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:39:44] "GET /api/reminders HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:39:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:39:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:00] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:40:05] "PUT /api/items/59 HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:40:05] "GET /api/stats HTTP/1.1" 200 -
192.168.2.10 - - [19/Apr/2026 10:40:05] "GET /api/items?limit=20&offset=0&sort_order=desc HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:40:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:40:50] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:40:57] "GET /api/items/68 HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:41:00] "GET /api/health HTTP/1.1" 404 -
192.168.2.10 - - [19/Apr/2026 10:41:01] "GET /api/items/68 HTTP/1.1" 200 -
127.0.0.1 - - [19/Apr/2026 10:41:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:33] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:40] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:48] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:41:50] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:42:00] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:42:10] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:42:18] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:42:20] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:42:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:42:30] "GET /api/health HTTP/1.1" 404 -
127.0.0.1 - - [19/Apr/2026 10:42:33] "GET /api/health HTTP/1.1" 404 -

1679
logs/app.log Normal file

File diff suppressed because it is too large Load Diff

9709
logs/server.log Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -768,6 +768,85 @@ INDEX_TEMPLATE = '''
.sidebar a { color: #adb5bd; text-decoration: none; padding: 10px 20px; display: block; }
.sidebar a:hover, .sidebar a.active { background: #495057; color: #fff; }
.content { padding: 20px; }
/* 连接状态指示器 */
.connection-status {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
transition: all 0.3s ease;
}
.connection-status.online {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
}
.connection-status.offline {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
}
.connection-status.connecting {
background: linear-gradient(135deg, #ffc107 0%, #fd7e14 100%);
color: #333;
}
.connection-status .status-icon {
margin-right: 8px;
}
.connection-status .status-text {
font-size: 14px;
font-weight: 500;
}
.connection-status.offline .status-text {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
/* 离线遮罩 */
.offline-overlay {
position: fixed;
top: 40px;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 9998;
display: none;
cursor: not-allowed;
}
.offline-overlay.show {
display: block;
}
.offline-overlay .offline-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 30px 50px;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
text-align: center;
}
.offline-overlay .offline-message h3 {
color: #dc3545;
margin-bottom: 10px;
}
.offline-overlay .offline-message p {
color: #666;
}
/* 内容区域偏移(为状态栏留空间) */
.content.with-status-bar {
padding-top: 60px;
}
.card { margin-bottom: 8px; transition: transform 0.2s; }
.card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
.card-body { padding: 8px 12px; }
@@ -793,6 +872,23 @@ INDEX_TEMPLATE = '''
</style>
</head>
<body>
<!-- 连接状态指示器 -->
<div class="connection-status online" id="connectionStatus">
<span class="status-icon"><i class="bi bi-wifi"></i></span>
<span class="status-text">已连接</span>
</div>
<!-- 离线遮罩 -->
<div class="offline-overlay" id="offlineOverlay">
<div class="offline-message">
<h3><i class="bi bi-wifi-off"></i> 服务器连接已断开</h3>
<p>无法进行编辑和新增操作,请检查网络连接</p>
<button class="btn btn-primary mt-3" onclick="checkConnection()">
<i class="bi bi-arrow-repeat"></i> 重新连接
</button>
</div>
</div>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
@@ -821,7 +917,7 @@ INDEX_TEMPLATE = '''
</div>
<!-- 主内容 -->
<div class="col-md-10 content">
<div class="col-md-10 content with-status-bar">
<!-- 提醒栏 -->
<div id="reminderBar" class="alert alert-warning alert-dismissible fade show mb-3" style="display:none;" role="alert">
<i class="bi bi-bell-fill"></i>
@@ -1392,6 +1488,92 @@ let currentFilter = { type: '', status: '', starred: null };
let currentSort = { sort_by: '', sort_order: '' };
let currentPage = 1;
const pageSize = 20;
// ============ 连接状态检测 ============
let isOnline = true;
let connectionCheckTimer = null;
const CONNECTION_CHECK_INTERVAL = 5000; // 5秒检测一次
const CONNECTION_TIMEOUT = 3000; // 3秒超时
function updateConnectionStatus(status) {
const statusEl = document.getElementById('connectionStatus');
const overlayEl = document.getElementById('offlineOverlay');
statusEl.classList.remove('online', 'offline', 'connecting');
statusEl.classList.add(status);
const iconEl = statusEl.querySelector('.status-icon');
const textEl = statusEl.querySelector('.status-text');
if (status === 'online') {
iconEl.innerHTML = '<i class="bi bi-wifi"></i>';
textEl.textContent = '已连接';
overlayEl.classList.remove('show');
isOnline = true;
} else if (status === 'offline') {
iconEl.innerHTML = '<i class="bi bi-wifi-off"></i>';
textEl.textContent = '连接已断开';
overlayEl.classList.add('show');
isOnline = false;
} else if (status === 'connecting') {
iconEl.innerHTML = '<i class="bi bi-arrow-repeat"></i>';
textEl.textContent = '正在连接...';
isOnline = false;
}
}
async function checkConnection() {
updateConnectionStatus('connecting');
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), CONNECTION_TIMEOUT);
const res = await fetch(`${API_BASE}/stats`, {
signal: controller.signal,
headers: { 'Cache-Control': 'no-cache' }
});
clearTimeout(timeoutId);
if (res.ok) {
updateConnectionStatus('online');
// 连接恢复后刷新数据
if (!isOnline) {
refreshData();
}
} else {
updateConnectionStatus('offline');
}
} catch (e) {
updateConnectionStatus('offline');
}
}
function startConnectionCheck() {
// 立即检测一次
checkConnection();
// 定时检测
connectionCheckTimer = setInterval(checkConnection, CONNECTION_CHECK_INTERVAL);
}
function stopConnectionCheck() {
if (connectionCheckTimer) {
clearInterval(connectionCheckTimer);
connectionCheckTimer = null;
}
}
// 离线时禁止操作
function checkOnlineBeforeAction(actionName) {
if (!isOnline) {
alert('服务器连接已断开,无法执行此操作');
return false;
}
return true;
}
function debounce(fn, delay) {
let timer;
return function(...args) {
@@ -1402,6 +1584,9 @@ function debounce(fn, delay) {
// 初始化
document.addEventListener('DOMContentLoaded', async () => {
// 启动连接状态检测
startConnectionCheck();
// 确保初始状态清空
document.getElementById('searchInput').value = '';
document.getElementById('typeFilter').value = '';
@@ -1750,6 +1935,9 @@ async function editDraft(draftId) {
// 发布草稿(转为正式条目)
async function publishDraft(draftId) {
// 离线检查
if (!checkOnlineBeforeAction('发布草稿')) return;
if (!confirm('确认发布这条草稿?')) return;
const res = await fetch(`${API_BASE}/drafts/${draftId}/publish`, { method: 'POST' });
@@ -1766,6 +1954,9 @@ async function publishDraft(draftId) {
// 删除草稿
async function deleteDraft(draftId) {
// 离线检查
if (!checkOnlineBeforeAction('删除草稿')) return;
if (!confirm('确认删除这条草稿?')) return;
const res = await fetch(`${API_BASE}/drafts/${draftId}`, { method: 'DELETE' });
@@ -1857,6 +2048,9 @@ function hideDraftIndicator() {
}
function showAddModal(type) {
// 离线检查
if (!checkOnlineBeforeAction('添加数据')) return;
// 设置类型
document.getElementById('addType').value = type;
@@ -1901,6 +2095,9 @@ function showAddModal(type) {
// 添加条目
async function addItem() {
// 离线检查
if (!checkOnlineBeforeAction('添加数据')) return;
const type = document.getElementById('addType').value;
const data = {
type,
@@ -1940,12 +2137,18 @@ async function addItem() {
// 完成待办
async function completeItem(id) {
// 离线检查
if (!checkOnlineBeforeAction('完成待办')) return;
await fetch(`${API_BASE}/items/${id}/done`, { method: 'POST' });
refreshData();
}
// 重新打开待办
async function reopenItem(id) {
// 离线检查
if (!checkOnlineBeforeAction('重新打开待办')) return;
const res = await fetch(`${API_BASE}/items/${id}/reopen`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -1963,6 +2166,9 @@ async function reopenItem(id) {
let convertItemData = null;
async function showConvertModal(itemId) {
// 离线检查
if (!checkOnlineBeforeAction('转换数据')) return;
const res = await fetch(`${API_BASE}/items/${itemId}`);
const data = await res.json();
@@ -2034,6 +2240,9 @@ async function executeConvert() {
// 删除条目
async function deleteItem(id) {
// 离线检查
if (!checkOnlineBeforeAction('删除数据')) return;
if (!confirm('确认删除?')) return;
await fetch(`${API_BASE}/items/${id}`, { method: 'DELETE' });
refreshData();
@@ -2149,6 +2358,9 @@ function editModalHideHandler(e) {
}
async function openEditModal(id) {
// 离线检查
if (!checkOnlineBeforeAction('编辑数据')) return;
currentDetailId = id;
const res = await fetch(`${API_BASE}/items/${id}`);
const data = await res.json();
@@ -2265,6 +2477,9 @@ function updateEditFieldsByType(type) {
// 保存编辑
async function saveEdit() {
// 离线检查
if (!checkOnlineBeforeAction('保存修改')) return;
const id = document.getElementById('editId').value;
const type = document.getElementById('editType').value; // 从下拉框获取新类型
@@ -2305,6 +2520,9 @@ async function saveEdit() {
// 切换重点关注状态
async function toggleStar(id) {
// 离线检查
if (!checkOnlineBeforeAction('切换关注状态')) return;
const res = await fetch(`${API_BASE}/items/${id}/star`, { method: 'POST' });
if (res.ok) {
refreshData();
@@ -2541,6 +2759,9 @@ async function loadTagManagerList() {
}
async function createTag() {
// 离线检查
if (!checkOnlineBeforeAction('创建标签')) return;
const name = document.getElementById('newTagName').value.trim();
if (!name) return;
@@ -2699,6 +2920,9 @@ async function processAIInput() {
}
async function confirmAIAdd() {
// 离线检查
if (!checkOnlineBeforeAction('AI添加')) return;
if (!aiParsedData) return;
const res = await fetch(`${API_BASE}/items`, {
@@ -2820,6 +3044,9 @@ async function hideTrash() {
}
async function restoreItem(id) {
// 离线检查
if (!checkOnlineBeforeAction('恢复数据')) return;
if (!confirm('确认恢复这条数据?')) return;
const res = await fetch(`${API_BASE}/items/${id}/restore`, { method: 'POST' });
@@ -2834,6 +3061,9 @@ async function restoreItem(id) {
}
async function deletePermanently(id) {
// 离线检查
if (!checkOnlineBeforeAction('彻底删除')) return;
if (!confirm('确认彻底删除这条数据?此操作不可恢复!')) return;
const res = await fetch(`${API_BASE}/items/${id}/permanent`, { method: 'DELETE' });
@@ -2848,6 +3078,9 @@ async function deletePermanently(id) {
}
async function emptyTrash() {
// 离线检查
if (!checkOnlineBeforeAction('清空回收站')) return;
if (!confirm('确认清空回收站?此操作不可恢复!')) return;
const res = await fetch(`${API_BASE}/trash`, { method: 'DELETE' });
@@ -2906,6 +3139,9 @@ async function loadBackupList() {
}
async function createManualBackup() {
// 离线检查
if (!checkOnlineBeforeAction('创建备份')) return;
const res = await fetch(`${API_BASE}/backups`, { method: 'POST' });
const data = await res.json();
@@ -2919,7 +3155,10 @@ async function createManualBackup() {
}
async function restoreBackup(name) {
if (!confirm(`确认恢复备份 "${name}"\n当前数据将被覆盖!`)) return;
// 离线检查
if (!checkOnlineBeforeAction('恢复备份')) return;
if (!confirm(`确认恢复备份 "${name}"\\n当前数据将被覆盖`)) return;
const res = await fetch(`${API_BASE}/backups/${name}`, { method: 'POST' });
const data = await res.json();
@@ -3004,6 +3243,9 @@ async function loadEmailManagerList() {
}
async function createEmail() {
// 离线检查
if (!checkOnlineBeforeAction('添加邮箱')) return;
const emailAddr = document.getElementById('newEmailAddr').value.trim();
const emailName = document.getElementById('newEmailName').value.trim();
@@ -3099,6 +3341,9 @@ async function showSendEmailModal(itemId) {
}
async function sendItemEmail() {
// 离线检查
if (!checkOnlineBeforeAction('发送邮件')) return;
const itemId = document.getElementById('sendEmailItemId').value;
let emailAddr = document.getElementById('sendEmailSelect').value;