From baf686e83f2cafbc88fb9d0581c0f01a98e1f38a Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 7 May 2025 15:27:22 +0200 Subject: [PATCH 1/2] Feat(modules): display clan.service modules --- pkgs/clan-cli/clan_lib/api/modules.py | 69 +++++++++--------- pkgs/webview-ui/app/src/queries/index.ts | 17 ++--- .../webview-ui/app/src/routes/modules/add.tsx | 6 +- .../app/src/routes/modules/details.tsx | 21 +++--- .../app/src/routes/modules/list.tsx | 70 +++++++++++++------ 5 files changed, 104 insertions(+), 79 deletions(-) diff --git a/pkgs/clan-cli/clan_lib/api/modules.py b/pkgs/clan-cli/clan_lib/api/modules.py index 3125cbb86..fc09127ea 100644 --- a/pkgs/clan-cli/clan_lib/api/modules.py +++ b/pkgs/clan-cli/clan_lib/api/modules.py @@ -1,13 +1,11 @@ -import json import re import tomllib from dataclasses import dataclass, field from pathlib import Path from typing import Any, TypedDict -from clan_cli.cmd import run_no_stdout -from clan_cli.errors import ClanCmdError, ClanError -from clan_cli.nix import nix_eval +from clan_cli.errors import ClanError +from clan_cli.flake import Flake from . import API @@ -143,53 +141,50 @@ def get_roles(module_path: Path) -> None | list[str]: ] +class ModuleManifest(TypedDict): + name: str + features: dict[str, bool] + + @dataclass class ModuleInfo: - description: str - readme: str - categories: list[str] - roles: list[str] | None - features: list[str] = field(default_factory=list) - constraints: dict[str, Any] = field(default_factory=dict) + manifest: ModuleManifest + roles: dict[str, None] -def get_modules(base_path: str) -> dict[str, str]: - cmd = nix_eval( - [ - f"{base_path}#clanInternals.inventory.modules", - "--json", - ] - ) - try: - proc = run_no_stdout(cmd) - res = proc.stdout.strip() - except ClanCmdError as e: - msg = "clanInternals might not have inventory.modules attributes" - raise ClanError( - msg, - location=f"list_modules {base_path}", - description="Evaluation failed on clanInternals.inventory.modules attribute", - ) from e - modules: dict[str, str] = json.loads(res) - return modules +class ModuleLists(TypedDict): + modulesPerSource: dict[str, dict[str, ModuleInfo]] + localModules: dict[str, ModuleInfo] @API.register -def list_modules(base_path: str) -> dict[str, ModuleInfo]: +def list_modules(base_path: str) -> ModuleLists: """ Show information about a module """ - modules = get_modules(base_path) - return { - module_name: get_module_info(module_name, Path(module_path)) - for module_name, module_path in modules.items() - } + flake = Flake(base_path) + modules = flake.select( + "clanInternals.inventoryClass.{?modulesPerSource,?localModules}" + ) + print("Modules found:", modules) + + return modules + + +@dataclass +class LegacyModuleInfo: + description: str + categories: list[str] + roles: None | list[str] + readme: str + features: list[str] + constraints: dict[str, Any] def get_module_info( module_name: str, module_path: Path, -) -> ModuleInfo: +) -> LegacyModuleInfo: """ Retrieves information about a module """ @@ -214,7 +209,7 @@ def get_module_info( readme, f"{module_path}/README.md" ) - return ModuleInfo( + return LegacyModuleInfo( description=frontmatter.description, categories=frontmatter.categories, roles=get_roles(module_path), diff --git a/pkgs/webview-ui/app/src/queries/index.ts b/pkgs/webview-ui/app/src/queries/index.ts index e7f0e8909..a8bcaf812 100644 --- a/pkgs/webview-ui/app/src/queries/index.ts +++ b/pkgs/webview-ui/app/src/queries/index.ts @@ -11,7 +11,10 @@ export const createModulesQuery = ( ) => createQuery(() => ({ queryKey: [uri, "list_modules"], - placeholderData: [], + placeholderData: { + localModules: {}, + modulesPerSource: {}, + }, enabled: !!uri, queryFn: async () => { console.log({ uri }); @@ -23,15 +26,13 @@ export const createModulesQuery = ( if (response.status === "error") { toast.error("Failed to fetch data"); } else { - if (!filter) { - return Object.entries(response.data); - } - return Object.entries(response.data).filter(([key, value]) => - filter.features.every((f) => (value.features || []).includes(f)), - ); + return response.data; } } - return []; + return { + localModules: {}, + modulesPerSource: {}, + }; }, })); diff --git a/pkgs/webview-ui/app/src/routes/modules/add.tsx b/pkgs/webview-ui/app/src/routes/modules/add.tsx index f21f4aae4..8ea2c7897 100644 --- a/pkgs/webview-ui/app/src/routes/modules/add.tsx +++ b/pkgs/webview-ui/app/src/routes/modules/add.tsx @@ -16,11 +16,11 @@ export const ModuleDetails = () => {

{params.id}

- + {/* i[0] === params.id)}> {(d) => } - + */}
); @@ -40,7 +40,7 @@ export const AddModule = (props: AddModuleProps) => { {(tags) => ( - + {(role) => ( <>
{role}s
diff --git a/pkgs/webview-ui/app/src/routes/modules/details.tsx b/pkgs/webview-ui/app/src/routes/modules/details.tsx index 38261bc6d..e95d92ab2 100644 --- a/pkgs/webview-ui/app/src/routes/modules/details.tsx +++ b/pkgs/webview-ui/app/src/routes/modules/details.tsx @@ -21,11 +21,11 @@ export const ModuleDetails = () => {

{params.id}

- + {/* i[0] === params.id)}> {(d) =>
} - + */}
); @@ -85,19 +85,24 @@ const Details = (props: DetailsProps) => { }; return (
-
{props.data.description}
- Categories + {/* TODO: bring this feature back */} + {/*
{props.data.description}
*/} + {/* Categories */}
- + {/* TODO: bring this feature back */} + {/* {(c) =>
{c}
} -
+
*/}
Roles
- {(r) =>
{r}
}
+ + {(r) =>
{r}
} +
- {props.data.readme} + {/* TODO: bring this feature back */} + {/* {props.data.readme} */}
@@ -82,7 +82,7 @@ const ModuleItem = (props: {
- + {/* */} {name} @@ -92,11 +92,12 @@ const ModuleItem = (props: {
- {info.description} + description + {/* TODO: {info.description} */}
- +
); }; @@ -161,23 +162,46 @@ export const ModuleList = () => { Loading.... -
- - {([k, v]) => ( - - )} - -
+ {(modules) => ( +
+ + {([sourceName, v]) => ( + <> +
+ + {sourceName} + +
+ + {([moduleName, moduleInfo]) => ( + + )} + + + )} +
+
{"localModules"}
+ + {([moduleName, moduleInfo]) => ( + + )} + +
+ )}
From afdb08643d1d7a902cdfdc573bf71190a79eaddc Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 7 May 2025 16:46:13 +0200 Subject: [PATCH 2/2] fix(tests/modules): list_modules returns moduleSets" --- lib/build-clan/module.nix | 2 +- pkgs/clan-cli/clan_cli/tests/test_modules.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/build-clan/module.nix b/lib/build-clan/module.nix index 60865b5e1..684e82669 100644 --- a/lib/build-clan/module.nix +++ b/lib/build-clan/module.nix @@ -45,7 +45,7 @@ let inherit inventory directory; flakeInputs = config.self.inputs; prefix = config._prefix ++ [ "inventoryClass" ]; - localModuleSet = config.self.clan.modules; + localModuleSet = config.modules; } ); diff --git a/pkgs/clan-cli/clan_cli/tests/test_modules.py b/pkgs/clan-cli/clan_cli/tests/test_modules.py index bb1412f27..fd3318355 100644 --- a/pkgs/clan-cli/clan_cli/tests/test_modules.py +++ b/pkgs/clan-cli/clan_cli/tests/test_modules.py @@ -27,10 +27,8 @@ def test_list_modules(test_flake_with_core: FlakeForTest) -> None: base_path = test_flake_with_core.path modules_info = list_modules(str(base_path)) - assert len(modules_info.items()) > 1 - # Random test for those two modules - assert "borgbackup" in modules_info - assert "syncthing" in modules_info + assert "localModules" in modules_info + assert "modulesPerSource" in modules_info @pytest.mark.impure