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