mirror of
https://gitcode.com/JianFeeeee/TrulyMEM-TrueHumanMEM.git
synced 2026-09-20 17:08:18 +00:00
多用户系统 + Admin 角色权限 + Web/TUI 同步 + 构建集成
本轮实现功能: 1. 多用户隔离:每个用户独立 config.json + graph.db 2. TUI 登录页 + 旧版自动迁移(core/migrate.py) 3. Admin/User 角色体系(core/embedded_db.py) 4. Web 后台管理 API(userinfo + admin CRUD) 5. Web 设置页用户管理区(仅 admin 可见) 6. TUI 侧栏配置区权限同步(非 admin 隐藏 Web 服务设置) 7. Web 服务打包为独立二进制(trulymem-web) 8. 双入口 PyInstaller 构建脚本(TUI + Web) 9. 活动记录器(core/activity_recorder.py) 10. 静态页面模板(登录/设置/首次引导)
This commit is contained in:
142
ui/login_screen.py
Normal file
142
ui/login_screen.py
Normal file
@ -0,0 +1,142 @@
|
||||
"""
|
||||
TUI 登录页面
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Center, Middle, Vertical
|
||||
from textual.widgets import Input, Button, Static, Label
|
||||
from textual.screen import Screen
|
||||
from core.embedded_db import EmbeddedGraphDB
|
||||
from core.migrate import need_migration, run_migration, is_migrated
|
||||
|
||||
|
||||
class LoginScreen(Screen):
|
||||
"""登录界面"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._migrating = False
|
||||
self._migration_username = ""
|
||||
self._migration_password = ""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# 检测是否需要迁移
|
||||
migrating = need_migration()
|
||||
migrated = is_migrated()
|
||||
|
||||
if migrating and not migrated:
|
||||
yield from self._compose_migration()
|
||||
else:
|
||||
yield from self._compose_login()
|
||||
|
||||
def _compose_login(self) -> ComposeResult:
|
||||
with Center():
|
||||
with Middle():
|
||||
with Vertical(id="login_container"):
|
||||
yield Static("🔐 TrulyMEM 登录", id="login_title")
|
||||
yield Label("用户名:")
|
||||
yield Input(placeholder="请输入用户名", id="username_input")
|
||||
yield Label("密码:")
|
||||
yield Input(placeholder="请输入密码", password=True, id="password_input")
|
||||
yield Button("登录", id="login_button", variant="primary")
|
||||
yield Static("", id="login_message")
|
||||
|
||||
def _compose_migration(self) -> ComposeResult:
|
||||
with Center():
|
||||
with Middle():
|
||||
with Vertical(id="migration_container"):
|
||||
yield Static("🔄 检测到旧版数据,需要迁移", id="migration_title")
|
||||
yield Static("请设置管理员账号以完成迁移", id="migration_subtitle")
|
||||
yield Label("用户名:")
|
||||
yield Input(placeholder="请输入管理员用户名", id="mig_username_input")
|
||||
yield Label("密码:")
|
||||
yield Input(placeholder="请输入管理员密码", password=True, id="mig_password_input")
|
||||
yield Button("开始迁移", id="migrate_button", variant="primary")
|
||||
yield Static("", id="migration_message")
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if event.button.id == "login_button":
|
||||
self._handle_login()
|
||||
elif event.button.id == "migrate_button":
|
||||
self._handle_migration()
|
||||
|
||||
def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
if event.input.id == "username_input":
|
||||
self.query_one("#password_input", Input).focus()
|
||||
elif event.input.id == "password_input":
|
||||
self._handle_login()
|
||||
elif event.input.id == "mig_username_input":
|
||||
self.query_one("#mig_password_input", Input).focus()
|
||||
elif event.input.id == "mig_password_input":
|
||||
self._handle_migration()
|
||||
|
||||
def _handle_login(self) -> None:
|
||||
username = self.query_one("#username_input", Input).value.strip()
|
||||
password = self.query_one("#password_input", Input).value
|
||||
|
||||
if not username or not password:
|
||||
self.query_one("#login_message", Static).update("❌ 用户名和密码不能为空")
|
||||
return
|
||||
|
||||
# 验证用户
|
||||
try:
|
||||
global_db_path = Path.home() / ".trulymem" / "trulymem.db"
|
||||
if not global_db_path.exists():
|
||||
self.query_one("#login_message", Static).update("❌ 全局数据库不存在,请先完成迁移")
|
||||
return
|
||||
|
||||
db = EmbeddedGraphDB(db_path=str(global_db_path))
|
||||
user_info = db.get_web_user(username)
|
||||
|
||||
if not user_info:
|
||||
self.query_one("#login_message", Static).update("❌ 用户不存在")
|
||||
db.close()
|
||||
return
|
||||
|
||||
# 验证密码
|
||||
import hashlib
|
||||
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
if db.verify_web_user(username, password):
|
||||
db.close()
|
||||
# 登录成功,通知应用
|
||||
self.app.on_login_success(username, user_info)
|
||||
else:
|
||||
self.query_one("#login_message", Static).update("❌ 密码错误")
|
||||
db.close()
|
||||
|
||||
except Exception as e:
|
||||
self.query_one("#login_message", Static).update(f"❌ 登录失败: {str(e)}")
|
||||
|
||||
def _handle_migration(self) -> None:
|
||||
username = self.query_one("#mig_username_input", Input).value.strip()
|
||||
password = self.query_one("#mig_password_input", Input).value
|
||||
|
||||
if not username or not password:
|
||||
self.query_one("#migration_message", Static).update("❌ 用户名和密码不能为空")
|
||||
return
|
||||
|
||||
self.query_one("#migration_message", Static).update("⏳ 正在迁移...")
|
||||
|
||||
# 执行迁移(异步)
|
||||
asyncio.create_task(self._do_migration(username, password))
|
||||
|
||||
async def _do_migration(self, username: str, password: str) -> None:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await loop.run_in_executor(
|
||||
None, run_migration, username, password
|
||||
)
|
||||
|
||||
if result.get("success"):
|
||||
self.query_one("#migration_message", Static).update("✅ 迁移成功!请登录")
|
||||
# 重新加载界面为登录界面
|
||||
await asyncio.sleep(1)
|
||||
self.app.pop_screen()
|
||||
self.app.push_screen(LoginScreen())
|
||||
else:
|
||||
self.query_one("#migration_message", Static).update(f"❌ 迁移失败: {result.get('error')}")
|
||||
except Exception as e:
|
||||
self.query_one("#migration_message", Static).update(f"❌ 迁移异常: {str(e)}")
|
||||
Reference in New Issue
Block a user