refactor: update imports fabledscryer → roundtable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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,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,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,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.).
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,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,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,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__)
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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__)
|
||||||
|
|
||||||
|
|||||||
@@ -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__)
|
||||||
|
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
# fabledscryer/settings/__init__.py
|
# roundtable/settings/__init__.py
|
||||||
|
|||||||
@@ -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,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 %}
|
||||||
|
|||||||
@@ -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://<host>/application/o/<slug>/.well-known/openid-configuration</code>
|
Authentik: <code>https://<host>/application/o/<slug>/.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,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,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,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,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
@@ -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
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,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():
|
||||||
|
|||||||
Reference in New Issue
Block a user