docs: add clan options search page

This provides a simpler and more intuitive search over a flat list of possible options.

Styling still to be improved
This commit is contained in:
DavHau
2025-06-24 23:06:26 +07:00
parent 3cb91769ca
commit 12682b608c
10 changed files with 421 additions and 112 deletions

View File

@@ -179,6 +179,7 @@ nav:
- 04-fetching-nix-from-python: decisions/04-fetching-nix-from-python.md
- 05-deployment-parameters: decisions/05-deployment-parameters.md
- Template: decisions/_template.md
- Options: options.md
docs_dir: site
site_dir: out

View File

@@ -7,6 +7,7 @@
asciinema-player-css,
roboto,
fira-code,
docs-options,
...
}:
let
@@ -55,5 +56,6 @@ pkgs.stdenv.mkDerivation {
installPhase = ''
cp -a out/ $out/
cp -r ${docs-options} $out/options-page
'';
}

View File

@@ -1,5 +1,8 @@
{ inputs, self, ... }:
{
imports = [
./options/flake-module.nix
];
perSystem =
{
config,
@@ -124,7 +127,7 @@
packages = {
docs = pkgs.python3.pkgs.callPackage ./default.nix {
clan-core = self;
inherit (self'.packages) clan-cli-docs inventory-api-docs;
inherit (self'.packages) clan-cli-docs docs-options inventory-api-docs;
inherit (inputs) nixpkgs;
inherit module-docs;
inherit asciinema-player-js;

View File

@@ -0,0 +1,167 @@
{ self, config, ... }:
{
perSystem =
{
inputs',
lib,
...
}:
let
inherit (lib)
mapAttrsToList
flip
mapAttrs
mkOption
types
splitString
stringLength
substring
;
inherit (self) clanLib;
serviceModules = self.clan.modules;
baseHref = "/options-page/";
evalService =
serviceModule:
lib.evalModules {
modules = [
{
imports = [
serviceModule
../../../lib/inventory/distributed-service/service-module.nix
];
}
];
};
getRoles = module: (evalService module).config.roles;
getManifest = module: (evalService module).config.manifest;
loadFile = file: if builtins.pathExists file then builtins.readFile file else "";
settingsModules =
module: flip mapAttrs (getRoles module) (_roleName: roleConfig: roleConfig.interface);
# Map each letter to its capitalized version
capitalizeChar =
char:
{
a = "A";
b = "B";
c = "C";
d = "D";
e = "E";
f = "F";
g = "G";
h = "H";
i = "I";
j = "J";
k = "K";
l = "L";
m = "M";
n = "N";
o = "O";
p = "P";
q = "Q";
r = "R";
s = "S";
t = "T";
u = "U";
v = "V";
w = "W";
x = "X";
y = "Y";
z = "Z";
}
.${char};
title =
name:
let
# split by -
parts = splitString "-" name;
# capitalize first letter of each part
capitalize = part: (capitalizeChar (substring 0 1 part)) + substring 1 (stringLength part) part;
capitalizedParts = map capitalize parts;
in
builtins.concatStringsSep " " capitalizedParts;
fakeInstanceOptions =
name: module:
let
manifest = getManifest module;
description = ''
# ${title name} (Clan Service)
**${manifest.description}**
${loadFile (module._file + "/../README.md")}
${
if manifest.categories != [ ] then
"Categories: " + builtins.concatStringsSep ", " manifest.categories
else
"No categories defined"
}
'';
in
{
options = {
_ = mkOption {
type = types.raw;
};
instances.${name} = lib.mkOption {
inherit description;
type = types.submodule {
options.roles = flip mapAttrs (settingsModules module) (
roleName: roleSettingsModule:
mkOption {
type = types.submodule {
imports = [
(import ../../../lib/inventory/build-inventory/roles-interface.nix {
inherit clanLib;
nestedSettingsOption = mkOption {
type = types.raw;
description = ''
See [instances.${name}.roles.${roleName}.settings](${baseHref}?option_scope=0&option=instances.${name}.roles.${roleName}.settings)
'';
};
settingsOption = mkOption {
type = types.submoduleWith {
modules = [ roleSettingsModule ];
};
};
})
];
};
}
);
};
};
};
};
mkScope = name: modules: {
inherit name;
modules = [
(import ../../../lib/inventory/build-inventory/interface.nix {
inherit clanLib;
noInstanceOptions = true;
})
] ++ mapAttrsToList fakeInstanceOptions modules;
urlPrefix = "https://github.com/nix-community/dream2nix/blob/main/";
};
in
{
packages.docs-options = inputs'.nuschtos.packages.mkMultiSearch {
inherit baseHref;
title = "Clan Options";
# scopes = mapAttrsToList mkScope serviceModules;
scopes = [ (mkScope "Clan Inventory" serviceModules) ];
};
};
}

View File

@@ -0,0 +1,14 @@
{% extends "base.html" %} {% block extrahead %}
<style>
.md-main__inner {
max-width: 100% !important;
}
.md-content {
max-width: 100% !important;
}
.md-main__inner {
margin-top: 0 !important;
}
</style>
{% endblock %} {% block site_nav %}{% endblock %} {% block content %} {{
page.content }} {% endblock %}

6
docs/site/options.md Normal file
View File

@@ -0,0 +1,6 @@
---
template: options.html
---
<iframe src="/options-page/" height="1000" width="100%"></iframe>

84
flake.lock generated
View File

@@ -67,6 +67,50 @@
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"ixx": {
"inputs": {
"flake-utils": [
"nuschtos",
"flake-utils"
],
"nixpkgs": [
"nuschtos",
"nixpkgs"
]
},
"locked": {
"lastModified": 1748294338,
"narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=",
"owner": "NuschtOS",
"repo": "ixx",
"rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85",
"type": "github"
},
"original": {
"owner": "NuschtOS",
"ref": "v0.0.8",
"repo": "ixx",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
@@ -128,6 +172,28 @@
"url": "https://nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz"
}
},
"nuschtos": {
"inputs": {
"flake-utils": "flake-utils",
"ixx": "ixx",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1749730855,
"narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=",
"owner": "NuschtOS",
"repo": "search",
"rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742",
"type": "github"
},
"original": {
"owner": "NuschtOS",
"repo": "search",
"type": "github"
}
},
"root": {
"inputs": {
"data-mesher": "data-mesher",
@@ -137,8 +203,9 @@
"nix-select": "nix-select",
"nixos-facter-modules": "nixos-facter-modules",
"nixpkgs": "nixpkgs",
"nuschtos": "nuschtos",
"sops-nix": "sops-nix",
"systems": "systems",
"systems": "systems_2",
"treefmt-nix": "treefmt-nix"
}
},
@@ -177,6 +244,21 @@
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [

View File

@@ -34,6 +34,9 @@
treefmt-nix.follows = "treefmt-nix";
};
};
nuschtos.url = "github:NuschtOS/search";
nuschtos.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =

View File

@@ -1,4 +1,8 @@
{ clanLib }:
{
clanLib,
# workaround for docs rendering to include fake instance options
noInstanceOptions ? false,
}:
{
lib,
config,
@@ -370,7 +374,11 @@ in
);
};
instances = lib.mkOption {
instances =
if noInstanceOptions then
{ }
else
lib.mkOption {
description = "Multi host service module instances";
type = types.attrsOf (
types.submoduleWith {
@@ -401,77 +409,12 @@ in
'';
};
};
default = { };
};
roles = lib.mkOption {
default = { };
type = types.attrsOf (
types.submodule {
options = {
# TODO: deduplicate
machines = lib.mkOption {
type = types.attrsOf (
types.submodule {
options.settings = lib.mkOption {
default = { };
type = clanLib.types.uniqueDeferredSerializableModule;
};
}
);
default = { };
};
tags = lib.mkOption {
type = types.attrsOf (types.submodule { });
default = { };
};
settings = lib.mkOption {
default = { };
type = clanLib.types.uniqueDeferredSerializableModule;
};
extraModules = lib.mkOption {
description = ''
List of additionally imported `.nix` expressions.
Supported types:
- **Strings**: Interpreted relative to the 'directory' passed to buildClan.
- **Paths**: should be relative to the current file.
- **Any**: Nix expression must be serializable to JSON.
!!! Note
**The import only happens if the machine is part of the service or role.**
Other types are passed through to the nixos configuration.
???+ Example
To import the `special.nix` file
```
. Clan Directory
flake.nix
...
modules
special.nix
...
```
```nix
{
extraModules = [ "modules/special.nix" ];
}
```
'';
apply = value: if lib.isString value then value else builtins.seq (builtins.toJSON value) value;
default = [ ];
type = types.listOf (
types.oneOf [
types.str
types.path
(types.attrsOf types.anything)
]
);
};
};
imports = [ (import ./roles-interface.nix { inherit clanLib; }) ];
}
);
};
@@ -485,6 +428,8 @@ in
};
services = lib.mkOption {
# services are deprecated in favor of `instances`
visible = false;
description = ''
Services of the inventory.

View File

@@ -0,0 +1,86 @@
{
clanLib,
settingsOption ? null,
nestedSettingsOption ? null,
}:
{ lib, ... }:
let
inherit (lib)
types
;
in
{
options = {
# TODO: deduplicate
machines = lib.mkOption {
type = types.attrsOf (
types.submodule {
options.settings =
if nestedSettingsOption != null then
nestedSettingsOption
else
lib.mkOption {
default = { };
type = clanLib.types.uniqueDeferredSerializableModule;
};
}
);
default = { };
};
tags = lib.mkOption {
type = types.attrsOf (types.submodule { });
default = { };
};
settings =
if settingsOption != null then
settingsOption
else
lib.mkOption {
default = { };
type = clanLib.types.uniqueDeferredSerializableModule;
};
extraModules = lib.mkOption {
description = ''
List of additionally imported `.nix` expressions.
Supported types:
- **Strings**: Interpreted relative to the 'directory' passed to buildClan.
- **Paths**: should be relative to the current file.
- **Any**: Nix expression must be serializable to JSON.
!!! Note
**The import only happens if the machine is part of the service or role.**
Other types are passed through to the nixos configuration.
???+ Example
To import the `special.nix` file
```
. Clan Directory
flake.nix
...
modules
special.nix
...
```
```nix
{
extraModules = [ "modules/special.nix" ];
}
```
'';
apply = value: if lib.isString value then value else builtins.seq (builtins.toJSON value) value;
default = [ ];
type = types.listOf (
types.oneOf [
types.str
types.path
(types.attrsOf types.anything)
]
);
};
};
}