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:
parent
23564f980e
commit
c0cccc298d
9 changed files with 170 additions and 45 deletions
57
topology/icons/devices/ethernet.svg
Normal file
57
topology/icons/devices/ethernet.svg
Normal 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 |
|
@ -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}";};
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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")
|
||||
]
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}")
|
||||
]
|
||||
)
|
||||
));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
'';
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue