Merge branch 'main' into Qubasa-Qubasa-main

This commit is contained in:
Mic92
2023-09-06 15:40:21 +00:00
50 changed files with 1701 additions and 677 deletions

View File

@@ -1,16 +1,23 @@
{ self, ... }: { { self, ... }: {
perSystem = { pkgs, lib, ... }: perSystem = { pkgs, lib, self', ... }:
let let
impureChecks = { impureChecks = {
check-clan-template = pkgs.writeShellScriptBin "check-clan-template" '' check-clan-template = pkgs.writeShellScriptBin "check-clan-template" ''
#!${pkgs.bash}/bin/bash #!${pkgs.bash}/bin/bash
set -euo pipefail set -euo pipefail
export TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d) export TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d)
trap "${pkgs.coreutils}/bin/chmod -R +w '$TMPDIR'; ${pkgs.coreutils}/bin/rm -rf '$TMPDIR'" EXIT trap "${pkgs.coreutils}/bin/chmod -R +w '$TMPDIR'; ${pkgs.coreutils}/bin/rm -rf '$TMPDIR'" EXIT
export PATH="${lib.makeBinPath [ export PATH="${lib.makeBinPath [
pkgs.coreutils
pkgs.curl
pkgs.gitMinimal pkgs.gitMinimal
pkgs.gnugrep
pkgs.jq
pkgs.openssh pkgs.openssh
pkgs.nix pkgs.nix
self'.packages.clan-cli
]}" ]}"
cd $TMPDIR cd $TMPDIR
@@ -18,8 +25,24 @@
echo initialize new clan echo initialize new clan
nix flake init -t ${self}#new-clan nix flake init -t ${self}#new-clan
echo override clan input to the current version
nix flake lock --override-input clan-core ${self}
nix flake lock --override-input nixpkgs ${self.inputs.nixpkgs}
echo ensure flake outputs can be listed echo ensure flake outputs can be listed
nix flake show nix flake show
echo create a machine
clan machines create machine1
echo check machine1 exists
clan machines list | grep -q machine1
echo check machine1 appears in nixosConfigurations
nix flake show --json | jq '.nixosConfigurations' | grep -q machine1
echo check machine1 jsonschema can be evaluated
nix eval .#nixosConfigurations.machine1.config.clanSchema
''; '';
}; };
in in

View File

@@ -5,13 +5,17 @@
imports = [ imports = [
(self.nixosModules.clanCore) (self.nixosModules.clanCore)
]; ];
environment.etc."secret".source = config.sops.secrets.foo.path; environment.etc."secret".source = config.sops.secrets.secret.path;
environment.etc."group-secret".source = config.sops.secrets.group-secret.path;
sops.age.keyFile = ./key.age; sops.age.keyFile = ./key.age;
clanCore.clanDir = "${./.}"; clanCore.clanDir = "${./.}";
clanCore.machineName = "machine"; clanCore.machineName = "machine";
networking.hostName = "machine"; networking.hostName = "machine";
}; };
testScript = '' testScript = ''
machine.succeed("cat /etc/secret >&2") machine.succeed("cat /etc/secret >&2")
machine.succeed("cat /etc/group-secret >&2")
''; '';
} }

View File

@@ -0,0 +1 @@
../../../groups/group

View File

@@ -0,0 +1,20 @@
{
"data": "ENC[AES256_GCM,data:FgF3,iv:QBbnqZ6405qmwGKhbolPr9iobngXt8rtfUwCBOnmwRA=,tag:7gqI1zLVnTkZ0xrNn/LEkA==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age15x8u838dwqflr3t6csf4tlghxm4tx77y379ncqxav7y2n8qp7yzqgrwt00",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArMHcxKzhUZzNHQmQrb28x\nRC9UMlZMeDN3S1l1eHdUWmV4VUVReHhhQ0RnCjAyUXVlY1FmclVmL2lEdFZuTmll\nVENpa3AwbjlDck5zdGdHUTRnNEdEOUkKLS0tIER3ZlNMSVFnRElkRDcxajZnVmFl\nZThyYzcvYUUvaWJYUmlwQ3dsSDdjSjgK+tj34yBzrsIjm6V+T9wTgz5FdNGOR7I/\nVB4fh8meW0vi/PCK/rajC8NbqmK8qq/lwsF/JwfZKDSdG0FOJUB1AA==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2023-09-03T12:44:56Z",
"mac": "ENC[AES256_GCM,data:d5a0WfE5ZRLKF1NZkBfOl+cVI8ZZHd2rC+qX/giALjyrzk09rLxBeY4lO827GFfMmVy/oC7ceH9pjv2O7ibUiQtcbGIQVBg/WP+dVn8fRMWtF0jpv9BhYTutkVk3kiddqPGhp3mpwvls2ot5jtCRczTPk3JSxN3B1JSJCmj9GfQ=,iv:YmlkTYFNUaFRWozO8+OpEVKaSQmh+N9zpatwUNMPNyw=,tag:mEGQ4tdo82qlhKWalQuufg==,type:str]",
"pgp": null,
"unencrypted_suffix": "_unencrypted",
"version": "3.7.3"
}
}

View File

@@ -0,0 +1 @@
../../../machines/machine

30
flake.lock generated
View File

@@ -7,11 +7,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1692199161, "lastModified": 1693677537,
"narHash": "sha256-GqKApvQ1JCf5DzH/Q+P4nwuHb6MaQGaWTu41lYzveF4=", "narHash": "sha256-F8ozidIQV4Sp/IfTE54U+qIOuC88b9WskFWK5VrHBs4=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "4eed2457b053c4bbad7d90d2b3a1d539c2c9009c", "rev": "06481a9836c37b7c1aba784092a984c2d2ef5431",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -27,11 +27,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1690933134, "lastModified": 1693611461,
"narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=", "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb", "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -98,11 +98,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1693003285, "lastModified": 1693663421,
"narHash": "sha256-5nm4yrEHKupjn62MibENtfqlP6pWcRTuSKrMiH9bLkc=", "narHash": "sha256-ImMIlWE/idjcZAfxKK8sQA7A1Gi/O58u5/CJA+mxvl8=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5690c4271f2998c304a45c91a0aeb8fb69feaea7", "rev": "e56990880811a451abd32515698c712788be5720",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -131,11 +131,11 @@
"nixpkgs-stable": [] "nixpkgs-stable": []
}, },
"locked": { "locked": {
"lastModified": 1693105804, "lastModified": 1693404499,
"narHash": "sha256-nlqNjW7dfucUJQqRGuG08MKPOSME8fLOCx/bd9hiEPs=", "narHash": "sha256-cx/7yvM/AP+o/3wPJmA9W9F+WHemJk5t+Xcr+Qwkqhg=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "0618c8f0ed5255ad74ee08d1618841ff5af85c86", "rev": "d9c5dc41c4b1f74c77f0dbffd0f3a4ebde447b7a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -151,11 +151,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1692972530, "lastModified": 1693689099,
"narHash": "sha256-LG+M7TjlLJ1lx2qbD1yaexvue1VAatpVandtHVEN5Lc=", "narHash": "sha256-NuilTRYMH+DDR/uBWQjDbX5mWCA05lwo2Sg9iTkkEs4=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "843e1e1b01ac7c9e858368fffd1692cbbdbe4a0e", "rev": "e3e0f9f6d47f8fc68aff15150eda1224fb46f4d4",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -1,28 +1,31 @@
{ nixpkgs, clan, lib }: { nixpkgs, self, lib }:
{ directory # The directory containing the machines subdirectory { directory # The directory containing the machines subdirectory
, specialArgs ? { } # Extra arguments to pass to nixosSystem i.e. useful to make self available , specialArgs ? { } # Extra arguments to pass to nixosSystem i.e. useful to make self available
, machines ? { } # allows to include machine-specific modules i.e. machines.${name} = { ... } , machines ? { } # allows to include machine-specific modules i.e. machines.${name} = { ... }
}: }:
let let
machinesDirs = machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (builtins.readDir (directory + /machines));
if builtins.pathExists (directory + /machines)
then builtins.readDir (directory + /machines)
else { };
machineSettings = machineName: machineSettings = machineName:
if builtins.pathExists (directory + /machines/${machineName}/settings.json) lib.optionalAttrs (builtins.pathExists "${directory}/machines/${machineName}/settings.json")
then builtins.fromJSON (builtins.readFile (directory + /machines/${machineName}/settings.json)) (builtins.fromJSON
else { }; (builtins.readFile (directory + /machines/${machineName}/settings.json)));
nixosConfigurations = lib.mapAttrs nixosConfigurations = lib.mapAttrs
(name: _: (name: _:
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
modules = [ modules = [
clan.nixosModules.clanCore self.nixosModules.clanCore
(machineSettings name) (machineSettings name)
(machines.${name} or { }) (machines.${name} or { })
{
clanCore.machineName = name;
clanCore.clanDir = directory;
# TODO: remove this once we have a hardware-config mechanism
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
]; ];
specialArgs = specialArgs; inherit specialArgs;
}) })
(machinesDirs // machines); (machinesDirs // machines);
in in

View File

@@ -1,4 +1,4 @@
{ lib, clan, nixpkgs, ... }: { lib, self, nixpkgs, ... }:
{ {
findNixFiles = folder: findNixFiles = folder:
lib.mapAttrs' lib.mapAttrs'
@@ -14,5 +14,5 @@
jsonschema = import ./jsonschema { inherit lib; }; jsonschema = import ./jsonschema { inherit lib; };
buildClan = import ./build-clan { inherit lib clan nixpkgs; }; buildClan = import ./build-clan { inherit lib self nixpkgs; };
} }

View File

@@ -1,5 +1,6 @@
{ lib { lib
, inputs , inputs
, self
, ... , ...
}: { }: {
imports = [ imports = [
@@ -7,6 +8,7 @@
]; ];
flake.lib = import ./default.nix { flake.lib = import ./default.nix {
inherit lib; inherit lib;
inherit (inputs) nixpkgs clan; inherit self;
inherit (inputs) nixpkgs;
}; };
} }

View File

@@ -3,16 +3,19 @@
*/ */
{ lib, ... }: { { lib, ... }: {
options = { options = {
# str
name = lib.mkOption { name = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "John Doe"; default = "John Doe";
description = "The name of the user"; description = "The name of the user";
}; };
# int
age = lib.mkOption { age = lib.mkOption {
type = lib.types.int; type = lib.types.int;
default = 42; default = 42;
description = "The age of the user"; description = "The age of the user";
}; };
# bool
isAdmin = lib.mkOption { isAdmin = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = false;
@@ -28,6 +31,7 @@
}; };
}; };
}; };
# attrs of int
userIds = lib.mkOption { userIds = lib.mkOption {
type = lib.types.attrsOf lib.types.int; type = lib.types.attrsOf lib.types.int;
description = "Some attributes"; description = "Some attributes";
@@ -37,6 +41,7 @@
albrecht = 3; albrecht = 3;
}; };
}; };
# list of str
kernelModules = lib.mkOption { kernelModules = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ "nvme" "xhci_pci" "ahci" ]; default = [ "nvme" "xhci_pci" "ahci" ];

View File

@@ -0,0 +1,9 @@
{ lib, ... }: {
options.clan.bloatware = lib.mkOption {
type = lib.types.submodule {
imports = [
../../../lib/jsonschema/example-interface.nix
];
};
};
}

View File

