Discover and share modules for DOCSight.
DOCSight can install catalog modules from Settings > Extensions > Community Modules. The catalog is defined in registry.json.
| Module | Type | What it adds | Requirements |
|---|---|---|---|
| VF Kabel Deutschland Community Thresholds | analysis |
Regional signal thresholds based on community recommendations for Vodafone/Kabel Deutschland cable connections | DOCSight 2026.2+ |
| UDM WAN-Monitor | integration |
WAN1/WAN2 status collection for Ubiquiti UDM Pro/SE setups, with DOCSight events and dashboard surfaces | DOCSight 2026.2+, UDM access on the local network |
The catalog is curated, but modules can be maintained either in this repository or in an external contributor repository. See Submitting a Module for the registry format and review checklist.
- Copy the
TEMPLATE/directory - Rename the directory to your module name
- Edit
manifest.jsonwith your module's details - Add your code
- Test locally
- Submit to the registry
A DOCSight module is a directory with a manifest.json at its root:
my-module/
├── manifest.json # Required — module metadata
├── __init__.py # Required — Python package marker
├── routes.py # Optional — Flask Blueprint (API endpoints / pages)
├── collector.py # Optional — data collector class
├── storage.py # Optional — SQLite storage helper
├── i18n/ # Optional — translation files
│ ├── en.json
│ ├── de.json
│ ├── es.json
│ └── fr.json
├── static/ # Optional — CSS/JS assets
│ ├── style.css # Auto-loaded if present
│ └── main.js # Auto-loaded if present
└── templates/ # Optional — Jinja2 templates
└── my_settings.html # Settings panel template
Every module needs a manifest.json:
{
"id": "community.mymodule",
"name": "My Module",
"description": "What this module does in one sentence",
"version": "1.0.0",
"author": "your-github-username",
"minAppVersion": "2026.2",
"type": "integration",
"contributes": {
"routes": "routes.py",
"i18n": "i18n/"
}
}| Field | Type | Description |
|---|---|---|
id |
string | Unique identifier. Must match ^[a-z][a-z0-9_.]+$. Use community. or your username as prefix. |
name |
string | Human-readable name shown in the UI |
description |
string | One-line description |
version |
string | Semantic version (major.minor.patch) |
author |
string | Your GitHub username |
minAppVersion |
string | Minimum DOCSight version (currently 2026.2) |
type |
string | One of: driver, integration, analysis, theme |
contributes |
object | What this module provides (see Contribution Types) |
| Field | Type | Description |
|---|---|---|
homepage |
string | URL to module documentation or repo |
license |
string | SPDX license identifier (e.g., MIT) |
config |
object | Default configuration values (details) |
config_secrets |
array | Module-owned sensitive config keys (details) |
hints |
object | Driver connection form hints (details) |
menu |
object | Sidebar navigation entry (details) |
Add a sidebar link for your module:
"menu": {
"label_key": "community.mymodule.nav_title",
"icon": "puzzle",
"order": 50
}label_key— i18n key for the label (namespaced with your module ID)icon— Lucide icon nameorder— Sort position in sidebar (higher = lower; built-in modules use 10–30)
Declare default values and they are automatically registered in DOCSight's config system:
{
"config": {
"mymodule_enabled": false,
"mymodule_api_url": "",
"mymodule_api_token": "",
"mymodule_interval": 300
},
"config_secrets": [
"mymodule_api_token"
]
}- Boolean values are auto-added to
BOOL_KEYS(parsed from checkbox forms) - Integer values are auto-added to
INT_KEYS(parsed from text inputs) - String values are stored as-is
- Keys must not conflict with existing core config keys
Use config_secrets for module passwords, API tokens, and other sensitive settings:
- Every key in
config_secretsmust also exist in the same manifest'sconfigobject with a string default, normally""; non-string defaults are rejected so encrypted values never enter scalar coercion. config_secretsmust be a list of unique strings.- Secret keys are encrypted at rest and masked in Settings after they are saved.
- The collector config proxy exposes only the module's own declared secret keys and hides core secrets. This API boundary is not a Python sandbox; installed code must be trusted.
- Settings templates should render secret fields as empty password/token inputs with
data-config-secret="true". Adddata-saved-secret="true"only when the masked config value indicates an existing saved secret. This explicit metadata makes an untouched field post the mask while an edited field posts the new value. Do not write saved secret values back into HTMLvalueattributes.
The manifest capability contract is owned by DOCSight core rather than duplicated in this catalog. With sibling checkouts, run python3 ../docsight/app/manifest_contract.py .; catalog CI performs the same check against current core before running the registry-specific validator.
The contributes object declares what your module provides. All keys are optional — include only what you need.
"contributes": { "routes": "routes.py" }Your routes.py must export a Flask Blueprint named bp or blueprint:
from flask import Blueprint, jsonify
bp = Blueprint("mymodule_bp", __name__)
@bp.route("/api/mymodule/data")
def api_data():
return jsonify({"status": "ok"})The Blueprint is automatically registered with the Flask app. No URL prefix is added — you control the full path.
Accessing core services in routes:
from app.web import get_storage, get_config_manager
@bp.route("/api/mymodule/data")
def api_data():
storage = get_storage() # SQLite storage instance
config = get_config_manager() # Config manager instance
return jsonify({...})Community modem driver support is restored on main and in images built from this fix. The tagged v2026-09-16.1 release does not include this restoration.
Declare "type": "driver" and "contributes": {"driver": "driver.py:ExampleDriver"}. The entry point must be a plain module-local Python filename and a concrete ModemDriver subclass implementing login(), get_docsis_data(), get_device_info(), and get_connection_info(). Inherit the constructor (url, user, password) or provide a compatible one. Relative imports such as from .helper import parse_channels work within the module directory.
The manifest id is the modem selection key, for example community.example. IDs use lowercase letters, digits, dots and underscores, start with a letter and must fit the browser's 128-character driver-key limit. Use a unique community prefix for new modem support. To explicitly override a built-in driver, use its exact key as the module ID, for example fritzbox. No separate override field is needed. The enabled module supplies the implementation and displayed name for that key within its application; the global built-in catalog is unchanged. If hints is omitted or empty, the built-in hints are inherited. Nonempty module hints replace the complete built-in hint object rather than merging with it. Community constructors receive only (url, user, password), without built-in-specific keyword arguments. Only enabled modules whose complete contribution plan validates are registered. Restart after installing, enabling or disabling a module. The driver then appears in Settings > Extensions with a Community badge and in the setup/settings modem choices. Select it and save the connection settings. If an override is disabled, removed or rejected, a newly started application uses the original built-in implementation, name and hints for that key. If an unavailable module has its own key with no built-in fallback, connection tests and polling fail safely. The saved selection is retained in both cases.
Driver contributions cannot be combined with collectors or publishers, even under another module type. Themes cannot contribute drivers. These are contribution rules, not a sandbox: community modules execute trusted Python in the DOCSight process and drivers receive modem credentials. Install only code you trust; these rules do not prevent malicious code from accessing data or the network.
Optional hints can contain boolean needs_user, needs_password, username_required, credentials_required, and string or null default_url, default_user, url_hint, user_hint, password_hint. A nonempty default_url must be an HTTP(S) URL without embedded credentials. Invalid hints reject the module before they can break setup/settings initialization.
Copy TEMPLATE-DRIVER/ for a minimal, inert starting point. Threshold profiles, including VFKD thresholds, remain analysis modules.
"contributes": { "collector": "collector.py:MyCollector" }Format: filename.py:ClassName. Your collector must extend the base class:
from app.collectors.base import Collector, CollectorResult
class MyCollector(Collector):
name = "mymodule"
def __init__(self, config_mgr, storage, web, poll_interval=300, **kwargs):
super().__init__(poll_interval)
self._config = config_mgr
self._storage = storage # core SnapshotStorage instance
self._web = web # web state manager
def is_enabled(self):
return self._config.get("mymodule_enabled", False)
def collect(self):
data = {"value": 42}
return CollectorResult(source=self.name, data=data)DOCSight passes config_mgr, storage, and web to every module collector. The base class provides exponential backoff on repeated failures (30s to 3600s max, auto-reset after 24h idle).
Note: Community modules receive a
_ModuleConfigProxyinstead of the rawConfigManager. This proxy hides DOCSight core secret keys such as modem passwords and API tokens. Declare module-owned passwords or tokens inconfig_secrets; only those declared secret keys are exposed through that proxy. Keep saved secret values out of rendered HTML and handle them through dedicated password/token fields.
"contributes": { "publisher": "publisher.py:MyPublisher" }Format: filename.py:ClassName. Publisher classes receive collected data and export it to external services.
"contributes": { "settings": "templates/mymodule_settings.html" }Your settings template is rendered in the Settings page under the Modules section. The panel ID must follow the pattern panel-mod-{module_id_with_dots_replaced_by_underscores}:
<div class="settings-panel" id="panel-mod-community_mymodule">
<div class="settings-card glass">
<div class="card-header">
<div class="card-title-group">
<div class="card-icon blue"><i data-lucide="puzzle"></i></div>
<div>
<div class="card-title">{{ t.get('community.mymodule.title', 'My Module') }}</div>
<div class="card-subtitle">{{ t.get('community.mymodule.desc', 'Module description') }}</div>
</div>
</div>
</div>
<div class="form-grid cols-2">
<div class="form-group">
<label for="mymodule_api_url">{{ t.get('community.mymodule.api_url', 'API URL') }}</label>
<input type="text" id="mymodule_api_url" name="mymodule_api_url"
value="{{ config.get('mymodule_api_url', '') }}"
placeholder="https://api.example.com">
</div>
<div class="form-group full-width">
<label class="toggle">
<input type="checkbox" name="mymodule_enabled"
{{ 'checked' if config.get('mymodule_enabled') }}>
<span class="toggle-slider"></span>
</label>
<label style="margin-left:8px;">{{ t.get('community.mymodule.enable', 'Enable') }}</label>
</div>
</div>
</div>
</div>Important: Template filename must be unique. Do not name it
settings.html(conflicts with core). Usemymodule_settings.html.
Available CSS classes:
settings-card glass- card container with glass effectcard-header,card-title-group,card-icon {color},card-title,card-subtitle- card headerform-grid cols-2- two-column form layoutform-group- single form field (label + input)form-group full-width- full-width field spanning both columnsform-hint- hint text below an inputtoggle,toggle-slider- toggle switch (replaces checkbox)
Icon colors: blue, purple, amber, green
"contributes": { "i18n": "i18n/" }Place JSON files in the i18n/ directory:
i18n/
├── en.json # English (required)
├── de.json # German (recommended)
├── es.json # Spanish (recommended)
└── fr.json # French (recommended)
Keys are automatically namespaced with your module ID:
{
"nav_title": "My Module",
"api_url": "API URL"
}Access in templates: {{ t['community.mymodule.nav_title'] }}
"contributes": { "tab": "templates/mymodule_tab.html" }Adds a tab to the main dashboard view switcher.
"contributes": { "card": "templates/mymodule_card.html" }Adds a card widget to the dashboard overview.
"contributes": { "thresholds": "thresholds.json" }A threshold module provides regional signal quality thresholds for DOCSight's health assessment. Only one threshold profile can be active at a time — enabling a new one automatically disables the previous one.
Use the TEMPLATE-THRESHOLDS/ directory as a starting point.
The thresholds.json file must contain these three sections, each with a _default key:
| Section | Keys | Format |
|---|---|---|
downstream_power |
Modulation names (e.g., 256QAM, 4096QAM) |
{ "good": [min, max], "warning": [min, max], "critical": [min, max] } |
upstream_power |
Channel types: sc_qam, ofdma |
Same [min, max] array format |
snr |
Modulation names | { "good_min": N, "warning_min": N, "critical_min": N } |
| Section | Purpose |
|---|---|
_meta |
Metadata (region, operator, docsis_variant, source, notes) |
upstream_modulation |
QAM order thresholds (critical_max_qam, warning_max_qam) |
errors |
Uncorrectable error rate (uncorrectable_pct: { warning: %, critical: % }) |
"contributes": { "theme": "theme.json" }A theme module provides dark and light color schemes. The theme.json must contain exactly two sections (dark and light), each with CSS custom property overrides:
{
"meta": { "family": "dark-first" },
"dark": {
"--bg": "#1a1b26",
"--surface": "#1f2335",
"--text": "#c0caf5",
"--accent": "#7aa2f7",
"--good": "#9ece6a",
"--warn": "#e0af68",
"--crit": "#f7768e",
"--muted": "#565f89"
},
"light": {
"--bg": "#f0f0f0",
"--surface": "#ffffff",
"--text": "#1a1b26",
"--accent": "#2e7de9"
}
}Only one theme can be active at a time. Users select themes in Settings > Appearance.
Security restriction: Theme modules cannot contribute collector, routes, publisher, or driver.
"contributes": { "static": "static/" }Files are served at /modules/<module-id>/static/. Two files are auto-detected and loaded on every page:
style.css— stylesheetmain.js— JavaScript
Other static files (images, fonts, etc.) are accessible at their path but not auto-loaded.
Module collectors can integrate with DOCSight's Smart Capture engine to trigger automated measurements when module-specific events are detected.
Pattern: Your collector receives the Smart Capture engine via post-construction injection and evaluates events through it after saving them:
class MyCollector(Collector):
name = "mymodule"
def __init__(self, config_mgr, storage, web, **kwargs):
super().__init__(300)
self._storage = storage
self._smart_capture = None
def set_smart_capture(self, smart_capture):
"""Inject Smart Capture engine for event evaluation."""
self._smart_capture = smart_capture
def collect(self):
events = self._detect_events()
if events and hasattr(self._storage, "save_events_with_ids"):
self._storage.save_events_with_ids(events)
if self._smart_capture:
self._smart_capture.evaluate(events)
return CollectorResult(source=self.name)Use save_events_with_ids() (not save_events()) so each event gets annotated with its database row ID for execution correlation.
The Smart Capture engine is wired to your collector in main.py after discover_collectors() returns. See the Connection Monitor module for a working example.
-
Mount the modules directory in your Docker setup:
# docker-compose.yml services: docsight: image: ghcr.io/itsdnns/docsight:latest volumes: - docsight_data:/data - ./modules:/data/modules ports: - "8765:8765"
-
Place your module in the
modules/directory:mkdir -p modules cp -r TEMPLATE modules/my-module # Edit modules/my-module/manifest.json and code -
Restart DOCSight to discover the new module:
docker compose restart docsight
-
Verify in Settings > Extensions. Your module should appear with a "Community" badge.
-
Check logs for any loading errors:
docker compose logs docsight | grep -i module
The standard persistent module path is /data/modules. An existing mount at /modules requires MODULES_DIR=/modules explicitly.
Contribution resolution failures reject the complete module plan while other modules continue loading. Installed Python code remains trusted:
- Invalid manifests are skipped with a warning
- Load failures are caught per-module and stored as error state
- Broken modules show an error badge in Settings > Modules
- Core functionality is never affected
The catalog supports two contribution styles:
- Curated in-repo modules: Small, reviewed modules can live directly in this repository under their own directory.
- External modules: Larger modules can stay in a contributor-owned GitHub repository and be referenced from
registry.json.
In both cases, the registry entry must point to an installable module directory through download_url.
- Your module works locally with DOCSight
- Your module has a README with setup, requirements, and support notes
- Your
manifest.jsonuses a unique ID that does not conflict with built-in DOCSight modules - Routes, collectors, settings forms, and stored configuration follow the safety checklist below
-
Fork this repository
-
Add or update your module files if the module lives in this catalog repository
-
Add your module to
registry.json:{ "id": "community.mymodule", "name": "My Module", "description": "What it does in one sentence", "author": "your-github-username", "repo": "https://github.com/your-username/docsight-mymodule", "version": "1.0.0", "min_app_version": "2026.2", "type": "integration", "download_url": "https://api.github.com/repos/your-username/docsight-mymodule/contents/my-module?ref=main", "verified": false } -
Open a Pull Request
-
We review: valid manifest, basic functionality, clear setup docs, and no malicious or unsafe behavior
-
After merge, your module appears in the catalog
Modules reviewed and tested by the DOCSight team receive "verified": true. Unverified modules are functional but marked accordingly in the catalog.
Before opening your PR, verify:
- Auth on all routes — every
@bp.routemust have@require_auth(import fromapp.web) - No credentials in HTML — password/token fields must use
value="",data-config-secret="true", and conditionaldata-saved-secret="true"metadata, nevervalue="{{ config.password }}" - Escape dynamic content — any value from API responses inserted via
innerHTMLmust be HTML-escaped; usetextContentwhere possible - English API errors — error messages returned by API endpoints must be English (UI translations go in i18n files)
- No exception details in responses — use generic error messages ("Connection failed"), log details server-side with
logger.exception() - i18n parity — all language files (EN, DE, FR, ES) must have identical keys; include a
template.jsonwith empty values - Validate user input — config values used in URLs or paths must be sanitized (e.g. site names: alphanumeric only)
- Trailing newlines — all JSON files must end with a newline
- Version consistency — version string in
manifest.jsonmust match any version shown in docstrings or templates
- One module per repository — keep it focused
- Semantic versioning — update
versionin both your manifest and the registry entry - No
docsight.*IDs — this prefix is reserved for built-in modules - English README required — additional languages welcome
| Type | Purpose | Example |
|---|---|---|
driver |
Modem support | Minimal driver template, explicit same-ID built-in override |
integration |
External service connection | Ping test, uptime monitor, API bridge |
analysis |
Data analysis/visualization | Custom charts, reports, threshold profiles |
theme |
UI customization | Color schemes, layouts |
These built-in DOCSight modules serve as examples:
| Module | Type | Contributes | Complexity |
|---|---|---|---|
| Reports | analysis | routes, i18n | Minimal |
| Journal | analysis | routes, i18n | Medium |
| Weather | integration | collector, routes, settings, i18n | Full |
| Backup | integration | collector, routes, settings, i18n | Full |
| Connection Monitor | integration | collector, routes, settings, i18n | Full + Smart Capture |
| Speedtest | integration | collector, routes, settings, i18n | Full |
| MQTT | integration | publisher, settings, i18n | Publisher |
| VFKD Thresholds | analysis | thresholds | Minimal |
| Classic Theme | theme | theme | Minimal |
MIT — same as DOCSight.