mirror of
https://github.com/ihmily/StreamCap.git
synced 2026-05-07 05:57:45 +08:00
feat: add authentication module and update UI
This commit is contained in:
@@ -28,6 +28,9 @@ class App:
|
||||
self.assets_dir = os.path.join(execute_dir, "assets")
|
||||
self.process_manager = AsyncProcessManager()
|
||||
self.config_manager = ConfigManager(self.run_path)
|
||||
self.is_web_mode = False
|
||||
self.auth_manager = None
|
||||
self.current_username = None
|
||||
self.content_area = ft.Column(
|
||||
controls=[],
|
||||
expand=True,
|
||||
@@ -56,7 +59,6 @@ class App:
|
||||
self.page.snack_bar_area,
|
||||
]
|
||||
)
|
||||
self.page.add(self.complete_page)
|
||||
self.snack_bar = ShowSnackBar(self.page)
|
||||
self.subprocess_start_up_info = utils.get_startup_info()
|
||||
self.record_card_manager = RecordingCardManager(self)
|
||||
|
||||
0
app/auth/__init__.py
Normal file
0
app/auth/__init__.py
Normal file
93
app/auth/auth_manager.py
Normal file
93
app/auth/auth_manager.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import hashlib
|
||||
import secrets
|
||||
from typing import Optional
|
||||
|
||||
from ..utils.logger import logger
|
||||
|
||||
|
||||
class AuthManager:
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.config_manager = app.config_manager
|
||||
self.is_authenticated = False
|
||||
self.session_token = None
|
||||
self.active_sessions = {}
|
||||
|
||||
async def initialize(self):
|
||||
web_auth = self.config_manager.load_web_auth_config()
|
||||
|
||||
if not web_auth.get("users"):
|
||||
default_username = "admin"
|
||||
default_password = "admin"
|
||||
|
||||
salt = secrets.token_hex(8)
|
||||
hashed_password = self._hash_password(default_password, salt)
|
||||
|
||||
web_auth["users"] = [{
|
||||
"username": default_username,
|
||||
"password_hash": hashed_password,
|
||||
"salt": salt,
|
||||
"is_admin": True
|
||||
}]
|
||||
|
||||
await self.config_manager.save_web_auth_config(web_auth)
|
||||
logger.info("Default account created: admin/admin")
|
||||
|
||||
def _hash_password(self, password: str, salt: str) -> str:
|
||||
"""Use SHA-256 and salt to hash password"""
|
||||
return hashlib.sha256((password + salt).encode()).hexdigest()
|
||||
|
||||
def _generate_session_token(self) -> str:
|
||||
"""Generate session token"""
|
||||
return secrets.token_hex(16)
|
||||
|
||||
async def authenticate(self, username: str, password: str) -> tuple[bool, Optional[str]]:
|
||||
web_auth = self.config_manager.load_web_auth_config()
|
||||
users = web_auth.get("users", [])
|
||||
|
||||
for user in users:
|
||||
if user["username"] == username:
|
||||
salt = user["salt"]
|
||||
hashed_password = self._hash_password(password, salt)
|
||||
|
||||
if hashed_password == user["password_hash"]:
|
||||
session_token = self._generate_session_token()
|
||||
self.active_sessions[session_token] = {
|
||||
"username": username,
|
||||
"is_admin": user.get("is_admin", False)
|
||||
}
|
||||
return True, session_token
|
||||
|
||||
return False, None
|
||||
|
||||
def validate_session(self, session_token: str) -> bool:
|
||||
"""Validate session token"""
|
||||
return session_token in self.active_sessions
|
||||
|
||||
def logout(self, session_token: str) -> bool:
|
||||
if session_token in self.active_sessions:
|
||||
del self.active_sessions[session_token]
|
||||
return True
|
||||
return False
|
||||
|
||||
async def change_password(self, username: str, old_password: str, new_password: str) -> bool:
|
||||
web_auth = self.config_manager.load_web_auth_config()
|
||||
users = web_auth.get("users", [])
|
||||
|
||||
for i, user in enumerate(users):
|
||||
if user["username"] == username:
|
||||
salt = user["salt"]
|
||||
hashed_old_password = self._hash_password(old_password, salt)
|
||||
|
||||
if hashed_old_password == user["password_hash"]:
|
||||
new_salt = secrets.token_hex(8)
|
||||
hashed_new_password = self._hash_password(new_password, new_salt)
|
||||
|
||||
web_auth["users"][i]["password_hash"] = hashed_new_password
|
||||
web_auth["users"][i]["salt"] = new_salt
|
||||
|
||||
await self.config_manager.save_web_auth_config(web_auth)
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -18,6 +18,7 @@ class ConfigManager:
|
||||
self.about_config_path = os.path.join(self.config_path, "version.json")
|
||||
self.recordings_config_path = os.path.join(self.config_path, "recordings.json")
|
||||
self.accounts_config_path = os.path.join(self.config_path, "accounts.json")
|
||||
self.web_auth_config_path = os.path.join(self.config_path, "web_auth.json")
|
||||
|
||||
os.makedirs(os.path.dirname(self.default_config_path), exist_ok=True)
|
||||
self.init()
|
||||
@@ -28,6 +29,7 @@ class ConfigManager:
|
||||
self.init_cookies_config()
|
||||
self.init_accounts_config()
|
||||
self.init_recordings_config()
|
||||
self.init_web_auth_config()
|
||||
|
||||
@staticmethod
|
||||
def _init_config(config_path, default_config=None):
|
||||
@@ -63,6 +65,10 @@ class ConfigManager:
|
||||
cookies_config = {}
|
||||
self._init_config(self.recordings_config_path, cookies_config)
|
||||
|
||||
def init_web_auth_config(self):
|
||||
cookies_config = {}
|
||||
self._init_config(self.web_auth_config_path, cookies_config)
|
||||
|
||||
@staticmethod
|
||||
def _load_config(config_path, error_message):
|
||||
"""Load configuration from a JSON file."""
|
||||
@@ -104,6 +110,9 @@ class ConfigManager:
|
||||
"""Load i18n configuration from a JSON file."""
|
||||
return self._load_config(path, "An error occurred while loading i18n config")
|
||||
|
||||
def load_web_auth_config(self):
|
||||
return self._load_config(self.web_auth_config_path, "An error occurred while loading web auth config")
|
||||
|
||||
@staticmethod
|
||||
async def _save_config(config_path, config, success_message, error_message):
|
||||
"""Save configuration to a JSON file."""
|
||||
@@ -130,6 +139,14 @@ class ConfigManager:
|
||||
error_message="An error occurred while saving accounts config",
|
||||
)
|
||||
|
||||
async def save_web_auth_config(self, config):
|
||||
await self._save_config(
|
||||
self.web_auth_config_path,
|
||||
config,
|
||||
success_message="Web auth configuration saved.",
|
||||
error_message="An error occurred while saving web auth config",
|
||||
)
|
||||
|
||||
async def save_user_config(self, config):
|
||||
await self._save_config(
|
||||
self.user_config_path,
|
||||
|
||||
180
app/ui/views/login_view.py
Normal file
180
app/ui/views/login_view.py
Normal file
@@ -0,0 +1,180 @@
|
||||
from typing import Callable
|
||||
|
||||
import flet as ft
|
||||
|
||||
from ...auth.auth_manager import AuthManager
|
||||
from ...utils.logger import logger
|
||||
|
||||
|
||||
class LoginPage:
|
||||
|
||||
def __init__(self, page: ft.Page, auth_manager: AuthManager, on_login_success: Callable):
|
||||
self.page = page
|
||||
self.auth_manager = auth_manager
|
||||
self.on_login_success = on_login_success
|
||||
|
||||
app = auth_manager.app
|
||||
language = app.language_manager.language
|
||||
self._ = language.get("login_page", {})
|
||||
|
||||
self.page.title = self._["login_title"]
|
||||
|
||||
self.username_field = ft.TextField(
|
||||
label=self._["username"],
|
||||
autofocus=True,
|
||||
width=320,
|
||||
border_radius=8,
|
||||
prefix_icon=ft.icons.PERSON,
|
||||
focused_border_color="#0078d4",
|
||||
focused_color="#0078d4",
|
||||
border_color="#d0d0d0",
|
||||
bgcolor="#f5f5f5",
|
||||
color="#333333",
|
||||
label_style=ft.TextStyle(color="#666666"),
|
||||
)
|
||||
|
||||
self.password_field = ft.TextField(
|
||||
label=self._["password"],
|
||||
password=True,
|
||||
can_reveal_password=True,
|
||||
width=320,
|
||||
border_radius=8,
|
||||
prefix_icon=ft.icons.LOCK_OUTLINE,
|
||||
focused_border_color="#0078d4",
|
||||
focused_color="#0078d4",
|
||||
border_color="#d0d0d0",
|
||||
bgcolor="#f5f5f5",
|
||||
color="#333333",
|
||||
label_style=ft.TextStyle(color="#666666"),
|
||||
)
|
||||
|
||||
self.login_button = ft.ElevatedButton(
|
||||
text=self._["login_button"],
|
||||
width=320,
|
||||
on_click=self.handle_login,
|
||||
style=ft.ButtonStyle(
|
||||
shape=ft.RoundedRectangleBorder(radius=8),
|
||||
color="#ffffff",
|
||||
bgcolor="#0078d4",
|
||||
elevation=0,
|
||||
padding=15,
|
||||
animation_duration=300,
|
||||
),
|
||||
)
|
||||
|
||||
self.error_text = ft.Text(
|
||||
color=ft.colors.RED_500,
|
||||
size=14,
|
||||
visible=False,
|
||||
)
|
||||
|
||||
self.logo = ft.Image(
|
||||
src="/icons/loading-animation.png",
|
||||
width=80,
|
||||
height=80,
|
||||
fit=ft.ImageFit.CONTAIN,
|
||||
)
|
||||
|
||||
login_card_content = ft.Column(
|
||||
controls=[
|
||||
ft.Container(
|
||||
content=self.logo,
|
||||
alignment=ft.alignment.center,
|
||||
margin=ft.margin.only(bottom=10),
|
||||
),
|
||||
ft.Text(
|
||||
"StreamCap",
|
||||
size=28,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color="#0078d4",
|
||||
text_align=ft.TextAlign.CENTER,
|
||||
),
|
||||
ft.Text(
|
||||
self._["login_subtitle"],
|
||||
size=16,
|
||||
color="#666666",
|
||||
text_align=ft.TextAlign.CENTER,
|
||||
),
|
||||
ft.Container(height=20),
|
||||
self.username_field,
|
||||
ft.Container(height=10),
|
||||
self.password_field,
|
||||
ft.Container(
|
||||
content=self.error_text,
|
||||
margin=ft.margin.only(top=10),
|
||||
alignment=ft.alignment.center,
|
||||
),
|
||||
ft.Container(height=20),
|
||||
self.login_button,
|
||||
ft.Container(height=10),
|
||||
ft.Text(
|
||||
self._["default_account_tip"],
|
||||
size=12,
|
||||
color="#999999",
|
||||
text_align=ft.TextAlign.CENTER,
|
||||
),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
spacing=5,
|
||||
)
|
||||
|
||||
self.login_card = ft.Container(
|
||||
content=login_card_content,
|
||||
width=400,
|
||||
height=600,
|
||||
padding=30,
|
||||
bgcolor=ft.colors.WHITE,
|
||||
border_radius=12,
|
||||
shadow=ft.BoxShadow(
|
||||
spread_radius=1,
|
||||
blur_radius=15,
|
||||
color=ft.colors.with_opacity(0.1, "#000000"),
|
||||
offset=ft.Offset(0, 4),
|
||||
),
|
||||
)
|
||||
|
||||
self.main_view = ft.Container(
|
||||
content=self.login_card,
|
||||
alignment=ft.alignment.center,
|
||||
expand=True,
|
||||
bgcolor="#f0f2f5",
|
||||
)
|
||||
|
||||
async def handle_login(self, e):
|
||||
username = self.username_field.value
|
||||
password = self.password_field.value
|
||||
|
||||
if not username or not password:
|
||||
self.show_error(self._["input_required"])
|
||||
return
|
||||
|
||||
original_text = self.login_button.text
|
||||
self.login_button.text = self._["login_in_progress"]
|
||||
self.login_button.disabled = True
|
||||
self.page.update()
|
||||
|
||||
success, token = await self.auth_manager.authenticate(username, password)
|
||||
|
||||
self.login_button.text = original_text
|
||||
self.login_button.disabled = False
|
||||
self.page.update()
|
||||
|
||||
if success:
|
||||
logger.info(f"Login successful: {username}")
|
||||
await self.page.client_storage.set_async("session_token", token)
|
||||
await self.on_login_success(token)
|
||||
else:
|
||||
self.show_error(self._["login_failed"])
|
||||
|
||||
def show_error(self, message: str):
|
||||
self.error_text.value = message
|
||||
self.error_text.visible = True
|
||||
self.page.update()
|
||||
|
||||
def clear_error(self):
|
||||
self.error_text.visible = False
|
||||
self.page.update()
|
||||
|
||||
def get_view(self) -> ft.Control:
|
||||
return self.main_view
|
||||
@@ -30,6 +30,7 @@ class SettingsPage(PageBase):
|
||||
self.tab_push = None
|
||||
self.tab_cookies = None
|
||||
self.tab_accounts = None
|
||||
self.tab_security = None
|
||||
self.has_unsaved_changes = {}
|
||||
self.delay_handler = DelayedTaskExecutor(self.app, self)
|
||||
self.load_language()
|
||||
@@ -37,8 +38,6 @@ class SettingsPage(PageBase):
|
||||
self.page.on_keyboard_event = self.on_keyboard
|
||||
|
||||
async def load(self):
|
||||
"""Load the settings page content with tabs for different categories."""
|
||||
|
||||
self.content_area.clean()
|
||||
language = self.app.language_manager.language
|
||||
self._ = language["settings_page"] | language["video_quality"] | language["base"]
|
||||
@@ -48,15 +47,21 @@ class SettingsPage(PageBase):
|
||||
self.tab_accounts = self.create_accounts_settings_tab()
|
||||
self.page.on_keyboard_event = self.on_keyboard
|
||||
|
||||
tabs = [
|
||||
ft.Tab(text=self._["recording_settings"], content=self.tab_recording),
|
||||
ft.Tab(text=self._["push_settings"], content=self.tab_push),
|
||||
ft.Tab(text=self._["cookies_settings"], content=self.tab_cookies),
|
||||
ft.Tab(text=self._["accounts_settings"], content=self.tab_accounts),
|
||||
]
|
||||
|
||||
if self.app.page.web:
|
||||
self.tab_security = self.create_security_settings_tab()
|
||||
tabs.append(ft.Tab(text=self._["security_settings"], content=self.tab_security))
|
||||
|
||||
settings_tabs = ft.Tabs(
|
||||
selected_index=0,
|
||||
animation_duration=300,
|
||||
tabs=[
|
||||
ft.Tab(text=self._["recording_settings"], content=self.tab_recording),
|
||||
ft.Tab(text=self._["push_settings"], content=self.tab_push),
|
||||
ft.Tab(text=self._["cookies_settings"], content=self.tab_cookies),
|
||||
ft.Tab(text=self._["accounts_settings"], content=self.tab_accounts),
|
||||
],
|
||||
tabs=tabs,
|
||||
)
|
||||
|
||||
scrollable_content = ft.Container(
|
||||
@@ -143,12 +148,12 @@ class SettingsPage(PageBase):
|
||||
self.user_config[key] = e.data.lower() == "true"
|
||||
else:
|
||||
self.user_config[key] = e.data
|
||||
|
||||
|
||||
if key in ["folder_name_platform", "folder_name_author", "folder_name_time", "folder_name_title"]:
|
||||
for recording in self.app.record_manager.recordings:
|
||||
recording.recording_dir = None
|
||||
self.page.run_task(self.app.record_manager.persist_recordings)
|
||||
|
||||
|
||||
if key == "language":
|
||||
self.load_language()
|
||||
self.app.language_manager.load()
|
||||
@@ -759,7 +764,7 @@ class SettingsPage(PageBase):
|
||||
self._["telegram"], ft.Icons.SMS, "telegram_enabled"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
if self.app.page.web:
|
||||
return ft.Row(
|
||||
controls=controls,
|
||||
@@ -778,7 +783,7 @@ class SettingsPage(PageBase):
|
||||
),
|
||||
expand=True,
|
||||
)
|
||||
|
||||
|
||||
def create_cookies_settings_tab(self):
|
||||
"""Create UI elements for push configuration."""
|
||||
platforms = [
|
||||
@@ -848,7 +853,7 @@ class SettingsPage(PageBase):
|
||||
)
|
||||
|
||||
def create_accounts_settings_tab(self):
|
||||
"""Create UI elements for push configuration."""
|
||||
"""Create UI elements for platform accounts configuration."""
|
||||
return ft.Column(
|
||||
[
|
||||
self.create_setting_group(
|
||||
@@ -946,7 +951,6 @@ class SettingsPage(PageBase):
|
||||
)
|
||||
|
||||
def create_folder_setting_row(self, label):
|
||||
"""Helper method to create a row of checkboxes for folder settings."""
|
||||
return ft.Row(
|
||||
[
|
||||
ft.Text(label, width=200, text_align=ft.TextAlign.RIGHT),
|
||||
@@ -1030,7 +1034,8 @@ class SettingsPage(PageBase):
|
||||
|
||||
def create_setting_row(self, label, control):
|
||||
"""Helper method to create a row for each setting."""
|
||||
control.on_focus = lambda e: self.set_focused_control(e.control)
|
||||
if hasattr(control, 'on_focus'):
|
||||
control.on_focus = lambda e: self.set_focused_control(e.control)
|
||||
return ft.Row(
|
||||
[ft.Text(label, width=200, text_align=ft.TextAlign.RIGHT), control],
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
@@ -1095,4 +1100,100 @@ class SettingsPage(PageBase):
|
||||
self.app.dialog_area.update()
|
||||
|
||||
if self.app.current_page == self and e.ctrl and e.key == "S":
|
||||
self.page.run_task(self.is_changed)
|
||||
self.page.run_task(self.is_changed)
|
||||
|
||||
def create_security_settings_tab(self):
|
||||
|
||||
async def change_password(_):
|
||||
old_password = old_password_field.value
|
||||
new_password = new_password_field.value
|
||||
confirm_password = confirm_password_field.value
|
||||
|
||||
if not old_password:
|
||||
await self.app.snack_bar.show_snack_bar(self._["old_password_required"], bgcolor=ft.Colors.RED)
|
||||
return
|
||||
|
||||
if not new_password:
|
||||
await self.app.snack_bar.show_snack_bar(self._["new_password_required"], bgcolor=ft.Colors.RED)
|
||||
return
|
||||
|
||||
if new_password != confirm_password:
|
||||
await self.app.snack_bar.show_snack_bar(self._["passwords_not_match"], bgcolor=ft.Colors.RED)
|
||||
return
|
||||
|
||||
_username = self.app.current_username
|
||||
if _username:
|
||||
success = await self.app.auth_manager.change_password(_username, old_password, new_password)
|
||||
|
||||
if success:
|
||||
old_password_field.value = ""
|
||||
new_password_field.value = ""
|
||||
confirm_password_field.value = ""
|
||||
old_password_field.update()
|
||||
new_password_field.update()
|
||||
confirm_password_field.update()
|
||||
|
||||
await self.app.snack_bar.show_snack_bar(self._["password_changed"], bgcolor=ft.Colors.GREEN)
|
||||
else:
|
||||
await self.app.snack_bar.show_snack_bar(self._["old_password_incorrect"], bgcolor=ft.Colors.RED)
|
||||
else:
|
||||
await self.app.snack_bar.show_snack_bar(self._["not_logged_in"], bgcolor=ft.Colors.RED)
|
||||
|
||||
username = self.app.current_username or "admin"
|
||||
|
||||
old_password_field = ft.TextField(
|
||||
password=True,
|
||||
width=300,
|
||||
label=self._["old_password"],
|
||||
)
|
||||
|
||||
new_password_field = ft.TextField(
|
||||
password=True,
|
||||
width=300,
|
||||
label=self._["new_password"],
|
||||
)
|
||||
|
||||
confirm_password_field = ft.TextField(
|
||||
password=True,
|
||||
width=300,
|
||||
label=self._["confirm_password"],
|
||||
)
|
||||
|
||||
change_password_button = ft.ElevatedButton(
|
||||
text=self._["change_password"],
|
||||
on_click=change_password,
|
||||
icon=ft.icons.LOCK_RESET,
|
||||
)
|
||||
|
||||
return ft.Column(
|
||||
[
|
||||
self.create_setting_group(
|
||||
self._["security_settings"],
|
||||
self._["web_login_configuration"],
|
||||
[
|
||||
self.create_setting_row(
|
||||
self._["current_username"],
|
||||
ft.Text(username),
|
||||
),
|
||||
self.create_setting_row(
|
||||
self._["old_password"],
|
||||
old_password_field,
|
||||
),
|
||||
self.create_setting_row(
|
||||
self._["new_password"],
|
||||
new_password_field,
|
||||
),
|
||||
self.create_setting_row(
|
||||
self._["confirm_password"],
|
||||
confirm_password_field,
|
||||
),
|
||||
self.create_setting_row(
|
||||
"",
|
||||
change_password_button,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
spacing=10,
|
||||
scroll=ft.ScrollMode.AUTO,
|
||||
)
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
"basic_settings": "Basic Settings",
|
||||
"cookies_settings": "Cookie Settings",
|
||||
"accounts_settings": "Account Settings",
|
||||
"security_settings": "Security Settings",
|
||||
"select": "select",
|
||||
"unsupported_select_path": "Path selection is not supported on the web 📂 Please enter manually",
|
||||
"program_config": "Basic configuration of the program",
|
||||
@@ -178,6 +179,18 @@
|
||||
"custom_script": "Execute Custom Script After Recording",
|
||||
"script_command": "Custom Script Execution Command",
|
||||
"default_platform_with_proxy": "Default Platform for Recording with Proxy",
|
||||
"web_login_configuration": "Web Backend Login Configuration",
|
||||
"current_username": "Current Username",
|
||||
"old_password": "Old Password",
|
||||
"new_password": "New Password",
|
||||
"confirm_password": "Confirm Password",
|
||||
"change_password": "Change Password",
|
||||
"old_password_required": "Old password cannot be empty",
|
||||
"new_password_required": "New password cannot be empty",
|
||||
"passwords_not_match": "The two passwords do not match",
|
||||
"password_changed": "Password changed successfully",
|
||||
"old_password_incorrect": "Old password is incorrect",
|
||||
"not_logged_in": "You are not logged in",
|
||||
"push_notifications": "Push Notifications",
|
||||
"stream_start_notification_enabled": "Live Status Notification",
|
||||
"open_broadcast_push_enabled": "Broadcast Start Push",
|
||||
@@ -402,5 +415,17 @@
|
||||
"saving_recordings": "Saving {active_recordings_count} recordings, please wait...",
|
||||
"confirm_exit": "Confirm Exit",
|
||||
"confirm_exit_content": "Are you sure you want to exit the program?"
|
||||
},
|
||||
"login_page": {
|
||||
"login_title": "StreamCap Login",
|
||||
"login_subtitle": "Please login to your account",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"login_button": "Login",
|
||||
"login_in_progress": "Logging in...",
|
||||
"default_account_tip": "Default account: admin / Password: admin",
|
||||
"input_required": "Please enter username and password",
|
||||
"login_failed": "Username or password is incorrect",
|
||||
"login_success": "Login successful"
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,7 @@
|
||||
"basic_settings": "基础设置",
|
||||
"cookies_settings": "Cookie设置",
|
||||
"accounts_settings": "账号设置",
|
||||
"security_settings": "安全设置",
|
||||
"select": "选择",
|
||||
"unsupported_select_path": "Web端不支持选择路径📂请手动输入",
|
||||
"program_config": "程序的基本设置",
|
||||
@@ -179,6 +180,18 @@
|
||||
"custom_script": "录制完成后执行自定义脚本",
|
||||
"script_command": "自定义脚本执行命令",
|
||||
"default_platform_with_proxy": "默认使用代理录制的平台",
|
||||
"web_login_configuration": "Web后台登录配置",
|
||||
"current_username": "当前用户名",
|
||||
"old_password": "旧密码",
|
||||
"new_password": "新密码",
|
||||
"confirm_password": "确认密码",
|
||||
"change_password": "修改密码",
|
||||
"old_password_required": "旧密码不能为空",
|
||||
"new_password_required": "新密码不能为空",
|
||||
"passwords_not_match": "两次输入的密码不一致",
|
||||
"password_changed": "密码修改成功",
|
||||
"old_password_incorrect": "旧密码不正确",
|
||||
"not_logged_in": "您尚未登录",
|
||||
"push_notifications": "推送通知",
|
||||
"stream_start_notification_enabled": "直播状态推送开关",
|
||||
"open_broadcast_push_enabled": "开播推送开启",
|
||||
@@ -403,5 +416,17 @@
|
||||
"saving_recordings": "正在保存 {active_recordings_count} 个录制内容,请稍候...",
|
||||
"confirm_exit": "确认退出",
|
||||
"confirm_exit_content": "确定要退出程序吗?"
|
||||
},
|
||||
"login_page": {
|
||||
"login_title": "StreamCap 登录",
|
||||
"login_subtitle": "请登录您的账号",
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"login_button": "登录",
|
||||
"login_in_progress": "登录中...",
|
||||
"default_account_tip": "默认账号: admin / 密码: admin",
|
||||
"input_required": "请输入用户名和密码",
|
||||
"login_failed": "用户名或密码错误",
|
||||
"login_success": "登录成功"
|
||||
}
|
||||
}
|
||||
47
main.py
47
main.py
@@ -7,8 +7,10 @@ from dotenv import load_dotenv
|
||||
from screeninfo import get_monitors
|
||||
|
||||
from app.app_manager import App, execute_dir
|
||||
from app.auth.auth_manager import AuthManager
|
||||
from app.lifecycle.app_close_handler import handle_app_close
|
||||
from app.ui.components.save_progress_overlay import SaveProgressOverlay
|
||||
from app.ui.views.login_view import LoginPage
|
||||
from app.utils.logger import logger
|
||||
|
||||
DEFAULT_HOST = "127.0.0.1"
|
||||
@@ -78,7 +80,7 @@ def handle_disconnect(page: ft.Page) -> callable:
|
||||
return disconnect
|
||||
|
||||
|
||||
def main(page: ft.Page) -> None:
|
||||
async def main(page: ft.Page) -> None:
|
||||
|
||||
page.title = "StreamCap"
|
||||
page.window.min_width = MIN_WIDTH
|
||||
@@ -89,6 +91,7 @@ def main(page: ft.Page) -> None:
|
||||
|
||||
app = App(page)
|
||||
page.data = app
|
||||
app.is_web_mode = is_web
|
||||
|
||||
theme_mode = app.settings.user_config.get("theme_mode", "light")
|
||||
if theme_mode == "dark":
|
||||
@@ -98,16 +101,44 @@ def main(page: ft.Page) -> None:
|
||||
|
||||
save_progress_overlay = SaveProgressOverlay(app)
|
||||
page.overlay.append(save_progress_overlay.overlay)
|
||||
|
||||
async def load_app():
|
||||
page.add(app.complete_page)
|
||||
|
||||
page.on_route_change = handle_route_change(page, app)
|
||||
page.window.prevent_close = True
|
||||
page.window.on_event = handle_window_event(page, app, save_progress_overlay)
|
||||
|
||||
page.on_route_change = handle_route_change(page, app)
|
||||
page.window.prevent_close = True
|
||||
page.window.on_event = handle_window_event(page, app, save_progress_overlay)
|
||||
if is_web:
|
||||
page.on_disconnect = handle_disconnect(page)
|
||||
|
||||
page.update()
|
||||
page.on_route_change(ft.RouteChangeEvent(route=page.route))
|
||||
|
||||
if is_web:
|
||||
page.on_disconnect = handle_disconnect(page)
|
||||
|
||||
page.update()
|
||||
page.on_route_change(ft.RouteChangeEvent(route=page.route))
|
||||
auth_manager = AuthManager(app)
|
||||
app.auth_manager = auth_manager
|
||||
await auth_manager.initialize()
|
||||
|
||||
session_token = await page.client_storage.get_async("session_token")
|
||||
if not session_token or not auth_manager.validate_session(session_token):
|
||||
async def on_login_success(token):
|
||||
_session_info = auth_manager.active_sessions.get(token, {})
|
||||
app.current_username = _session_info.get("username")
|
||||
|
||||
page.clean()
|
||||
await load_app()
|
||||
|
||||
page.clean()
|
||||
|
||||
login_page = LoginPage(page, auth_manager, on_login_success)
|
||||
page.add(login_page.get_view())
|
||||
return
|
||||
else:
|
||||
session_info = auth_manager.active_sessions.get(session_token, {})
|
||||
app.current_username = session_info.get("username")
|
||||
|
||||
await load_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user