mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 23:00:39 +02:00
refactor(topology): move html to svg to own renderer
This commit is contained in:
parent
c5ff8418ac
commit
23564f980e
7 changed files with 235 additions and 154 deletions
|
@ -16,12 +16,10 @@
|
||||||
substituters = [
|
substituters = [
|
||||||
"https://cache.nixos.org"
|
"https://cache.nixos.org"
|
||||||
"https://nix-community.cachix.org"
|
"https://nix-community.cachix.org"
|
||||||
"https://nix-config.cachix.org"
|
|
||||||
];
|
];
|
||||||
trusted-public-keys = [
|
trusted-public-keys = [
|
||||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||||
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
|
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
|
||||||
"nix-config.cachix.org-1:Vd6raEuldeIZpttVQfrUbLvXJHzzzkS0pezXCVVjDG4="
|
|
||||||
];
|
];
|
||||||
cores = 0;
|
cores = 0;
|
||||||
max-jobs = "auto";
|
max-jobs = "auto";
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
buildNpmPackage,
|
buildNpmPackage,
|
||||||
lib,
|
lib,
|
||||||
}:
|
}:
|
||||||
buildNpmPackage rec {
|
buildNpmPackage {
|
||||||
pname = "html-to-svg";
|
pname = "html-to-svg";
|
||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ in
|
||||||
|
|
||||||
hidden = mkOption {
|
hidden = mkOption {
|
||||||
description = "Whether this service should be hidden from graphs";
|
description = "Whether this service should be hidden from graphs";
|
||||||
default = true;
|
default = false;
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,14 @@ in {
|
||||||
defaultText = literalExpression ''config.renderers.${config.renderer}.output'';
|
defaultText = literalExpression ''config.renderers.${config.renderer}.output'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lib = lib.mkOption {
|
||||||
|
default = {};
|
||||||
|
type = lib.types.attrsOf lib.types.attrs;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
This option allows modules to define helper functions, constants, etc.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
assertions = mkOption {
|
assertions = mkOption {
|
||||||
internal = true;
|
internal = true;
|
||||||
default = [];
|
default = [];
|
||||||
|
|
|
@ -17,7 +17,7 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config.renderers.d2.output = pkgs.runCommand "build-d2-topology" {} ''
|
config.renderers.d2.output = pkgs.runCommand "topology-d2" {} ''
|
||||||
mkdir -p $out
|
mkdir -p $out
|
||||||
# cp ${import ./network.nix args} $out/network.d2
|
# cp ${import ./network.nix args} $out/network.d2
|
||||||
ln -s ${import ./network.nix args} $out/svgs
|
ln -s ${import ./network.nix args} $out/svgs
|
||||||
|
|
|
@ -8,50 +8,8 @@
|
||||||
(lib)
|
(lib)
|
||||||
attrValues
|
attrValues
|
||||||
concatLines
|
concatLines
|
||||||
filter
|
|
||||||
hasSuffix
|
|
||||||
head
|
|
||||||
optionalString
|
|
||||||
splitString
|
|
||||||
tail
|
|
||||||
;
|
;
|
||||||
|
|
||||||
getIcon = registry: iconName:
|
|
||||||
if iconName == null
|
|
||||||
then null
|
|
||||||
else config.icons.${registry}.${iconName}.file or null;
|
|
||||||
|
|
||||||
mkImage = twAttrs: file:
|
|
||||||
if file == null
|
|
||||||
then ''
|
|
||||||
<div tw="flex flex-none bg-[#000000] ${twAttrs}"></div>
|
|
||||||
''
|
|
||||||
else if hasSuffix ".svg" file
|
|
||||||
then let
|
|
||||||
withoutPrefix = head (tail (splitString "<svg " (builtins.readFile file)));
|
|
||||||
content = head (splitString "</svg>" withoutPrefix);
|
|
||||||
in ''<svg tw="${twAttrs}" ${content}</svg>''
|
|
||||||
else if hasSuffix ".png" file
|
|
||||||
# FIXME: TODO png, jpg, ...
|
|
||||||
then ''
|
|
||||||
<img tw="${twAttrs}" src="data:image/png;base64,${"TODO"}/>"
|
|
||||||
''
|
|
||||||
else builtins.throw "Unsupported icon file type: ${file}";
|
|
||||||
|
|
||||||
mkSpacer = name:
|
|
||||||
/*
|
|
||||||
html
|
|
||||||
*/
|
|
||||||
''
|
|
||||||
<div tw="flex flex-row w-full items-center">
|
|
||||||
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
|
||||||
<div tw="flex px-4">
|
|
||||||
<span tw="text-[#b6beca] font-bold">${name}</span>
|
|
||||||
</div>
|
|
||||||
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
|
||||||
</div>
|
|
||||||
'';
|
|
||||||
|
|
||||||
netToD2 = net: ''
|
netToD2 = net: ''
|
||||||
${net.id}: ${net.name} {
|
${net.id}: ${net.name} {
|
||||||
info: |md
|
info: |md
|
||||||
|
@ -79,117 +37,13 @@
|
||||||
# ${node.id}.${interface.id} -- ${x.node}.${x.interface}
|
# ${node.id}.${interface.id} -- ${x.node}.${x.interface}
|
||||||
#''));
|
#''));
|
||||||
|
|
||||||
nodeInterfaceHtmlSpacing = ''
|
|
||||||
<div tw="flex mt-2"></div>
|
|
||||||
'';
|
|
||||||
nodeInterfaceHtml = interface: let
|
|
||||||
color =
|
|
||||||
if interface.virtual
|
|
||||||
then "#242931"
|
|
||||||
else "#70a5eb";
|
|
||||||
in
|
|
||||||
/*
|
|
||||||
html
|
|
||||||
*/
|
|
||||||
''
|
|
||||||
<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)}
|
|
||||||
<span tw="font-bold">${interface.id}</span>
|
|
||||||
</div>
|
|
||||||
<span>addrs: ${toString interface.addresses}</span>
|
|
||||||
</div>
|
|
||||||
'';
|
|
||||||
|
|
||||||
nodeServiceDetailsHeader =
|
|
||||||
/*
|
|
||||||
html
|
|
||||||
*/
|
|
||||||
''
|
|
||||||
<div tw="flex pt-2"></div>
|
|
||||||
'';
|
|
||||||
|
|
||||||
nodeServiceDetail = detail:
|
|
||||||
/*
|
|
||||||
html
|
|
||||||
*/
|
|
||||||
''
|
|
||||||
<div tw="flex flex-row mt-1">
|
|
||||||
<span tw="flex flex-none w-20 font-bold pl-1">${detail.name}</span>
|
|
||||||
<span tw="flex grow">${detail.text}</span>
|
|
||||||
</div>
|
|
||||||
'';
|
|
||||||
|
|
||||||
nodeServiceDetails = service:
|
|
||||||
optionalString (service.details != {}) nodeServiceDetailsHeader
|
|
||||||
# FIXME: order not respected
|
|
||||||
+ concatLines ((map nodeServiceDetail) (attrValues service.details));
|
|
||||||
|
|
||||||
nodeServiceHtml = service:
|
|
||||||
/*
|
|
||||||
html
|
|
||||||
*/
|
|
||||||
''
|
|
||||||
<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)}
|
|
||||||
<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>''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${nodeServiceDetails service}
|
|
||||||
</div>
|
|
||||||
'';
|
|
||||||
|
|
||||||
nodeHtml = node: let
|
|
||||||
services = filter (x: !x.hidden) (attrValues node.services);
|
|
||||||
in
|
|
||||||
/*
|
|
||||||
html
|
|
||||||
*/
|
|
||||||
''
|
|
||||||
<div tw="flex flex-col w-full h-full items-center font-mono text-[#e3e6eb]" style="font-family: 'JetBrains Mono'">
|
|
||||||
<div tw="flex flex-col w-full h-full bg-[#101419] py-2 rounded-xl">
|
|
||||||
<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" style="font-family: 'Segoe UI Emoji'">${node.type}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${optionalString (node.interfaces != {}) (mkSpacer "Interfaces" + nodeInterfaceHtmlSpacing)}
|
|
||||||
${concatLines (map nodeInterfaceHtml (attrValues node.interfaces))}
|
|
||||||
${optionalString (node.interfaces != {}) nodeInterfaceHtmlSpacing}
|
|
||||||
|
|
||||||
${optionalString (services != []) (mkSpacer "Services")}
|
|
||||||
${concatLines (map nodeServiceHtml services)}
|
|
||||||
|
|
||||||
<div tw="flex mb-2"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
'';
|
|
||||||
|
|
||||||
nodeToD2 = node: ''
|
nodeToD2 = node: ''
|
||||||
${node.id}: ${node.name} {}
|
${node.id}: ${node.name} {}
|
||||||
|
|
||||||
${concatLines (map (nodeInterfaceToD2 node) (attrValues node.interfaces))}
|
${concatLines (map (nodeInterfaceToD2 node) (attrValues node.interfaces))}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
generateNodeSvg = node: ''
|
|
||||||
${lib.getExe pkgs.html-to-svg} \
|
|
||||||
--font ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Regular.ttf \
|
|
||||||
--font-bold ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Bold.ttf \
|
|
||||||
--width 680 \
|
|
||||||
${pkgs.writeText "${node.name}.html" (nodeHtml node)} \
|
|
||||||
$out/${node.name}.svg
|
|
||||||
'';
|
|
||||||
in
|
in
|
||||||
#pkgs.writeText "network.d2" ''
|
pkgs.writeText "network.d2" ''
|
||||||
# ${concatLines (map netToD2 (attrValues config.networks))}
|
${concatLines (map netToD2 (attrValues config.networks))}
|
||||||
# ${concatLines (map nodeToD2 (attrValues config.nodes))}
|
${concatLines (map nodeToD2 (attrValues config.nodes))}
|
||||||
#''
|
|
||||||
pkgs.runCommand "generate-node-svgs" {} ''
|
|
||||||
mkdir -p $out
|
|
||||||
${concatLines (map generateNodeSvg (attrValues config.nodes))}
|
|
||||||
''
|
''
|
||||||
|
|
221
topology/topology/renderers/svg/default.nix
Normal file
221
topology/topology/renderers/svg/default.nix
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(lib)
|
||||||
|
attrValues
|
||||||
|
concatLines
|
||||||
|
filter
|
||||||
|
flip
|
||||||
|
hasSuffix
|
||||||
|
head
|
||||||
|
mkOption
|
||||||
|
optionalString
|
||||||
|
splitString
|
||||||
|
tail
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
htmlToSvgCommand = inFile: outFile: ''
|
||||||
|
${lib.getExe pkgs.html-to-svg} \
|
||||||
|
--font ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Regular.ttf \
|
||||||
|
--font-bold ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Bold.ttf \
|
||||||
|
--width 680 \
|
||||||
|
${inFile} ${outFile}
|
||||||
|
'';
|
||||||
|
|
||||||
|
renderHtmlToSvg = html: name: let
|
||||||
|
drv = pkgs.runCommand "generate-svg-${name}" {} ''
|
||||||
|
mkdir -p $out
|
||||||
|
${htmlToSvgCommand (pkgs.writeText "${name}.html" html) "$out/${name}.svg"}
|
||||||
|
'';
|
||||||
|
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
|
||||||
|
then ''
|
||||||
|
<div tw="flex flex-none bg-[#000000] ${twAttrs}"></div>
|
||||||
|
''
|
||||||
|
else if hasSuffix ".svg" file
|
||||||
|
then let
|
||||||
|
withoutPrefix = head (tail (splitString "<svg " (builtins.readFile file)));
|
||||||
|
content = head (splitString "</svg>" withoutPrefix);
|
||||||
|
in ''<svg tw="${twAttrs}" ${content}</svg>''
|
||||||
|
else if hasSuffix ".png" file
|
||||||
|
# FIXME: TODO png, jpg, ...
|
||||||
|
then ''
|
||||||
|
<img tw="${twAttrs}" src="data:image/png;base64,${"TODO"}/>"
|
||||||
|
''
|
||||||
|
else builtins.throw "Unsupported icon file type: ${file}";
|
||||||
|
|
||||||
|
mkSpacer = name:
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
<div tw="flex flex-row w-full items-center">
|
||||||
|
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
||||||
|
<div tw="flex px-4">
|
||||||
|
<span tw="text-[#b6beca] font-bold">${name}</span>
|
||||||
|
</div>
|
||||||
|
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
||||||
|
</div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkRootContainer = contents:
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
<div tw="flex flex-col w-full h-full items-center">
|
||||||
|
${contents}
|
||||||
|
</div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkRootCard = twAttrs: contents:
|
||||||
|
mkRootContainer
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
<div tw="flex flex-col w-full h-full bg-[#101419] text-[#e3e6eb] font-mono ${twAttrs}" style="font-family: 'JetBrains Mono'">
|
||||||
|
${contents}
|
||||||
|
</div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
spacingMt2 = ''
|
||||||
|
<div tw="flex mt-2"></div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
node = rec {
|
||||||
|
mkInterface = interface: let
|
||||||
|
color =
|
||||||
|
if interface.virtual
|
||||||
|
then "#242931"
|
||||||
|
else "#70a5eb";
|
||||||
|
in
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
<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)}
|
||||||
|
<span tw="font-bold">${interface.id}</span>
|
||||||
|
</div>
|
||||||
|
<span>addrs: ${toString interface.addresses}</span>
|
||||||
|
</div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceDetail = detail:
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
<div tw="flex flex-row mt-1">
|
||||||
|
<span tw="flex flex-none w-20 font-bold pl-1">${detail.name}</span>
|
||||||
|
<span tw="flex grow">${detail.text}</span>
|
||||||
|
</div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceDetails = service:
|
||||||
|
optionalString (service.details != {}) ''<div tw="flex pt-2"></div>''
|
||||||
|
# FIXME: order not respected
|
||||||
|
+ concatLines ((map serviceDetail) (attrValues service.details));
|
||||||
|
|
||||||
|
mkService = service:
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
<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)}
|
||||||
|
<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>''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${serviceDetails service}
|
||||||
|
</div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkTitle = node:
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkInfoCardTitle = node:
|
||||||
|
mkRootCard "rounded-t-xl"
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
${mkTitle node}
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkInfoCardFull = node: let
|
||||||
|
services = filter (x: !x.hidden) (attrValues node.services);
|
||||||
|
in
|
||||||
|
mkRootCard "rounded-xl"
|
||||||
|
/*
|
||||||
|
html
|
||||||
|
*/
|
||||||
|
''
|
||||||
|
${mkTitle node}
|
||||||
|
|
||||||
|
${optionalString (node.interfaces != {}) (mkSpacer "Interfaces" + spacingMt2)}
|
||||||
|
${concatLines (map mkInterface (attrValues node.interfaces))}
|
||||||
|
${optionalString (node.interfaces != {}) spacingMt2}
|
||||||
|
|
||||||
|
${optionalString (services != []) (mkSpacer "Services")}
|
||||||
|
${concatLines (map mkService services)}
|
||||||
|
|
||||||
|
<div tw="flex mb-2"></div>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options.renderers.svg = {
|
||||||
|
# FIXME: colors.bg0 = mkColorOption "bg0" "#";
|
||||||
|
|
||||||
|
output = mkOption {
|
||||||
|
description = "The derivation containing the rendered output";
|
||||||
|
type = types.path;
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
lib.renderers.svg.node = {
|
||||||
|
mkInfoCardFull = node: renderHtmlToSvg (html.node.mkInfoCardFull node) node.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderers.svg.output = pkgs.runCommand "topology-svgs" {} ''
|
||||||
|
mkdir -p $out/nodes
|
||||||
|
${concatLines (flip map (attrValues config.nodes) (
|
||||||
|
node:
|
||||||
|
htmlToSvgCommand (
|
||||||
|
pkgs.writeText "node-${node.name}.html" (html.node.mkInfoCardFull node)
|
||||||
|
) "$out/nodes/${node.name}.svg"
|
||||||
|
))}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue