| 171 | |
| 172 | |
| 173 | class AstrBotDashboard: |
| 174 | def __init__( |
| 175 | self, |
| 176 | core_lifecycle: AstrBotCoreLifecycle, |
| 177 | db: BaseDatabase, |
| 178 | shutdown_event: asyncio.Event, |
| 179 | webui_dir: str | None = None, |
| 180 | ) -> None: |
| 181 | self.core_lifecycle = core_lifecycle |
| 182 | self.config = core_lifecycle.astrbot_config |
| 183 | self.db = db |
| 184 | |
| 185 | # Path priority: |
| 186 | # 1. Explicit webui_dir argument |
| 187 | # 2. data/dist/ when it matches the core version |
| 188 | # 3. astrbot/dashboard/dist/ when it matches the core version |
| 189 | if webui_dir and os.path.exists(webui_dir): |
| 190 | self.data_path = os.path.abspath(webui_dir) |
| 191 | else: |
| 192 | user_dist = os.path.join(get_astrbot_data_path(), "dist") |
| 193 | bundled_dist = get_bundled_dashboard_dist_path() |
| 194 | user_version = get_dashboard_dist_version(user_dist) |
| 195 | if os.path.exists(user_dist) and is_dashboard_dist_compatible( |
| 196 | user_dist, |
| 197 | VERSION, |
| 198 | ): |
| 199 | self.data_path = os.path.abspath(user_dist) |
| 200 | elif should_use_bundled_dashboard_dist( |
| 201 | user_dist, |
| 202 | VERSION, |
| 203 | ) or is_dashboard_dist_compatible(bundled_dist, VERSION): |
| 204 | self.data_path = str(bundled_dist) |
| 205 | logger.info("Using bundled dashboard dist: %s", self.data_path) |
| 206 | elif ( |
| 207 | os.path.exists(user_dist) and (Path(user_dist) / "index.html").is_file() |
| 208 | ): |
| 209 | logger.warning( |
| 210 | "Using existing data/dist as a fallback even though WebUI version mismatches core: %s, expected v%s. " |
| 211 | "Some dashboard features may not work until the matching WebUI is available.", |
| 212 | user_version, |
| 213 | VERSION, |
| 214 | ) |
| 215 | self.data_path = os.path.abspath(user_dist) |
| 216 | elif os.path.exists(user_dist): |
| 217 | logger.warning( |
| 218 | "Ignoring data/dist because WebUI files are incomplete for core v%s.", |
| 219 | VERSION, |
| 220 | ) |
| 221 | self.data_path = None |
| 222 | else: |
| 223 | # Fall back to expected user path (will fail gracefully later) |
| 224 | self.data_path = os.path.abspath(user_dist) |
| 225 | |
| 226 | self._rate_limiter_registry = _RateLimiterRegistry() |
| 227 | self._init_jwt_secret() |
| 228 | self.asgi_app = create_dashboard_asgi_app( |
| 229 | core_lifecycle=core_lifecycle, |
| 230 | db=db, |
no outgoing calls