diff --git a/README.md b/README.md index 1cc1944..55c5d16 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 | --- @@ -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 | --- diff --git a/TEMPLATE-THRESHOLDS/manifest.json b/TEMPLATE-THRESHOLDS/manifest.json index 34ec465..b131feb 100644 --- a/TEMPLATE-THRESHOLDS/manifest.json +++ b/TEMPLATE-THRESHOLDS/manifest.json @@ -5,7 +5,7 @@ "version": "1.0.0", "author": "YOUR_NAME", "minAppVersion": "2026.3", - "type": "driver", + "type": "analysis", "contributes": { "thresholds": "thresholds.json" } diff --git a/registry.json b/registry.json index 644cca5..0df8620 100644 --- a/registry.json +++ b/registry.json @@ -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" }, diff --git a/registry.schema.json b/registry.schema.json index 17421fe..80dc921 100644 --- a/registry.schema.json +++ b/registry.schema.json @@ -42,7 +42,7 @@ }, "type": { "type": "string", - "enum": ["driver", "integration", "analysis", "theme"], + "enum": ["integration", "analysis", "theme"], "description": "Module type" }, "download_url": { diff --git a/scripts/validate_registry.py b/scripts/validate_registry.py index 098c5ab..7cb4a97 100644 --- a/scripts/validate_registry.py +++ b/scripts/validate_registry.py @@ -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: @@ -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: diff --git a/tests/test_validate_registry.py b/tests/test_validate_registry.py index 45b99bf..dcf09e2 100644 --- a/tests/test_validate_registry.py +++ b/tests/test_validate_registry.py @@ -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", { @@ -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, } @@ -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"}) @@ -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) diff --git a/thresholds-vfkd-community/manifest.json b/thresholds-vfkd-community/manifest.json index 5d4d57e..fbbb01e 100644 --- a/thresholds-vfkd-community/manifest.json +++ b/thresholds-vfkd-community/manifest.json @@ -5,7 +5,7 @@ "version": "1.0.0", "author": "itsDNNS", "minAppVersion": "2026.2", - "type": "driver", + "type": "analysis", "contributes": { "thresholds": "thresholds.json" }