refactor: update imports fabledscryer → roundtable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 17:13:55 -04:00
parent 8aad2ab43d
commit 94a35da86e
45 changed files with 147 additions and 147 deletions
+6 -6
View File
@@ -1,13 +1,13 @@
from __future__ import annotations
from datetime import datetime, timezone
from quart import Blueprint, render_template, request, redirect, url_for, current_app, session
from fabledscryer.core.audit import log_audit
from roundtable.core.audit import log_audit
from sqlalchemy import select
from fabledscryer.auth.middleware import require_role
from fabledscryer.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertOperator, MaintenanceWindow
from fabledscryer.models.hosts import Host
from fabledscryer.models.metrics import PluginMetric
from fabledscryer.models.users import UserRole
from roundtable.auth.middleware import require_role
from roundtable.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertOperator, MaintenanceWindow
from roundtable.models.hosts import Host
from roundtable.models.metrics import PluginMetric
from roundtable.models.users import UserRole
alerts_bp = Blueprint("alerts", __name__, url_prefix="/alerts")
+2 -2
View File
@@ -1,4 +1,4 @@
# fabledscryer/ansible/executor.py
# roundtable/ansible/executor.py
from __future__ import annotations
import asyncio
import logging
@@ -77,7 +77,7 @@ async def start_run(
async def _flush(final_status: str | None = None) -> None:
nonlocal lines_since_flush, last_flush_at
from sqlalchemy import update
from fabledscryer.models.ansible import AnsibleRun, AnsibleRunStatus
from roundtable.models.ansible import AnsibleRun, AnsibleRunStatus
values: dict = {"output": db_output}
if final_status is not None:
+5 -5
View File
@@ -1,4 +1,4 @@
# fabledscryer/ansible/routes.py
# roundtable/ansible/routes.py
from __future__ import annotations
import asyncio
import uuid
@@ -11,10 +11,10 @@ from quart import (
)
from sqlalchemy import select, update
from fabledscryer.ansible import executor, sources as src_module
from fabledscryer.auth.middleware import require_role
from fabledscryer.models.ansible import AnsibleRun, AnsibleRunStatus
from fabledscryer.models.users import UserRole
from roundtable.ansible import executor, sources as src_module
from roundtable.auth.middleware import require_role
from roundtable.models.ansible import AnsibleRun, AnsibleRunStatus
from roundtable.models.users import UserRole
ansible_bp = Blueprint("ansible", __name__, url_prefix="/ansible")
+2 -2
View File
@@ -17,7 +17,7 @@ def get_sources(ansible_cfg: dict) -> list[dict]:
Config structure in config.yaml::
ansible:
cache_dir: /var/cache/fabledscryer/ansible
cache_dir: /var/cache/roundtable/ansible
sources:
- name: my-playbooks
type: local
@@ -29,7 +29,7 @@ def get_sources(ansible_cfg: dict) -> list[dict]:
pull_interval_seconds: 3600
"""
sources = ansible_cfg.get("sources", [])
cache_dir = ansible_cfg.get("cache_dir", "/var/cache/fabledscryer/ansible")
cache_dir = ansible_cfg.get("cache_dir", "/var/cache/roundtable/ansible")
result = []
for src in sources:
src_type = src.get("type", "local")
+1 -1
View File
@@ -1,4 +1,4 @@
# fabledscryer/app.py
# roundtable/app.py
from __future__ import annotations
import asyncio
from pathlib import Path
+3 -3
View File
@@ -2,9 +2,9 @@ from __future__ import annotations
import json
from quart import Blueprint, render_template, request, current_app
from sqlalchemy import select
from fabledscryer.auth.middleware import require_role
from fabledscryer.models.audit import AuditEvent
from fabledscryer.models.users import UserRole
from roundtable.auth.middleware import require_role
from roundtable.models.audit import AuditEvent
from roundtable.models.users import UserRole
audit_bp = Blueprint("audit", __name__, url_prefix="/audit")
+1 -1
View File
@@ -1,4 +1,4 @@
"""fabledscryer/auth/ldap_auth.py
"""roundtable/auth/ldap_auth.py
LDAP authentication helper. Requires the optional `ldap3` package.
Falls back gracefully if ldap3 is not installed.
+1 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
import functools
from quart import session, redirect, url_for, abort, g
from fabledscryer.models.users import UserRole
from roundtable.models.users import UserRole
_ROLE_ORDER = [UserRole.viewer, UserRole.operator, UserRole.admin]
+1 -1
View File
@@ -1,4 +1,4 @@
"""fabledscryer/auth/oidc.py
"""roundtable/auth/oidc.py
OIDC Authorization Code flow helpers using httpx.
No extra dependencies — works with any OIDC provider (Authentik, Keycloak, etc.).
+9 -9
View File
@@ -2,8 +2,8 @@ from __future__ import annotations
import bcrypt
from quart import Blueprint, render_template, request, redirect, url_for, session
from sqlalchemy import select, func
from fabledscryer.auth.middleware import login_user, logout_user
from fabledscryer.models.users import User, UserRole
from roundtable.auth.middleware import login_user, logout_user
from roundtable.models.users import User, UserRole
auth_bp = Blueprint("auth", __name__)
@@ -15,7 +15,7 @@ async def _provision_external_user(app, username: str, email: str, role_str: str
Their role is updated on every login to reflect current IdP group membership.
"""
import secrets
from fabledscryer.models.users import UserRole
from roundtable.models.users import UserRole
async with app.db_sessionmaker() as db:
result = await db.execute(select(User).where(User.username == username))
user = result.scalar_one_or_none()
@@ -60,7 +60,7 @@ async def login():
@auth_bp.post("/login")
async def login_post():
from quart import current_app
from fabledscryer.core.audit import log_audit
from roundtable.core.audit import log_audit
form = await request.form
username = form.get("username", "").strip()
password_str = form.get("password", "")
@@ -77,7 +77,7 @@ async def login_post():
# Try LDAP fallback if enabled
ldap_cfg = current_app.config.get("LDAP", {})
if ldap_cfg.get("enabled"):
from fabledscryer.auth.ldap_auth import ldap_authenticate
from roundtable.auth.ldap_auth import ldap_authenticate
ldap_info = await ldap_authenticate(ldap_cfg, username, password_str)
if ldap_info:
user = await _provision_external_user(
@@ -99,7 +99,7 @@ async def login_post():
@auth_bp.get("/login/oidc")
async def login_oidc():
from quart import current_app
from fabledscryer.auth.oidc import get_discovery, build_authorize_url, generate_state
from roundtable.auth.oidc import get_discovery, build_authorize_url, generate_state
oidc_cfg = current_app.config.get("OIDC", {})
if not oidc_cfg.get("enabled") or not oidc_cfg.get("discovery_url"):
return redirect(url_for("auth.login"))
@@ -123,8 +123,8 @@ async def login_oidc():
@auth_bp.get("/login/oidc/callback")
async def login_oidc_callback():
from quart import current_app
from fabledscryer.auth.oidc import get_discovery, exchange_code, get_userinfo, map_role
from fabledscryer.core.audit import log_audit
from roundtable.auth.oidc import get_discovery, exchange_code, get_userinfo, map_role
from roundtable.core.audit import log_audit
oidc_cfg = current_app.config.get("OIDC", {})
error = request.args.get("error")
@@ -190,7 +190,7 @@ async def setup():
@auth_bp.post("/setup")
async def setup_post():
from quart import current_app
from fabledscryer.core.audit import log_audit
from roundtable.core.audit import log_audit
if await get_user_count(current_app) > 0:
return redirect(url_for("auth.login"))
form = await request.form
+1 -1
View File
@@ -1,4 +1,4 @@
# fabledscryer/config.py
# roundtable/config.py
from __future__ import annotations
import logging
import os
+3 -3
View File
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
from sqlalchemy import and_, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from fabledscryer.models.alerts import (
from roundtable.models.alerts import (
AlertEvent,
AlertOperator,
AlertRule,
@@ -16,7 +16,7 @@ from fabledscryer.models.alerts import (
AlertStateEnum,
MaintenanceWindow,
)
from fabledscryer.models.metrics import PluginMetric
from roundtable.models.metrics import PluginMetric
if TYPE_CHECKING:
from quart import Quart
@@ -226,7 +226,7 @@ async def _dispatch_notification(
event_id: str,
) -> None:
"""Run after the transaction commits. Sends notifications and updates the event row."""
from fabledscryer.core.notifications import dispatch_notifications
from roundtable.core.notifications import dispatch_notifications
alert_data = {
"rule_name": rule.name,
+2 -2
View File
@@ -1,4 +1,4 @@
"""fabledscryer/core/audit.py
"""roundtable/core/audit.py
Helpers for writing audit log entries. Each call opens its own DB
session so audit events are committed independently of the calling
@@ -23,7 +23,7 @@ async def log_audit(
detail: dict | None = None,
) -> None:
"""Write one audit event. Never raises — failures are logged and swallowed."""
from fabledscryer.models.audit import AuditEvent
from roundtable.models.audit import AuditEvent
try:
async with app.db_sessionmaker() as db:
async with db.begin():
+3 -3
View File
@@ -5,9 +5,9 @@ from typing import TYPE_CHECKING
from sqlalchemy import delete
from fabledscryer.models.monitors import DnsResult, PingResult
from fabledscryer.models.metrics import PluginMetric
from fabledscryer.models.ansible import AnsibleRun
from roundtable.models.monitors import DnsResult, PingResult
from roundtable.models.metrics import PluginMetric
from roundtable.models.ansible import AnsibleRun
if TYPE_CHECKING:
from quart import Quart
+1 -1
View File
@@ -1,4 +1,4 @@
# fabledscryer/core/migration_runner.py
# roundtable/core/migration_runner.py
from __future__ import annotations
import logging
from pathlib import Path
+1 -1
View File
@@ -34,7 +34,7 @@ async def _send_email(cfg: dict, alert: dict) -> dict:
try:
msg = EmailMessage()
msg["Subject"] = f"[Fabled Scryer] {alert['state']}{alert['rule_name']}"
msg["From"] = cfg.get("username", "fabledscryer@localhost")
msg["From"] = cfg.get("username", "roundtable@localhost")
msg["To"] = ", ".join(cfg["recipients"])
msg.set_content(
f"Alert: {alert['state']}\n"
+1 -1
View File
@@ -1,4 +1,4 @@
# fabledscryer/core/plugin_index.py
# roundtable/core/plugin_index.py
"""Remote plugin catalog: fetch, parse, and cache the plugin index.yaml."""
from __future__ import annotations
import logging
+14 -14
View File
@@ -1,4 +1,4 @@
# fabledscryer/core/plugin_manager.py
# roundtable/core/plugin_manager.py
from __future__ import annotations
import hashlib
import importlib
@@ -41,7 +41,7 @@ def _import_plugin(name: str, plugin_path: Path):
"""
import importlib.util
module_key = f"_fabledscryer_plugin_{name}"
module_key = f"_roundtable_plugin_{name}"
if module_key in sys.modules:
return sys.modules[module_key]
@@ -78,7 +78,7 @@ def load_plugins(app: "Quart") -> None:
7. Register blueprint at /plugins/<name>/ if get_blueprint() exists
8. Append get_scheduled_tasks() results to app._task_registry
"""
import fabledscryer
import roundtable
plugin_dir = Path(app.config["PLUGIN_DIR"])
plugins_cfg: dict = app.config["PLUGINS"]
@@ -126,13 +126,13 @@ def load_plugins(app: "Quart") -> None:
min_ver = meta.get("min_app_version")
if min_ver:
try:
if Version(fabledscryer.__version__) < Version(min_ver):
if Version(roundtable.__version__) < Version(min_ver):
_FAILED_PLUGINS[name] = (
f"Requires fabledscryer>={min_ver}, running {fabledscryer.__version__}"
f"Requires roundtable>={min_ver}, running {roundtable.__version__}"
)
logger.error(
"Plugin %r: requires fabledscryer>=%s but running %s, skipping",
name, min_ver, fabledscryer.__version__,
"Plugin %r: requires roundtable>=%s but running %s, skipping",
name, min_ver, roundtable.__version__,
)
continue
except Exception as exc:
@@ -267,7 +267,7 @@ async def download_and_install_plugin(
# Detect a single top-level directory to strip. Handles:
# name/ (clean plugin zip)
# name-v1.0.0/ (GitHub release tag archive)
# fabledscryer-plugins-main/name/ (whole-repo archive)
# roundtable-plugins-main/name/ (whole-repo archive)
# Strategy: if there is exactly one top-level item and it is a
# directory whose name starts with the plugin name (case-insensitive),
# strip it. Otherwise extract flat into dest.
@@ -302,7 +302,7 @@ async def download_and_install_plugin(
try:
mdir = plugin_dir / name / "migrations"
if mdir.exists():
from fabledscryer.core.migration_runner import (
from roundtable.core.migration_runner import (
run_plugin_migrations,
discover_all_plugin_migration_dirs,
)
@@ -325,7 +325,7 @@ def hot_reload_plugin(app: "Quart", name: str) -> tuple[bool, str]:
Returns (success, message).
"""
import fabledscryer
import roundtable
if name in _LOADED_PLUGINS:
return False, "Plugin already loaded — restart required to apply updates"
@@ -352,10 +352,10 @@ def hot_reload_plugin(app: "Quart", name: str) -> tuple[bool, str]:
min_ver = meta.get("min_app_version")
if min_ver:
try:
if Version(fabledscryer.__version__) < Version(min_ver):
if Version(roundtable.__version__) < Version(min_ver):
return False, (
f"Plugin requires fabledscryer>={min_ver} "
f"but running {fabledscryer.__version__}"
f"Plugin requires roundtable>={min_ver} "
f"but running {roundtable.__version__}"
)
except Exception:
return False, f"Invalid min_app_version: {min_ver!r}"
@@ -381,7 +381,7 @@ def hot_reload_plugin(app: "Quart", name: str) -> tuple[bool, str]:
mdir = plugin_path / "migrations"
if mdir.exists():
try:
from fabledscryer.core.migration_runner import (
from roundtable.core.migration_runner import (
run_plugin_migrations,
discover_all_plugin_migration_dirs,
)
+8 -8
View File
@@ -1,4 +1,4 @@
"""fabledscryer/core/reports.py
"""roundtable/core/reports.py
Weekly digest report: uptime, active alerts, alert events, top metrics.
Called by the scheduler (hourly check) or triggered manually from Settings.
@@ -13,10 +13,10 @@ from email.message import EmailMessage
from sqlalchemy import and_, case, func, select
from fabledscryer.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertEvent
from fabledscryer.models.hosts import Host
from fabledscryer.models.metrics import PluginMetric
from fabledscryer.models.monitors import PingResult, PingStatus
from roundtable.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertEvent
from roundtable.models.hosts import Host
from roundtable.models.metrics import PluginMetric
from roundtable.models.monitors import PingResult, PingStatus
logger = logging.getLogger(__name__)
@@ -226,13 +226,13 @@ async def send_report(app) -> tuple[bool, str]:
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = smtp_cfg.get("username", "fabledscryer@localhost")
msg["From"] = smtp_cfg.get("username", "roundtable@localhost")
msg["To"] = ", ".join(smtp_cfg["recipients"])
msg.set_content(body)
try:
loop = asyncio.get_running_loop()
from fabledscryer.core.notifications import _smtp_send_sync
from roundtable.core.notifications import _smtp_send_sync
await loop.run_in_executor(None, _smtp_send_sync, smtp_cfg, msg)
logger.info("Weekly report sent to %s", smtp_cfg["recipients"])
return True, f"Report sent to {', '.join(smtp_cfg['recipients'])}."
@@ -243,7 +243,7 @@ async def send_report(app) -> tuple[bool, str]:
async def check_and_send(app) -> None:
"""Hourly scheduler hook: send if day/hour match and not sent recently."""
from fabledscryer.core.settings import get_setting, set_setting
from roundtable.core.settings import get_setting, set_setting
async with app.db_sessionmaker() as db:
enabled = await get_setting(db, "reports.enabled")
+4 -4
View File
@@ -1,15 +1,15 @@
# fabledscryer/core/settings.py
# roundtable/core/settings.py
"""DB-backed application settings.
Keys use dotted notation (e.g. "smtp.host").
Values are JSON-encoded in the DB.
Usage in create_app() (before event loop):
from fabledscryer.core.settings import load_settings_sync
from roundtable.core.settings import load_settings_sync
settings = load_settings_sync(db_url)
Usage at runtime (inside async handlers):
from fabledscryer.core.settings import get_setting, set_setting
from roundtable.core.settings import get_setting, set_setting
async with app.db_sessionmaker() as session:
value = await get_setting(session, "smtp.host")
await set_setting(session, "smtp.host", "mail.example.com")
@@ -24,7 +24,7 @@ from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from fabledscryer.models.settings import AppSetting
from roundtable.models.settings import AppSetting
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -1,4 +1,4 @@
# fabledscryer/core/time_range.py
# roundtable/core/time_range.py
"""Shared time-range utilities for scoping queries and sparkline data."""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
+1 -1
View File
@@ -1,4 +1,4 @@
# fabledscryer/core/widgets.py
# roundtable/core/widgets.py
#
# Central widget registry. Each entry describes one widget type that can be
# placed on a dashboard. Plugin widgets declare which plugin must be enabled.
+6 -6
View File
@@ -6,12 +6,12 @@ from datetime import datetime, timezone, timedelta
from urllib.parse import urlencode
from quart import Blueprint, render_template, current_app, abort, redirect, request, session
from sqlalchemy import select, func, update
from fabledscryer.auth.middleware import require_role, current_user_id
from fabledscryer.models.users import UserRole
from fabledscryer.models.hosts import Host
from fabledscryer.models.monitors import PingResult, DnsResult
from fabledscryer.models.dashboard import Dashboard, DashboardWidget, DashboardShareToken
from fabledscryer.core.widgets import get_available_widgets, WIDGET_REGISTRY
from roundtable.auth.middleware import require_role, current_user_id
from roundtable.models.users import UserRole
from roundtable.models.hosts import Host
from roundtable.models.monitors import PingResult, DnsResult
from roundtable.models.dashboard import Dashboard, DashboardWidget, DashboardShareToken
from roundtable.core.widgets import get_available_widgets, WIDGET_REGISTRY
dashboard_bp = Blueprint("dashboard", __name__)
+4 -4
View File
@@ -1,10 +1,10 @@
from __future__ import annotations
from quart import Blueprint, current_app, render_template
from sqlalchemy import select, func
from fabledscryer.auth.middleware import require_role
from fabledscryer.models.users import UserRole
from fabledscryer.models.hosts import Host
from fabledscryer.models.monitors import DnsResult
from roundtable.auth.middleware import require_role
from roundtable.models.users import UserRole
from roundtable.models.hosts import Host
from roundtable.models.monitors import DnsResult
dns_bp = Blueprint("dns", __name__, url_prefix="/dns")
+5 -5
View File
@@ -2,11 +2,11 @@ from __future__ import annotations
from datetime import datetime, timedelta, timezone
from quart import Blueprint, render_template, request, redirect, url_for, current_app, session
from sqlalchemy import and_, case, select, func
from fabledscryer.auth.middleware import require_role
from fabledscryer.core.audit import log_audit
from fabledscryer.models.hosts import Host, ProbeType
from fabledscryer.models.monitors import PingResult, DnsResult, PingStatus
from fabledscryer.models.users import UserRole
from roundtable.auth.middleware import require_role
from roundtable.core.audit import log_audit
from roundtable.models.hosts import Host, ProbeType
from roundtable.models.monitors import PingResult, DnsResult, PingStatus
from roundtable.models.users import UserRole
hosts_bp = Blueprint("hosts", __name__, url_prefix="/hosts")
+2 -2
View File
@@ -4,8 +4,8 @@ from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context
from fabledscryer.models.base import Base
import fabledscryer.models # noqa: F401 — registers all models in metadata
from roundtable.models.base import Base
import roundtable.models # noqa: F401 — registers all models in metadata
config = context.config
if config.config_file_name is not None:
+1 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from datetime import datetime, timezone
from sqlalchemy import DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledscryer.models.base import Base
from roundtable.models.base import Base
class AppSetting(Base):
+3 -3
View File
@@ -6,9 +6,9 @@ import socket
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from fabledscryer.core.alerts import record_metric
from fabledscryer.models.hosts import Host
from fabledscryer.models.monitors import DnsResult, DnsStatus
from roundtable.core.alerts import record_metric
from roundtable.models.hosts import Host
from roundtable.models.monitors import DnsResult, DnsStatus
logger = logging.getLogger(__name__)
+3 -3
View File
@@ -5,9 +5,9 @@ import time
from sqlalchemy.ext.asyncio import AsyncSession
from fabledscryer.core.alerts import record_metric
from fabledscryer.models.hosts import Host, ProbeType
from fabledscryer.models.monitors import PingResult, PingStatus
from roundtable.core.alerts import record_metric
from roundtable.models.hosts import Host, ProbeType
from roundtable.models.monitors import PingResult, PingStatus
logger = logging.getLogger(__name__)
+6 -6
View File
@@ -2,12 +2,12 @@ from __future__ import annotations
import logging
from quart import Blueprint, current_app, render_template, request, redirect, url_for
from sqlalchemy import select, func, case
from fabledscryer.auth.middleware import require_role
from fabledscryer.models.users import UserRole
from fabledscryer.models.hosts import Host
from fabledscryer.models.monitors import PingResult
from fabledscryer.core.settings import get_all_settings, set_setting, DEFAULTS
from fabledscryer.core.time_range import parse_range, DEFAULT_RANGE
from roundtable.auth.middleware import require_role
from roundtable.models.users import UserRole
from roundtable.models.hosts import Host
from roundtable.models.monitors import PingResult
from roundtable.core.settings import get_all_settings, set_setting, DEFAULTS
from roundtable.core.time_range import parse_range, DEFAULT_RANGE
ping_bp = Blueprint("ping", __name__, url_prefix="/ping")
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -1 +1 @@
# fabledscryer/settings/__init__.py
# roundtable/settings/__init__.py
+21 -21
View File
@@ -1,14 +1,14 @@
# fabledscryer/settings/routes.py
# roundtable/settings/routes.py
from __future__ import annotations
import json
import logging
from pathlib import Path
from quart import Blueprint, current_app, render_template, request, redirect, url_for, session
from fabledscryer.auth.middleware import require_role
from fabledscryer.core.audit import log_audit
from fabledscryer.models.users import UserRole
from fabledscryer.core.settings import (
from roundtable.auth.middleware import require_role
from roundtable.core.audit import log_audit
from roundtable.models.users import UserRole
from roundtable.core.settings import (
DEFAULTS, get_all_settings, set_setting,
to_smtp_cfg, to_webhook_cfg, to_ansible_cfg, to_plugins_cfg,
to_oidc_cfg, to_ldap_cfg,
@@ -232,12 +232,12 @@ async def ansible_sync_source(idx: int):
source = sources[idx]
if source.get("type") != "git":
return '<span style="color:var(--text-muted);font-size:0.8rem;">Not a git source</span>'
from fabledscryer.ansible.sources import git_pull
from fabledscryer.core.settings import to_ansible_cfg
from roundtable.ansible.sources import git_pull
from roundtable.core.settings import to_ansible_cfg
async with current_app.db_sessionmaker() as db:
settings = await get_all_settings(db)
ansible_cfg = to_ansible_cfg(settings)
from fabledscryer.ansible.sources import get_sources
from roundtable.ansible.sources import get_sources
resolved = next((s for s in get_sources(ansible_cfg) if s["name"] == source["name"]), None)
if resolved is None:
return '<span style="color:var(--red);font-size:0.8rem;">Could not resolve source path</span>'
@@ -295,7 +295,7 @@ async def save_auth_settings():
await log_audit(current_app, session.get("user_id"), session.get("username", ""),
"settings.saved", detail={"section": "auth"})
# Clear OIDC discovery cache so new settings take effect
from fabledscryer.auth.oidc import _discovery_cache
from roundtable.auth.oidc import _discovery_cache
_discovery_cache.clear()
return redirect(url_for("settings.auth_settings"))
@@ -327,7 +327,7 @@ async def save_reports():
@settings_bp.post("/reports/send-now/")
@require_role(UserRole.admin)
async def send_report_now():
from fabledscryer.core.reports import send_report
from roundtable.core.reports import send_report
async with current_app.db_sessionmaker() as db:
settings = await get_all_settings(db)
ok, message = await send_report(current_app._get_current_object())
@@ -442,7 +442,7 @@ async def plugin_detail(name: str):
if plugin is None:
return redirect(url_for("settings.plugins"))
_merge_plugin_config([plugin], to_plugins_cfg(settings))
from fabledscryer.core.plugin_manager import _LOADED_PLUGINS, get_plugin_failures
from roundtable.core.plugin_manager import _LOADED_PLUGINS, get_plugin_failures
return await render_template(
"settings/plugin_detail.html",
plugin=plugin,
@@ -471,7 +471,7 @@ async def save_plugin_detail(name: str):
await set_setting(db, f"plugin.{name}", plugin_cfg)
await _reload_app_config()
from fabledscryer.core.plugin_index import clear_catalog_cache
from roundtable.core.plugin_index import clear_catalog_cache
clear_catalog_cache()
new_enabled = plugin_cfg.get("enabled", False)
@@ -480,7 +480,7 @@ async def save_plugin_detail(name: str):
await log_audit(current_app, session.get("user_id"), session.get("username", ""),
action, entity_type="plugin", entity_id=name)
if new_enabled and not old_enabled:
from fabledscryer.core.plugin_manager import hot_reload_plugin
from roundtable.core.plugin_manager import hot_reload_plugin
hot_reload_plugin(current_app._get_current_object(), name)
if name == "ups":
@@ -505,7 +505,7 @@ async def plugin_repos_add():
async with current_app.db_sessionmaker() as db:
async with db.begin():
await _save_plugin_repos(db, repos)
from fabledscryer.core.plugin_index import clear_catalog_cache
from roundtable.core.plugin_index import clear_catalog_cache
clear_catalog_cache()
return await _plugin_repos_partial({"plugins.repositories": repos})
@@ -521,7 +521,7 @@ async def plugin_repos_remove(idx: int):
async with current_app.db_sessionmaker() as db:
async with db.begin():
await _save_plugin_repos(db, repos)
from fabledscryer.core.plugin_index import clear_catalog_cache
from roundtable.core.plugin_index import clear_catalog_cache
clear_catalog_cache()
return await _plugin_repos_partial({"plugins.repositories": repos})
@@ -541,7 +541,7 @@ async def plugin_repos_save(idx: int):
async with current_app.db_sessionmaker() as db:
async with db.begin():
await _save_plugin_repos(db, repos)
from fabledscryer.core.plugin_index import clear_catalog_cache
from roundtable.core.plugin_index import clear_catalog_cache
clear_catalog_cache()
return await _plugin_repos_partial({"plugins.repositories": repos})
@@ -595,8 +595,8 @@ async def plugins_catalog():
error="No plugin repositories configured.",
)
from fabledscryer.core.plugin_index import fetch_catalog
from fabledscryer.core.plugin_manager import _LOADED_PLUGINS
from roundtable.core.plugin_index import fetch_catalog
from roundtable.core.plugin_manager import _LOADED_PLUGINS
force = request.args.get("refresh") == "1"
try:
@@ -637,7 +637,7 @@ async def install_plugin(name: str):
message="No download URL provided.",
)
from fabledscryer.core.plugin_manager import download_and_install_plugin, hot_reload_plugin
from roundtable.core.plugin_manager import download_and_install_plugin, hot_reload_plugin
ok, msg = await download_and_install_plugin(
current_app._get_current_object(), name, download_url, checksum
@@ -671,7 +671,7 @@ async def install_plugin(name: str):
@require_role(UserRole.admin)
async def reload_plugin(name: str):
"""Hot-reload a plugin that was installed but not yet active."""
from fabledscryer.core.plugin_manager import hot_reload_plugin
from roundtable.core.plugin_manager import hot_reload_plugin
ok, msg = hot_reload_plugin(current_app._get_current_object(), name)
return await render_template(
"settings/_install_result.html",
@@ -687,7 +687,7 @@ async def reload_plugin(name: str):
async def restart_app_route():
"""Trigger an in-app restart (replaces the process via os.execv)."""
import asyncio
from fabledscryer.core.plugin_manager import restart_app
from roundtable.core.plugin_manager import restart_app
# Schedule the restart slightly after we return the response
async def _delayed_restart():
+1 -1
View File
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/ansible.html #}
{# roundtable/templates/settings/ansible.html #}
{% extends "base.html" %}
{% block title %}Settings — Ansible — Fabled Scryer{% endblock %}
{% block content %}
+5 -5
View File
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/auth.html #}
{# roundtable/templates/settings/auth.html #}
{% extends "base.html" %}
{% block title %}Settings — Auth — Fabled Scryer{% endblock %}
{% block content %}
@@ -25,7 +25,7 @@
<div class="form-group">
<label>Discovery URL</label>
<input type="text" name="oidc.discovery_url" value="{{ settings['oidc.discovery_url'] }}"
placeholder="https://auth.example.com/application/o/fabledscryer/.well-known/openid-configuration">
placeholder="https://auth.example.com/application/o/roundtable/.well-known/openid-configuration">
<p style="font-size:0.75rem;color:var(--text-muted);margin-top:0.2rem;">
Authentik: <code>https://&lt;host&gt;/application/o/&lt;slug&gt;/.well-known/openid-configuration</code>
</p>
@@ -72,13 +72,13 @@
<div class="form-group">
<label>Admin group name</label>
<input type="text" name="oidc.admin_group" value="{{ settings['oidc.admin_group'] }}"
placeholder="fabledscryer-admins">
placeholder="roundtable-admins">
<p style="font-size:0.75rem;color:var(--text-muted);margin-top:0.2rem;">Members → admin role</p>
</div>
<div class="form-group">
<label>Operator group name</label>
<input type="text" name="oidc.operator_group" value="{{ settings['oidc.operator_group'] }}"
placeholder="fabledscryer-operators">
placeholder="roundtable-operators">
<p style="font-size:0.75rem;color:var(--text-muted);margin-top:0.2rem;">Members → operator role; others → viewer</p>
</div>
</div>
@@ -119,7 +119,7 @@
<div class="form-group">
<label>Service account bind DN</label>
<input type="text" name="ldap.bind_dn" value="{{ settings['ldap.bind_dn'] }}"
placeholder="cn=svc-fabledscryer,ou=service,dc=example,dc=com">
placeholder="cn=svc-roundtable,ou=service,dc=example,dc=com">
</div>
<div class="form-group">
<label>Bind password <span style="color:var(--text-dim);font-size:0.8rem;">(blank = keep current)</span></label>
+1 -1
View File
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/general.html #}
{# roundtable/templates/settings/general.html #}
{% extends "base.html" %}
{% block title %}Settings — General — Fabled Scryer{% endblock %}
{% block content %}
+1 -1
View File
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/index.html #}
{# roundtable/templates/settings/index.html #}
{% extends "base.html" %}
{% block title %}Settings — Fabled Scryer{% endblock %}
{% block content %}
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/notifications.html #}
{# roundtable/templates/settings/notifications.html #}
{% extends "base.html" %}
{% block title %}Settings — Notifications — Fabled Scryer{% endblock %}
{% block content %}
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/plugin_detail.html #}
{# roundtable/templates/settings/plugin_detail.html #}
{% extends "base.html" %}
{% block title %}{{ plugin.get('name', plugin._dir) }} Settings — Fabled Scryer{% endblock %}
{% block content %}
+1 -1
View File
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/plugins.html #}
{# roundtable/templates/settings/plugins.html #}
{% extends "base.html" %}
{% block title %}Settings — Plugins — Fabled Scryer{% endblock %}
{% block content %}
+1 -1
View File
@@ -1,4 +1,4 @@
{# fabledscryer/templates/settings/reports.html #}
{# roundtable/templates/settings/reports.html #}
{% extends "base.html" %}
{% block title %}Settings — Reports — Fabled Scryer{% endblock %}
{% block content %}
+1 -1
View File
@@ -2,7 +2,7 @@ import pytest
import pytest_asyncio
from pathlib import Path
import textwrap
from fabledscryer.app import create_app
from roundtable.app import create_app
@pytest.fixture
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest
from fabledscryer.app import create_app
from roundtable.app import create_app
@pytest.mark.asyncio
+6 -6
View File
@@ -1,7 +1,7 @@
import pytest
import bcrypt
from unittest.mock import AsyncMock, patch
from fabledscryer.models.users import User, UserRole
from roundtable.models.users import User, UserRole
def make_user(role=UserRole.admin):
@@ -23,7 +23,7 @@ async def test_login_page_returns_200(client):
@pytest.mark.asyncio
async def test_login_redirects_to_setup_when_no_users(client, app):
with patch("fabledscryer.auth.routes.get_user_count", new=AsyncMock(return_value=0)):
with patch("roundtable.auth.routes.get_user_count", new=AsyncMock(return_value=0)):
response = await client.get("/login")
assert response.status_code == 302
assert "/setup" in response.headers["Location"]
@@ -32,8 +32,8 @@ async def test_login_redirects_to_setup_when_no_users(client, app):
@pytest.mark.asyncio
async def test_login_success_redirects_to_dashboard(client, app):
user = make_user()
with patch("fabledscryer.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \
patch("fabledscryer.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)):
with patch("roundtable.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \
patch("roundtable.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)):
response = await client.post("/login", form={"username": "admin", "password": "password"})
assert response.status_code == 302
assert "/" in response.headers["Location"]
@@ -42,8 +42,8 @@ async def test_login_success_redirects_to_dashboard(client, app):
@pytest.mark.asyncio
async def test_login_failure_returns_400(client, app):
user = make_user()
with patch("fabledscryer.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \
patch("fabledscryer.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)):
with patch("roundtable.auth.routes.get_user_count", new=AsyncMock(return_value=1)), \
patch("roundtable.auth.routes.get_user_by_username", new=AsyncMock(return_value=user)):
response = await client.post("/login", form={"username": "admin", "password": "wrong"})
assert response.status_code == 400
+3 -3
View File
@@ -1,18 +1,18 @@
import os
import textwrap
import pytest
from fabledscryer.config import load_bootstrap
from roundtable.config import load_bootstrap
def test_load_bootstrap_from_yaml(tmp_path):
cfg_file = tmp_path / "config.yaml"
cfg_file.write_text(textwrap.dedent("""\
database:
url: postgresql+asyncpg://user:pass@localhost/fabledscryer
url: postgresql+asyncpg://user:pass@localhost/roundtable
secret_key: test-secret
"""))
cfg = load_bootstrap(cfg_file)
assert cfg["database_url"] == "postgresql+asyncpg://user:pass@localhost/fabledscryer"
assert cfg["database_url"] == "postgresql+asyncpg://user:pass@localhost/roundtable"
assert cfg["secret_key"] == "test-secret"
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest
from fabledscryer.models.users import User, UserRole
from roundtable.models.users import User, UserRole
def test_user_model_fields():