Add read_config/ConfigError unit tests covering flat key-value parsing,
blank lines/comments, list fields, defaults, missing required keys, and
malformed lines. Also add tests/__init__.py to fix pytest namespace
collision with tests/plugins/ shadowing the root plugins/ package.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Records the mid-execution pivot: test harness uses testing=True which
mocks the DB, so plugin tests stick to pure-Python unit tests (agent +
metric expander). Server routes are verified manually.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Design for a push-based Python agent that collects CPU/memory/storage/
load/uptime from remote Linux hosts and POSTs to a new host_agent
plugin. Uses per-host bearer tokens, systemd install via curl|sh,
in-memory ring buffer with exponential backoff, and writes to the
existing PluginMetric bus plus a plugin-private registrations table.
No core schema changes — plugin stays strictly isolated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The UPS plugin's default driver was snmp-ups, so NUT was just translating
network SNMP back into its own protocol — no value over polling the UPS
directly through the existing snmp plugin. The managed-NUT entrypoint path
also broke container startup when config was incomplete.
Deletes nut_setup.py, the NUT block from entrypoint.sh, NUT packages from
the Dockerfile, the ups entry from the plugin catalog example, and the
ups wiring in alerts METRIC_CATALOG, widgets, settings routes, and the
rules form. The plugins/ups source tree (untracked) is also removed from
the working copy. Existing ups_* DB tables are orphaned but harmless in
dev; a drop migration isn't needed.
Follow-up: rebuild "on battery → Ansible shutdown" automation on top of
the alerts system as a new action type that runs a playbook.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sweeps README, docs/, example configs, code docstrings, report and
notification subject lines, plugin catalog URL default, and stray
comments in Dockerfile/entrypoint.sh/.gitignore. Adds a legacy note
to docs/core/configuration.md explaining the FABLEDSCRYER_* fallback.
docker-compose DB credentials intentionally left as fabledscryer to
preserve the existing pgdata volume.
Updates nav brand, page <title> blocks across all templates, and the
read-only share view footer. Removes duplicate Roundtable heading from
login card now that the nav wordmark reads correctly.
Service, container_name, image, and env var name flipped to roundtable.
DB credentials kept as fabledscryer to preserve the existing pgdata
volume — a separate migration can rename the role/db later if desired.
Reads ROUNDTABLE_<NAME> first, falls back to FABLEDSCRYER_<NAME> so
existing deployments keep working through the rebrand transition.
Covers DATABASE_URL, DATABASE__URL, SECRET_KEY, PLUGIN_DIR, CONFIG.
Keeps the image buildable after the package rename. Image name, container
name, compose service, and env vars still flip in PR 4.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Meta path finder redirects fabledscryer[.*] imports to roundtable[.*],
keeping unmodified plugin repos working during the rebrand transition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Plugin settings UI:
- Settings → Plugins now shows a clean list (status dot, name, enabled badge)
with a Settings button per plugin linking to its own detail page
- Per-plugin detail page at /settings/plugins/<name>/ shows enable toggle,
all config fields, load status, and failure notice if the plugin errored
- Config saving now scoped per-plugin via POST /settings/plugins/<name>/;
removes the monolithic save-all-plugins form
Plugin repositories:
- Replace single Plugin Index URL field with a managed list of repositories
(HTMX add/edit/remove per entry, same pattern as Ansible sources)
- plugin_index.py: fetch_catalog() now accepts a list of URLs, caches per-URL,
and merges results deduplicating by plugin name (first repo wins)
- Legacy plugins.index_url is auto-migrated to the new list format on first
access so existing installs don't lose their configured URL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plugin failures:
- Track load-time failures in _FAILED_PLUGINS dict with human-readable reasons
- Inject plugin_failures into all templates via context processor
- Show admin-only warning banner in base.html when any plugin fails to load
- Show inline failure notice per plugin card in Settings → Plugins
NUT managed mode:
- entrypoint.sh: make nut_setup.py failure non-fatal so the container starts
even if nut_ups_host isn't set yet (allows fixing config via UI)
- save_plugins: when nut_managed is enabled, auto-set nut_host=localhost so
the plugin connects to the managed NUT instance without extra manual config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace the raw JSON textarea in Settings → Ansible with a proper
per-source card UI: add, inline-edit, remove, and git-sync each
source independently via HTMX without a page reload
- Each source card shows type badge (git/local), URL or path, branch,
and pull interval; git sources get a Sync button that triggers
git pull and returns inline status
- Add source form uses a type-aware select that shows URL/branch/interval
fields for git or path field for local, collapsed in a details accordion
- Add new settings routes: sources/add, sources/<idx>/remove,
sources/<idx>/save, sources/<idx>/sync
- Update browse.html and run_detail.html to use CSS vars throughout
(removed hardcoded hex colors); link empty state to Settings page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace up/down arrow reorder buttons on dashboard edit page with
SortableJS drag handles; add POST /d/<id>/edit/reorder endpoint
accepting ordered widget ID array
- Add {% block extra_scripts %} hook to base.html for page-specific JS
- Add dynamic alert rule field loading via HTMX partial (_rule_fields)
so metric/threshold fields update when source is changed
- Add docs/plugins/index.yaml.example showing catalog index format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add managed NUT mode: configure NUT daemon from Settings → Plugins
(UPS plugin), writes /data/nut_managed.json read by entrypoint on
restart — no env vars or USB passthrough required
- entrypoint.sh: start NUT from /data/nut_managed.json or NUT_MANAGED=1
env var; drop to app user via gosu after NUT daemons start
- nut_setup.py: support --from-file <json> in addition to env vars
- Dockerfile: add nut, nut-client, gosu packages and entrypoint
- docker-compose.yml: document optional NUT_MANAGED env var block
- Fix plugin hot-reload migration failure: pass all plugin migration
dirs to Alembic so previously-stamped revisions from other plugins
remain resolvable (fixes UPS and any plugin with depends_on)
- Fix plugin list-type config (e.g. SNMP devices) rendering as broken
text input — now shows a read-only note to edit plugin.yaml instead
- Fix _sync_nut_managed_config: only write JSON when nut_ups_host is
non-empty, preventing NUT startup loop on container restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>