@@ -1,8 +1,20 @@
{ self, inputs, lib, ... }: { { self, inputs, lib, ... }: {
flake.nixosModules.clanCore = { pkgs, ... }: { flake.nixosModules.clanCore = { pkgs, options, ... }: {
imports = [
./secrets
./zerotier.nix
inputs.sops-nix.nixosModules.sops
# just some example options. Can be removed later
./bloatware
];
options.clanSchema = lib.mkOption {
type = lib.types.attrs;
description = "The json schema for the .clan options namespace";
default = self.lib.jsonschema.parseOptions options.clan;
};
options.clanCore = { options.clanCore = {
clanDir = lib.mkOption { clanDir = lib.mkOption {
type = lib.types.str; type = lib.types.either lib.types.path lib.types.str;
description = '' description = ''
the location of the flake repo, used to calculate the location of facts and secrets the location of the flake repo, used to calculate the location of facts and secrets
''; '';
@@ -23,10 +35,5 @@
utility outputs for clan management of this machine utility outputs for clan management of this machine
''; '';
}; };
imports = [
./secrets
./zerotier.nix
inputs.sops-nix.nixosModules.sops
];
}; };
} }

View File

@@ -49,7 +49,7 @@
description = '' description = ''
path to a fact which is generated by the generator path to a fact which is generated by the generator
''; '';
default = "${config.clanCore.clanDir}/facts/${config.clanCore.machineName}/${fact.config._module.args.name}"; default = "${config.clanCore.clanDir}/machines/${config.clanCore.machineName}/facts/${fact.config._module.args.name}";
}; };
value = lib.mkOption { value = lib.mkOption {
default = builtins.readFile fact.config.path; default = builtins.readFile fact.config.path;

View File

@@ -1,7 +1,28 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let
secretsDir = config.clanCore.clanDir + "/sops/secrets";
groupsDir = config.clanCore.clanDir + "/sops/groups";
# My symlink is in the nixos module detected as a directory also it works in the repl. Is this because of pure evaluation?
containsSymlink = path:
builtins.pathExists path && (builtins.readFileType path == "directory" || builtins.readFileType path == "symlink");
containsMachine = parent: name: type:
type == "directory" && containsSymlink "${parent}/${name}/machines/${config.clanCore.machineName}";
containsMachineOrGroups = name: type:
(containsMachine secretsDir name type) || lib.any (group: type == "directory" && containsSymlink "${secretsDir}/${name}/groups/${group}") groups;
filterDir = filter: dir:
lib.optionalAttrs (builtins.pathExists dir)
(lib.filterAttrs filter (builtins.readDir dir));
groups = builtins.attrNames (filterDir (containsMachine groupsDir) groupsDir);
secrets = filterDir containsMachineOrGroups secretsDir;
in
{ {
config = { config = {
system.clan.generateSecrets = pkgs.writeScript "generate_secrets" '' system.clan.generateSecrets = pkgs.writeScript "generate-secrets" ''
#!/bin/sh #!/bin/sh
set -efu set -efu
set -x # remove for prod set -x # remove for prod
@@ -43,21 +64,13 @@
fi) fi)
'') "" config.clanCore.secrets} '') "" config.clanCore.secrets}
''; '';
sops.secrets = sops.secrets = builtins.mapAttrs
let (name: _: {
encryptedForThisMachine = name: type: sopsFile = config.clanCore.clanDir + "/sops/secrets/${name}/secret";
let format = "binary";
symlink = config.clanCore.clanDir + "/sops/secrets/${name}/machines/${config.clanCore.machineName}"; })
in secrets;
# WTF, nix bug, my symlink is in the nixos module detected as a directory also it works in the repl # To get proper error messages about missing secrets we need a dummy secret file that is always present
type == "directory" && (builtins.readFileType symlink == "directory" || builtins.readFileType symlink == "symlink"); sops.defaultSopsFile = lib.mkIf config.sops.validateSopsFiles (lib.mkDefault (builtins.toString (pkgs.writeText "dummy.yaml" "")));
secrets = lib.filterAttrs encryptedForThisMachine (builtins.readDir (config.clanCore.clanDir + "/sops/secrets"));
in
builtins.mapAttrs
(name: _: {
sopsFile = config.clanCore.clanDir + "/sops/secrets/${name}/secret";
format = "binary";
})
secrets;
}; };
} }

View File

@@ -5,9 +5,11 @@ import os
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Any, Optional, Type, Union from typing import Any, Optional, Type
from clan_cli.dirs import get_clan_flake_toplevel
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from clan_cli.nix import nix_eval
script_dir = Path(__file__).parent script_dir = Path(__file__).parent
@@ -91,16 +93,58 @@ def cast(value: Any, type: Type, opt_description: str) -> Any:
) )
def read_option(option: str) -> str: def options_for_machine(machine_name: str, flake: Optional[Path] = None) -> dict:
if flake is None:
flake = get_clan_flake_toplevel()
# use nix eval to lib.evalModules .#clanModules.machine-{machine_name}
proc = subprocess.run(
nix_eval(
flags=[
"--json",
"--show-trace",
"--impure",
"--expr",
f"""
let
flake = builtins.getFlake (toString {flake});
lib = flake.inputs.nixpkgs.lib;
options = flake.nixosConfigurations.{machine_name}.options;
# this is actually system independent as it uses toFile
docs = flake.inputs.nixpkgs.legacyPackages.x86_64-linux.nixosOptionsDoc {{
inherit options;
}};
opts = builtins.fromJSON (builtins.readFile docs.optionsJSON.options);
in
opts
""",
],
),
capture_output=True,
text=True,
)
if proc.returncode != 0:
print(proc.stderr, file=sys.stderr)
raise Exception(
f"Failed to read options for machine {machine_name}:\n{proc.stderr}"
)
options = json.loads(proc.stdout)
return options
def read_machine_option_value(machine_name: str, option: str) -> str:
# use nix eval to read from .#nixosConfigurations.default.config.{option} # use nix eval to read from .#nixosConfigurations.default.config.{option}
# this will give us the evaluated config with the options attribute # this will give us the evaluated config with the options attribute
proc = subprocess.run( proc = subprocess.run(
[ nix_eval(
"nix", flags=[
"eval", "--json",
"--json", "--show-trace",
f".#nixosConfigurations.default.config.{option}", "--extra-experimental-features",
], "nix-command flakes",
f".#nixosConfigurations.{machine_name}.config.{option}",
],
),
capture_output=True, capture_output=True,
text=True, text=True,
) )
@@ -119,18 +163,44 @@ def read_option(option: str) -> str:
return out return out
def process_args( def get_or_set_option(args: argparse.Namespace) -> None:
if args.value == []:
print(read_machine_option_value(args.machine, args.option))
else:
# load options
print(args.options_file)
if args.options_file is None:
options = options_for_machine(machine_name=args.machine)
else:
with open(args.options_file) as f:
options = json.load(f)
# compute settings json file location
if args.settings_file is None:
flake = get_clan_flake_toplevel()
settings_file = flake / "machines" / f"{args.machine}.json"
else:
settings_file = args.settings_file
# set the option with the given value
set_option(
option=args.option,
value=args.value,
options=options,
settings_file=settings_file,
option_description=args.option,
)
if not args.quiet:
new_value = read_machine_option_value(args.machine, args.option)
print(f"New Value for {args.option}:")
print(new_value)
def set_option(
option: str, option: str,
value: Any, value: Any,
options: dict, options: dict,
settings_file: Path, settings_file: Path,
quiet: bool = False,
option_description: str = "", option_description: str = "",
) -> None: ) -> None:
if value == []:
print(read_option(option))
return
option_path = option.split(".") option_path = option.split(".")
# if the option cannot be found, then likely the type is attrs and we need to # if the option cannot be found, then likely the type is attrs and we need to
@@ -140,12 +210,11 @@ def process_args(
raise ClanError(f"Option {option_description} not found") raise ClanError(f"Option {option_description} not found")
option_parent = option_path[:-1] option_parent = option_path[:-1]
attr = option_path[-1] attr = option_path[-1]
return process_args( return set_option(
option=".".join(option_parent), option=".".join(option_parent),
value={attr: value}, value={attr: value},
options=options, options=options,
settings_file=settings_file, settings_file=settings_file,
quiet=quiet,
option_description=option, option_description=option,
) )
@@ -170,45 +239,14 @@ def process_args(
current_config = {} current_config = {}
# merge and save the new config file # merge and save the new config file
new_config = merge(current_config, result) new_config = merge(current_config, result)
settings_file.parent.mkdir(parents=True, exist_ok=True)
with open(settings_file, "w") as f: with open(settings_file, "w") as f:
json.dump(new_config, f, indent=2) json.dump(new_config, f, indent=2)
if not quiet:
new_value = read_option(option)
print(f"New Value for {option}:")
print(new_value)
def register_parser(
parser: argparse.ArgumentParser,
options_file: Optional[Union[str, Path]] = os.environ.get("CLAN_OPTIONS_FILE"),
) -> None:
if not options_file:
# use nix eval to evaluate .#clanOptions
# this will give us the evaluated config with the options attribute
proc = subprocess.run(
[
"nix",
"eval",
"--raw",
".#clanOptions",
],
check=True,
capture_output=True,
text=True,
)
file = proc.stdout.strip()
with open(file) as f:
options = json.load(f)
else:
with open(options_file) as f:
options = json.load(f)
return _register_parser(parser, options)
# takes a (sub)parser and configures it # takes a (sub)parser and configures it
def _register_parser( def register_parser(
parser: Optional[argparse.ArgumentParser], parser: Optional[argparse.ArgumentParser],
options: dict[str, Any],
) -> None: ) -> None:
if parser is None: if parser is None:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@@ -216,31 +254,35 @@ def _register_parser(
) )
# inject callback function to process the input later # inject callback function to process the input later
parser.set_defaults( parser.set_defaults(func=get_or_set_option)
func=lambda args: process_args(
option=args.option,
value=args.value,
options=options,
quiet=args.quiet,
settings_file=args.settings_file,
)
)
# add --quiet option # add --machine argument
parser.add_argument( parser.add_argument(
"--quiet", "--machine",
"-q", "-m",
help="Suppress output", help="Machine to configure",
action="store_true", type=str,
default="default",
) )
# add argument to pass output file # add --options-file argument
parser.add_argument(
"--options-file",
help="JSON file with options",
type=Path,
)
# add --settings-file argument
parser.add_argument( parser.add_argument(
"--settings-file", "--settings-file",
"-o", help="JSON file with settings",
help="Output file",
type=Path, type=Path,
default=Path("clan-settings.json"), )
# add --quiet argument
parser.add_argument(
"--quiet",
help="Do not print the value",
action="store_true",
) )
# add single positional argument for the option (e.g. "foo.bar") # add single positional argument for the option (e.g. "foo.bar")
@@ -248,7 +290,6 @@ def _register_parser(
"option", "option",
help="Option to configure", help="Option to configure",
type=str, type=str,
choices=AllContainer(list(options.keys())),
) )
# add a single optional argument for the value # add a single optional argument for the value
@@ -264,14 +305,8 @@ def main(argv: Optional[list[str]] = None) -> None:
if argv is None: if argv is None:
argv = sys.argv argv = sys.argv
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( register_parser(parser)
"schema", parser.parse_args(argv[1:])
help="The schema to use for the configuration",
type=Path,
)
args = parser.parse_args(argv[1:2])
register_parser(parser, args.schema)
parser.parse_args(argv[2:])
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -6,8 +6,9 @@ from typing import Optional
from fastapi import HTTPException from fastapi import HTTPException
from clan_cli.dirs import get_clan_flake_toplevel, nixpkgs from clan_cli.dirs import get_clan_flake_toplevel, nixpkgs_source
from clan_cli.machines.folders import machine_folder, machine_settings_file from clan_cli.machines.folders import machine_folder, machine_settings_file
from clan_cli.nix import nix_eval
def config_for_machine(machine_name: str) -> dict: def config_for_machine(machine_name: str) -> dict:
@@ -42,30 +43,27 @@ def schema_for_machine(machine_name: str, flake: Optional[Path] = None) -> dict:
flake = get_clan_flake_toplevel() flake = get_clan_flake_toplevel()
# use nix eval to lib.evalModules .#nixosModules.machine-{machine_name} # use nix eval to lib.evalModules .#nixosModules.machine-{machine_name}
proc = subprocess.run( proc = subprocess.run(
[ nix_eval(
"nix", flags=[
"eval", "--json",
"--json", "--impure",
"--impure", "--show-trace",
"--show-trace", "--extra-experimental-features",
"--extra-experimental-features", "nix-command flakes",
"nix-command flakes", "--expr",
"--expr", f"""
f""" let
let flake = builtins.getFlake (toString {flake});
flake = builtins.getFlake (toString {flake}); lib = import {nixpkgs_source()}/lib;
lib = import {nixpkgs()}/lib; options = flake.nixosConfigurations.{machine_name}.options;
module = builtins.trace (builtins.attrNames flake) flake.nixosModules.machine-{machine_name}; clanOptions = options.clan;
evaled = lib.evalModules {{ jsonschemaLib = import {Path(__file__).parent / "jsonschema"} {{ inherit lib; }};
modules = [module]; jsonschema = jsonschemaLib.parseOptions clanOptions;
}}; in
clanOptions = evaled.options.clan; jsonschema
jsonschemaLib = import {Path(__file__).parent / "jsonschema"} {{ inherit lib; }}; """,
jsonschema = jsonschemaLib.parseOptions clanOptions; ],
in ),
jsonschema
""",
],
capture_output=True, capture_output=True,
text=True, text=True,
) )

View File

@@ -30,11 +30,11 @@ def module_root() -> Path:
return Path(__file__).parent return Path(__file__).parent
def flake_registry() -> Path: def nixpkgs_flake() -> Path:
return module_root() / "nixpkgs" / "flake-registry.json" return (module_root() / "nixpkgs").resolve()
def nixpkgs() -> Path: def nixpkgs_source() -> Path:
return (module_root() / "nixpkgs" / "path").resolve() return (module_root() / "nixpkgs" / "path").resolve()

View File

@@ -6,6 +6,9 @@ from .folders import machine_folder
def create_machine(name: str) -> None: def create_machine(name: str) -> None:
folder = machine_folder(name) folder = machine_folder(name)
folder.mkdir(parents=True, exist_ok=True) folder.mkdir(parents=True, exist_ok=True)
# create empty settings.json file inside the folder
with open(folder / "settings.json", "w") as f:
f.write("{}")
def create_command(args: argparse.Namespace) -> None: def create_command(args: argparse.Namespace) -> None:

View File

@@ -1,4 +1,5 @@
import argparse import argparse
import shutil
from ..errors import ClanError from ..errors import ClanError
from .folders import machine_folder from .folders import machine_folder
@@ -7,7 +8,7 @@ from .folders import machine_folder
def delete_command(args: argparse.Namespace) -> None: def delete_command(args: argparse.Namespace) -> None:
folder = machine_folder(args.host) folder = machine_folder(args.host)
if folder.exists(): if folder.exists():
folder.rmdir() shutil.rmtree(folder)
else: else:
raise ClanError(f"Machine {args.host} does not exist") raise ClanError(f"Machine {args.host} does not exist")

View File

@@ -1,6 +1,33 @@
import os import os
import tempfile
from .dirs import flake_registry, unfree_nixpkgs from .dirs import nixpkgs_flake, nixpkgs_source, unfree_nixpkgs
def nix_eval(flags: list[str]) -> list[str]:
if os.environ.get("IN_NIX_SANDBOX"):
with tempfile.TemporaryDirectory() as nix_store:
return [
"nix",
"eval",
"--show-trace",
"--extra-experimental-features",
"nix-command flakes",
"--override-input",
"nixpkgs",
str(nixpkgs_source()),
# --store is required to prevent this error:
# error: cannot unlink '/nix/store/6xg259477c90a229xwmb53pdfkn6ig3g-default-builder.sh': Operation not permitted
"--store",
nix_store,
] + flags
return [
"nix",
"eval",
"--show-trace",
"--extra-experimental-features",
"nix-command flakes",
] + flags
def nix_shell(packages: list[str], cmd: list[str]) -> list[str]: def nix_shell(packages: list[str], cmd: list[str]) -> list[str]:
@@ -15,8 +42,8 @@ def nix_shell(packages: list[str], cmd: list[str]) -> list[str]:
"shell", "shell",
"--extra-experimental-features", "--extra-experimental-features",
"nix-command flakes", "nix-command flakes",
"--flake-registry", "--inputs-from",
str(flake_registry()), f"{str(nixpkgs_flake())}",
] ]
+ wrapped_packages + wrapped_packages
+ ["-c"] + ["-c"]

