1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-10 14:50:40 +02:00

feat(topology): generalize icon definitions and assertions

This commit is contained in:
oddlama 2024-03-18 23:37:26 +01:00
parent 23564f980e
commit c0cccc298d
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
9 changed files with 170 additions and 45 deletions

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<g>
<g>
<path d="M168,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
C180,189.412,174.624,184.036,168,184.036z"/>
</g>
</g>
<g>
<g>
<path d="M256,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
C268,189.412,262.624,184.036,256,184.036z"/>
</g>
</g>
<g>
<g>
<path d="M212,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
C224,189.412,218.624,184.036,212,184.036z"/>
</g>
</g>
<g>
<g>
<path d="M460,0H52C23.28,0,0,23.28,0,52v408c0,28.72,23.28,52,52,52h408c28.72,0,52-23.28,52-52V52C512,23.28,488.72,0,460,0z
M444,284h-88.024c-2.212,0-3.976,1.64-3.976,3.848V348H160v-60.152c0-2.208-1.616-3.848-3.828-3.848H68V68h44v92.184
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h20v92.184
c0,6.624,5.368,12,12,12c6.624,0,12-5.376,12-12V68h44V284z"/>
</g>
</g>
<g>
<g>
<path d="M124,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
C136,189.412,130.624,184.036,124,184.036z"/>
</g>
</g>
<g>
<g>
<path d="M388,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
C400,189.412,394.624,184.036,388,184.036z"/>
</g>
</g>
<g>
<g>
<path d="M344,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
C356,189.412,350.624,184.036,344,184.036z"/>
</g>
</g>
<g>
<g>
<path d="M300,184.036c-6.632,0-12,5.376-12,12v23.752c0,6.628,5.368,12,12,12c6.624,0,12-5.372,12-12v-23.752
C312,189.412,306.624,184.036,300,184.036z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -14,7 +14,7 @@ in {
openssh = mkIf config.services.openssh.enable {
hidden = mkDefault true; # Causes a lot of much clutter
name = "OpenSSH";
icon = "openssh";
icon = "services.openssh";
info = "port: ${concatStringsSep ", " (map toString config.services.openssh.ports)}";
};
@ -25,7 +25,7 @@ in {
in
mkIf config.services.vaultwarden.enable {
name = "Vaultwarden";
icon = "vaultwarden";
icon = "services.vaultwarden";
info = mkIf (domain != null) domain;
details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";};
};

View file

@ -45,10 +45,18 @@ in {
default = config.networking.hostName;
type = types.str;
};
isMainModule = mkOption {
description = "Whether this is the toplevel topology module.";
readOnly = true;
internal = true;
default = false;
type = types.bool;
};
};
config.topology = {
# Ensure a node exists for this host
nodes.${config.topology.id}.type = "nixos";
nodes.${config.topology.id}.deviceType = "nixos";
};
}

View file

@ -7,7 +7,11 @@ f: {
(lib)
concatStringsSep
filterAttrs
getAttrFromPath
hasAttrByPath
hasPrefix
init
mapAttrs
mapAttrs'
mkOption
nameValuePair
@ -30,22 +34,38 @@ f: {
};
};
};
mkIcons = name:
mkOption {
description = "Defines icons for ${name}.";
default = {};
type = types.attrsOf iconType;
};
in
f {
options.icons = {
interfaces = mkIcons "network interface types";
services = mkIcons "services";
options.icons = mkOption {
description = "All predefined icons by category.";
default = {};
type = types.attrsOf (types.attrsOf iconType);
};
config.icons = {
interfaces = iconsFromFiles ../icons/interfaces;
services = iconsFromFiles ../icons/services;
config = {
# The necessary assertion for an icon type
lib.assertions.iconValid = icon: path: {
assertion = icon != null -> (!hasPrefix "/" icon -> hasAttrByPath (splitString "." icon) config.icons);
message = "topology: ${path} refers to an unknown icon icons.${icon}";
};
# The function that returns the image path for an icon option
lib.icons.get = icon: let
attrPath = splitString "." icon;
in
if icon == null
then null
# Icon is a path
else if hasPrefix "/" icon
then icon
# Icon is a valid icon name
else if hasAttrByPath attrPath config.icons
then (getAttrFromPath attrPath config.icons).file
# Icon is not valid
else throw "Reference to unknown icon ${icon} detected. Aborting.";
icons =
mapAttrs (n: _: iconsFromFiles ../icons/${n})
(filterAttrs (_: v: v == "directory") (builtins.readDir ../icons));
};
}

View file

@ -9,6 +9,7 @@ f: {
flatten
flip
mkDefault
mkIf
mkOption
types
;
@ -48,8 +49,9 @@ in
};
icon = mkOption {
description = "The icon for this interface. Must be a valid entry in icons.interfaces. If null, an icon will be selected based on the type.";
type = types.nullOr types.str;
description = "The icon representing this interface's type. Must be a path to an image or a valid icon name (<category>.<name>). By default an icon will be selected based on the type.";
type = types.nullOr (types.either types.path types.str);
default = null;
};
addresses = mkOption {
@ -90,14 +92,9 @@ in
};
config = {
icon = mkDefault (
{
ethernet = "ethernet";
wireguard = "wireguard";
wifi = "wifi";
}
.${submod.config.type}
or null
# Set the default icon, if an icon exists with a matching name
icon = mkIf (config.topology.isMainModule && config.icons.interfaces ? ${submod.config.type}) (
mkDefault ("interfaces." + submod.config.type)
);
};
}));
@ -116,10 +113,8 @@ in
assertion = interface.network != null -> config.networks ? ${interface.network};
message = "topology: nodes.${node.id}.interfaces.${interface.id} refers to an unknown network '${interface.network}'";
}
{
assertion = interface.icon != null -> config.icons.interfaces ? ${interface.icon};
message = "topology: nodes.${node.id}.interfaces.${interface.id} refers to an unknown icon icons.interfaces.${interface.icon}";
}
(config.lib.assertions.iconValid
interface.icon "nodes.${node.id}.interfaces.${interface.id}")
]
++ flip map interface.physicalConnections (
physicalConnection: {

View file

@ -5,7 +5,12 @@ f: {
}: let
inherit
(lib)
attrValues
flatten
flip
literalExpression
mkDefault
mkIf
mkOption
types
;
@ -33,22 +38,52 @@ in
defaultText = literalExpression ''"<name>"'';
};
icon = mkOption {
description = "The icon representing this node. Usually shown next to the name. Must be a path to an image or a valid icon name (<category>.<name>).";
type = types.nullOr (types.either types.path types.str);
default = null;
};
# FIXME: TODO emoji / icon
# FIXME: TODO hardware description "Odroid H3"
# FIXME: TODO hardware image
# FIXME: TODO are these good types? how about nixos vs router vs ...
type = mkOption {
deviceType = mkOption {
description = "TODO";
type = types.enum ["nixos" "microvm" "nixos-container"];
};
deviceIcon = mkOption {
description = "The icon representing this node's type. Must be a path to an image or a valid icon name (<category>.<name>). By default an icon will be selected based on the deviceType.";
type = types.nullOr (types.either types.path types.str);
default = null;
};
parent = mkOption {
description = "The id of the parent node, if this node has a parent.";
default = null;
type = types.nullOr types.str;
};
};
config = {
# Set the default icon, if an icon exists with a matching name
deviceIcon = mkIf (config.topology.isMainModule && config.icons.devices ? ${nodeSubmod.config.deviceType}) (
mkDefault ("interfaces." + nodeSubmod.config.deviceType)
);
};
}));
};
config = {
assertions = flatten (
flip map (attrValues config.nodes) (
node: [
(config.lib.assertions.iconValid
node.deviceIcon "nodes.${node.id}.deviceIcon")
]
)
);
};
}

View file

@ -8,6 +8,8 @@ f: {
attrValues
flatten
flip
mkDefault
mkIf
mkOption
types
;
@ -40,8 +42,8 @@ in
};
icon = mkOption {
description = "The icon for this service. Must be a valid entry in icons.services.";
type = types.nullOr types.str;
description = "The icon for this service. Must be a path to an image or a valid icon name (<category>.<name>).";
type = types.nullOr (types.either types.path types.str);
default = null;
};
@ -77,6 +79,13 @@ in
}));
};
};
config = {
# Set the default icon, if an icon exists with a matching name
icon = mkIf (config.topology.isMainModule && config.icons.services ? ${submod.config.id}) (
mkDefault ("services." + submod.config.id)
);
};
}));
};
};
@ -88,10 +97,8 @@ in
node:
flip map (attrValues node.services) (
service: [
{
assertion = service.icon != null -> config.icons.services ? ${service.icon};
message = "topology: nodes.${node.id}.services.${service.id} refers to an unknown icon icons.services.${service.icon}";
}
(config.lib.assertions.iconValid
service.icon "nodes.${node.id}.services.${service.id}")
]
)
));

View file

@ -93,6 +93,14 @@ in {
};
});
};
topology.isMainModule = mkOption {
description = "Whether this is the toplevel topology module.";
readOnly = true;
internal = true;
default = true;
type = types.bool;
};
};
config = let

View file

@ -34,11 +34,6 @@
'';
in "${drv}/${name}.svg";
getIcon = registry: iconName:
if iconName == null
then null
else config.icons.${registry}.${iconName}.file or null;
html = rec {
mkImage = twAttrs: file:
if file == null
@ -110,7 +105,7 @@
<div tw="flex flex-row items-center my-2">
<div tw="flex flex-row flex-none bg-[${color}] w-4 h-1"></div>
<div tw="flex flex-row flex-none items-center bg-[${color}] text-[#101419] rounded-lg px-2 py-1 w-46 h-8 mr-4">
${mkImage "w-6 h-6 mr-2" (getIcon "interfaces" interface.icon)}
${mkImage "w-6 h-6 mr-2" (config.lib.icons.get interface.icon)}
<span tw="font-bold">${interface.id}</span>
</div>
<span>addrs: ${toString interface.addresses}</span>
@ -140,7 +135,7 @@
''
<div tw="flex flex-col mx-4 mt-4 bg-[#21262e] rounded-lg p-2">
<div tw="flex flex-row items-center">
${mkImage "w-16 h-16 mr-4 rounded-lg" (getIcon "services" service.icon)}
${mkImage "w-16 h-16 mr-4 rounded-lg" (config.lib.icons.get service.icon)}
<div tw="flex flex-col grow">
<h1 tw="text-xl font-bold m-0">${service.name}</h1>
${optionalString (service.info != "") ''<p tw="text-base m-0">${service.info}</p>''}
@ -158,7 +153,7 @@
<div tw="flex flex-row mx-6 my-2">
<h2 tw="grow text-4xl font-bold">${node.name}</h2>
<div tw="flex grow"></div>
<h2 tw="text-4xl">${node.type}</h2>
<h2 tw="text-4xl">${node.deviceType}</h2>
</div>
'';