mirror of
https://github.com/smittix/intercept.git
synced 2026-04-26 23:59:59 -07:00
- Add offline.stadia_key to OFFLINE_DEFAULTS in routes/offline.py - Add stadia_dark and tactical tile providers to Settings.tileProviders - Update getTileConfig() to inject Stadia API key or fall back to CartoDB dark - Add setStadiaKey() method for saving and applying the API key - Show/hide Stadia key row in setTileProvider() and _updateUI() - Add Stadia options to tile provider select in settings modal - Add Stadia API key input row to settings modal - Add TDD tests for stadia_key backend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
149 lines
4.7 KiB
Python
149 lines
4.7 KiB
Python
"""
|
|
Offline mode routes - Asset management and settings for offline operation.
|
|
"""
|
|
|
|
import os
|
|
|
|
from flask import Blueprint, request
|
|
|
|
from utils.database import get_setting, set_setting
|
|
from utils.responses import api_error, api_success
|
|
|
|
offline_bp = Blueprint("offline", __name__, url_prefix="/offline")
|
|
|
|
# Default offline settings
|
|
OFFLINE_DEFAULTS = {
|
|
"offline.enabled": False,
|
|
# Default to bundled assets/fonts to avoid third-party CDN privacy blocks.
|
|
"offline.assets_source": "local",
|
|
"offline.fonts_source": "local",
|
|
"offline.tile_provider": "cartodb_dark_cyan",
|
|
"offline.tile_server_url": "",
|
|
"offline.stadia_key": "",
|
|
}
|
|
|
|
# Asset paths to check
|
|
ASSET_PATHS = {
|
|
"leaflet": ["static/vendor/leaflet/leaflet.js", "static/vendor/leaflet/leaflet.css"],
|
|
"chartjs": ["static/vendor/chartjs/chart.umd.min.js"],
|
|
"inter": [
|
|
"static/vendor/fonts/Inter-Regular.woff2",
|
|
"static/vendor/fonts/Inter-Medium.woff2",
|
|
"static/vendor/fonts/Inter-SemiBold.woff2",
|
|
"static/vendor/fonts/Inter-Bold.woff2",
|
|
],
|
|
"jetbrains": [
|
|
"static/vendor/fonts/JetBrainsMono-Regular.woff2",
|
|
"static/vendor/fonts/JetBrainsMono-Medium.woff2",
|
|
"static/vendor/fonts/JetBrainsMono-SemiBold.woff2",
|
|
"static/vendor/fonts/JetBrainsMono-Bold.woff2",
|
|
],
|
|
"leaflet_images": [
|
|
"static/vendor/leaflet/images/marker-icon.png",
|
|
"static/vendor/leaflet/images/marker-icon-2x.png",
|
|
"static/vendor/leaflet/images/marker-shadow.png",
|
|
"static/vendor/leaflet/images/layers.png",
|
|
"static/vendor/leaflet/images/layers-2x.png",
|
|
],
|
|
"leaflet_heat": ["static/vendor/leaflet-heat/leaflet-heat.js"],
|
|
}
|
|
|
|
|
|
def get_offline_settings():
|
|
"""Get all offline settings with defaults."""
|
|
settings = {}
|
|
for key, default in OFFLINE_DEFAULTS.items():
|
|
settings[key] = get_setting(key, default)
|
|
return settings
|
|
|
|
|
|
@offline_bp.route("/settings", methods=["GET"])
|
|
def get_settings():
|
|
"""Get current offline settings."""
|
|
settings = get_offline_settings()
|
|
return api_success(data={"settings": settings})
|
|
|
|
|
|
@offline_bp.route("/settings", methods=["POST"])
|
|
def save_setting():
|
|
"""Save an offline setting."""
|
|
data = request.get_json()
|
|
if not data or "key" not in data or "value" not in data:
|
|
return api_error("Missing key or value", 400)
|
|
|
|
key = data["key"]
|
|
value = data["value"]
|
|
|
|
# Validate key is an allowed setting
|
|
if key not in OFFLINE_DEFAULTS:
|
|
return api_error(f"Unknown setting: {key}", 400)
|
|
|
|
# Validate value type matches default
|
|
default_type = type(OFFLINE_DEFAULTS[key])
|
|
if not isinstance(value, default_type):
|
|
# Try to convert
|
|
try:
|
|
if default_type == bool:
|
|
value = str(value).lower() in ("true", "1", "yes")
|
|
else:
|
|
value = default_type(value)
|
|
except (ValueError, TypeError):
|
|
return api_error(f"Invalid value type for {key}", 400)
|
|
|
|
set_setting(key, value)
|
|
|
|
return api_success(data={"key": key, "value": value})
|
|
|
|
|
|
@offline_bp.route("/status", methods=["GET"])
|
|
def get_status():
|
|
"""Check status of local assets."""
|
|
# Get the app root directory
|
|
app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
results = {}
|
|
all_available = True
|
|
|
|
for asset_name, paths in ASSET_PATHS.items():
|
|
available = True
|
|
missing = []
|
|
for path in paths:
|
|
full_path = os.path.join(app_root, path)
|
|
if not os.path.exists(full_path):
|
|
available = False
|
|
missing.append(path)
|
|
|
|
results[asset_name] = {"available": available, "missing": missing if not available else []}
|
|
|
|
if not available:
|
|
all_available = False
|
|
|
|
return api_success(
|
|
data={
|
|
"all_available": all_available,
|
|
"assets": results,
|
|
"offline_enabled": get_setting("offline.enabled", False),
|
|
}
|
|
)
|
|
|
|
|
|
@offline_bp.route("/check-asset", methods=["GET"])
|
|
def check_asset():
|
|
"""Check if a specific asset file exists."""
|
|
path = request.args.get("path", "")
|
|
if not path:
|
|
return api_error("Missing path parameter", 400)
|
|
|
|
# Security: only allow checking within static/vendor
|
|
if not path.startswith("/static/vendor/"):
|
|
return api_error("Invalid path", 400)
|
|
|
|
# Remove leading slash and construct full path
|
|
relative_path = path.lstrip("/")
|
|
app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
full_path = os.path.join(app_root, relative_path)
|
|
|
|
exists = os.path.exists(full_path)
|
|
|
|
return api_success(data={"path": path, "exists": exists})
|