View File

@@ -36,7 +36,7 @@ def import_sops(args: argparse.Namespace) -> None:
file=sys.stderr, file=sys.stderr,
) )
continue continue
if (sops_secrets_folder() / k).exists(): if (sops_secrets_folder() / k / "secret").exists():
print( print(
f"WARNING: {k} already exists, skipping", f"WARNING: {k} already exists, skipping",
file=sys.stderr, file=sys.stderr,

View File

@@ -212,7 +212,13 @@ def set_command(args: argparse.Namespace) -> None:
def rename_command(args: argparse.Namespace) -> None: def rename_command(args: argparse.Namespace) -> None:
pass old_path = sops_secrets_folder() / args.secret
new_path = sops_secrets_folder() / args.new_name
if not old_path.exists():
raise ClanError(f"Secret '{args.secret}' does not exist")
if new_path.exists():
raise ClanError(f"Secret '{args.new_name}' already exists")
os.rename(old_path, new_path)
def register_secrets_parser(subparser: argparse._SubParsersAction) -> None: def register_secrets_parser(subparser: argparse._SubParsersAction) -> None:
@@ -250,9 +256,7 @@ def register_secrets_parser(subparser: argparse._SubParsersAction) -> None:
parser_rename = subparser.add_parser("rename", help="rename a secret") parser_rename = subparser.add_parser("rename", help="rename a secret")
add_secret_argument(parser_rename) add_secret_argument(parser_rename)
parser_rename.add_argument( parser_rename.add_argument("new_name", type=str, help="the new name of the secret")
"new_name", help="the new name of the secret", type=secret_name_type
)
parser_rename.set_defaults(func=rename_command) parser_rename.set_defaults(func=rename_command)
parser_remove = subparser.add_parser("remove", help="remove a secret") parser_remove = subparser.add_parser("remove", help="remove a secret")

View File

@@ -1,10 +1,8 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.routing import APIRoute from fastapi.routing import APIRoute
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from .assets import asset_path from .assets import asset_path
from .config import settings
from .routers import health, machines, root from .routers import health, machines, root
@@ -14,17 +12,7 @@ def setup_app() -> FastAPI:
app.include_router(machines.router) app.include_router(machines.router)
app.include_router(root.router) app.include_router(root.router)
if settings.env.is_development(): app.mount("/static", StaticFiles(directory=asset_path()), name="static")
# TODO make this configurable
app.add_middleware(
CORSMiddleware,
allow_origins="http://${settings.dev_host}:${settings.dev_port}",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
else:
app.mount("/static", StaticFiles(directory=asset_path()), name="static")
for route in app.routes: for route in app.routes:
if isinstance(route, APIRoute): if isinstance(route, APIRoute):

View File

@@ -1,38 +0,0 @@
# config.py
import logging
import os
from enum import Enum
from pydantic import BaseSettings
logger = logging.getLogger(__name__)
class EnvType(Enum):
production = "production"
development = "development"
@staticmethod
def from_environment() -> "EnvType":
t = os.environ.get("CLAN_WEBUI_ENV", "production")
try:
return EnvType[t]
except KeyError:
logger.warning(f"Invalid environment type: {t}, fallback to production")
return EnvType.production
def is_production(self) -> bool:
return self == EnvType.production
def is_development(self) -> bool:
return self == EnvType.development
class Settings(BaseSettings):
env: EnvType = EnvType.from_environment()
dev_port: int = int(os.environ.get("CLAN_WEBUI_DEV_PORT", 3000))
dev_host: str = os.environ.get("CLAN_WEBUI_DEV_HOST", "localhost")
# global instance
settings = Settings()

View File

@@ -1,6 +1,5 @@
import argparse import argparse
import logging import logging
import os
import subprocess import subprocess
import time import time
import urllib.request import urllib.request
@@ -27,11 +26,23 @@ def defer_open_browser(base_url: str) -> None:
@contextmanager @contextmanager
def spawn_node_dev_server() -> Iterator[None]: def spawn_node_dev_server(host: str, port: int) -> Iterator[None]:
logger.info("Starting node dev server...") logger.info("Starting node dev server...")
path = Path(__file__).parent.parent.parent.parent / "ui" path = Path(__file__).parent.parent.parent.parent / "ui"
with subprocess.Popen( with subprocess.Popen(
["direnv", "exec", path, "npm", "run", "dev"], [
"direnv",
"exec",
path,
"npm",
"run",
"dev",
"--",
"--hostname",
host,
"--port",
str(port),
],
cwd=path, cwd=path,
) as proc: ) as proc:
try: try:
@@ -42,16 +53,21 @@ def spawn_node_dev_server() -> Iterator[None]:
def start_server(args: argparse.Namespace) -> None: def start_server(args: argparse.Namespace) -> None:
with ExitStack() as stack: with ExitStack() as stack:
headers: list[tuple[str, str]] = []
if args.dev: if args.dev:
os.environ["CLAN_WEBUI_ENV"] = "development" stack.enter_context(spawn_node_dev_server(args.dev_host, args.dev_port))
os.environ["CLAN_WEBUI_DEV_PORT"] = str(args.dev_port)
os.environ["CLAN_WEBUI_DEV_HOST"] = args.dev_host
stack.enter_context(spawn_node_dev_server())
open_url = f"http://{args.dev_host}:{args.dev_port}" open_url = f"http://{args.dev_host}:{args.dev_port}"
host = args.dev_host
if ":" in host:
host = f"[{host}]"
headers = [
(
"Access-Control-Allow-Origin",
f"http://{host}:{args.dev_port}",
)
]
else: else:
os.environ["CLAN_WEBUI_ENV"] = "production"
open_url = f"http://[{args.host}]:{args.port}" open_url = f"http://[{args.host}]:{args.port}"
if not args.no_open: if not args.no_open:
@@ -63,5 +79,5 @@ def start_server(args: argparse.Namespace) -> None:
port=args.port, port=args.port,
log_level=args.log_level, log_level=args.log_level,
reload=args.reload, reload=args.reload,
headers=[("Access-Control-Allow-Origin", "*")], headers=headers,
) )

View File

@@ -44,24 +44,36 @@ let
checkPython = python3.withPackages (_ps: dependencies ++ testDependencies); checkPython = python3.withPackages (_ps: dependencies ++ testDependencies);
# - vendor the jsonschema nix lib (copy instead of symlink). # - vendor the jsonschema nix lib (copy instead of symlink).
# - lib.cleanSource prevents unnecessary rebuilds when `self` changes.
source = runCommand "clan-cli-source" { } '' source = runCommand "clan-cli-source" { } ''
cp -r ${./.} $out cp -r ${./.} $out
chmod -R +w $out chmod -R +w $out
rm $out/clan_cli/config/jsonschema rm $out/clan_cli/config/jsonschema
ln -sTf ${nixpkgs} $out/clan_cli/nixpkgs cp -r ${nixpkgs} $out/clan_cli/nixpkgs
cp -r ${../../lib/jsonschema} $out/clan_cli/config/jsonschema cp -r ${../../lib/jsonschema} $out/clan_cli/config/jsonschema
ln -s ${ui-assets} $out/clan_cli/webui/assets ln -s ${ui-assets} $out/clan_cli/webui/assets
''; '';
nixpkgs = runCommand "nixpkgs" { } '' nixpkgs = runCommand "nixpkgs" { nativeBuildInputs = [ pkgs.nix ]; } ''
mkdir $out
mkdir -p $out/unfree mkdir -p $out/unfree
cat > $out/unfree/default.nix <<EOF cat > $out/unfree/default.nix <<EOF
import "${pkgs.path}" { config = { allowUnfree = true; overlays = []; }; } import "${pkgs.path}" { config = { allowUnfree = true; overlays = []; }; }
EOF EOF
cat > $out/flake-registry.json <<EOF cat > $out/flake.nix << EOF
{ "flakes": [{"exact":true,"from":{"id":"nixpkgs", "type": "indirect"},"to": {"path":"${pkgs.path}", "type":"path"}}], "version": 2} {
description = "dependencies for the clan-cli";
inputs = {
nixpkgs.url = "nixpkgs";
};
outputs = _inputs: { };
}
EOF EOF
ln -s ${pkgs.path} $out/path ln -s ${pkgs.path} $out/path
nix flake lock $out \
--store ./. \
--experimental-features 'nix-command flakes' \
--override-input nixpkgs ${pkgs.path}
''; '';
in in
python3.pkgs.buildPythonPackage { python3.pkgs.buildPythonPackage {
@@ -77,7 +89,7 @@ python3.pkgs.buildPythonPackage {
]; ];
propagatedBuildInputs = dependencies; propagatedBuildInputs = dependencies;
passthru.tests.clan-pytest = runCommand "clan-tests" passthru.tests.clan-pytest = runCommand "clan-pytest"
{ {
nativeBuildInputs = [ age zerotierone bubblewrap sops nix openssh rsync stdenv.cc ]; nativeBuildInputs = [ age zerotierone bubblewrap sops nix openssh rsync stdenv.cc ];
} '' } ''
@@ -106,7 +118,7 @@ python3.pkgs.buildPythonPackage {
passthru.testDependencies = dependencies ++ testDependencies; passthru.testDependencies = dependencies ++ testDependencies;
postInstall = '' postInstall = ''
ln -sTf ${nixpkgs} $out/${python3.sitePackages}/clan_cli/nixpkgs cp -r ${nixpkgs} $out/${python3.sitePackages}/clan_cli/nixpkgs
installShellCompletion --bash --name clan \ installShellCompletion --bash --name clan \
<(${argcomplete}/bin/register-python-argcomplete --shell bash clan) <(${argcomplete}/bin/register-python-argcomplete --shell bash clan)
installShellCompletion --fish --name clan.fish \ installShellCompletion --fish --name clan.fish \

View File

@@ -0,0 +1,9 @@
{
description = "dependencies for the clan-cli";
inputs = {
nixpkgs.url = "nixpkgs";
};
outputs = _inputs: { };
}

View File

@@ -6,7 +6,7 @@ from typing import Generator
import pytest import pytest
from clan_cli.dirs import nixpkgs from clan_cli.dirs import nixpkgs_source
sys.path.append(os.path.join(os.path.dirname(__file__), "helpers")) sys.path.append(os.path.join(os.path.dirname(__file__), "helpers"))
@@ -44,10 +44,8 @@ def machine_flake(monkeymodule: pytest.MonkeyPatch) -> Generator[Path, None, Non
# provided by get_clan_flake_toplevel # provided by get_clan_flake_toplevel
flake_nix = flake / "flake.nix" flake_nix = flake / "flake.nix"
flake_nix.write_text( flake_nix.write_text(
flake_nix.read_text().replace("__NIXPKGS__", str(nixpkgs())) flake_nix.read_text().replace("__NIXPKGS__", str(nixpkgs_source()))
) )
# check that an empty config is returned if no json file exists # check that an empty config is returned if no json file exists
monkeymodule.chdir(flake) monkeymodule.chdir(flake)
# monkeypatch get_clan_flake_toplevel to return the flake
monkeymodule.setattr("clan_cli.dirs.get_clan_flake_toplevel", lambda: flake)
yield flake yield flake

View File

@@ -4,7 +4,20 @@
nixpkgs.url = "__NIXPKGS__"; nixpkgs.url = "__NIXPKGS__";
}; };
outputs = _inputs: { outputs = inputs: {
nixosModules.machine-machine1 = ./nixosModules/machine1.nix; nixosConfigurations.machine1 = inputs.nixpkgs.lib.nixosSystem {
modules = [
./nixosModules/machine1.nix
(if builtins.pathExists ./machines/machine1.json
then builtins.fromJSON (builtins.readFile ./machines/machine1.json)
else { })
{
nixpkgs.hostPlatform = "x86_64-linux";
# speed up by not instantiating nixpkgs twice and disable documentation
nixpkgs.pkgs = inputs.nixpkgs.legacyPackages.x86_64-linux;
documentation.enable = false;
}
];
};
}; };
} }

