fix(plugins): complete steward rename across bundled plugins; clear lint debt
CI / lint (push) Successful in 3s
CI / unit (push) Successful in 7s
CI / integration (push) Successful in 2m8s

The fabledscryer->steward rename had only ever reached host_agent. The other
five bundled plugins (http, snmp, traefik, unifi, docker) still imported
`from fabledscryer.*` (package no longer exists) and read FABLEDSCRYER_* env
vars — so every one of them was broken at import since the original rebrand.
CI stayed green only because none are enabled by default and migrations don't
import plugin modules. Now that they version in-tree, complete the rename:
- fabledscryer.* -> steward.* imports across all five plugins
- FABLEDSCRYER_* -> STEWARD_* in plugin migration env.py files
- author/repository/homepage + user-facing 'Fabled Scryer' strings -> Steward
- snmp/scheduler.py: also drop dead `now`/datetime; record_metric from steward

Adds tests/test_no_legacy_names.py — fails if 'scryer'/'roundtable' ever
reappear in shipped code (the drift bit twice; this stops a third time).

Also clears pre-existing ruff lint debt (unused imports, semicolon statements,
mid-file import) surfaced by the new lint lane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 11:22:20 -04:00
parent d925709c77
commit af60ca446d
48 changed files with 142 additions and 124 deletions
+4 -4
View File
@@ -13,8 +13,8 @@ from alembic import context
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
from fabledscryer.models.base import Base from steward.models.base import Base
import fabledscryer.models # noqa: F401 import steward.models # noqa: F401
from plugins.docker.models import DockerContainer, DockerMetric # noqa: F401 from plugins.docker.models import DockerContainer, DockerMetric # noqa: F401
config = context.config config = context.config
@@ -26,14 +26,14 @@ target_metadata = Base.metadata
def _get_url() -> str: def _get_url() -> str:
import yaml import yaml
cfg_path = os.environ.get("FABLEDSCRYER_CONFIG", "config.yaml") cfg_path = os.environ.get("STEWARD_CONFIG", "config.yaml")
try: try:
with open(cfg_path) as f: with open(cfg_path) as f:
cfg = yaml.safe_load(f) or {} cfg = yaml.safe_load(f) or {}
url = cfg.get("database", {}).get("url", "") url = cfg.get("database", {}).get("url", "")
except FileNotFoundError: except FileNotFoundError:
url = "" url = ""
return os.environ.get("FABLEDSCRYER_DATABASE__URL", url) return os.environ.get("STEWARD_DATABASE__URL", url)
def run_migrations_offline() -> None: def run_migrations_offline() -> None:
+1 -1
View File
@@ -4,7 +4,7 @@ import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, Integer, String, Text from sqlalchemy import DateTime, Float, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from fabledscryer.models.base import Base from steward.models.base import Base
class DockerContainer(Base): class DockerContainer(Base):
+3 -3
View File
@@ -1,11 +1,11 @@
name: docker name: docker
version: "1.0.0" version: "1.0.0"
description: "Docker container status, resource usage, restart tracking via Docker socket" description: "Docker container status, resource usage, restart tracking via Docker socket"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/docker" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/docker"
tags: tags:
- containers - containers
- docker - docker
+3 -4
View File
@@ -1,13 +1,12 @@
# plugins/docker/routes.py # plugins/docker/routes.py
from __future__ import annotations from __future__ import annotations
import json import json
from datetime import datetime, timezone
from quart import Blueprint, current_app, render_template, request from quart import Blueprint, current_app, render_template, request
from sqlalchemy import Integer, cast, func, select from sqlalchemy import Integer, cast, func, select
from fabledscryer.auth.middleware import require_role from steward.auth.middleware import require_role
from fabledscryer.models.users import UserRole from steward.models.users import UserRole
from fabledscryer.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds from steward.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds
from .models import DockerContainer, DockerMetric from .models import DockerContainer, DockerMetric
docker_bp = Blueprint("docker", __name__, template_folder="templates") docker_bp = Blueprint("docker", __name__, template_folder="templates")
+2 -2
View File
@@ -4,8 +4,8 @@ import json
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from fabledscryer.core.scheduler import ScheduledTask from steward.core.scheduler import ScheduledTask
from fabledscryer.core.alerts import record_metric from steward.core.alerts import record_metric
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
-1
View File
@@ -106,7 +106,6 @@ async def scrape_docker(socket_path: str, include_stopped: bool) -> list[dict]:
) from exc ) from exc
results = [] results = []
now = datetime.now(timezone.utc)
for container, stats in pairs: for container, stats in pairs:
names = container.get("Names") or [] names = container.get("Names") or []
name = names[0].lstrip("/") if names else _short_id(container.get("Id", "")) name = names[0].lstrip("/") if names else _short_id(container.get("Id", ""))
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Docker — Fabled Scryer{% endblock %} {% block title %}Docker — Steward{% endblock %}
{% block content %} {% block content %}
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem;flex-wrap:wrap;"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem;flex-wrap:wrap;">
<h1 class="page-title" style="margin-bottom:0;">Docker</h1> <h1 class="page-title" style="margin-bottom:0;">Docker</h1>
-1
View File
@@ -2,7 +2,6 @@
"""Core HTTP check logic — runs a single monitor check and returns a result dict.""" """Core HTTP check logic — runs a single monitor check and returns a result dict."""
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import json
import logging import logging
import ssl import ssl
import time import time
+4 -4
View File
@@ -13,8 +13,8 @@ from alembic import context
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
from fabledscryer.models.base import Base from steward.models.base import Base
import fabledscryer.models # noqa: F401 import steward.models # noqa: F401
from plugins.http.models import HttpMonitor, HttpResult # noqa: F401 from plugins.http.models import HttpMonitor, HttpResult # noqa: F401
config = context.config config = context.config
@@ -26,14 +26,14 @@ target_metadata = Base.metadata
def _get_url() -> str: def _get_url() -> str:
import yaml import yaml
cfg_path = os.environ.get("FABLEDSCRYER_CONFIG", "config.yaml") cfg_path = os.environ.get("STEWARD_CONFIG", "config.yaml")
try: try:
with open(cfg_path) as f: with open(cfg_path) as f:
cfg = yaml.safe_load(f) or {} cfg = yaml.safe_load(f) or {}
url = cfg.get("database", {}).get("url", "") url = cfg.get("database", {}).get("url", "")
except FileNotFoundError: except FileNotFoundError:
url = "" url = ""
return os.environ.get("FABLEDSCRYER_DATABASE__URL", url) return os.environ.get("STEWARD_DATABASE__URL", url)
def run_migrations_offline() -> None: def run_migrations_offline() -> None:
+1 -1
View File
@@ -4,7 +4,7 @@ import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlalchemy import Boolean, DateTime, Float, Integer, String, Text from sqlalchemy import Boolean, DateTime, Float, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from fabledscryer.models.base import Base from steward.models.base import Base
class HttpMonitor(Base): class HttpMonitor(Base):
+3 -3
View File
@@ -1,11 +1,11 @@
name: http name: http
version: "1.0.0" version: "1.0.0"
description: "Synthetic HTTP endpoint monitoring — status code, response time, content match, TLS expiry" description: "Synthetic HTTP endpoint monitoring — status code, response time, content match, TLS expiry"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/http" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/http"
tags: tags:
- monitoring - monitoring
- http - http
+3 -4
View File
@@ -1,14 +1,13 @@
# plugins/http/routes.py # plugins/http/routes.py
from __future__ import annotations from __future__ import annotations
import json
import uuid import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
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 Integer, cast, func, select from sqlalchemy import Integer, cast, func, select
from fabledscryer.auth.middleware import require_role from steward.auth.middleware import require_role
from fabledscryer.models.users import UserRole from steward.models.users import UserRole
from fabledscryer.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds from steward.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds
from .models import HttpMonitor, HttpResult from .models import HttpMonitor, HttpResult
http_bp = Blueprint("http", __name__, template_folder="templates") http_bp = Blueprint("http", __name__, template_folder="templates")
+3 -3
View File
@@ -3,10 +3,10 @@ from __future__ import annotations
import asyncio import asyncio
import json import json
import logging import logging
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone
from fabledscryer.core.scheduler import ScheduledTask from steward.core.scheduler import ScheduledTask
from fabledscryer.core.alerts import record_metric from steward.core.alerts import record_metric
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+1 -1
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}HTTP Monitors — Fabled Scryer{% endblock %} {% block title %}HTTP Monitors — Steward{% endblock %}
{% block content %} {% block content %}
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem;flex-wrap:wrap;"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem;flex-wrap:wrap;">
<h1 class="page-title" style="margin-bottom:0;">HTTP Monitors</h1> <h1 class="page-title" style="margin-bottom:0;">HTTP Monitors</h1>
+22 -22
View File
@@ -1,6 +1,6 @@
# fabledscryer-plugins / index.yaml # steward-plugins / index.yaml
# #
# Plugin catalog for Fabled Scryer. # Plugin catalog for Steward.
# Fetched by the app at: Settings → Plugins → Plugin Catalog # Fetched by the app at: Settings → Plugins → Plugin Catalog
# #
# After updating an entry, commit and push — changes are live within 5 minutes # After updating an entry, commit and push — changes are live within 5 minutes
@@ -16,12 +16,12 @@ plugins:
- name: http - name: http
version: "1.0.0" version: "1.0.0"
description: "Synthetic HTTP endpoint monitoring — status code, response time, content match, TLS expiry" description: "Synthetic HTTP endpoint monitoring — status code, response time, content match, TLS expiry"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/http" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/http"
download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/http-v1.0.0/http.zip" download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/http-v1.0.0/http.zip"
checksum_sha256: "" checksum_sha256: ""
tags: tags:
- monitoring - monitoring
@@ -32,12 +32,12 @@ plugins:
- name: docker - name: docker
version: "1.0.0" version: "1.0.0"
description: "Docker container status, resource usage, and restart tracking via Docker socket" description: "Docker container status, resource usage, and restart tracking via Docker socket"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/docker" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/docker"
download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/docker-v1.0.0/docker.zip" download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/docker-v1.0.0/docker.zip"
checksum_sha256: "" checksum_sha256: ""
tags: tags:
- containers - containers
@@ -47,12 +47,12 @@ plugins:
- name: traefik - name: traefik
version: "1.0.0" version: "1.0.0"
description: "Traefik reverse proxy metrics and access log integration" description: "Traefik reverse proxy metrics and access log integration"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/traefik" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/traefik"
download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/traefik-v1.0.0/traefik.zip" download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/traefik-v1.0.0/traefik.zip"
checksum_sha256: "" checksum_sha256: ""
tags: tags:
- proxy - proxy
@@ -62,12 +62,12 @@ plugins:
- name: unifi - name: unifi
version: "1.0.0" version: "1.0.0"
description: "UniFi Network controller integration — WAN health, devices, clients, DPI" description: "UniFi Network controller integration — WAN health, devices, clients, DPI"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/unifi" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/unifi"
download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/unifi-v1.0.0/unifi.zip" download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/unifi-v1.0.0/unifi.zip"
checksum_sha256: "" checksum_sha256: ""
tags: tags:
- network - network
@@ -77,12 +77,12 @@ plugins:
- name: ups - name: ups
version: "1.0.0" version: "1.0.0"
description: "UPS monitoring via NUT (Network UPS Tools) with Ansible shutdown automation" description: "UPS monitoring via NUT (Network UPS Tools) with Ansible shutdown automation"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/ups" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/ups"
download_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/releases/download/ups-v1.0.0/ups.zip" download_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins/releases/download/ups-v1.0.0/ups.zip"
checksum_sha256: "" checksum_sha256: ""
tags: tags:
- ups - ups
+3 -3
View File
@@ -2,11 +2,11 @@
name: snmp name: snmp
version: "1.0.0" version: "1.0.0"
description: "SNMP polling for network devices — switches, routers, printers, UPSes, etc." description: "SNMP polling for network devices — switches, routers, printers, UPSes, etc."
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/snmp" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/snmp"
tags: tags:
- snmp - snmp
- network - network
+2 -2
View File
@@ -3,7 +3,7 @@
Synchronous SNMP GET helper, run via executor. Synchronous SNMP GET helper, run via executor.
Requires pysnmp-lextudio (maintained pysnmp fork): Requires pysnmp-lextudio (maintained pysnmp fork):
pip install 'fabledscryer[snmp]' pip install 'steward[snmp]'
If pysnmp is not installed, poll_device() returns an empty dict and logs a warning. If pysnmp is not installed, poll_device() returns an empty dict and logs a warning.
""" """
@@ -40,7 +40,7 @@ def poll_device_sync(
""" """
if not _pysnmp_available(): if not _pysnmp_available():
logger.warning("pysnmp not installed — SNMP polling disabled. " logger.warning("pysnmp not installed — SNMP polling disabled. "
"Install with: pip install 'fabledscryer[snmp]'") "Install with: pip install 'steward[snmp]'")
return {} return {}
from pysnmp.hlapi import ( from pysnmp.hlapi import (
+3 -3
View File
@@ -5,9 +5,9 @@ from datetime import datetime, timedelta, timezone
from quart import Blueprint, current_app, render_template, request from quart import Blueprint, current_app, render_template, request
from sqlalchemy import func, select from sqlalchemy import func, select
from fabledscryer.auth.middleware import require_role from steward.auth.middleware import require_role
from fabledscryer.models.users import UserRole from steward.models.users import UserRole
from fabledscryer.models.metrics import PluginMetric from steward.models.metrics import PluginMetric
snmp_bp = Blueprint("snmp", __name__, template_folder="templates") snmp_bp = Blueprint("snmp", __name__, template_folder="templates")
+2 -4
View File
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from quart import Quart from quart import Quart
from fabledscryer.core.scheduler import ScheduledTask from steward.core.scheduler import ScheduledTask
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -27,16 +27,14 @@ def make_poll_task(app: "Quart") -> ScheduledTask:
async def _do_poll(app: "Quart") -> None: async def _do_poll(app: "Quart") -> None:
from datetime import datetime, timezone
from .poller import poll_device_sync from .poller import poll_device_sync
from fabledscryer.core.alerts import record_metric from steward.core.alerts import record_metric
devices: list[dict] = app.config["PLUGINS"]["snmp"].get("devices", []) devices: list[dict] = app.config["PLUGINS"]["snmp"].get("devices", [])
if not devices: if not devices:
return return
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
now = datetime.now(timezone.utc)
async with app.db_sessionmaker() as session: async with app.db_sessionmaker() as session:
async with session.begin(): async with session.begin():
+1 -1
View File
@@ -1,6 +1,6 @@
{# plugins/snmp/templates/snmp/device.html #} {# plugins/snmp/templates/snmp/device.html #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}SNMP — {{ device_name }} — Fabled Scryer{% endblock %} {% block title %}SNMP — {{ device_name }} — Steward{% endblock %}
{% block content %} {% block content %}
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;flex-wrap:wrap;"> <div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;flex-wrap:wrap;">
<a href="/plugins/snmp/" style="color:var(--text-muted);font-size:0.85rem;">&#8592; SNMP</a> <a href="/plugins/snmp/" style="color:var(--text-muted);font-size:0.85rem;">&#8592; SNMP</a>
+1 -1
View File
@@ -1,6 +1,6 @@
{# plugins/snmp/templates/snmp/index.html #} {# plugins/snmp/templates/snmp/index.html #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}SNMP — Fabled Scryer{% endblock %} {% block title %}SNMP — Steward{% endblock %}
{% block content %} {% block content %}
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;"> <div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;">
<h1 class="page-title" style="margin:0;">SNMP Devices</h1> <h1 class="page-title" style="margin:0;">SNMP Devices</h1>
+5 -5
View File
@@ -15,11 +15,11 @@ 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
# Make fabledscryer importable # Make steward importable
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
from fabledscryer.models.base import Base from steward.models.base import Base
import fabledscryer.models # noqa: F401 — core models import steward.models # noqa: F401 — core models
from plugins.traefik.models import TraefikMetric # noqa: F401 — plugin model from plugins.traefik.models import TraefikMetric # noqa: F401 — plugin model
config = context.config config = context.config
@@ -31,14 +31,14 @@ target_metadata = Base.metadata
def _get_url() -> str: def _get_url() -> str:
import yaml import yaml
cfg_path = os.environ.get("FABLEDSCRYER_CONFIG", "config.yaml") cfg_path = os.environ.get("STEWARD_CONFIG", "config.yaml")
try: try:
with open(cfg_path) as f: with open(cfg_path) as f:
cfg = yaml.safe_load(f) or {} cfg = yaml.safe_load(f) or {}
url = cfg.get("database", {}).get("url", "") url = cfg.get("database", {}).get("url", "")
except FileNotFoundError: except FileNotFoundError:
url = "" url = ""
return os.environ.get("FABLEDSCRYER_DATABASE__URL", url) return os.environ.get("STEWARD_DATABASE__URL", url)
def run_migrations_offline() -> None: def run_migrations_offline() -> None:
+1 -1
View File
@@ -4,7 +4,7 @@ import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlalchemy import DateTime, Float, Integer, String, Text from sqlalchemy import DateTime, Float, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from fabledscryer.models.base import Base from steward.models.base import Base
class TraefikMetric(Base): class TraefikMetric(Base):
+3 -3
View File
@@ -2,11 +2,11 @@
name: traefik name: traefik
version: "1.0.0" version: "1.0.0"
description: "Traefik reverse proxy metrics and access log integration" description: "Traefik reverse proxy metrics and access log integration"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/traefik" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/traefik"
tags: tags:
- proxy - proxy
- metrics - metrics
+3 -3
View File
@@ -6,9 +6,9 @@ from quart import Blueprint, current_app, render_template, request
from sqlalchemy import Integer, cast, func, select from sqlalchemy import Integer, cast, func, select
import json import json
from fabledscryer.auth.middleware import require_role from steward.auth.middleware import require_role
from fabledscryer.models.users import UserRole from steward.models.users import UserRole
from fabledscryer.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds, subsample from steward.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds, subsample
from .models import TraefikAccessSummary, TraefikCert, TraefikGlobalStat, TraefikMetric from .models import TraefikAccessSummary, TraefikCert, TraefikGlobalStat, TraefikMetric
traefik_bp = Blueprint("traefik", __name__, template_folder="templates") traefik_bp = Blueprint("traefik", __name__, template_folder="templates")
+2 -3
View File
@@ -5,7 +5,7 @@ import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from datetime import datetime from datetime import datetime
from fabledscryer.core.scheduler import ScheduledTask from steward.core.scheduler import ScheduledTask
if TYPE_CHECKING: if TYPE_CHECKING:
from quart import Quart from quart import Quart
@@ -41,7 +41,6 @@ async def _do_scrape(app: "Quart") -> None:
global _prev_metrics, _prev_time global _prev_metrics, _prev_time
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlalchemy import select
from .models import TraefikCert, TraefikGlobalStat, TraefikMetric from .models import TraefikCert, TraefikGlobalStat, TraefikMetric
from .scraper import ( from .scraper import (
compute_global_stats, compute_global_stats,
@@ -49,7 +48,7 @@ async def _do_scrape(app: "Quart") -> None:
extract_certs, extract_certs,
fetch_metrics, fetch_metrics,
) )
from fabledscryer.core.alerts import record_metric from steward.core.alerts import record_metric
metrics_url: str = app.config["PLUGINS"]["traefik"]["metrics_url"] metrics_url: str = app.config["PLUGINS"]["traefik"]["metrics_url"]
+1 -1
View File
@@ -1,6 +1,6 @@
{# plugins/traefik/templates/traefik/index.html #} {# plugins/traefik/templates/traefik/index.html #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Traefik — Fabled Scryer{% endblock %} {% block title %}Traefik — Steward{% endblock %}
{% block content %} {% block content %}
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.75rem;margin-bottom:1.5rem;"> <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.75rem;margin-bottom:1.5rem;">
<div style="display:flex;align-items:baseline;gap:1rem;"> <div style="display:flex;align-items:baseline;gap:1rem;">
+4 -4
View File
@@ -15,8 +15,8 @@ from alembic import context
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
from fabledscryer.models.base import Base from steward.models.base import Base
import fabledscryer.models # noqa: F401 import steward.models # noqa: F401
from plugins.unifi.models import UnifiWanStat, UnifiClientSnapshot, UnifiDevice # noqa: F401 from plugins.unifi.models import UnifiWanStat, UnifiClientSnapshot, UnifiDevice # noqa: F401
config = context.config config = context.config
@@ -28,14 +28,14 @@ target_metadata = Base.metadata
def _get_url() -> str: def _get_url() -> str:
import yaml import yaml
cfg_path = os.environ.get("FABLEDSCRYER_CONFIG", "config.yaml") cfg_path = os.environ.get("STEWARD_CONFIG", "config.yaml")
try: try:
with open(cfg_path) as f: with open(cfg_path) as f:
cfg = yaml.safe_load(f) or {} cfg = yaml.safe_load(f) or {}
url = cfg.get("database", {}).get("url", "") url = cfg.get("database", {}).get("url", "")
except FileNotFoundError: except FileNotFoundError:
url = "" url = ""
return os.environ.get("FABLEDSCRYER_DATABASE__URL", url) return os.environ.get("STEWARD_DATABASE__URL", url)
def run_migrations_offline() -> None: def run_migrations_offline() -> None:
+1 -1
View File
@@ -4,7 +4,7 @@ import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlalchemy import Boolean, DateTime, Float, Integer, String, Text from sqlalchemy import Boolean, DateTime, Float, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from fabledscryer.models.base import Base from steward.models.base import Base
# ── Time-series (one row per scrape) ────────────────────────────────────────── # ── Time-series (one row per scrape) ──────────────────────────────────────────
+3 -3
View File
@@ -1,11 +1,11 @@
name: unifi name: unifi
version: "1.0.0" version: "1.0.0"
description: "UniFi Network controller integration — WAN health, devices, clients, DPI" description: "UniFi Network controller integration — WAN health, devices, clients, DPI"
author: "FabledScryer" author: "Steward"
license: "MIT" license: "MIT"
min_app_version: "0.1.0" min_app_version: "0.1.0"
repository_url: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins" repository_url: "https://git.fabledsword.com/bvandeusen/Steward-plugins"
homepage: "https://git.fabledsword.com/bvandeusen/FabledScryer-plugins/src/branch/main/unifi" homepage: "https://git.fabledsword.com/bvandeusen/Steward-plugins/src/branch/main/unifi"
tags: tags:
- network - network
- unifi - unifi
+3 -4
View File
@@ -1,13 +1,12 @@
# plugins/unifi/routes.py # plugins/unifi/routes.py
from __future__ import annotations from __future__ import annotations
import json import json
from datetime import datetime, timezone
from quart import Blueprint, current_app, render_template, request from quart import Blueprint, current_app, render_template, request
from sqlalchemy import Integer, cast, func, select from sqlalchemy import Integer, cast, func, select
from fabledscryer.auth.middleware import require_role from steward.auth.middleware import require_role
from fabledscryer.models.users import UserRole from steward.models.users import UserRole
from fabledscryer.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds, subsample from steward.core.time_range import parse_range, DEFAULT_RANGE, bucket_seconds
from .models import ( from .models import (
UnifiAlarm, UnifiClientSnapshot, UnifiDevice, UnifiDpiSnapshot, UnifiAlarm, UnifiClientSnapshot, UnifiDevice, UnifiDpiSnapshot,
UnifiEvent, UnifiFwRule, UnifiKnownClient, UnifiNetwork, UnifiEvent, UnifiFwRule, UnifiKnownClient, UnifiNetwork,
+2 -2
View File
@@ -5,7 +5,7 @@ import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from fabledscryer.core.scheduler import ScheduledTask from steward.core.scheduler import ScheduledTask
if TYPE_CHECKING: if TYPE_CHECKING:
from quart import Quart from quart import Quart
@@ -35,7 +35,7 @@ async def _do_poll(app: "Quart") -> None:
from .client import UnifiClient from .client import UnifiClient
from .models import UnifiClientSnapshot, UnifiDevice, UnifiWanStat from .models import UnifiClientSnapshot, UnifiDevice, UnifiWanStat
from fabledscryer.core.alerts import record_metric from steward.core.alerts import record_metric
cfg = app.config["PLUGINS"]["unifi"] cfg = app.config["PLUGINS"]["unifi"]
+1 -1
View File
@@ -1,6 +1,6 @@
{# plugins/unifi/templates/unifi/index.html #} {# plugins/unifi/templates/unifi/index.html #}
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}UniFi — Fabled Scryer{% endblock %} {% block title %}UniFi — Steward{% endblock %}
{% block content %} {% block content %}
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.75rem;margin-bottom:1.25rem;"> <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.75rem;margin-bottom:1.25rem;">
<div style="display:flex;align-items:baseline;gap:1rem;"> <div style="display:flex;align-items:baseline;gap:1rem;">
+3 -4
View File
@@ -3,13 +3,12 @@ from __future__ import annotations
import asyncio import asyncio
import uuid import uuid
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path
from quart import ( from quart import (
Blueprint, Response, current_app, redirect, render_template, Blueprint, Response, current_app, render_template,
request, session, url_for, request, session,
) )
from sqlalchemy import select, update from sqlalchemy import select
from steward.ansible import executor, sources as src_module from steward.ansible import executor, sources as src_module
from steward.auth.middleware import require_role from steward.auth.middleware import require_role
-1
View File
@@ -8,7 +8,6 @@ we trust the HTTPS connection to the discovery-documented token/userinfo endpoin
""" """
from __future__ import annotations from __future__ import annotations
import base64 import base64
import hashlib
import json import json
import logging import logging
import os import os
-1
View File
@@ -1,7 +1,6 @@
# steward/core/plugin_manager.py # steward/core/plugin_manager.py
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
import importlib
import logging import logging
import os import os
import sys import sys
-2
View File
@@ -6,8 +6,6 @@ Called by the scheduler (hourly check) or triggered manually from Settings.
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import logging import logging
import smtplib
import ssl
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from email.message import EmailMessage from email.message import EmailMessage
+2 -1
View File
@@ -15,7 +15,8 @@ target_metadata = Base.metadata
def get_url() -> str: def get_url() -> str:
import os, yaml import os
import yaml
def _env(suffix: str) -> str | None: def _env(suffix: str) -> str | None:
return os.environ.get(f"STEWARD_{suffix}") return os.environ.get(f"STEWARD_{suffix}")
+1 -2
View File
@@ -1,6 +1,5 @@
# steward/settings/routes.py # steward/settings/routes.py
from __future__ import annotations from __future__ import annotations
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
@@ -9,7 +8,7 @@ from steward.auth.middleware import require_role
from steward.core.audit import log_audit from steward.core.audit import log_audit
from steward.models.users import UserRole from steward.models.users import UserRole
from steward.core.settings import ( from steward.core.settings import (
DEFAULTS, get_all_settings, set_setting, 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,
) )
+2 -2
View File
@@ -144,12 +144,12 @@
<div class="form-group"> <div class="form-group">
<label>Admin group DN</label> <label>Admin group DN</label>
<input type="text" name="ldap.admin_group_dn" value="{{ settings['ldap.admin_group_dn'] }}" <input type="text" name="ldap.admin_group_dn" value="{{ settings['ldap.admin_group_dn'] }}"
placeholder="cn=scryer-admins,ou=groups,dc=example,dc=com"> placeholder="cn=steward-admins,ou=groups,dc=example,dc=com">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Operator group DN</label> <label>Operator group DN</label>
<input type="text" name="ldap.operator_group_dn" value="{{ settings['ldap.operator_group_dn'] }}" <input type="text" name="ldap.operator_group_dn" value="{{ settings['ldap.operator_group_dn'] }}"
placeholder="cn=scryer-operators,ou=groups,dc=example,dc=com"> placeholder="cn=steward-operators,ou=groups,dc=example,dc=com">
</div> </div>
</div> </div>
+1 -1
View File
@@ -81,7 +81,7 @@
<div style="margin-bottom:2rem;max-width:720px;"> <div style="margin-bottom:2rem;max-width:720px;">
<div class="section-title" style="margin-bottom:0.75rem;">Plugin Repositories</div> <div class="section-title" style="margin-bottom:0.75rem;">Plugin Repositories</div>
<p style="color:var(--text-muted);font-size:0.84rem;margin-bottom:0.75rem;"> <p style="color:var(--text-muted);font-size:0.84rem;margin-bottom:0.75rem;">
Repositories are remote <code>index.yaml</code> files that Scryer checks for available Repositories are remote <code>index.yaml</code> files that Steward checks for available
and updated plugins. Add repos from other developers to expand the plugin catalog. and updated plugins. Add repos from other developers to expand the plugin catalog.
</p> </p>
{% include "settings/_plugin_repos.html" %} {% include "settings/_plugin_repos.html" %}
-1
View File
@@ -1,6 +1,5 @@
import pytest import pytest
import pytest_asyncio import pytest_asyncio
from pathlib import Path
import textwrap import textwrap
from steward.app import create_app from steward.app import create_app
@@ -1,3 +1,4 @@
import urllib.error
from unittest.mock import patch from unittest.mock import patch
from plugins.host_agent import agent as a from plugins.host_agent import agent as a
@@ -55,10 +56,6 @@ def test_build_payload_wraps_samples():
assert payload["samples"][0]["cpu_pct"] == 5.0 assert payload["samples"][0]["cpu_pct"] == 5.0
import urllib.error
from unittest.mock import MagicMock, patch
def test_post_payload_success(): def test_post_payload_success():
from plugins.host_agent import agent as a from plugins.host_agent import agent as a
captured = {} captured = {}
@@ -3,7 +3,9 @@ from plugins.host_agent.agent import RingBuffer
def test_ring_buffer_preserves_order(): def test_ring_buffer_preserves_order():
rb = RingBuffer(maxlen=3) rb = RingBuffer(maxlen=3)
rb.push(1); rb.push(2); rb.push(3) rb.push(1)
rb.push(2)
rb.push(3)
assert list(rb.drain()) == [1, 2, 3] assert list(rb.drain()) == [1, 2, 3]
assert len(rb) == 0 assert len(rb) == 0
@@ -17,7 +19,8 @@ def test_ring_buffer_drops_oldest_when_full():
def test_ring_buffer_drain_clears_and_is_atomic(): def test_ring_buffer_drain_clears_and_is_atomic():
rb = RingBuffer(maxlen=5) rb = RingBuffer(maxlen=5)
rb.push("a"); rb.push("b") rb.push("a")
rb.push("b")
out = list(rb.drain()) out = list(rb.drain())
rb.push("c") rb.push("c")
assert out == ["a", "b"] assert out == ["a", "b"]
@@ -2,7 +2,6 @@
import subprocess import subprocess
from pathlib import Path from pathlib import Path
import pytest
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
from plugins.host_agent.routes import _agent_version, AGENT_SOURCE_PATH from plugins.host_agent.routes import _agent_version, AGENT_SOURCE_PATH
-1
View File
@@ -1,4 +1,3 @@
import os
import textwrap import textwrap
import pytest import pytest
from steward.config import load_bootstrap from steward.config import load_bootstrap
-1
View File
@@ -1,4 +1,3 @@
import pytest
from steward.models.users import User, UserRole from steward.models.users import User, UserRole
+35
View File
@@ -0,0 +1,35 @@
"""Regression guard: shipped code must not reference old project names.
The FabledScryer -> Roundtable -> Steward renames repeatedly left stale package
imports behind in the (then separate) plugins (e.g. `from fabledscryer.models...`),
silently breaking every non-host_agent plugin. Now that plugins are folded in-tree
and version with core, this test fails loudly if either legacy name reappears in
the shipped Python / templates / plugin manifests.
"""
from __future__ import annotations
from pathlib import Path
import pytest
_ROOT = Path(__file__).resolve().parent.parent
_TREES = [_ROOT / "steward", _ROOT / "plugins"]
_EXTENSIONS = {".py", ".yaml", ".yml", ".html", ".j2"}
_FORBIDDEN = ("scryer", "roundtable") # case-insensitive; old project names
def _shipped_files():
for tree in _TREES:
for path in tree.rglob("*"):
if path.suffix in _EXTENSIONS and "__pycache__" not in path.parts:
yield path
@pytest.mark.parametrize("name", _FORBIDDEN)
def test_no_legacy_project_name(name):
offenders = [
str(p.relative_to(_ROOT))
for p in _shipped_files()
if name in p.read_text(encoding="utf-8", errors="ignore").lower()
]
assert not offenders, f"legacy name {name!r} found in shipped code: {offenders}"