Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions assets/gui/css/advanced-material-alas.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@
1. CSS 变量定义
========================================== */

/* 引入 Google Fonts 字体:Inter,包含常规、中等和半粗字重 */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');

:root {
/* === 色彩系统 === */
--alas-apple-bg: #f5f5f7;
--alas-apple-bg-image: url("https://api.yppp.net/api.php");
/* 主题样式通过 WebSocket 内联注入,资源路径相对页面解析。 */
--alas-apple-bg-image: url("static/assets/gui/css/bj.jpg");
--alas-apple-bg-noise: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
--alas-apple-card-bg: rgba(255, 255, 255, 0.72);
--alas-apple-card-bg-solid: #ffffff;
Expand Down
6 changes: 3 additions & 3 deletions assets/gui/css/alas.css
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
@font-face {
font-family: 'MiSans';
src: url('/static/assets/spa/MiSans-Demibold.ttf') format('truetype');
src: url('../../spa/MiSans-Demibold.ttf') format('truetype');
font-weight: 450;
font-style: normal;
font-display: optional;
}

@font-face {
font-family: 'JetBrains Mono NL';
src: url('/static/assets/spa/JetBrainsMonoNL-Regular.ttf') format('truetype');
src: url('../../spa/JetBrainsMonoNL-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
Expand Down Expand Up @@ -1120,7 +1120,7 @@ body.alas-login-page::after,
width: 160px;
height: 160px;
margin: 0 auto;
background: url('/static/doc/logo.webp') center / contain no-repeat;
background: url('../../../doc/logo.webp') center / contain no-repeat;
}

.alas-login-page #input-container .card-body {
Expand Down
9 changes: 5 additions & 4 deletions assets/spa/manifest.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
{
"name": "Alas Web UI",
"short_name": "Alas",
"start_url": "/?app=index",
"start_url": "../../../?app=index",
"scope": "../../../",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/static/assets/spa/spa-icon-192x192.png",
"src": "spa-icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/assets/spa/spa-icon-512x512.png",
"src": "spa-icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
}
22 changes: 21 additions & 1 deletion deploy/Windows/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import sys
from typing import Optional, Union

from deploy.geo import get_country_code
from deploy.Windows.logger import logger
from deploy.Windows.utils import DEPLOY_CONFIG, DEPLOY_TEMPLATE, cached_property, poor_yaml_read, poor_yaml_write


GIT_OVER_CDN_REPOSITORY = 'git://git.pull/AzurPilot'
GIT_OVER_CDN_FALLBACK_REPOSITORY = 'https://gitcode.com/ddl2/AzurLaneAutoScript'
GITHUB_REPOSITORY = 'https://github.com/wess09/AzurPilot'


class ExecutionError(Exception):
Expand All @@ -18,7 +20,7 @@ class ExecutionError(Exception):

class ConfigModel:
# Git 配置
Repository: str = "https://github.com/wess09/AzurPilot"
Repository: str = GITHUB_REPOSITORY
Branch: str = "master"
GitExecutable: str = "./.venv/Scripts/git/cmd/git.exe"
GitProxy: Optional[str] = None
Expand Down Expand Up @@ -86,6 +88,7 @@ def __init__(self, file=DEPLOY_CONFIG):
self.file = file
self.config = {}
self.config_template = {}
self._github_location_checked = False
self.read()

self.show_config()
Expand Down Expand Up @@ -125,13 +128,30 @@ def config_redirect(self):
每次 `read()` 之后必须调用。
"""
self.config.pop('AutoUpdate', None)
self._redirect_github_repository()
# 绕过 webui.config.DeployConfig.__setattr__(),不写入 deploy.yaml
super().__setattr__('GitOverCdn', self.Repository in ['cn', GIT_OVER_CDN_REPOSITORY])
if self.Repository in ['global']:
super().__setattr__('Repository', 'https://github.com/wess09/AzurPilot')
if self.Repository in ['cn', GIT_OVER_CDN_REPOSITORY]:
super().__setattr__('Repository', GIT_OVER_CDN_FALLBACK_REPOSITORY)

def _redirect_github_repository(self):
"""为官方 GitHub 源一次性选择适合当前网络的更新镜像。"""
if self._github_location_checked or self.Repository != GITHUB_REPOSITORY:
return

self._github_location_checked = True
country_code = get_country_code()
if country_code == 'cn':
logger.info('检测到中国大陆网络,切换至国内 Git 更新源')
self.Repository = GIT_OVER_CDN_REPOSITORY
self.config['Repository'] = GIT_OVER_CDN_REPOSITORY
elif country_code is None:
logger.warning('无法检测网络所在国家,保留 GitHub 更新源')
else:
logger.info('当前网络不在中国大陆,保留 GitHub 更新源')

def filepath(self, path):
"""获取绝对文件路径。

Expand Down
22 changes: 21 additions & 1 deletion deploy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import sys
from typing import Optional, Union

from deploy.geo import get_country_code
from deploy.logger import logger
from deploy.utils import *


GIT_OVER_CDN_REPOSITORY = 'git://git.pull/AzurPilot'
GIT_OVER_CDN_FALLBACK_REPOSITORY = 'https://gitcode.com/ddl2/AzurLaneAutoScript'
GITHUB_REPOSITORY = 'https://github.com/wess09/AzurPilot'


class ExecutionError(Exception):
Expand All @@ -16,7 +18,7 @@ class ExecutionError(Exception):

class ConfigModel:
# Git 配置
Repository: str = "https://github.com/wess09/AzurPilot"
Repository: str = GITHUB_REPOSITORY
Branch: str = "master"
GitExecutable: str = "./.venv/Scripts/git/cmd/git.exe" if sys.platform == "win32" else "./.venv/bin/git"
GitProxy: Optional[str] = None
Expand Down Expand Up @@ -85,6 +87,7 @@ def __init__(self, file=DEPLOY_CONFIG):
self.template_file = get_deploy_template()
self.config = {}
self.config_template = {}
self._github_location_checked = False
self.read()

self.show_config()
Expand Down Expand Up @@ -125,6 +128,7 @@ def config_redirect(self):
每次 `read()` 之后必须调用。
"""
self.config.pop('AutoUpdate', None)
self._redirect_github_repository()
if self.Repository in [
'https://gitee.com/LmeSzinc/AzurLaneAutoScript',
'https://gitee.com/lmeszinc/azur-lane-auto-script-mirror',
Expand Down Expand Up @@ -160,6 +164,22 @@ def config_redirect(self):
if self.Repository in ['cn']:
super().__setattr__('Repository', GIT_OVER_CDN_REPOSITORY)

def _redirect_github_repository(self):
"""为官方 GitHub 源一次性选择适合当前网络的更新镜像。"""
if self._github_location_checked or self.Repository != GITHUB_REPOSITORY:
return

self._github_location_checked = True
country_code = get_country_code()
if country_code == 'cn':
logger.info('检测到中国大陆网络,切换至国内 Git 更新源')
self.Repository = GIT_OVER_CDN_REPOSITORY
self.config['Repository'] = GIT_OVER_CDN_REPOSITORY
elif country_code is None:
logger.warning('无法检测网络所在国家,保留 GitHub 更新源')
else:
logger.info('当前网络不在中国大陆,保留 GitHub 更新源')

def filepath(self, key):
"""根据配置键获取绝对文件路径。

Expand Down
32 changes: 32 additions & 0 deletions deploy/geo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Optional

import requests


IP9_LOCATION_URL = 'https://ip9.com.cn/get'


def get_country_code(timeout=5) -> Optional[str]:
"""查询当前公网 IP 所在国家的 ISO 代码。

查询失败或响应格式不符合预期时返回 ``None``,调用方应保留当前更新源。
"""
try:
response = requests.get(
IP9_LOCATION_URL,
timeout=timeout,
headers={'User-Agent': 'AzurPilot'},
)
response.raise_for_status()
payload = response.json()
except (requests.RequestException, ValueError):
return None

try:
country_code = payload['data']['country_code']
except (KeyError, TypeError):
return None

if isinstance(country_code, str):
return country_code.lower()
return None
22 changes: 18 additions & 4 deletions module/webui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
该模块是 WebUI 的顶层入口,被 gui.py 启动时引用。
"""

from hashlib import sha256
from pathlib import Path

from module.webui.app_dashboard import DashboardMixin
from module.webui.app_dependencies import (
Dict,
Expand All @@ -29,7 +32,6 @@
local,
logger,
login,
os,
popup,
run_js,
set_env,
Expand Down Expand Up @@ -76,7 +78,16 @@
from module.webui.app_task_config import TaskConfigMixin


INITIAL_WEBUI_CSS = "/static/assets/gui/css/alas.css"
PROJECT_ROOT = Path(__file__).resolve().parents[2]


def _versioned_static_asset(relative_path: str) -> str:
"""返回带内容哈希的相对静态资源地址。"""
digest = sha256((PROJECT_ROOT / relative_path).read_bytes()).hexdigest()[:12]
return f"static/{relative_path}?v={digest}"
Comment on lines +84 to +87

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Handle missing/invalid asset files in _versioned_static_asset to avoid startup-time crashes.

If this file doesn’t exist or the path is wrong, this will raise (e.g. FileNotFoundError) when INITIAL_WEBUI_CSS is resolved and can prevent the app from starting. Please catch file/IO errors here and either fall back to an unversioned path or return/log a safe default so a missing asset doesn’t break the web UI.

Original comment in English

issue (bug_risk): Handle missing/invalid asset files in _versioned_static_asset to avoid startup-time crashes.

If this file doesn’t exist or the path is wrong, this will raise (e.g. FileNotFoundError) when INITIAL_WEBUI_CSS is resolved and can prevent the app from starting. Please catch file/IO errors here and either fall back to an unversioned path or return/log a safe default so a missing asset doesn’t break the web UI.



INITIAL_WEBUI_CSS = _versioned_static_asset("assets/gui/css/alas.css")


class AlasGUI(
Expand Down Expand Up @@ -167,7 +178,10 @@ def app():
from deploy.atomic import atomic_failure_cleanup

atomic_failure_cleanup("./config")
static_path = os.getcwd()
static_mounts = {
"/static/assets": str(PROJECT_ROOT / "assets"),
"/static/doc": str(PROJECT_ROOT / "doc"),
}

def _block_restricted_device() -> bool:
if is_demo_mode():
Expand Down Expand Up @@ -231,7 +245,7 @@ def manage() -> None:
application = asgi_app(
applications=[index, manage],
cdn=cdn,
static_dir=static_path,
static_mounts=static_mounts,
debug=True,
on_startup=[
startup,
Expand Down
4 changes: 2 additions & 2 deletions module/webui/app_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,13 +251,13 @@ def _load_deferred_client_assets(self) -> None:
"}"
"if (!document.querySelector('link[rel=\"manifest\"]')) {"
"var manifest=document.createElement('link');"
"manifest.rel='manifest';manifest.href='/static/assets/spa/manifest.json';"
"manifest.rel='manifest';manifest.href='static/assets/spa/manifest.json';"
"document.head.appendChild(manifest);"
"}"
"if (!document.getElementById('alas-utils-script')) {"
"var script=document.createElement('script');"
"script.id='alas-utils-script';script.async=true;"
"script.src='/static/assets/gui/js/alas-utils.js';"
"script.src='static/assets/gui/js/alas-utils.js';"
"document.head.appendChild(script);"
"}"
"}"
Expand Down
15 changes: 11 additions & 4 deletions module/webui/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import asyncio
import logging
import os
from collections.abc import Mapping
from typing import Any, cast

import uvicorn
Expand Down Expand Up @@ -34,7 +35,7 @@

logger = logging.getLogger(__name__)

STATIC_ASSET_CACHE_CONTROL = "public, max-age=86400, must-revalidate"
STATIC_ASSET_CACHE_CONTROL = "no-cache"
NO_CACHE_CONTROL = "no-cache"
HTTP_GZIP_MINIMUM_SIZE = 1024
HTTP_GZIP_COMPRESS_LEVEL = 5
Expand All @@ -51,7 +52,7 @@ async def dispatch(self, request, call_next):
200 <= response.status_code < 300 or response.status_code == 304
)
if request.method in {"GET", "HEAD"} and is_static_asset and is_cacheable_response:
# 静态资源路径未使用内容哈希,缓存一天后仍通过 ETag 重新验证
# 部分静态资源没有内容哈希,必须在每次使用前重新验证
response.headers["Cache-Control"] = STATIC_ASSET_CACHE_CONTROL
else:
response.headers["Cache-Control"] = NO_CACHE_CONTROL
Expand Down Expand Up @@ -116,11 +117,12 @@ def patch_pywebio_websocket_connection():

def asgi_app(
applications,
cdn: str | bool = True,
cdn: str | bool = False,
static_dir=None,
debug: bool = False,
allowed_origins=None,
check_origin=None,
static_mounts: Mapping[str, str] | None = None,
**starlette_settings,
):
debug = bool(os.environ.get("PYWEBIO_DEBUG", debug))
Expand All @@ -137,6 +139,9 @@ def asgi_app(
check_origin=check_origin,
)
routes.insert(0, Route("/robots.txt", robots_txt, methods=["GET", "HEAD"]))
if static_mounts:
for mount_path, directory in static_mounts.items():
routes.append(Mount(mount_path, app=StaticFiles(directory=directory)))
if static_dir:
routes.append(
Mount("/static", app=StaticFiles(directory=static_dir), name="static")
Expand Down Expand Up @@ -176,20 +181,22 @@ def start_server(
applications,
port=0,
host="",
cdn: str | bool = True,
cdn: str | bool = False,
static_dir=None,
remote_access=False,
debug=False,
allowed_origins=None,
check_origin=None,
auto_open_webbrowser=False,
static_mounts: Mapping[str, str] | None = None,
**uvicorn_settings,
):

app = asgi_app(
applications,
cdn=cdn,
static_dir=static_dir,
static_mounts=static_mounts,
debug=debug,
allowed_origins=allowed_origins,
check_origin=check_origin,
Expand Down
Loading
Loading