View File

@@ -28,20 +28,44 @@ example_options = f"{Path(config.__file__).parent}/jsonschema/options.json"
def test_set_some_option( def test_set_some_option(
args: list[str], args: list[str],
expected: dict[str, Any], expected: dict[str, Any],
monkeypatch: pytest.MonkeyPatch,
) -> None: ) -> None:
monkeypatch.setenv("CLAN_OPTIONS_FILE", example_options)
# create temporary file for out_file # create temporary file for out_file
with tempfile.NamedTemporaryFile() as out_file: with tempfile.NamedTemporaryFile() as out_file:
with open(out_file.name, "w") as f: with open(out_file.name, "w") as f:
json.dump({}, f) json.dump({}, f)
cli = Cli() cli = Cli()
cli.run(["config", "--quiet", "--settings-file", out_file.name] + args) cli.run(
[
"config",
"--quiet",
"--options-file",
example_options,
"--settings-file",
out_file.name,
]
+ args
)
json_out = json.loads(open(out_file.name).read()) json_out = json.loads(open(out_file.name).read())
assert json_out == expected assert json_out == expected
def test_configure_machine(
machine_flake: Path,
temporary_dir: Path,
capsys: pytest.CaptureFixture,
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("HOME", str(temporary_dir))
cli = Cli()
cli.run(["config", "-m", "machine1", "clan.jitsi.enable", "true"])
# clear the output buffer
capsys.readouterr()
# read a option value
cli.run(["config", "-m", "machine1", "clan.jitsi.enable"])
# read the output
assert capsys.readouterr().out == "true\n"
def test_walk_jsonschema_all_types() -> None: def test_walk_jsonschema_all_types() -> None:
schema = dict( schema = dict(
type="object", type="object",

View File

@@ -129,9 +129,9 @@ def test_secrets(
with pytest.raises(ClanError): # does not exist yet with pytest.raises(ClanError): # does not exist yet
cli.run(["secrets", "get", "nonexisting"]) cli.run(["secrets", "get", "nonexisting"])
cli.run(["secrets", "set", "key"]) cli.run(["secrets", "set", "initialkey"])
capsys.readouterr() capsys.readouterr()
cli.run(["secrets", "get", "key"]) cli.run(["secrets", "get", "initialkey"])
assert capsys.readouterr().out == "foo" assert capsys.readouterr().out == "foo"
capsys.readouterr() capsys.readouterr()
cli.run(["secrets", "users", "list"]) cli.run(["secrets", "users", "list"])
@@ -139,6 +139,8 @@ def test_secrets(
assert len(users) == 1, f"users: {users}" assert len(users) == 1, f"users: {users}"
owner = users[0] owner = users[0]
cli.run(["secrets", "rename", "initialkey", "key"])
capsys.readouterr() # empty the buffer capsys.readouterr() # empty the buffer
cli.run(["secrets", "list"]) cli.run(["secrets", "list"])
assert capsys.readouterr().out == "key\n" assert capsys.readouterr().out == "key\n"

View File

@@ -7,7 +7,6 @@ import pytest_subprocess.fake_process
from pytest_subprocess import utils from pytest_subprocess import utils
import clan_cli import clan_cli
from clan_cli.dirs import flake_registry
from clan_cli.ssh import cli from clan_cli.ssh import cli
@@ -34,10 +33,7 @@ def test_ssh_no_pass(
"shell", "shell",
"--extra-experimental-features", "--extra-experimental-features",
"nix-command flakes", "nix-command flakes",
"--flake-registry", fp.any(),
str(flake_registry()),
"nixpkgs#tor",
"nixpkgs#openssh",
"-c", "-c",
"torify", "torify",
"ssh", "ssh",
@@ -68,11 +64,7 @@ def test_ssh_with_pass(
"shell", "shell",
"--extra-experimental-features", "--extra-experimental-features",
"nix-command flakes", "nix-command flakes",
"--flake-registry", fp.any(),
str(flake_registry()),
"nixpkgs#tor",
"nixpkgs#openssh",
"nixpkgs#sshpass",
"-c", "-c",
"torify", "torify",
"sshpass", "sshpass",

View File

@@ -254,7 +254,7 @@
}; };
}; };
"@babel/runtime" = { "@babel/runtime" = {
"7.22.10" = { "7.22.11" = {
depInfo = { depInfo = {
regenerator-runtime = { regenerator-runtime = {
descriptor = "^0.14.0"; descriptor = "^0.14.0";
@@ -263,13 +263,13 @@
}; };
}; };
fetchInfo = { fetchInfo = {
narHash = "sha256-5ecEDXI/B/XZUtU3VFGYjC1yAMqmmoqb9Jyu03CI1rQ="; narHash = "sha256-u4IYeznySCACZfl7/j6Fwdz0J5eRLYRntlijjEtZQb0=";
type = "tarball"; type = "tarball";
url = "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz"; url = "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz";
}; };
ident = "@babel/runtime"; ident = "@babel/runtime";
ltype = "file"; ltype = "file";
version = "7.22.10"; version = "7.22.11";
}; };
}; };
"@babel/types" = { "@babel/types" = {
@@ -311,7 +311,7 @@
}; };
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.18.3"; descriptor = "^7.18.3";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@emotion/hash" = { "@emotion/hash" = {
@@ -459,7 +459,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.18.3"; descriptor = "^7.18.3";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@emotion/babel-plugin" = { "@emotion/babel-plugin" = {
@@ -574,7 +574,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.18.3"; descriptor = "^7.18.3";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@emotion/babel-plugin" = { "@emotion/babel-plugin" = {
@@ -1104,7 +1104,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.22.6"; descriptor = "^7.22.6";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@emotion/is-prop-valid" = { "@emotion/is-prop-valid" = {
@@ -1119,7 +1119,7 @@
}; };
"@mui/utils" = { "@mui/utils" = {
descriptor = "^5.14.5"; descriptor = "^5.14.5";
pin = "5.14.5"; pin = "5.14.7";
runtime = true; runtime = true;
}; };
"@popperjs/core" = { "@popperjs/core" = {
@@ -1183,7 +1183,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.22.6"; descriptor = "^7.22.6";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
}; };
@@ -1214,7 +1214,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.22.6"; descriptor = "^7.22.6";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@mui/base" = { "@mui/base" = {
@@ -1239,7 +1239,7 @@
}; };
"@mui/utils" = { "@mui/utils" = {
descriptor = "^5.14.5"; descriptor = "^5.14.5";
pin = "5.14.5"; pin = "5.14.7";
runtime = true; runtime = true;
}; };
"@types/react-transition-group" = { "@types/react-transition-group" = {
@@ -1308,12 +1308,12 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.22.6"; descriptor = "^7.22.6";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@mui/utils" = { "@mui/utils" = {
descriptor = "^5.14.5"; descriptor = "^5.14.5";
pin = "5.14.5"; pin = "5.14.7";
runtime = true; runtime = true;
}; };
prop-types = { prop-types = {
@@ -1346,7 +1346,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.21.0"; descriptor = "^7.21.0";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@emotion/cache" = { "@emotion/cache" = {
@@ -1393,7 +1393,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.22.6"; descriptor = "^7.22.6";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@mui/private-theming" = { "@mui/private-theming" = {
@@ -1413,7 +1413,7 @@
}; };
"@mui/utils" = { "@mui/utils" = {
descriptor = "^5.14.5"; descriptor = "^5.14.5";
pin = "5.14.5"; pin = "5.14.7";
runtime = true; runtime = true;
}; };
clsx = { clsx = {
@@ -1479,11 +1479,11 @@
}; };
}; };
"@mui/utils" = { "@mui/utils" = {
"5.14.5" = { "5.14.7" = {
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.22.6"; descriptor = "^7.22.10";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
"@types/prop-types" = { "@types/prop-types" = {
@@ -1508,9 +1508,9 @@
}; };
}; };
fetchInfo = { fetchInfo = {
narHash = "sha256-mym+STz4KseB2TDlXB8qkcPKpvNQDU4r+9xTC99m84U="; narHash = "sha256-bvWlZoYxVVHqprNjDYZQtl6vrpx6BZNUe/t8J+REcHk=";
type = "tarball"; type = "tarball";
url = "https://registry.npmjs.org/@mui/utils/-/utils-5.14.5.tgz"; url = "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz";
}; };
ident = "@mui/utils"; ident = "@mui/utils";
ltype = "file"; ltype = "file";
@@ -1519,7 +1519,7 @@
descriptor = "^17.0.0 || ^18.0.0"; descriptor = "^17.0.0 || ^18.0.0";
}; };
}; };
version = "5.14.5"; version = "5.14.7";
}; };
}; };
"@next/env" = { "@next/env" = {
@@ -2080,6 +2080,172 @@
version = "2.11.8"; version = "2.11.8";
}; };
}; };
"@rjsf/core" = {
"5.12.1" = {
depInfo = {
lodash = {
descriptor = "^4.17.21";
pin = "4.17.21";
runtime = true;
};
lodash-es = {
descriptor = "^4.17.21";
pin = "4.17.21";
runtime = true;
};
markdown-to-jsx = {
descriptor = "^7.3.2";
pin = "7.3.2";
runtime = true;
};
nanoid = {
descriptor = "^3.3.6";
pin = "3.3.6";
runtime = true;
};
prop-types = {
descriptor = "^15.8.1";
pin = "15.8.1";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-TYa/k9q0Au9+0l7qyLaa2XMyQ6bHZqRniGzzo7BFDWk=";
type = "tarball";
url = "https://registry.npmjs.org/@rjsf/core/-/core-5.12.1.tgz";
};
ident = "@rjsf/core";
ltype = "file";
peerInfo = {
"@rjsf/utils" = {
descriptor = "^5.8.x";
};
react = {
descriptor = "^16.14.0 || >=17";
};
};
version = "5.12.1";
};
};
"@rjsf/mui" = {
"5.12.1" = {
fetchInfo = {
narHash = "sha256-HS37nzO3SsWJycV8yvqrEjtcb9w8GxtivBBWArBhziU=";
type = "tarball";
url = "https://registry.npmjs.org/@rjsf/mui/-/mui-5.12.1.tgz";
};
ident = "@rjsf/mui";
ltype = "file";
peerInfo = {
"@emotion/react" = {
descriptor = "^11.7.0";
};
"@emotion/styled" = {
descriptor = "^11.6.0";
};
"@mui/icons-material" = {
descriptor = "^5.2.0";
};
"@mui/material" = {
descriptor = "^5.2.2";
};
"@rjsf/core" = {
descriptor = "^5.8.x";
};
"@rjsf/utils" = {
descriptor = "^5.8.x";
};
react = {
descriptor = ">=17";
};
};
treeInfo = { };
version = "5.12.1";
};
};
"@rjsf/utils" = {
"5.12.1" = {
depInfo = {
json-schema-merge-allof = {
descriptor = "^0.8.1";
pin = "0.8.1";
runtime = true;
};
jsonpointer = {
descriptor = "^5.0.1";
pin = "5.0.1";
runtime = true;
};
lodash = {
descriptor = "^4.17.21";
pin = "4.17.21";
runtime = true;
};
lodash-es = {
descriptor = "^4.17.21";
pin = "4.17.21";
runtime = true;
};
react-is = {
descriptor = "^18.2.0";
pin = "18.2.0";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-CR5Jmw9hCiLFWgoxBDdhuzItZz/Y60pAX0T0IxMQKJM=";
type = "tarball";
url = "https://registry.npmjs.org/@rjsf/utils/-/utils-5.12.1.tgz";
};
ident = "@rjsf/utils";
ltype = "file";
peerInfo = {
react = {
descriptor = "^16.14.0 || >=17";
};
};
version = "5.12.1";
};
};
"@rjsf/validator-ajv8" = {
"5.12.1" = {
depInfo = {
ajv = {
descriptor = "^8.12.0";
pin = "8.12.0";
runtime = true;
};
ajv-formats = {
descriptor = "^2.1.1";
pin = "2.1.1";
runtime = true;
};
lodash = {
descriptor = "^4.17.21";
pin = "4.17.21";
runtime = true;
};
lodash-es = {
descriptor = "^4.17.21";
pin = "4.17.21";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-w28JPlFA1Pnc3K/qBmPqwnlgQf6Pa/b7e7UY1zCvJjg=";
type = "tarball";
url = "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.12.1.tgz";
};
ident = "@rjsf/validator-ajv8";
ltype = "file";
peerInfo = {
"@rjsf/utils" = {
descriptor = "^5.8.x";
};
};
version = "5.12.1";
};
};
"@rollup/plugin-commonjs" = { "@rollup/plugin-commonjs" = {
"22.0.2" = { "22.0.2" = {
depInfo = { depInfo = {
@@ -4627,7 +4793,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.12.5"; descriptor = "^7.12.5";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
cosmiconfig = { cosmiconfig = {
@@ -5209,6 +5375,69 @@
version = "4.1.4"; version = "4.1.4";
}; };
}; };
compute-gcd = {
"1.2.1" = {
depInfo = {
"validate.io-array" = {
descriptor = "^1.0.3";
pin = "1.0.6";
runtime = true;
};
"validate.io-function" = {
descriptor = "^1.0.2";
pin = "1.0.2";
runtime = true;
};
"validate.io-integer-array" = {
descriptor = "^1.0.0";
pin = "1.0.0";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-d0KQIsd8wClVDFno5ovxwZeZrxT8Eds/EZeee1vP9tk=";
type = "tarball";
url = "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz";
};
ident = "compute-gcd";
ltype = "file";
version = "1.2.1";
};
};
compute-lcm = {
"1.1.2" = {
depInfo = {
compute-gcd = {
descriptor = "^1.2.1";
pin = "1.2.1";
runtime = true;
};
"validate.io-array" = {
descriptor = "^1.0.3";
pin = "1.0.6";
runtime = true;
};
"validate.io-function" = {
descriptor = "^1.0.2";
pin = "1.0.2";
runtime = true;
};
"validate.io-integer-array" = {
descriptor = "^1.0.0";
pin = "1.0.0";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-1KY8MWyNiiL/EbcaST1NDtJ/EVlphHN1zvMkEkEBUDA=";
type = "tarball";
url = "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz";
};
ident = "compute-lcm";
ltype = "file";
version = "1.1.2";
};
};
concat-map = { concat-map = {
"0.0.1" = { "0.0.1" = {
fetchInfo = { fetchInfo = {
@@ -5834,7 +6063,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.1.2"; descriptor = "^7.1.2";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
}; };
@@ -5851,7 +6080,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.8.7"; descriptor = "^7.8.7";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
csstype = { csstype = {
@@ -7430,7 +7659,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.20.7"; descriptor = "^7.20.7";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
aria-query = { aria-query = {
@@ -9857,6 +10086,54 @@
version = "2.3.1"; version = "2.3.1";
}; };
}; };
json-schema-compare = {
"0.2.2" = {
depInfo = {
lodash = {
descriptor = "^4.17.4";
pin = "4.17.21";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-C0qcy7sHg0SseMH51wBxWKNSOuMKIsdYJrKZiorAD6g=";
type = "tarball";
url = "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz";
};
ident = "json-schema-compare";
ltype = "file";
version = "0.2.2";
};
};
json-schema-merge-allof = {
"0.8.1" = {
depInfo = {
compute-lcm = {
descriptor = "^1.1.2";
pin = "1.1.2";
runtime = true;
};
json-schema-compare = {
descriptor = "^0.2.2";
pin = "0.2.2";
runtime = true;
};
lodash = {
descriptor = "^4.17.20";
pin = "4.17.21";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-XjBzD/iGKHCog9JktMJ7IV/hD3y/B7P7GPpCx+z3Ah4=";
type = "tarball";
url = "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz";
};
ident = "json-schema-merge-allof";
ltype = "file";
version = "0.8.1";
};
};
json-schema-ref-parser = { json-schema-ref-parser = {
"5.1.3" = { "5.1.3" = {
depInfo = { depInfo = {
@@ -10223,6 +10500,19 @@
version = "4.17.21"; version = "4.17.21";
}; };
}; };
lodash-es = {
"4.17.21" = {
fetchInfo = {
narHash = "sha256-2l4E89z3xMFn6MP9E0rVVNFWnB1oUINVGzno0F9CL3g=";
type = "tarball";
url = "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz";
};
ident = "lodash-es";
ltype = "file";
treeInfo = { };
version = "4.17.21";
};
};
"lodash.get" = { "lodash.get" = {
"4.4.2" = { "4.4.2" = {
fetchInfo = { fetchInfo = {
@@ -10402,6 +10692,24 @@
version = "0.25.9"; version = "0.25.9";
}; };
}; };
markdown-to-jsx = {
"7.3.2" = {
fetchInfo = {
narHash = "sha256-9sSiMN7r0b//8QFL72wsY4tkOpztRB0yDqV+1RUN97Q=";
type = "tarball";
url = "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.3.2.tgz";
};
ident = "markdown-to-jsx";
ltype = "file";
peerInfo = {
react = {
descriptor = ">= 0.14.0";
};
};
treeInfo = { };
version = "7.3.2";
};
};
matcher = { matcher = {
"1.1.1" = { "1.1.1" = {
depInfo = { depInfo = {
@@ -10584,6 +10892,26 @@
pin = "5.14.5"; pin = "5.14.5";
runtime = true; runtime = true;
}; };
"@rjsf/core" = {
descriptor = "^5.12.1";
pin = "5.12.1";
runtime = true;
};
"@rjsf/mui" = {
descriptor = "^5.12.1";
pin = "5.12.1";
runtime = true;
};
"@rjsf/validator-ajv8" = {
descriptor = "^5.12.1";
pin = "5.12.1";
runtime = true;
};
"@types/json-schema" = {
descriptor = "^7.0.12";
pin = "7.0.12";
runtime = true;
};
"@types/node" = { "@types/node" = {
descriptor = "20.4.7"; descriptor = "20.4.7";
pin = "20.4.7"; pin = "20.4.7";
@@ -10810,7 +11138,7 @@
key = "supports-color/5.5.0"; key = "supports-color/5.5.0";
}; };
"node_modules/@babel/runtime" = { "node_modules/@babel/runtime" = {
key = "@babel/runtime/7.22.10"; key = "@babel/runtime/7.22.11";
}; };
"node_modules/@babel/types" = { "node_modules/@babel/types" = {
key = "@babel/types/7.22.10"; key = "@babel/types/7.22.10";
@@ -10956,7 +11284,7 @@
key = "@mui/types/7.2.4"; key = "@mui/types/7.2.4";
}; };
"node_modules/@mui/utils" = { "node_modules/@mui/utils" = {
key = "@mui/utils/5.14.5"; key = "@mui/utils/5.14.7";
}; };
"node_modules/@next/env" = { "node_modules/@next/env" = {
key = "@next/env/13.4.12"; key = "@next/env/13.4.12";
@@ -11049,6 +11377,24 @@
"node_modules/@popperjs/core" = { "node_modules/@popperjs/core" = {
key = "@popperjs/core/2.11.8"; key = "@popperjs/core/2.11.8";
}; };
"node_modules/@rjsf/core" = {
key = "@rjsf/core/5.12.1";
};
"node_modules/@rjsf/mui" = {
key = "@rjsf/mui/5.12.1";
};
"node_modules/@rjsf/utils" = {
key = "@rjsf/utils/5.12.1";
};
"node_modules/@rjsf/validator-ajv8" = {
key = "@rjsf/validator-ajv8/5.12.1";
};
"node_modules/@rjsf/validator-ajv8/node_modules/ajv" = {
key = "ajv/8.12.0";
};
"node_modules/@rjsf/validator-ajv8/node_modules/json-schema-traverse" = {
key = "json-schema-traverse/1.0.0";
};
"node_modules/@rollup/plugin-commonjs" = { "node_modules/@rollup/plugin-commonjs" = {
dev = true; dev = true;
key = "@rollup/plugin-commonjs/22.0.2"; key = "@rollup/plugin-commonjs/22.0.2";
@@ -11256,7 +11602,6 @@
key = "@types/estree/0.0.39"; key = "@types/estree/0.0.39";
}; };
"node_modules/@types/json-schema" = { "node_modules/@types/json-schema" = {
dev = true;
key = "@types/json-schema/7.0.12"; key = "@types/json-schema/7.0.12";
}; };
"node_modules/@types/json5" = { "node_modules/@types/json5" = {
@@ -11334,15 +11679,12 @@
key = "ajv/6.12.6"; key = "ajv/6.12.6";
}; };
"node_modules/ajv-formats" = { "node_modules/ajv-formats" = {
dev = true;
key = "ajv-formats/2.1.1"; key = "ajv-formats/2.1.1";
}; };
"node_modules/ajv-formats/node_modules/ajv" = { "node_modules/ajv-formats/node_modules/ajv" = {
dev = true;
key = "ajv/8.12.0"; key = "ajv/8.12.0";
}; };
"node_modules/ajv-formats/node_modules/json-schema-traverse" = { "node_modules/ajv-formats/node_modules/json-schema-traverse" = {
dev = true;
key = "json-schema-traverse/1.0.0"; key = "json-schema-traverse/1.0.0";
}; };
"node_modules/ansi-colors" = { "node_modules/ansi-colors" = {
@@ -11542,6 +11884,12 @@
dev = true; dev = true;
key = "compare-versions/4.1.4"; key = "compare-versions/4.1.4";
}; };
"node_modules/compute-gcd" = {
key = "compute-gcd/1.2.1";
};
"node_modules/compute-lcm" = {
key = "compute-lcm/1.1.2";
};
"node_modules/concat-map" = { "node_modules/concat-map" = {
key = "concat-map/0.0.1"; key = "concat-map/0.0.1";
}; };
@@ -11936,7 +12284,6 @@
key = "execa/5.1.1"; key = "execa/5.1.1";
}; };
"node_modules/fast-deep-equal" = { "node_modules/fast-deep-equal" = {
dev = true;
key = "fast-deep-equal/3.1.3"; key = "fast-deep-equal/3.1.3";
}; };
"node_modules/fast-equals" = { "node_modules/fast-equals" = {
@@ -12323,6 +12670,12 @@
"node_modules/json-parse-even-better-errors" = { "node_modules/json-parse-even-better-errors" = {
key = "json-parse-even-better-errors/2.3.1"; key = "json-parse-even-better-errors/2.3.1";
}; };
"node_modules/json-schema-compare" = {
key = "json-schema-compare/0.2.2";
};
"node_modules/json-schema-merge-allof" = {
key = "json-schema-merge-allof/0.8.1";
};
"node_modules/json-schema-ref-parser" = { "node_modules/json-schema-ref-parser" = {
dev = true; dev = true;
key = "json-schema-ref-parser/5.1.3"; key = "json-schema-ref-parser/5.1.3";
@@ -12364,7 +12717,6 @@
key = "jsonpath-plus/7.1.0"; key = "jsonpath-plus/7.1.0";
}; };
"node_modules/jsonpointer" = { "node_modules/jsonpointer" = {
dev = true;
key = "jsonpointer/5.0.1"; key = "jsonpointer/5.0.1";
}; };
"node_modules/jsonschema" = { "node_modules/jsonschema" = {
@@ -12404,6 +12756,9 @@
"node_modules/lodash" = { "node_modules/lodash" = {
key = "lodash/4.17.21"; key = "lodash/4.17.21";
}; };
"node_modules/lodash-es" = {
key = "lodash-es/4.17.21";
};
"node_modules/lodash.get" = { "node_modules/lodash.get" = {
dev = true; dev = true;
key = "lodash.get/4.4.2"; key = "lodash.get/4.4.2";
@@ -12451,6 +12806,9 @@
dev = true; dev = true;
key = "magic-string/0.25.9"; key = "magic-string/0.25.9";
}; };
"node_modules/markdown-to-jsx" = {
key = "markdown-to-jsx/7.3.2";
};
"node_modules/matcher" = { "node_modules/matcher" = {
dev = true; dev = true;
key = "matcher/1.1.1"; key = "matcher/1.1.1";
@@ -12742,7 +13100,6 @@
key = "proxy-from-env/1.1.0"; key = "proxy-from-env/1.1.0";
}; };
"node_modules/punycode" = { "node_modules/punycode" = {
dev = true;
key = "punycode/2.3.0"; key = "punycode/2.3.0";
}; };
"node_modules/queue-microtask" = { "node_modules/queue-microtask" = {
@@ -12822,7 +13179,6 @@
key = "require-directory/2.1.1"; key = "require-directory/2.1.1";
}; };
"node_modules/require-from-string" = { "node_modules/require-from-string" = {
dev = true;
key = "require-from-string/2.0.2"; key = "require-from-string/2.0.2";
}; };
"node_modules/reserved" = { "node_modules/reserved" = {
@@ -13100,7 +13456,6 @@
key = "update-browserslist-db/1.0.11"; key = "update-browserslist-db/1.0.11";
}; };
"node_modules/uri-js" = { "node_modules/uri-js" = {
dev = true;
key = "uri-js/4.4.1"; key = "uri-js/4.4.1";
}; };
"node_modules/urijs" = { "node_modules/urijs" = {
@@ -13121,6 +13476,21 @@
dev = true; dev = true;
key = "validate-npm-package-name/3.0.0"; key = "validate-npm-package-name/3.0.0";
}; };
"node_modules/validate.io-array" = {
key = "validate.io-array/1.0.6";
};
"node_modules/validate.io-function" = {
key = "validate.io-function/1.0.2";
};
"node_modules/validate.io-integer" = {
key = "validate.io-integer/1.0.5";
};
"node_modules/validate.io-integer-array" = {
key = "validate.io-integer-array/1.0.0";
};
"node_modules/validate.io-number" = {
key = "validate.io-number/1.0.3";
};
"node_modules/validator" = { "node_modules/validator" = {
dev = true; dev = true;
key = "validator/13.11.0"; key = "validator/13.11.0";
@@ -15143,7 +15513,7 @@
depInfo = { depInfo = {
"@babel/runtime" = { "@babel/runtime" = {
descriptor = "^7.5.5"; descriptor = "^7.5.5";
pin = "7.22.10"; pin = "7.22.11";
runtime = true; runtime = true;
}; };
dom-helpers = { dom-helpers = {
@@ -17311,6 +17681,88 @@
version = "3.0.0"; version = "3.0.0";
}; };
}; };
"validate.io-array" = {
"1.0.6" = {
fetchInfo = {
narHash = "sha256-hTj+pWYWlZgbr1jdb6kfr7k2vnYZAyN8d1VwQdBITjg=";
type = "tarball";
url = "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz";
};
ident = "validate.io-array";
ltype = "file";
treeInfo = { };
version = "1.0.6";
};
};
"validate.io-function" = {
"1.0.2" = {
fetchInfo = {
narHash = "sha256-MG3+IDs5WavAbTvbFkZczGZ/NfcAG3QP94E2r2bnchQ=";
type = "tarball";
url = "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz";
};
ident = "validate.io-function";
ltype = "file";
treeInfo = { };
version = "1.0.2";
};
};
"validate.io-integer" = {
"1.0.5" = {
depInfo = {
"validate.io-number" = {
descriptor = "^1.0.3";
pin = "1.0.3";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-yf1eZKbJtm4w+AwPpBtwiCOgIk08joKjkqAmXDjPj3k=";
type = "tarball";
url = "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz";
};
ident = "validate.io-integer";
ltype = "file";
version = "1.0.5";
};
};
"validate.io-integer-array" = {
"1.0.0" = {
depInfo = {
"validate.io-array" = {
descriptor = "^1.0.3";
pin = "1.0.6";
runtime = true;
};
"validate.io-integer" = {
descriptor = "^1.0.4";
pin = "1.0.5";
runtime = true;
};
};
fetchInfo = {
narHash = "sha256-2yabi9Qb/A7B2T29xrl2nxTfgV97SCQe9eOl8GE36gQ=";
type = "tarball";
url = "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz";
};
ident = "validate.io-integer-array";
ltype = "file";
version = "1.0.0";
};
};
"validate.io-number" = {
"1.0.3" = {
fetchInfo = {
narHash = "sha256-tlQD45K0CSB8ih58xTdP8blRdYk1fzLWF3+2r8VEAXw=";
type = "tarball";
url = "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz";
};
ident = "validate.io-number";
ltype = "file";
treeInfo = { };
version = "1.0.3";
};
};
validator = { validator = {
"13.11.0" = { "13.11.0" = {
fetchInfo = { fetchInfo = {

View File

@@ -12,6 +12,10 @@
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.3", "@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.3", "@mui/material": "^5.14.3",
"@rjsf/core": "^5.12.1",
"@rjsf/mui": "^5.12.1",
"@rjsf/validator-ajv8": "^5.12.1",
"@types/json-schema": "^7.0.12",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"axios": "^1.4.0", "axios": "^1.4.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
@@ -353,9 +357,9 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.22.10", "version": "7.22.11",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
}, },
@@ -955,11 +959,11 @@
} }
}, },
"node_modules/@mui/utils": { "node_modules/@mui/utils": {
"version": "5.14.5", "version": "5.14.7",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.5.tgz", "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz",
"integrity": "sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==", "integrity": "sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.22.6", "@babel/runtime": "^7.22.10",
"@types/prop-types": "^15.7.5", "@types/prop-types": "^15.7.5",
"@types/react-is": "^18.2.1", "@types/react-is": "^18.2.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
@@ -1277,6 +1281,98 @@
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
} }
}, },
"node_modules/@rjsf/core": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.12.1.tgz",
"integrity": "sha512-1YFhZ90/uHRx1akQmDdIjBxGMjs/5gtuTLUFwl6GbOwTm2fhZRh3qXRFyTXz81Oy6TGcbrxBJEYvFg2iHjYKCA==",
"dependencies": {
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"markdown-to-jsx": "^7.3.2",
"nanoid": "^3.3.6",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@rjsf/utils": "^5.8.x",
"react": "^16.14.0 || >=17"
}
},
"node_modules/@rjsf/mui": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@rjsf/mui/-/mui-5.12.1.tgz",
"integrity": "sha512-d7cNFIdt6N24m5NPrNSqfCe2SUyUjX48Goo7z4J9vOHWxo5kdDfBEa3UwNA/DR9lo+9cYY7QTvKbgrTkxWU58A==",
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.2.0",
"@mui/material": "^5.2.2",
"@rjsf/core": "^5.8.x",
"@rjsf/utils": "^5.8.x",
"react": ">=17"
}
},
"node_modules/@rjsf/utils": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.12.1.tgz",
"integrity": "sha512-/k8+7WdLwhaYsOQvH5BQINipj2IJvjEW3QQv4jQQ7sXtkpdUjieZayRfaE8DHfRdm9HjgJURJFDy3EODkWPl6A==",
"peer": true,
"dependencies": {
"json-schema-merge-allof": "^0.8.1",
"jsonpointer": "^5.0.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"react-is": "^18.2.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "^16.14.0 || >=17"
}
},
"node_modules/@rjsf/validator-ajv8": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.12.1.tgz",
"integrity": "sha512-m4QO44yp60LTIfd4RPUu/h07B8U9umbD3I4Nh4iv9oyUudncaZFFXRopKcBm08v30VkN0tjMwuu0SxGDpzMtHA==",
"dependencies": {
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@rjsf/utils": "^5.8.x"
}
},
"node_modules/@rjsf/validator-ajv8/node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@rjsf/validator-ajv8/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/@rollup/plugin-commonjs": { "node_modules/@rollup/plugin-commonjs": {
"version": "22.0.2", "version": "22.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz",
@@ -1987,8 +2083,7 @@
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.12", "version": "7.0.12",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
"dev": true
}, },
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
@@ -2218,7 +2313,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"dev": true,
"dependencies": { "dependencies": {
"ajv": "^8.0.0" "ajv": "^8.0.0"
}, },
@@ -2235,7 +2329,6 @@
"version": "8.12.0", "version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",
@@ -2250,8 +2343,7 @@
"node_modules/ajv-formats/node_modules/json-schema-traverse": { "node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
"dev": true
}, },
"node_modules/ansi-colors": { "node_modules/ansi-colors": {
"version": "4.1.3", "version": "4.1.3",
@@ -2869,6 +2961,29 @@
"integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==", "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==",
"dev": true "dev": true
}, },
"node_modules/compute-gcd": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz",
"integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==",
"peer": true,
"dependencies": {
"validate.io-array": "^1.0.3",
"validate.io-function": "^1.0.2",
"validate.io-integer-array": "^1.0.0"
}
},
"node_modules/compute-lcm": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz",
"integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==",
"peer": true,
"dependencies": {
"compute-gcd": "^1.2.1",
"validate.io-array": "^1.0.3",
"validate.io-function": "^1.0.2",
"validate.io-integer-array": "^1.0.0"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -4226,8 +4341,7 @@
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
"dev": true
}, },
"node_modules/fast-equals": { "node_modules/fast-equals": {
"version": "5.0.1", "version": "5.0.1",
@@ -5353,6 +5467,29 @@
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
}, },
"node_modules/json-schema-compare": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz",
"integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==",
"peer": true,
"dependencies": {
"lodash": "^4.17.4"
}
},
"node_modules/json-schema-merge-allof": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz",
"integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==",
"peer": true,
"dependencies": {
"compute-lcm": "^1.1.2",
"json-schema-compare": "^0.2.2",
"lodash": "^4.17.20"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/json-schema-ref-parser": { "node_modules/json-schema-ref-parser": {
"version": "5.1.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-5.1.3.tgz", "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-5.1.3.tgz",
@@ -5452,7 +5589,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -5551,6 +5687,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.get": { "node_modules/lodash.get": {
"version": "4.4.2", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -5637,6 +5778,17 @@
"sourcemap-codec": "^1.4.8" "sourcemap-codec": "^1.4.8"
} }
}, },
"node_modules/markdown-to-jsx": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.3.2.tgz",
"integrity": "sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==",
"engines": {
"node": ">= 10"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/matcher": { "node_modules/matcher": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz", "resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz",
@@ -6688,7 +6840,6 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@@ -6962,7 +7113,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -7875,7 +8025,6 @@
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"dependencies": { "dependencies": {
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
@@ -7917,6 +8066,43 @@
"builtins": "^1.0.3" "builtins": "^1.0.3"
} }
}, },
"node_modules/validate.io-array": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
"integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==",
"peer": true
},
"node_modules/validate.io-function": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
"integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==",
"peer": true
},
"node_modules/validate.io-integer": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz",
"integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==",
"peer": true,
"dependencies": {
"validate.io-number": "^1.0.3"
}
},
"node_modules/validate.io-integer-array": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz",
"integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==",
"peer": true,
"dependencies": {
"validate.io-array": "^1.0.3",
"validate.io-integer": "^1.0.4"
}
},
"node_modules/validate.io-number": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz",
"integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==",
"peer": true
},
"node_modules/validator": { "node_modules/validator": {
"version": "13.11.0", "version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",

View File

@@ -16,6 +16,10 @@
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.3", "@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.3", "@mui/material": "^5.14.3",
"@rjsf/core": "^5.12.1",
"@rjsf/mui": "^5.12.1",
"@rjsf/validator-ajv8": "^5.12.1",
"@types/json-schema": "^7.0.12",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"axios": "^1.4.0", "axios": "^1.4.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",

View File

@@ -1,355 +1,7 @@
"use client"; "use client";
import React, { ReactNode, useEffect, useMemo, useState } from "react";
import {
Box,
Button,
MenuItem,
Select,
Step,
StepLabel,
Stepper,
Typography,
} from "@mui/material";
import {
Control,
Controller,
Form,
useForm,
UseFormWatch,
} from "react-hook-form";
import { DashboardCard } from "@/components/card";
import Info from "@mui/icons-material/Info";
import { Check, Usb } from "@mui/icons-material";
import toast from "react-hot-toast"; import { CreateMachineForm } from "@/components/createMachineForm";
import { buffer } from "stream/consumers";
type StepId = "select" | "create" | "install"; export default function CreateMachine() {
return <CreateMachineForm />;
type Step = {
id: StepId;
label: string;
children?: ReactNode;
};
const steps: Step[] = [
{
id: "select",
label: "Image",
},
{
id: "create",
label: "Customize new template",
},
{
id: "install",
label: "Install",
},
];
const serverImagesData = [
{
id: "1",
name: "Cassies Gaming PC",
},
{
id: "2",
name: "Ivern office",
},
{
id: "3",
name: "Dad's working pc",
},
{
id: "4",
name: "Sisters's pony preset",
},
];
interface StepContentProps {
id: StepId;
control: Control<FormValues>;
watch: UseFormWatch<FormValues>;
}
const StepContent = (props: StepContentProps) => {
const { id, control, watch } = props;
const [hasWebUsb, setHasWebUsb] = useState<boolean>(false);
useEffect(() => {
setHasWebUsb(Boolean(navigator?.usb));
}, []);
const content: Record<StepId, ReactNode> = {
select: (
<div>
<div className="">
<Typography component="div" variant="overline" className="h-full">
Select an image
</Typography>
<Controller
name="image"
control={control}
render={({ field }) => (
<Select
{...field}
defaultValue={control._defaultValues.image}
fullWidth
>
{imageOptions.map(({ id, label }) => (
<MenuItem key={id} value={id}>
{label}
</MenuItem>
))}
</Select>
)}
/>
<div className="w-full py-4">
<DashboardCard title={<Info />}>
<div className="w-full py-2">
<Typography className="pb-4">
{watch("image") === "new"
? `You selected the option to create a new system image. Configure your predefined options, such as programs, clans, etc. in
the following steps.`
: `You selected the option to reuse an existing system image. Please select one
from the list below`}
</Typography>
{watch("image") === "existing" && (
<Controller
name="source"
control={control}
render={({ field }) => (
<Select
{...field}
defaultValue={control._defaultValues.source}
fullWidth
>
{serverImagesData.map(({ id, name }) => (
<MenuItem key={id} value={id}>
{name}
</MenuItem>
))}
</Select>
)}
/>
)}
</div>
</DashboardCard>
</div>
</div>
</div>
),
create: (
<div className="flex w-full flex-col">
<div className="my-3 w-full p-4">
Formular generated from nix flake jsonschema
</div>
</div>
),
install: (
<div className="flex w-full justify-center">
<Button
color="secondary"
type="submit"
startIcon={<Usb />}
variant="contained"
>
{hasWebUsb ? "Flash USB Device" : "Download installer image"}
</Button>
</div>
),
};
return (
<div className="mt-4 flex p-4">
<div className="flex w-full flex-col">
<Typography
component="div"
variant="overline"
className="flex w-full justify-center"
>
{watch("image") == "new"
? "Create system template"
: "Choose existing"}
</Typography>
<div className="my-3 w-full p-4">{content[id]}</div>
</div>
</div>
);
};
type FormValues = {
image: ImageOption;
source: string;
};
type ImageOption = "new" | "existing";
type ImageOptions = {
id: ImageOption;
label: string;
}[];
const imageOptions: ImageOptions = [
{
id: "new",
label: "New image",
},
{
id: "existing",
label: "Previously created image",
},
];
const defaultValues: FormValues = {
image: "new",
source: serverImagesData[0].id,
};
export default function AddNode() {
const { handleSubmit, control, watch, reset, formState } =
useForm<FormValues>({
defaultValues,
});
const [activeStep, setActiveStep] = useState<number>(0);
const [usb, setUsb] = useState<USB | undefined>(undefined);
useEffect(() => {
setUsb(navigator?.usb);
}, []);
const handleNext = () => {
if (activeStep < visibleSteps.length - 1) {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
}
};
const handleBack = () => {
if (activeStep > 0) {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
}
};
const handleReset = () => {
setActiveStep(0);
reset();
};
async function onSubmit(data: any) {
console.log({ data }, "To be submitted");
if (usb) {
let device;
try {
device = await usb.requestDevice({
filters: [{}],
});
toast.success(`Connected to '${device.productName}'`);
} catch (error) {
console.log({ error });
toast.error("Couldn't connect to usb device");
}
if (device) {
// await device.open();
// await device.selectConfiguration(1);
// await device.claimInterface(0);
// const data = new Uint8Array([1, 2, 3]);
// device.transferOut(2, data);
}
} else {
//Offer the image as download
const blob = new Blob(["data"]);
let url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = "image.iso";
a.click();
}
return true;
}
const imageValue = watch("image");
const visibleSteps = useMemo(
() =>
steps.filter((s) => {
if (imageValue == "existing" && s.id == "create") {
return false;
}
return true;
}),
[imageValue],
);
// console.log({})
const currentStep = visibleSteps.at(activeStep);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Box sx={{ width: "100%" }}>
<Stepper activeStep={activeStep} color="secondary">
{visibleSteps.map(({ label }, index) => {
const stepProps: { completed?: boolean } = {};
const labelProps: {
optional?: React.ReactNode;
} = {};
return (
<Step
sx={{
".MuiStepIcon-root.Mui-active": {
color: "secondary.main",
},
".MuiStepIcon-root.Mui-completed": {
color: "secondary.main",
},
}}
key={label}
{...stepProps}
>
<StepLabel {...labelProps}>{label}</StepLabel>
</Step>
);
})}
</Stepper>
{activeStep === visibleSteps.length ? (
<>
<Typography variant="h5" sx={{ mt: 2, mb: 1 }}>
Image succesfully downloaded
</Typography>
<Box sx={{ display: "flex", flexDirection: "row", pt: 2 }}>
<Box sx={{ flex: "1 1 auto" }} />
<Button color="secondary" onClick={handleReset}>
Reset
</Button>
</Box>
</>
) : (
<>
{currentStep && (
<StepContent
id={currentStep.id}
control={control}
watch={watch}
/>
)}
<Box sx={{ display: "flex", flexDirection: "row", pt: 2 }}>
<Button
color="secondary"
disabled={activeStep === 0}
onClick={handleBack}
sx={{ mr: 1 }}
>
Back
</Button>
<Box sx={{ flex: "1 1 auto" }} />
{activeStep !== visibleSteps.length - 1 && (
<Button onClick={handleNext} color="secondary">
{activeStep <= visibleSteps.length - 1 && "Next"}
</Button>
)}
{activeStep === visibleSteps.length - 1 && (
<Button color="secondary" onClick={handleReset}>
Reset
</Button>
)}
</Box>
</>
)}
</Box>
</form>
);
} }

View File

@@ -0,0 +1,164 @@
"use client";
import { useGetMachineSchema } from "@/api/default/default";
import { Check, Error } from "@mui/icons-material";
import {
Box,
Button,
LinearProgress,
List,
ListItem,
ListItemIcon,
ListItemText,
Paper,
Typography,
} from "@mui/material";
import { IChangeEvent, FormProps } from "@rjsf/core";
import { Form } from "@rjsf/mui";
import validator from "@rjsf/validator-ajv8";
import toast from "react-hot-toast";
import { JSONSchema7 } from "json-schema";
import { useMemo, useRef } from "react";
import { FormStepContentProps } from "./interfaces";
import {
ErrorListProps,
FormContextType,
RJSFSchema,
StrictRJSFSchema,
TranslatableString,
} from "@rjsf/utils";
interface PureCustomConfigProps extends FormStepContentProps {
schema: JSONSchema7;
initialValues: any;
}
export function CustomConfig(props: FormStepContentProps) {
const { formHooks } = props;
const { data, isLoading, error } = useGetMachineSchema("mama");
const schema = useMemo(() => {
if (!isLoading && !error?.message && data?.data) {
return data?.data.schema;
}
return {};
}, [data, isLoading, error]);
const initialValues = useMemo(
() =>
Object.entries(schema?.properties || {}).reduce((acc, [key, value]) => {
/*@ts-ignore*/
const init: any = value?.default;
if (init) {
return {
...acc,
[key]: init,
};
}
return acc;
}, {}),
[schema],
);
return isLoading ? (
<LinearProgress variant="indeterminate" />
) : error?.message ? (
<div>{error?.message}</div>
) : (
<PureCustomConfig
formHooks={formHooks}
initialValues={initialValues}
schema={schema}
/>
);
}
function ErrorList<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any,
>({ errors, registry }: ErrorListProps<T, S, F>) {
const { translateString } = registry;
return (
<Paper elevation={0}>
<Box mb={2} p={2}>
<Typography variant="h6">
{translateString(TranslatableString.ErrorsLabel)}
</Typography>
<List dense={true}>
{errors.map((error, i: number) => {
return (
<ListItem key={i}>
<ListItemIcon>
<Error color="error" />
</ListItemIcon>
<ListItemText primary={error.stack} />
</ListItem>
);
})}
</List>
</Box>
</Paper>
);
}
function PureCustomConfig(props: PureCustomConfigProps) {
const { schema, initialValues, formHooks } = props;
const { setValue, watch } = formHooks;
console.log({ schema });
const configData = watch("config") as IChangeEvent<any>;
console.log({ configData });
const setConfig = (data: IChangeEvent<any>) => {
console.log({ data });
setValue("config", data);
};
const formRef = useRef<any>();
const validate = () => {
const isValid: boolean = formRef?.current?.validateForm();
console.log({ isValid }, formRef.current);
if (!isValid) {
formHooks.setError("config", {
message: "invalid config",
});
toast.error(
"Configuration is invalid. Please check the highlighted fields for details.",
);
} else {
formHooks.clearErrors("config");
toast.success("Config seems valid");
}
};
return (
<Form
ref={formRef}
onChange={setConfig}
formData={configData.formData}
acceptcharset="utf-8"
schema={schema}
validator={validator}
liveValidate={true}
templates={{
// ObjectFieldTemplate:
ErrorListTemplate: ErrorList,
ButtonTemplates: {
SubmitButton: (props) => (
<div className="flex w-full items-center justify-center">
<Button
onClick={validate}
startIcon={<Check />}
variant="outlined"
color="secondary"
>
Validate
</Button>
</div>
),
},
}}
/>
);
}

View File

@@ -0,0 +1,160 @@
import {
Box,
Button,
MobileStepper,
Step,
StepLabel,
Stepper,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import React, { ReactNode, useState } from "react";
import { useForm, UseFormReturn } from "react-hook-form";
import { CustomConfig } from "./customConfig";
import { CreateMachineForm, FormStep } from "./interfaces";
const SC = (props: { children: ReactNode }) => {
return <>{props.children}</>;
};
export function CreateMachineForm() {
const formHooks = useForm<CreateMachineForm>({
defaultValues: {
name: "",
config: {},
},
});
const { handleSubmit, control, watch, reset, formState } = formHooks;
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [activeStep, setActiveStep] = useState<number>(0);
const steps: FormStep[] = [
{
id: "template",
label: "Template",
content: <div></div>,
},
{
id: "modules",
label: "Modules",
content: <div></div>,
},
{
id: "config",
label: "Customize",
content: <CustomConfig formHooks={formHooks} />,
},
{
id: "save",
label: "Save",
content: <div></div>,
},
];
const handleNext = () => {
if (activeStep < steps.length - 1) {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
}
};
const handleBack = () => {
if (activeStep > 0) {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
}
};
const handleReset = () => {
setActiveStep(0);
reset();
};
const currentStep = steps.at(activeStep);
async function onSubmit(data: any) {
console.log({ data }, "Aggregated Data; creating machine from");
}
const BackButton = () => (
<Button
color="secondary"
disabled={activeStep === 0}
onClick={handleBack}
sx={{ mr: 1 }}
>
Back
</Button>
);
const NextButton = () => (
<>
{activeStep !== steps.length - 1 && (
<Button
disabled={!formHooks.formState.isValid}
onClick={handleNext}
color="secondary"
>
{activeStep <= steps.length - 1 && "Next"}
</Button>
)}
{activeStep === steps.length - 1 && (
<Button color="secondary" onClick={handleReset}>
Reset
</Button>
)}
</>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Box sx={{ width: "100%" }}>
{isMobile && (
<MobileStepper
activeStep={activeStep}
color="secondary"
backButton={<BackButton />}
nextButton={<NextButton />}
steps={steps.length}
/>
)}
{!isMobile && (
<Stepper activeStep={activeStep} color="secondary">
{steps.map(({ label }, index) => {
const stepProps: { completed?: boolean } = {};
const labelProps: {
optional?: React.ReactNode;
} = {};
return (
<Step
sx={{
".MuiStepIcon-root.Mui-active": {
color: "secondary.main",
},
".MuiStepIcon-root.Mui-completed": {
color: "secondary.main",
},
}}
key={label}
{...stepProps}
>
<StepLabel {...labelProps}>{label}</StepLabel>
</Step>
);
})}
</Stepper>
)}
{/* <CustomConfig formHooks={formHooks} /> */}
{/* The step Content */}
{currentStep && currentStep.content}
{/* Desktop step controls */}
{!isMobile && (
<Box sx={{ display: "flex", flexDirection: "row", pt: 2 }}>
<BackButton />
<Box sx={{ flex: "1 1 auto" }} />
<NextButton />
</Box>
)}
</Box>
</form>
);
}

View File

@@ -0,0 +1,23 @@
import { ReactElement, ReactNode } from "react";
import { UseFormReturn } from "react-hook-form";
export type StepId = "template" | "modules" | "config" | "save";
export type CreateMachineForm = {
name: string;
config: any;
};
export type FormHooks = UseFormReturn<CreateMachineForm>;
export type FormStep = {
id: StepId;
label: string;
content: FormStepContent;
};
export interface FormStepContentProps {
formHooks: FormHooks;
}
export type FormStepContent = ReactElement<FormStepContentProps>;

View File

@@ -0,0 +1,88 @@
import { RJSFSchema } from "@rjsf/utils";
export const schema: RJSFSchema = {
properties: {
bloatware: {
properties: {
age: {
default: 42,
description: "The age of the user",
type: "integer",
},
isAdmin: {
default: false,
description: "Is the user an admin?",
type: "boolean",
},
kernelModules: {
default: ["nvme", "xhci_pci", "ahci"],
description: "A list of enabled kernel modules",
items: {
type: "string",
},
type: "array",
},
name: {
default: "John Doe",
description: "The name of the user",
type: "string",
},
services: {
properties: {
opt: {
default: "foo",
description: "A submodule option",
type: "string",
},
},
type: "object",
},
userIds: {
additionalProperties: {
type: "integer",
},
default: {
albrecht: 3,
horst: 1,
peter: 2,
},
description: "Some attributes",
type: "object",
},
},
type: "object",
},
networking: {
properties: {
zerotier: {
properties: {
controller: {
properties: {
enable: {
default: false,
description:
"Whether to enable turn this machine into the networkcontroller.",
type: "boolean",
},
public: {
default: false,
description:
"everyone can join a public network without having the administrator to accept\n",
type: "boolean",
},
},
type: "object",
},
networkId: {
description: "zerotier networking id\n",
type: "string",
},
},
required: ["networkId"],
type: "object",
},
},
type: "object",
},
},
type: "object",
};

View File

@@ -0,0 +1,111 @@
import { RJSFSchema } from "@rjsf/utils";
export const schema: RJSFSchema = {
type: "object",
properties: {
name: {
type: "string",
default: "John-nixi",
description: "The name of the machine",
},
age: {
type: "integer",
default: 42,
description: "The age of the user",
maximum: 40,
},
role: {
enum: ["New York", "Amsterdam", "Hong Kong"],
description: "Role of the user",
},
kernelModules: {
type: "array",
items: {
type: "string",
},
default: ["nvme", "xhci_pci", "ahci"],
description: "A list of enabled kernel modules",
},
userIds: {
type: "array",
items: {
type: "object",
properties: {
user: {
type: "string",
},
id: {
type: "integer",
},
},
},
default: [
{
user: "John",
id: 12,
},
],
description: "Some attributes",
},
xdg: {
type: "object",
properties: {
portal: {
type: "object",
properties: {
xdgOpenUsePortal: {
type: "boolean",
default: false,
},
enable: {
type: "boolean",
default: false,
},
lxqt: {
type: "object",
properties: {
enable: {
type: "boolean",
default: false,
},
styles: {
type: "array",
items: {
type: "string",
},
},
},
},
extraPortals: {
type: "array",
items: {
type: "string",
},
},
wlr: {
type: "object",
properties: {
enable: {
type: "boolean",
default: false,
},
settings: {
type: "object",
default: {
screencast: {
output_name: "HDMI-A-1",
max_fps: 30,
exec_before: "disable_notifications.sh",
exec_after: "enable_notifications.sh",
chooser_type: "simple",
chooser_cmd: "${pkgs.slurp}/bin/slurp -f %o -or",
},
},
},
},
},
},
},
},
},
},
};

View File

@@ -3,9 +3,21 @@
inputs.clan-core.url = "git+https://git.clan.lol/clan/clan-core"; inputs.clan-core.url = "git+https://git.clan.lol/clan/clan-core";
outputs = { clan-core, ... }: { outputs = { self, clan-core, ... }:
nixosConfigurations = clan-core.lib.buildClan { let
directory = ./.; system = "x86_64-linux";
pkgs = clan-core.inputs.nixpkgs.legacyPackages.${system};
in
{
# all machines managed by cLAN
nixosConfigurations = clan-core.lib.buildClan {
directory = self;
};
# add the cLAN cli tool to the dev shell
devShells.${system}.default = pkgs.mkShell {
packages = [
clan-core.packages.${system}.clan-cli
];
};
}; };
};
} }