Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 4 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Every module needs a `manifest.json`:
| `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` |
| `type` | string | One of: `integration`, `analysis`, `theme` |
| `contributes` | object | What this module provides (see [Contribution Types](#contribution-types)) |

#### Optional Fields
Expand Down Expand Up @@ -350,44 +350,6 @@ The `thresholds.json` file must contain these three sections, each with a `_defa
| `upstream_modulation` | QAM order thresholds (`critical_max_qam`, `warning_max_qam`) |
| `errors` | Uncorrectable error rate (`uncorrectable_pct: { warning: %, critical: % }`) |

### `driver` — Modem/Router Hardware Driver

> Since DOCSight v2026-03-03

```json
"contributes": { "driver": "driver.py:MyModemDriver" }
```

Format: `filename.py:ClassName`. Your driver must extend `ModemDriver` from `app/drivers/base.py`:

```python
from app.drivers.base import ModemDriver

class MyModemDriver(ModemDriver):
def login(self):
"""Authenticate with the modem. Called before each poll cycle."""
pass

def get_docsis_data(self):
"""Return DOCSIS channel data (see Adding-Modem-Support wiki)."""
return {"channelDs": {"docsis30": [], "docsis31": []},
"channelUs": {"docsis30": [], "docsis31": []}}

def get_device_info(self):
"""Return device model and firmware info."""
return {"manufacturer": "...", "model": "...", "sw_version": "..."}

def get_connection_info(self):
"""Return internet connection info. Empty dict if unavailable."""
return {}
```

The driver is registered in DOCSight's `DriverRegistry` on startup. Module drivers take priority over built-in drivers with the same key, allowing community modules to override or improve existing drivers.

**Security restriction:** Driver modules **cannot** also contribute `collector` or `publisher`. This prevents a driver module from exfiltrating modem credentials to external services. If a manifest declares both, DOCSight rejects the module on startup.

See the [Adding Modem Support](https://github.com/itsDNNS/docsight/wiki/Adding-Modem-Support) wiki page for the full `get_docsis_data()` return format and driver development tips.

### `theme` — Color Theme

```json
Expand Down Expand Up @@ -594,9 +556,8 @@ Before opening your PR, verify:

| Type | Purpose | Example |
|------|---------|---------|
| `driver` | Hardware/modem support | Custom modem driver |
| `integration` | External service connection | Ping test, uptime monitor, API bridge |
| `analysis` | Data analysis/visualization | Custom charts, reports |
| `analysis` | Data analysis/visualization | Custom charts, reports, threshold profiles |
| `theme` | UI customization | Color schemes, layouts |

---
Expand All @@ -614,9 +575,8 @@ These built-in DOCSight modules serve as examples:
| [Connection Monitor](https://github.com/itsDNNS/docsight/tree/main/app/modules/connection_monitor) | integration | collector, routes, settings, i18n | Full + Smart Capture |
| [Speedtest](https://github.com/itsDNNS/docsight/tree/main/app/modules/speedtest) | integration | collector, routes, settings, i18n | Full |
| [MQTT](https://github.com/itsDNNS/docsight/tree/main/app/modules/mqtt) | integration | publisher, settings, i18n | Publisher |
| [VFKD Thresholds](https://github.com/itsDNNS/docsight/tree/main/app/modules/thresholds_vfkd) | driver | thresholds | Minimal |
| [Classic Theme](https://github.com/itsDNNS/docsight/tree/main/app/modules/theme_classic) | theme | theme | Minimal |
| [GenericDriver](https://github.com/itsDNNS/docsight/blob/main/app/drivers/generic.py) | driver | driver | Minimal |
| [VFKD Thresholds](https://github.com/itsDNNS/docsight/blob/main/app/threshold_profiles.py) | analysis | thresholds | Minimal |
| [Classic Theme](https://github.com/itsDNNS/docsight/blob/main/app/theme_registry.py) | theme | theme | Minimal |

---

Expand Down
2 changes: 1 addition & 1 deletion TEMPLATE-THRESHOLDS/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "1.0.0",
"author": "YOUR_NAME",
"minAppVersion": "2026.3",
"type": "driver",
"type": "analysis",
"contributes": {
"thresholds": "thresholds.json"
}
Expand Down
2 changes: 1 addition & 1 deletion registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"repo": "https://github.com/itsDNNS/docsight-modules",
"version": "1.0.0",
"min_app_version": "2026.2",
"type": "driver",
"type": "analysis",
"verified": true,
"download_url": "https://api.github.com/repos/itsDNNS/docsight-modules/contents/thresholds-vfkd-community"
},
Expand Down
2 changes: 1 addition & 1 deletion registry.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"type": {
"type": "string",
"enum": ["driver", "integration", "analysis", "theme"],
"enum": ["integration", "analysis", "theme"],
"description": "Module type"
},
"download_url": {
Expand Down
11 changes: 10 additions & 1 deletion scripts/validate_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}
REGISTRY_OPTIONAL_FIELDS = {"verified"}
REGISTRY_ALLOWED_FIELDS = REGISTRY_REQUIRED_FIELDS | REGISTRY_OPTIONAL_FIELDS
ALLOWED_TYPES = {"driver", "integration", "analysis", "theme"}
ALLOWED_TYPES = {"integration", "analysis", "theme"}


def load_json(path: Path, errors: list[str]) -> object | None:
Expand Down Expand Up @@ -112,6 +112,15 @@ def validate_registry_entry(root: Path, entry: object, index: int, errors: list[
errors.append(
f"registry.json {module_id}: manifest version {manifest.get('version')!r} does not match registry version {entry.get('version')!r}"
)
manifest_type = manifest.get("type")
if manifest_type not in ALLOWED_TYPES:
errors.append(
f"registry.json {module_id}: manifest type {manifest_type!r} is not supported"
)
if manifest_type != entry.get("type"):
errors.append(
f"registry.json {module_id}: manifest type {manifest_type!r} does not match registry type {entry.get('type')!r}"
)


def validate_i18n(module_dir: Path, errors: list[str]) -> None:
Expand Down
37 changes: 34 additions & 3 deletions tests/test_validate_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ def write_json(path: Path, data: dict) -> None:
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")


def make_valid_fixture(root: Path, *, registry_id: str = "community.sample", manifest_id: str = "community.sample") -> None:
def make_valid_fixture(
root: Path,
*,
registry_id: str = "community.sample",
manifest_id: str = "community.sample",
registry_type: str = "integration",
manifest_type: str = "integration",
) -> None:
module_dir = root / "sample-module"
module_dir.mkdir(parents=True)
write_json(root / "registry.json", {
Expand All @@ -24,7 +31,7 @@ def make_valid_fixture(root: Path, *, registry_id: str = "community.sample", man
"repo": "https://github.com/example/docsight-sample",
"version": "1.0.0",
"min_app_version": "2026.2",
"type": "integration",
"type": registry_type,
"download_url": "https://api.github.com/repos/example/docsight-sample/contents/sample-module?ref=main",
"verified": False,
}
Expand All @@ -37,7 +44,7 @@ def make_valid_fixture(root: Path, *, registry_id: str = "community.sample", man
"version": "1.0.0",
"author": "sample-author",
"minAppVersion": "2026.2",
"type": "integration",
"type": manifest_type,
"contributes": {"i18n": "i18n/"},
})
write_json(module_dir / "i18n" / "en.json", {"sample.title": "Sample"})
Expand All @@ -63,6 +70,30 @@ def test_registry_id_must_match_manifest_id(self):

self.assertTrue(any("manifest ID" in error and "community.sample" in error for error in errors))

def test_removed_driver_type_is_rejected_for_registry_and_manifest(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
make_valid_fixture(root, registry_type="driver", manifest_type="driver")

errors = validate_repository(root)

self.assertTrue(any("invalid type 'driver'" in error for error in errors))
self.assertTrue(any("manifest type 'driver' is not supported" in error for error in errors))

def test_registry_type_must_match_manifest_type(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
make_valid_fixture(root, registry_type="analysis", manifest_type="integration")

errors = validate_repository(root)

self.assertTrue(
any(
"manifest type 'integration' does not match registry type 'analysis'" in error
for error in errors
)
)

def test_i18n_files_must_have_matching_keys(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
Expand Down
2 changes: 1 addition & 1 deletion thresholds-vfkd-community/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "1.0.0",
"author": "itsDNNS",
"minAppVersion": "2026.2",
"type": "driver",
"type": "analysis",
"contributes": {
"thresholds": "thresholds.json"
}
Expand Down
Loading