diff --git a/clanModules/admin/default.nix b/clanModules/admin/default.nix index cfe59e11c..32916eed7 100644 --- a/clanModules/admin/default.nix +++ b/clanModules/admin/default.nix @@ -2,9 +2,12 @@ { options.clan.admin = { allowedKeys = lib.mkOption { - default = [ ]; - type = lib.types.listOf lib.types.str; + default = { }; + type = lib.types.attrsOf lib.types.str; description = "The allowed public keys for ssh access to the admin user"; + example = { + "key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD..."; + }; }; }; imports = [ @@ -12,6 +15,6 @@ ../root-password ]; config = { - users.users.root.openssh.authorizedKeys.keys = config.clan.admin.allowedKeys; + users.users.root.openssh.authorizedKeys.keys = builtins.attrValues config.clan.admin.allowedKeys; }; } diff --git a/pkgs/clan-cli/clan_cli/__init__.py b/pkgs/clan-cli/clan_cli/__init__.py index d747f0cf4..ed40f26d7 100644 --- a/pkgs/clan-cli/clan_cli/__init__.py +++ b/pkgs/clan-cli/clan_cli/__init__.py @@ -5,12 +5,12 @@ from pathlib import Path from types import ModuleType # These imports are unused, but necessary for @API.register to run once. -from clan_cli.api import directory, disk, mdns_discovery, modules +from clan_cli.api import admin, directory, disk, mdns_discovery, modules from clan_cli.arg_actions import AppendOptionAction from clan_cli.clan import show, update # API endpoints that are not used in the cli. -__all__ = ["directory", "mdns_discovery", "modules", "update", "disk"] +__all__ = ["directory", "mdns_discovery", "modules", "update", "disk", "admin"] from . import ( backups, diff --git a/pkgs/clan-cli/clan_cli/api/admin.py b/pkgs/clan-cli/clan_cli/api/admin.py index 14b908e67..8fb18e231 100644 --- a/pkgs/clan-cli/clan_cli/api/admin.py +++ b/pkgs/clan-cli/clan_cli/api/admin.py @@ -23,7 +23,10 @@ def get_admin_service(base_url: str) -> ServiceAdmin | None: @API.register def set_admin_service( - base_url: str, allowed_keys: list[str], instance_name: str = "admin" + base_url: str, + allowed_keys: dict[str, str], + instance_name: str = "admin", + extra_machines: list[str] = [], ) -> None: """ Set the admin service of a clan @@ -34,20 +37,20 @@ def set_admin_service( if not allowed_keys: raise ValueError("At least one key must be provided to ensure access") - keys = [] - for keyfile in allowed_keys: + keys = {} + for name, keyfile in allowed_keys.items(): if not keyfile.startswith("/"): raise ValueError(f"Keyfile '{keyfile}' must be an absolute path") with open(keyfile) as f: pubkey = f.read() - keys.append(pubkey) + keys[name] = pubkey instance = ServiceAdmin( meta=ServiceMeta(name=instance_name), roles=ServiceAdminRole( default=ServiceAdminRoleDefault( config=AdminConfig(allowedKeys=keys), - machines=[], + machines=extra_machines, tags=["all"], ) ), diff --git a/pkgs/clan-cli/clan_cli/inventory/classes.py b/pkgs/clan-cli/clan_cli/inventory/classes.py index 7501a3e74..11cf6daee 100644 --- a/pkgs/clan-cli/clan_cli/inventory/classes.py +++ b/pkgs/clan-cli/clan_cli/inventory/classes.py @@ -32,7 +32,7 @@ class Meta: @dataclass class AdminConfig: - allowedKeys: list[str] = field(default_factory = list) + allowedKeys: dict[str, str] = field(default_factory = dict) @dataclass diff --git a/pkgs/classgen/main.py b/pkgs/classgen/main.py index c698c480a..e6eb072b0 100644 --- a/pkgs/classgen/main.py +++ b/pkgs/classgen/main.py @@ -127,7 +127,7 @@ def field_def_from_default_value( default_factory=nested_class_name, ) - elif f"dict[str, {nested_class_name}]" in serialised_types: + elif "dict[str," in serialised_types: return finalize_field( field_types=field_types, default_factory="dict", diff --git a/pkgs/webview-ui/app/src/index.tsx b/pkgs/webview-ui/app/src/index.tsx index 5c09a5148..b0d5b5f0b 100644 --- a/pkgs/webview-ui/app/src/index.tsx +++ b/pkgs/webview-ui/app/src/index.tsx @@ -15,6 +15,7 @@ import { CreateMachine } from "./routes/machines/create"; import { HostList } from "./routes/hosts/view"; import { Welcome } from "./routes/welcome"; import { Toaster } from "solid-toast"; +import { Details } from "./routes/clan/details"; const client = new QueryClient(); @@ -66,7 +67,7 @@ export const routes: AppRoute[] = [ ], }, { - path: "/clan", + path: "/clans", label: "Clans", icon: "groups", children: [ @@ -84,6 +85,7 @@ export const routes: AppRoute[] = [ path: "/:id", label: "Details", hidden: true, + component: () =>
, }, ], }, diff --git a/pkgs/webview-ui/app/src/routes/clan/details.tsx b/pkgs/webview-ui/app/src/routes/clan/details.tsx new file mode 100644 index 000000000..27ac2f542 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/clan/details.tsx @@ -0,0 +1,67 @@ +import { callApi, SuccessData } from "@/src/api"; +import { BackButton } from "@/src/components/BackButton"; +import { useParams } from "@solidjs/router"; +import { createQuery } from "@tanstack/solid-query"; +import { createEffect, For, Match, Switch } from "solid-js"; +import { Show } from "solid-js"; +import { DiskView } from "../disk/view"; +import { Accessor } from "solid-js"; + +type AdminData = SuccessData<"get_admin_service">["data"]; + +interface ClanDetailsProps { + admin: AdminData; +} + +const ClanDetails = (props: ClanDetailsProps) => { + const items = () => + Object.entries( + (props.admin?.config?.allowedKeys as Record) || {}, + ); + + return ( +
+

Clan Details

+ + {([name, key]) => ( +
+ {name} + {key} +
+ )} +
+
+ ); +}; + +export const Details = () => { + const params = useParams(); + const clan_dir = window.atob(params.id); + const query = createQuery(() => ({ + queryKey: [clan_dir, "get_admin_service"], + queryFn: async () => { + const result = await callApi("get_admin_service", { + base_url: clan_dir, + }); + if (result.status === "error") throw new Error("Failed to fetch data"); + return result.data || null; + }, + })); + + return ( +
+ + } + > + + + {(d) => } + + + {clan_dir} + +
+ ); +}; diff --git a/pkgs/webview-ui/app/src/routes/machines/create.tsx b/pkgs/webview-ui/app/src/routes/machines/create.tsx index 0c8e7e12c..637e9fe34 100644 --- a/pkgs/webview-ui/app/src/routes/machines/create.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/create.tsx @@ -17,6 +17,7 @@ export function CreateMachine() { loc: activeURI() || "", }, machine: { + tags: ["all"], deploy: { targetHost: "", }, diff --git a/pkgs/webview-ui/app/src/routes/settings/index.tsx b/pkgs/webview-ui/app/src/routes/settings/index.tsx index 214c54070..629d6fff0 100644 --- a/pkgs/webview-ui/app/src/routes/settings/index.tsx +++ b/pkgs/webview-ui/app/src/routes/settings/index.tsx @@ -5,7 +5,8 @@ import { createQuery } from "@tanstack/solid-query"; import { useFloating } from "@/src/floating"; import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom"; import { EditClanForm } from "../clan/editClan"; -import { useNavigate } from "@solidjs/router"; +import { useNavigate, A } from "@solidjs/router"; +import { fileURLToPath } from "url"; export const registerClan = async () => { try { @@ -145,7 +146,9 @@ const ClanDetails = (props: ClanDetailsProps) => {
-
{details.data?.name}
+ +
{details.data?.name}
+
{details.data?.description}
diff --git a/pkgs/webview-ui/app/src/routes/welcome/index.tsx b/pkgs/webview-ui/app/src/routes/welcome/index.tsx index aae282d1c..f82ea5f6b 100644 --- a/pkgs/webview-ui/app/src/routes/welcome/index.tsx +++ b/pkgs/webview-ui/app/src/routes/welcome/index.tsx @@ -13,7 +13,7 @@ export const Welcome = () => {