mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 14:50:40 +02:00
feat(topology): add automatic network coloring
This commit is contained in:
parent
dc4d82c828
commit
c3dcc619af
6 changed files with 217 additions and 65 deletions
|
@ -223,12 +223,12 @@
|
|||
networks.home-lan = {
|
||||
name = "Home LAN";
|
||||
cidrv4 = "192.168.1.0/24";
|
||||
color = "#78dba9";
|
||||
#color = "#78dba9";
|
||||
};
|
||||
networks.home-fritzbox = {
|
||||
name = "Home Fritzbox";
|
||||
cidrv4 = "192.168.178.0/24";
|
||||
color = "#f1cf8a";
|
||||
#color = "#f1cf8a";
|
||||
};
|
||||
|
||||
nodes.ward.interfaces.lan.network = "home-lan";
|
||||
|
|
|
@ -6,8 +6,6 @@ f: {
|
|||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
all
|
||||
assertMsg
|
||||
attrNames
|
||||
attrValues
|
||||
concatLines
|
||||
|
@ -18,51 +16,26 @@ f: {
|
|||
flatten
|
||||
flip
|
||||
foldl'
|
||||
isAttrs
|
||||
length
|
||||
mapAttrsToList
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
mkOptionType
|
||||
optional
|
||||
optionalAttrs
|
||||
recursiveUpdate
|
||||
reverseList
|
||||
showOption
|
||||
types
|
||||
unique
|
||||
warnIf
|
||||
;
|
||||
|
||||
# Checks whether the value is a lazy value without causing
|
||||
# it's value to be evaluated
|
||||
isLazyValue = x: isAttrs x && x ? _lazyValue;
|
||||
# Constructs a lazy value holding the given value.
|
||||
lazyValue = value: {_lazyValue = value;};
|
||||
|
||||
# Represents a lazy value of the given type, which
|
||||
# holds the actual value as an attrset like { _lazyValue = <actual value>; }.
|
||||
# This allows the option to be defined and filtered from a defintion
|
||||
# list without evaluating the value.
|
||||
lazyValueOf = type:
|
||||
mkOptionType rec {
|
||||
name = "lazyValueOf ${type.name}";
|
||||
inherit (type) description descriptionClass emptyValue getSubOptions getSubModules;
|
||||
check = isLazyValue;
|
||||
merge = loc: defs:
|
||||
assert assertMsg
|
||||
(all (x: type.check x._lazyValue) defs)
|
||||
"The option `${showOption loc}` is defined with a lazy value holding an invalid type";
|
||||
types.mergeOneOption loc defs;
|
||||
substSubModules = m: types.uniq (type.substSubModules m);
|
||||
functor = (types.defaultFunctor name) // {wrapped = type;};
|
||||
nestedTypes.elemType = type;
|
||||
};
|
||||
|
||||
# Represents a value or lazy value of the given type that will
|
||||
# automatically be coerced to the given type when merged.
|
||||
lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type;
|
||||
inherit
|
||||
(import ../topology/lazy.nix lib)
|
||||
isLazyValue
|
||||
lazyOf
|
||||
lazyValue
|
||||
;
|
||||
|
||||
allNodes = attrNames config.nodes;
|
||||
allInterfacesOf = node: attrNames config.nodes.${node}.interfaces;
|
||||
|
@ -323,6 +296,12 @@ in
|
|||
description = "The other node's interface id.";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
renderer.reverse = mkOption {
|
||||
description = "Whether to reverse the edge. Can be useful to affect node positioning if the layouter is directional.";
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,16 +1,101 @@
|
|||
f: {
|
||||
lib,
|
||||
config,
|
||||
options,
|
||||
...
|
||||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
attrNames
|
||||
attrValues
|
||||
elemAt
|
||||
flatten
|
||||
flip
|
||||
foldl'
|
||||
imap0
|
||||
length
|
||||
mapAttrsToList
|
||||
mkOption
|
||||
mod
|
||||
optional
|
||||
optionalAttrs
|
||||
recursiveUpdate
|
||||
subtractLists
|
||||
types
|
||||
unique
|
||||
warnIf
|
||||
;
|
||||
|
||||
inherit
|
||||
(import ../topology/lazy.nix lib)
|
||||
isLazyValue
|
||||
lazyOf
|
||||
lazyValue
|
||||
;
|
||||
|
||||
style = primaryColor: secondaryColor: pattern: {
|
||||
inherit primaryColor secondaryColor pattern;
|
||||
};
|
||||
|
||||
predefinedStyles = [
|
||||
(style "#f1cf8a" null "solid")
|
||||
(style "#70a5eb" null "solid")
|
||||
(style "#9dd68d" null "solid")
|
||||
(style "#5fe1ff" null "solid")
|
||||
(style "#e05f65" null "solid")
|
||||
(style "#f9a872" null "solid")
|
||||
(style "#78dba9" null "solid")
|
||||
(style "#9378de" null "solid")
|
||||
(style "#c68aee" null "solid")
|
||||
(style "#f5a6b8" null "solid")
|
||||
(style "#70a5eb" null "dashed")
|
||||
(style "#9dd68d" null "dashed")
|
||||
(style "#f1cf8a" null "dashed")
|
||||
(style "#5fe1ff" null "dashed")
|
||||
(style "#e05f65" null "dashed")
|
||||
(style "#f9a872" null "dashed")
|
||||
(style "#78dba9" null "dashed")
|
||||
(style "#9378de" null "dashed")
|
||||
(style "#c68aee" null "dashed")
|
||||
(style "#f5a6b8" null "dashed")
|
||||
(style "#e05f65" "#e3e6eb" "dashed")
|
||||
(style "#70a5eb" "#e3e6eb" "dashed")
|
||||
(style "#9378de" "#e3e6eb" "dashed")
|
||||
(style "#9dd68d" "#707379" "dashed")
|
||||
(style "#f1cf8a" "#707379" "dashed")
|
||||
(style "#5fe1ff" "#707379" "dashed")
|
||||
(style "#f9a872" "#707379" "dashed")
|
||||
(style "#78dba9" "#707379" "dashed")
|
||||
(style "#c68aee" "#707379" "dashed")
|
||||
(style "#f5a6b8" "#707379" "dashed")
|
||||
];
|
||||
|
||||
# A map containing all networks that have an explicitly assigned style, and the style.
|
||||
explicitStyles = foldl' recursiveUpdate {} (flatten (
|
||||
flip map options.networks.definitions (mapAttrsToList (
|
||||
netId: net:
|
||||
optional (net ? style && !isLazyValue net.style) {
|
||||
${netId} = net.style;
|
||||
}
|
||||
))
|
||||
));
|
||||
|
||||
# All unused predefined styles
|
||||
remainingStyles = subtractLists (unique (attrValues explicitStyles)) predefinedStyles;
|
||||
# All networks without styles
|
||||
remainingNets = subtractLists (attrNames explicitStyles) (attrNames config.networks);
|
||||
# Fold over all networks that have no style and assign the next free one. Warn and repeat from beginning if necessary.
|
||||
computedStyles =
|
||||
explicitStyles
|
||||
// warnIf
|
||||
(length remainingNets > length remainingStyles)
|
||||
"topology: There are more networks without styles than predefined styles. Some styles will have to be reused!"
|
||||
(
|
||||
foldl' recursiveUpdate {} (imap0 (i: net: {
|
||||
${net} = elemAt remainingStyles (mod i (length remainingStyles));
|
||||
})
|
||||
remainingNets)
|
||||
);
|
||||
in
|
||||
f {
|
||||
options.networks = mkOption {
|
||||
|
@ -37,15 +122,20 @@ in
|
|||
default = null;
|
||||
};
|
||||
|
||||
color = mkOption {
|
||||
description = "The color of this network";
|
||||
default = "random";
|
||||
apply = x:
|
||||
if x == "random"
|
||||
then "#ff00ff" # FIXME: TODO: lookuptable linear probing hashing into palette
|
||||
else x;
|
||||
type = types.either (types.strMatching "^#[0-9a-f]{6}$") (types.enum ["random"]);
|
||||
};
|
||||
style =
|
||||
mkOption {
|
||||
description = ''
|
||||
A style for this network, usually used to draw connections.
|
||||
Must be an attrset consisting of three attributes:
|
||||
- primaryColor (#rrggbb): The primary color, usually the color of edges.
|
||||
- secondaryColor (#rrggbb): The secondary color, usually the background of a dashed line and only shown when pattern != solid. Set to null for transparent.
|
||||
- pattern (solid, dashed, dotted): The pattern to use.
|
||||
'';
|
||||
type = lazyOf types.attrs;
|
||||
}
|
||||
// optionalAttrs config.topology.isMainModule {
|
||||
default = lazyValue computedStyles.${networkSubmod.config.id};
|
||||
};
|
||||
|
||||
cidrv4 = mkOption {
|
||||
description = "The CIDRv4 address space of this network or null if it doesn't use ipv4";
|
||||
|
|
42
topology/topology/lazy.nix
Normal file
42
topology/topology/lazy.nix
Normal file
|
@ -0,0 +1,42 @@
|
|||
lib: let
|
||||
inherit
|
||||
(lib)
|
||||
all
|
||||
assertMsg
|
||||
isAttrs
|
||||
mkOptionType
|
||||
showOption
|
||||
types
|
||||
;
|
||||
|
||||
# Checks whether the value is a lazy value without causing
|
||||
# it's value to be evaluated
|
||||
isLazyValue = x: isAttrs x && x ? _lazyValue;
|
||||
# Constructs a lazy value holding the given value.
|
||||
lazyValue = value: {_lazyValue = value;};
|
||||
|
||||
# Represents a lazy value of the given type, which
|
||||
# holds the actual value as an attrset like { _lazyValue = <actual value>; }.
|
||||
# This allows the option to be defined and filtered from a defintion
|
||||
# list without evaluating the value.
|
||||
lazyValueOf = type:
|
||||
mkOptionType rec {
|
||||
name = "lazyValueOf ${type.name}";
|
||||
inherit (type) description descriptionClass emptyValue getSubOptions getSubModules;
|
||||
check = isLazyValue;
|
||||
merge = loc: defs:
|
||||
assert assertMsg
|
||||
(all (x: type.check x._lazyValue) defs)
|
||||
"The option `${showOption loc}` is defined with a lazy value holding an invalid type";
|
||||
types.mergeOneOption loc defs;
|
||||
substSubModules = m: types.uniq (type.substSubModules m);
|
||||
functor = (types.defaultFunctor name) // {wrapped = type;};
|
||||
nestedTypes.elemType = type;
|
||||
};
|
||||
|
||||
# Represents a value or lazy value of the given type that will
|
||||
# automatically be coerced to the given type when merged.
|
||||
lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type;
|
||||
in {
|
||||
inherit isLazyValue lazyValue lazyValueOf lazyOf;
|
||||
}
|
|
@ -54,14 +54,17 @@
|
|||
else value
|
||||
);
|
||||
|
||||
mkEdge = from: to: extra: {
|
||||
edges."${from}__${to}" =
|
||||
{
|
||||
sources = [from];
|
||||
targets = [to];
|
||||
}
|
||||
// extra;
|
||||
};
|
||||
mkEdge = from: to: reverse: extra:
|
||||
if reverse
|
||||
then mkEdge to from false extra
|
||||
else {
|
||||
edges."${from}__${to}" =
|
||||
{
|
||||
sources = [from];
|
||||
targets = [to];
|
||||
}
|
||||
// extra;
|
||||
};
|
||||
|
||||
mkPort = recursiveUpdate {
|
||||
width = 8;
|
||||
|
@ -81,6 +84,32 @@
|
|||
// extraStyle;
|
||||
};
|
||||
|
||||
pathStyleFromNetworkStyle = style:
|
||||
{
|
||||
solid = {
|
||||
stroke = style.primaryColor;
|
||||
};
|
||||
dashed =
|
||||
{
|
||||
stroke = style.primaryColor;
|
||||
stroke-dasharray = "10,8";
|
||||
stroke-linecap = "round";
|
||||
}
|
||||
// optionalAttrs (style.secondaryColor != null) {
|
||||
background = style.secondaryColor;
|
||||
};
|
||||
dotted =
|
||||
{
|
||||
stroke = style.primaryColor;
|
||||
stroke-dasharray = "2,6";
|
||||
stroke-linecap = "round";
|
||||
}
|
||||
// optionalAttrs (style.secondaryColor != null) {
|
||||
background = style.secondaryColor;
|
||||
};
|
||||
}
|
||||
.${style.pattern};
|
||||
|
||||
netToElk = net: [
|
||||
{
|
||||
children.network.children."net:${net.id}" = {
|
||||
|
@ -100,15 +129,18 @@
|
|||
idForInterface = node: interfaceId: "children.node:${node.id}.ports.interface:${interfaceId}";
|
||||
|
||||
nodeInterfaceToElk = node: interface: let
|
||||
netStyle = optionalAttrs (interface.network != null) {
|
||||
fill = config.networks.${interface.network}.style.primaryColor;
|
||||
};
|
||||
interfaceLabels =
|
||||
{
|
||||
"00-name" = mkLabel interface.id 1 {};
|
||||
}
|
||||
// optionalAttrs (interface.mac != null) {
|
||||
"50-mac" = mkLabel interface.mac 1 {fill = "#70a5eb";};
|
||||
"50-mac" = mkLabel interface.mac 1 netStyle;
|
||||
}
|
||||
// optionalAttrs (interface.addresses != []) {
|
||||
"60-addrs" = mkLabel (toString interface.addresses) 1 {fill = "#f9a872";};
|
||||
"60-addrs" = mkLabel (toString interface.addresses) 1 netStyle;
|
||||
};
|
||||
in
|
||||
[
|
||||
|
@ -131,8 +163,8 @@
|
|||
|
||||
# Edge in network-centric view
|
||||
(optionalAttrs (interface.network != null) (
|
||||
mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}.ports.default" {
|
||||
style.stroke = config.networks.${interface.network}.color;
|
||||
mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}.ports.default" false {
|
||||
style = pathStyleFromNetworkStyle config.networks.${interface.network}.style;
|
||||
}
|
||||
))
|
||||
]
|
||||
|
@ -149,10 +181,11 @@
|
|||
mkEdge
|
||||
(idForInterface node interface.id)
|
||||
(idForInterface config.nodes.${conn.node} conn.interface)
|
||||
conn.renderer.reverse
|
||||
{
|
||||
style = optionalAttrs (interface.network != null) {
|
||||
stroke = config.networks.${interface.network}.color;
|
||||
};
|
||||
style = optionalAttrs (interface.network != null) (
|
||||
pathStyleFromNetworkStyle config.networks.${interface.network}.style
|
||||
);
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -196,7 +229,7 @@
|
|||
labels."00-name" = mkLabel "guests" 1 {};
|
||||
};
|
||||
}
|
||||
// mkEdge "children.node:${node.parent}.ports.guests" "children.node:${node.id}" {
|
||||
// mkEdge "children.node:${node.parent}.ports.guests" "children.node:${node.id}" false {
|
||||
style.stroke-dasharray = "10,8";
|
||||
style.stroke-linecap = "round";
|
||||
}
|
||||
|
|
|
@ -113,10 +113,18 @@
|
|||
mkCard = net: {
|
||||
width = 480;
|
||||
html = let
|
||||
netColor =
|
||||
if net.color != null
|
||||
then net.color
|
||||
else "#b6beca";
|
||||
netStylePreview = let
|
||||
secondaryColor =
|
||||
if net.style.secondaryColor == null
|
||||
then "#00000000"
|
||||
else net.style.secondaryColor;
|
||||
in
|
||||
{
|
||||
solid = ''<div tw="flex flex-none bg-[${net.style.primaryColor}] w-8 h-4 mr-4 rounded-md"></div>'';
|
||||
dashed = ''<div tw="flex flex-none w-8 h-4 mr-4 rounded-md" style="backgroundImage: linear-gradient(90deg, ${net.style.primaryColor} 0%, ${net.style.primaryColor} 50%, ${secondaryColor} 50.01%, ${secondaryColor} 100%);"></div>'';
|
||||
dotted = ''<div tw="flex flex-none w-8 h-4 mr-4 rounded-md" style="backgroundImage: radial-gradient(circle, ${net.style.primaryColor} 30%, ${secondaryColor} 30.01%);"></div>'';
|
||||
}
|
||||
.${net.style.pattern};
|
||||
in
|
||||
mkCardContainer
|
||||
/*
|
||||
|
@ -124,7 +132,7 @@
|
|||
*/
|
||||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
<div tw="flex flex-none bg-[${netColor}] w-8 h-8 mr-4 rounded-lg"></div>
|
||||
${netStylePreview}
|
||||
<h2 tw="text-2xl font-bold">${net.name}</h2>
|
||||
<div tw="flex grow min-w-8"></div>
|
||||
${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get net.icon)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue