Merge branch 'main' into Qubasa-Qubasa-main
This commit is contained in:
@@ -1,16 +1,23 @@
|
||||
{ self, ... }: {
|
||||
perSystem = { pkgs, lib, ... }:
|
||||
perSystem = { pkgs, lib, self', ... }:
|
||||
let
|
||||
impureChecks = {
|
||||
check-clan-template = pkgs.writeShellScriptBin "check-clan-template" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
export TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d)
|
||||
trap "${pkgs.coreutils}/bin/chmod -R +w '$TMPDIR'; ${pkgs.coreutils}/bin/rm -rf '$TMPDIR'" EXIT
|
||||
|
||||
export PATH="${lib.makeBinPath [
|
||||
pkgs.coreutils
|
||||
pkgs.curl
|
||||
pkgs.gitMinimal
|
||||
pkgs.gnugrep
|
||||
pkgs.jq
|
||||
pkgs.openssh
|
||||
pkgs.nix
|
||||
self'.packages.clan-cli
|
||||
]}"
|
||||
|
||||
cd $TMPDIR
|
||||
@@ -18,8 +25,24 @@
|
||||
echo initialize 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
|
||||
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
|
||||
|
||||
@@ -5,13 +5,17 @@
|
||||
imports = [
|
||||
(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;
|
||||
|
||||
clanCore.clanDir = "${./.}";
|
||||
clanCore.machineName = "machine";
|
||||
|
||||
networking.hostName = "machine";
|
||||
};
|
||||
testScript = ''
|
||||
machine.succeed("cat /etc/secret >&2")
|
||||
machine.succeed("cat /etc/group-secret >&2")
|
||||
'';
|
||||
}
|
||||
|
||||
1
checks/secrets/sops/secrets/group-secret/groups/group
Symbolic link
1
checks/secrets/sops/secrets/group-secret/groups/group
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../groups/group
|
||||
20
checks/secrets/sops/secrets/group-secret/secret
Normal file
20
checks/secrets/sops/secrets/group-secret/secret
Normal 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"
|
||||
}
|
||||
}
|
||||
1
checks/secrets/sops/secrets/secret/machines/machine
Symbolic link
1
checks/secrets/sops/secrets/secret/machines/machine
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../machines/machine
|
||||
30
flake.lock
generated
30
flake.lock
generated
@@ -7,11 +7,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692199161,
|
||||
"narHash": "sha256-GqKApvQ1JCf5DzH/Q+P4nwuHb6MaQGaWTu41lYzveF4=",
|
||||
"lastModified": 1693677537,
|
||||
"narHash": "sha256-F8ozidIQV4Sp/IfTE54U+qIOuC88b9WskFWK5VrHBs4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "4eed2457b053c4bbad7d90d2b3a1d539c2c9009c",
|
||||
"rev": "06481a9836c37b7c1aba784092a984c2d2ef5431",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -27,11 +27,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1690933134,
|
||||
"narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
|
||||
"lastModified": 1693611461,
|
||||
"narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
|
||||
"rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -98,11 +98,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1693003285,
|
||||
"narHash": "sha256-5nm4yrEHKupjn62MibENtfqlP6pWcRTuSKrMiH9bLkc=",
|
||||
"lastModified": 1693663421,
|
||||
"narHash": "sha256-ImMIlWE/idjcZAfxKK8sQA7A1Gi/O58u5/CJA+mxvl8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5690c4271f2998c304a45c91a0aeb8fb69feaea7",
|
||||
"rev": "e56990880811a451abd32515698c712788be5720",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -131,11 +131,11 @@
|
||||
"nixpkgs-stable": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1693105804,
|
||||
"narHash": "sha256-nlqNjW7dfucUJQqRGuG08MKPOSME8fLOCx/bd9hiEPs=",
|
||||
"lastModified": 1693404499,
|
||||
"narHash": "sha256-cx/7yvM/AP+o/3wPJmA9W9F+WHemJk5t+Xcr+Qwkqhg=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "0618c8f0ed5255ad74ee08d1618841ff5af85c86",
|
||||
"rev": "d9c5dc41c4b1f74c77f0dbffd0f3a4ebde447b7a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -151,11 +151,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692972530,
|
||||
"narHash": "sha256-LG+M7TjlLJ1lx2qbD1yaexvue1VAatpVandtHVEN5Lc=",
|
||||
"lastModified": 1693689099,
|
||||
"narHash": "sha256-NuilTRYMH+DDR/uBWQjDbX5mWCA05lwo2Sg9iTkkEs4=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "843e1e1b01ac7c9e858368fffd1692cbbdbe4a0e",
|
||||
"rev": "e3e0f9f6d47f8fc68aff15150eda1224fb46f4d4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
{ nixpkgs, clan, lib }:
|
||||
{ nixpkgs, self, lib }:
|
||||
{ directory # The directory containing the machines subdirectory
|
||||
, 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} = { ... }
|
||||
}:
|
||||
let
|
||||
machinesDirs =
|
||||
if builtins.pathExists (directory + /machines)
|
||||
then builtins.readDir (directory + /machines)
|
||||
else { };
|
||||
machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (builtins.readDir (directory + /machines));
|
||||
|
||||
machineSettings = machineName:
|
||||
if builtins.pathExists (directory + /machines/${machineName}/settings.json)
|
||||
then builtins.fromJSON (builtins.readFile (directory + /machines/${machineName}/settings.json))
|
||||
else { };
|
||||
lib.optionalAttrs (builtins.pathExists "${directory}/machines/${machineName}/settings.json")
|
||||
(builtins.fromJSON
|
||||
(builtins.readFile (directory + /machines/${machineName}/settings.json)));
|
||||
|
||||
nixosConfigurations = lib.mapAttrs
|
||||
(name: _:
|
||||
nixpkgs.lib.nixosSystem {
|
||||
modules = [
|
||||
clan.nixosModules.clanCore
|
||||
self.nixosModules.clanCore
|
||||
(machineSettings name)
|
||||
(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);
|
||||
in
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ lib, clan, nixpkgs, ... }:
|
||||
{ lib, self, nixpkgs, ... }:
|
||||
{
|
||||
findNixFiles = folder:
|
||||
lib.mapAttrs'
|
||||
@@ -14,5 +14,5 @@
|
||||
|
||||
jsonschema = import ./jsonschema { inherit lib; };
|
||||
|
||||
buildClan = import ./build-clan { inherit lib clan nixpkgs; };
|
||||
buildClan = import ./build-clan { inherit lib self nixpkgs; };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{ lib
|
||||
, inputs
|
||||
, self
|
||||
, ...
|
||||
}: {
|
||||
imports = [
|
||||
@@ -7,6 +8,7 @@
|
||||
];
|
||||
flake.lib = import ./default.nix {
|
||||
inherit lib;
|
||||
inherit (inputs) nixpkgs clan;
|
||||
inherit self;
|
||||
inherit (inputs) nixpkgs;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
*/
|
||||
{ lib, ... }: {
|
||||
options = {
|
||||
# str
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "John Doe";
|
||||
description = "The name of the user";
|
||||
};
|
||||
# int
|
||||
age = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 42;
|
||||
description = "The age of the user";
|
||||
};
|
||||
# bool
|
||||
isAdmin = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
@@ -28,6 +31,7 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
# attrs of int
|
||||
userIds = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.int;
|
||||
description = "Some attributes";
|
||||
@@ -37,6 +41,7 @@
|
||||
albrecht = 3;
|
||||
};
|
||||
};
|
||||
# list of str
|
||||
kernelModules = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "nvme" "xhci_pci" "ahci" ];
|
||||
|
||||
9
nixosModules/clanCore/bloatware/default.nix
Normal file
9
nixosModules/clanCore/bloatware/default.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
{ lib, ... }: {
|
||||
options.clan.bloatware = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
imports = [
|
||||
../../../lib/jsonschema/example-interface.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,20 @@
|
||||
{ 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 = {
|
||||
clanDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
type = lib.types.either lib.types.path lib.types.str;
|
||||
description = ''
|
||||
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
|
||||
'';
|
||||
};
|
||||
imports = [
|
||||
./secrets
|
||||
./zerotier.nix
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
description = ''
|
||||
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 {
|
||||
default = builtins.readFile fact.config.path;
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
{ 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 = {
|
||||
system.clan.generateSecrets = pkgs.writeScript "generate_secrets" ''
|
||||
system.clan.generateSecrets = pkgs.writeScript "generate-secrets" ''
|
||||
#!/bin/sh
|
||||
set -efu
|
||||
set -x # remove for prod
|
||||
@@ -43,21 +64,13 @@
|
||||
fi)
|
||||
'') "" config.clanCore.secrets}
|
||||
'';
|
||||
sops.secrets =
|
||||
let
|
||||
encryptedForThisMachine = name: type:
|
||||
let
|
||||
symlink = config.clanCore.clanDir + "/sops/secrets/${name}/machines/${config.clanCore.machineName}";
|
||||
in
|
||||
# WTF, nix bug, my symlink is in the nixos module detected as a directory also it works in the repl
|
||||
type == "directory" && (builtins.readFileType symlink == "directory" || builtins.readFileType symlink == "symlink");
|
||||
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;
|
||||
sops.secrets = builtins.mapAttrs
|
||||
(name: _: {
|
||||
sopsFile = config.clanCore.clanDir + "/sops/secrets/${name}/secret";
|
||||
format = "binary";
|
||||
})
|
||||
secrets;
|
||||
# To get proper error messages about missing secrets we need a dummy secret file that is always present
|
||||
sops.defaultSopsFile = lib.mkIf config.sops.validateSopsFiles (lib.mkDefault (builtins.toString (pkgs.writeText "dummy.yaml" "")));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
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.nix import nix_eval
|
||||
|
||||
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}
|
||||
# this will give us the evaluated config with the options attribute
|
||||
proc = subprocess.run(
|
||||
[
|
||||
"nix",
|
||||
"eval",
|
||||
"--json",
|
||||
f".#nixosConfigurations.default.config.{option}",
|
||||
],
|
||||
nix_eval(
|
||||
flags=[
|
||||
"--json",
|
||||
"--show-trace",
|
||||
"--extra-experimental-features",
|
||||
"nix-command flakes",
|
||||
f".#nixosConfigurations.{machine_name}.config.{option}",
|
||||
],
|
||||
),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
@@ -119,18 +163,44 @@ def read_option(option: str) -> str:
|
||||
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,
|
||||
value: Any,
|
||||
options: dict,
|
||||
settings_file: Path,
|
||||
quiet: bool = False,
|
||||
option_description: str = "",
|
||||
) -> None:
|
||||
if value == []:
|
||||
print(read_option(option))
|
||||
return
|
||||
|
||||
option_path = option.split(".")
|
||||
|
||||
# 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")
|
||||
option_parent = option_path[:-1]
|
||||
attr = option_path[-1]
|
||||
return process_args(
|
||||
return set_option(
|
||||
option=".".join(option_parent),
|
||||
value={attr: value},
|
||||
options=options,
|
||||
settings_file=settings_file,
|
||||
quiet=quiet,
|
||||
option_description=option,
|
||||
)
|
||||
|
||||
@@ -170,45 +239,14 @@ def process_args(
|
||||
current_config = {}
|
||||
# merge and save the new config file
|
||||
new_config = merge(current_config, result)
|
||||
settings_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(settings_file, "w") as f:
|
||||
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
|
||||
def _register_parser(
|
||||
def register_parser(
|
||||
parser: Optional[argparse.ArgumentParser],
|
||||
options: dict[str, Any],
|
||||
) -> None:
|
||||
if parser is None:
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -216,31 +254,35 @@ def _register_parser(
|
||||
)
|
||||
|
||||
# inject callback function to process the input later
|
||||
parser.set_defaults(
|
||||
func=lambda args: process_args(
|
||||
option=args.option,
|
||||
value=args.value,
|
||||
options=options,
|
||||
quiet=args.quiet,
|
||||
settings_file=args.settings_file,
|
||||
)
|
||||
)
|
||||
parser.set_defaults(func=get_or_set_option)
|
||||
|
||||
# add --quiet option
|
||||
# add --machine argument
|
||||
parser.add_argument(
|
||||
"--quiet",
|
||||
"-q",
|
||||
help="Suppress output",
|
||||
action="store_true",
|
||||
"--machine",
|
||||
"-m",
|
||||
help="Machine to configure",
|
||||
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(
|
||||
"--settings-file",
|
||||
"-o",
|
||||
help="Output file",
|
||||
help="JSON file with settings",
|
||||
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")
|
||||
@@ -248,7 +290,6 @@ def _register_parser(
|
||||
"option",
|
||||
help="Option to configure",
|
||||
type=str,
|
||||
choices=AllContainer(list(options.keys())),
|
||||
)
|
||||
|
||||
# add a single optional argument for the value
|
||||
@@ -264,14 +305,8 @@ def main(argv: Optional[list[str]] = None) -> None:
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"schema",
|
||||
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:])
|
||||
register_parser(parser)
|
||||
parser.parse_args(argv[1:])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,8 +6,9 @@ from typing import Optional
|
||||
|
||||
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.nix import nix_eval
|
||||
|
||||
|
||||
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()
|
||||
# use nix eval to lib.evalModules .#nixosModules.machine-{machine_name}
|
||||
proc = subprocess.run(
|
||||
[
|
||||
"nix",
|
||||
"eval",
|
||||
"--json",
|
||||
"--impure",
|
||||
"--show-trace",
|
||||
"--extra-experimental-features",
|
||||
"nix-command flakes",
|
||||
"--expr",
|
||||
f"""
|
||||
let
|
||||
flake = builtins.getFlake (toString {flake});
|
||||
lib = import {nixpkgs()}/lib;
|
||||
module = builtins.trace (builtins.attrNames flake) flake.nixosModules.machine-{machine_name};
|
||||
evaled = lib.evalModules {{
|
||||
modules = [module];
|
||||
}};
|
||||
clanOptions = evaled.options.clan;
|
||||
jsonschemaLib = import {Path(__file__).parent / "jsonschema"} {{ inherit lib; }};
|
||||
jsonschema = jsonschemaLib.parseOptions clanOptions;
|
||||
in
|
||||
jsonschema
|
||||
""",
|
||||
],
|
||||
nix_eval(
|
||||
flags=[
|
||||
"--json",
|
||||
"--impure",
|
||||
"--show-trace",
|
||||
"--extra-experimental-features",
|
||||
"nix-command flakes",
|
||||
"--expr",
|
||||
f"""
|
||||
let
|
||||
flake = builtins.getFlake (toString {flake});
|
||||
lib = import {nixpkgs_source()}/lib;
|
||||
options = flake.nixosConfigurations.{machine_name}.options;
|
||||
clanOptions = options.clan;
|
||||
jsonschemaLib = import {Path(__file__).parent / "jsonschema"} {{ inherit lib; }};
|
||||
jsonschema = jsonschemaLib.parseOptions clanOptions;
|
||||
in
|
||||
jsonschema
|
||||
""",
|
||||
],
|
||||
),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
@@ -30,11 +30,11 @@ def module_root() -> Path:
|
||||
return Path(__file__).parent
|
||||
|
||||
|
||||
def flake_registry() -> Path:
|
||||
return module_root() / "nixpkgs" / "flake-registry.json"
|
||||
def nixpkgs_flake() -> Path:
|
||||
return (module_root() / "nixpkgs").resolve()
|
||||
|
||||
|
||||
def nixpkgs() -> Path:
|
||||
def nixpkgs_source() -> Path:
|
||||
return (module_root() / "nixpkgs" / "path").resolve()
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ from .folders import machine_folder
|
||||
def create_machine(name: str) -> None:
|
||||
folder = machine_folder(name)
|
||||
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:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import argparse
|
||||
import shutil
|
||||
|
||||
from ..errors import ClanError
|
||||
from .folders import machine_folder
|
||||
@@ -7,7 +8,7 @@ from .folders import machine_folder
|
||||
def delete_command(args: argparse.Namespace) -> None:
|
||||
folder = machine_folder(args.host)
|
||||
if folder.exists():
|
||||
folder.rmdir()
|
||||
shutil.rmtree(folder)
|
||||
else:
|
||||
raise ClanError(f"Machine {args.host} does not exist")
|
||||
|
||||
|
||||
@@ -1,6 +1,33 @@
|
||||
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]:
|
||||
@@ -15,8 +42,8 @@ def nix_shell(packages: list[str], cmd: list[str]) -> list[str]:
|
||||
"shell",
|
||||
"--extra-experimental-features",
|
||||
"nix-command flakes",
|
||||
"--flake-registry",
|
||||
str(flake_registry()),
|
||||
"--inputs-from",
|
||||
f"{str(nixpkgs_flake())}",
|
||||
]
|
||||
+ wrapped_packages
|
||||
+ ["-c"]
|
||||
|
||||
@@ -36,7 +36,7 @@ def import_sops(args: argparse.Namespace) -> None:
|
||||
file=sys.stderr,
|
||||
)
|
||||
continue
|
||||
if (sops_secrets_folder() / k).exists():
|
||||
if (sops_secrets_folder() / k / "secret").exists():
|
||||
print(
|
||||
f"WARNING: {k} already exists, skipping",
|
||||
file=sys.stderr,
|
||||
|
||||
@@ -212,7 +212,13 @@ def set_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:
|
||||
@@ -250,9 +256,7 @@ def register_secrets_parser(subparser: argparse._SubParsersAction) -> None:
|
||||
|
||||
parser_rename = subparser.add_parser("rename", help="rename a secret")
|
||||
add_secret_argument(parser_rename)
|
||||
parser_rename.add_argument(
|
||||
"new_name", help="the new name of the secret", type=secret_name_type
|
||||
)
|
||||
parser_rename.add_argument("new_name", type=str, help="the new name of the secret")
|
||||
parser_rename.set_defaults(func=rename_command)
|
||||
|
||||
parser_remove = subparser.add_parser("remove", help="remove a secret")
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.routing import APIRoute
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .assets import asset_path
|
||||
from .config import settings
|
||||
from .routers import health, machines, root
|
||||
|
||||
|
||||
@@ -14,17 +12,7 @@ def setup_app() -> FastAPI:
|
||||
app.include_router(machines.router)
|
||||
app.include_router(root.router)
|
||||
|
||||
if settings.env.is_development():
|
||||
# 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")
|
||||
app.mount("/static", StaticFiles(directory=asset_path()), name="static")
|
||||
|
||||
for route in app.routes:
|
||||
if isinstance(route, APIRoute):
|
||||
|
||||
@@ -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()
|
||||
@@ -1,6 +1,5 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.request
|
||||
@@ -27,11 +26,23 @@ def defer_open_browser(base_url: str) -> None:
|
||||
|
||||
|
||||
@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...")
|
||||
path = Path(__file__).parent.parent.parent.parent / "ui"
|
||||
with subprocess.Popen(
|
||||
["direnv", "exec", path, "npm", "run", "dev"],
|
||||
[
|
||||
"direnv",
|
||||
"exec",
|
||||
path,
|
||||
"npm",
|
||||
"run",
|
||||
"dev",
|
||||
"--",
|
||||
"--hostname",
|
||||
host,
|
||||
"--port",
|
||||
str(port),
|
||||
],
|
||||
cwd=path,
|
||||
) as proc:
|
||||
try:
|
||||
@@ -42,16 +53,21 @@ def spawn_node_dev_server() -> Iterator[None]:
|
||||
|
||||
def start_server(args: argparse.Namespace) -> None:
|
||||
with ExitStack() as stack:
|
||||
headers: list[tuple[str, str]] = []
|
||||
if args.dev:
|
||||
os.environ["CLAN_WEBUI_ENV"] = "development"
|
||||
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())
|
||||
stack.enter_context(spawn_node_dev_server(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:
|
||||
os.environ["CLAN_WEBUI_ENV"] = "production"
|
||||
open_url = f"http://[{args.host}]:{args.port}"
|
||||
|
||||
if not args.no_open:
|
||||
@@ -63,5 +79,5 @@ def start_server(args: argparse.Namespace) -> None:
|
||||
port=args.port,
|
||||
log_level=args.log_level,
|
||||
reload=args.reload,
|
||||
headers=[("Access-Control-Allow-Origin", "*")],
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
@@ -44,24 +44,36 @@ let
|
||||
checkPython = python3.withPackages (_ps: dependencies ++ testDependencies);
|
||||
|
||||
# - vendor the jsonschema nix lib (copy instead of symlink).
|
||||
# - lib.cleanSource prevents unnecessary rebuilds when `self` changes.
|
||||
source = runCommand "clan-cli-source" { } ''
|
||||
cp -r ${./.} $out
|
||||
chmod -R +w $out
|
||||
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
|
||||
ln -s ${ui-assets} $out/clan_cli/webui/assets
|
||||
'';
|
||||
nixpkgs = runCommand "nixpkgs" { } ''
|
||||
nixpkgs = runCommand "nixpkgs" { nativeBuildInputs = [ pkgs.nix ]; } ''
|
||||
mkdir $out
|
||||
mkdir -p $out/unfree
|
||||
cat > $out/unfree/default.nix <<EOF
|
||||
import "${pkgs.path}" { config = { allowUnfree = true; overlays = []; }; }
|
||||
EOF
|
||||
cat > $out/flake-registry.json <<EOF
|
||||
{ "flakes": [{"exact":true,"from":{"id":"nixpkgs", "type": "indirect"},"to": {"path":"${pkgs.path}", "type":"path"}}], "version": 2}
|
||||
cat > $out/flake.nix << EOF
|
||||
{
|
||||
description = "dependencies for the clan-cli";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = _inputs: { };
|
||||
}
|
||||
EOF
|
||||
ln -s ${pkgs.path} $out/path
|
||||
nix flake lock $out \
|
||||
--store ./. \
|
||||
--experimental-features 'nix-command flakes' \
|
||||
--override-input nixpkgs ${pkgs.path}
|
||||
'';
|
||||
in
|
||||
python3.pkgs.buildPythonPackage {
|
||||
@@ -77,7 +89,7 @@ python3.pkgs.buildPythonPackage {
|
||||
];
|
||||
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 ];
|
||||
} ''
|
||||
@@ -106,7 +118,7 @@ python3.pkgs.buildPythonPackage {
|
||||
passthru.testDependencies = dependencies ++ testDependencies;
|
||||
|
||||
postInstall = ''
|
||||
ln -sTf ${nixpkgs} $out/${python3.sitePackages}/clan_cli/nixpkgs
|
||||
cp -r ${nixpkgs} $out/${python3.sitePackages}/clan_cli/nixpkgs
|
||||
installShellCompletion --bash --name clan \
|
||||
<(${argcomplete}/bin/register-python-argcomplete --shell bash clan)
|
||||
installShellCompletion --fish --name clan.fish \
|
||||
|
||||
9
pkgs/clan-cli/deps-flake.nix
Normal file
9
pkgs/clan-cli/deps-flake.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
description = "dependencies for the clan-cli";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = _inputs: { };
|
||||
}
|
||||
@@ -6,7 +6,7 @@ from typing import Generator
|
||||
|
||||
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"))
|
||||
|
||||
@@ -44,10 +44,8 @@ def machine_flake(monkeymodule: pytest.MonkeyPatch) -> Generator[Path, None, Non
|
||||
# provided by get_clan_flake_toplevel
|
||||
flake_nix = flake / "flake.nix"
|
||||
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
|
||||
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
|
||||
|
||||
0
pkgs/clan-cli/tests/machine_flake/.clan-flake
Normal file
0
pkgs/clan-cli/tests/machine_flake/.clan-flake
Normal file
@@ -4,7 +4,20 @@
|
||||
nixpkgs.url = "__NIXPKGS__";
|
||||
};
|
||||
|
||||
outputs = _inputs: {
|
||||
nixosModules.machine-machine1 = ./nixosModules/machine1.nix;
|
||||
outputs = inputs: {
|
||||
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;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,20 +28,44 @@ example_options = f"{Path(config.__file__).parent}/jsonschema/options.json"
|
||||
def test_set_some_option(
|
||||
args: list[str],
|
||||
expected: dict[str, Any],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv("CLAN_OPTIONS_FILE", example_options)
|
||||
|
||||
# create temporary file for out_file
|
||||
with tempfile.NamedTemporaryFile() as out_file:
|
||||
with open(out_file.name, "w") as f:
|
||||
json.dump({}, f)
|
||||
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())
|
||||
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:
|
||||
schema = dict(
|
||||
type="object",
|
||||
|
||||
@@ -129,9 +129,9 @@ def test_secrets(
|
||||
|
||||
with pytest.raises(ClanError): # does not exist yet
|
||||
cli.run(["secrets", "get", "nonexisting"])
|
||||
cli.run(["secrets", "set", "key"])
|
||||
cli.run(["secrets", "set", "initialkey"])
|
||||
capsys.readouterr()
|
||||
cli.run(["secrets", "get", "key"])
|
||||
cli.run(["secrets", "get", "initialkey"])
|
||||
assert capsys.readouterr().out == "foo"
|
||||
capsys.readouterr()
|
||||
cli.run(["secrets", "users", "list"])
|
||||
@@ -139,6 +139,8 @@ def test_secrets(
|
||||
assert len(users) == 1, f"users: {users}"
|
||||
owner = users[0]
|
||||
|
||||
cli.run(["secrets", "rename", "initialkey", "key"])
|
||||
|
||||
capsys.readouterr() # empty the buffer
|
||||
cli.run(["secrets", "list"])
|
||||
assert capsys.readouterr().out == "key\n"
|
||||
|
||||
@@ -7,7 +7,6 @@ import pytest_subprocess.fake_process
|
||||
from pytest_subprocess import utils
|
||||
|
||||
import clan_cli
|
||||
from clan_cli.dirs import flake_registry
|
||||
from clan_cli.ssh import cli
|
||||
|
||||
|
||||
@@ -34,10 +33,7 @@ def test_ssh_no_pass(
|
||||
"shell",
|
||||
"--extra-experimental-features",
|
||||
"nix-command flakes",
|
||||
"--flake-registry",
|
||||
str(flake_registry()),
|
||||
"nixpkgs#tor",
|
||||
"nixpkgs#openssh",
|
||||
fp.any(),
|
||||
"-c",
|
||||
"torify",
|
||||
"ssh",
|
||||
@@ -68,11 +64,7 @@ def test_ssh_with_pass(
|
||||
"shell",
|
||||
"--extra-experimental-features",
|
||||
"nix-command flakes",
|
||||
"--flake-registry",
|
||||
str(flake_registry()),
|
||||
"nixpkgs#tor",
|
||||
"nixpkgs#openssh",
|
||||
"nixpkgs#sshpass",
|
||||
fp.any(),
|
||||
"-c",
|
||||
"torify",
|
||||
"sshpass",
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
};
|
||||
};
|
||||
"@babel/runtime" = {
|
||||
"7.22.10" = {
|
||||
"7.22.11" = {
|
||||
depInfo = {
|
||||
regenerator-runtime = {
|
||||
descriptor = "^0.14.0";
|
||||
@@ -263,13 +263,13 @@
|
||||
};
|
||||
};
|
||||
fetchInfo = {
|
||||
narHash = "sha256-5ecEDXI/B/XZUtU3VFGYjC1yAMqmmoqb9Jyu03CI1rQ=";
|
||||
narHash = "sha256-u4IYeznySCACZfl7/j6Fwdz0J5eRLYRntlijjEtZQb0=";
|
||||
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";
|
||||
ltype = "file";
|
||||
version = "7.22.10";
|
||||
version = "7.22.11";
|
||||
};
|
||||
};
|
||||
"@babel/types" = {
|
||||
@@ -311,7 +311,7 @@
|
||||
};
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.18.3";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@emotion/hash" = {
|
||||
@@ -459,7 +459,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.18.3";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@emotion/babel-plugin" = {
|
||||
@@ -574,7 +574,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.18.3";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@emotion/babel-plugin" = {
|
||||
@@ -1104,7 +1104,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.22.6";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@emotion/is-prop-valid" = {
|
||||
@@ -1119,7 +1119,7 @@
|
||||
};
|
||||
"@mui/utils" = {
|
||||
descriptor = "^5.14.5";
|
||||
pin = "5.14.5";
|
||||
pin = "5.14.7";
|
||||
runtime = true;
|
||||
};
|
||||
"@popperjs/core" = {
|
||||
@@ -1183,7 +1183,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.22.6";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
};
|
||||
@@ -1214,7 +1214,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.22.6";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@mui/base" = {
|
||||
@@ -1239,7 +1239,7 @@
|
||||
};
|
||||
"@mui/utils" = {
|
||||
descriptor = "^5.14.5";
|
||||
pin = "5.14.5";
|
||||
pin = "5.14.7";
|
||||
runtime = true;
|
||||
};
|
||||
"@types/react-transition-group" = {
|
||||
@@ -1308,12 +1308,12 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.22.6";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@mui/utils" = {
|
||||
descriptor = "^5.14.5";
|
||||
pin = "5.14.5";
|
||||
pin = "5.14.7";
|
||||
runtime = true;
|
||||
};
|
||||
prop-types = {
|
||||
@@ -1346,7 +1346,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.21.0";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@emotion/cache" = {
|
||||
@@ -1393,7 +1393,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.22.6";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@mui/private-theming" = {
|
||||
@@ -1413,7 +1413,7 @@
|
||||
};
|
||||
"@mui/utils" = {
|
||||
descriptor = "^5.14.5";
|
||||
pin = "5.14.5";
|
||||
pin = "5.14.7";
|
||||
runtime = true;
|
||||
};
|
||||
clsx = {
|
||||
@@ -1479,11 +1479,11 @@
|
||||
};
|
||||
};
|
||||
"@mui/utils" = {
|
||||
"5.14.5" = {
|
||||
"5.14.7" = {
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.22.6";
|
||||
pin = "7.22.10";
|
||||
descriptor = "^7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
"@types/prop-types" = {
|
||||
@@ -1508,9 +1508,9 @@
|
||||
};
|
||||
};
|
||||
fetchInfo = {
|
||||
narHash = "sha256-mym+STz4KseB2TDlXB8qkcPKpvNQDU4r+9xTC99m84U=";
|
||||
narHash = "sha256-bvWlZoYxVVHqprNjDYZQtl6vrpx6BZNUe/t8J+REcHk=";
|
||||
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";
|
||||
ltype = "file";
|
||||
@@ -1519,7 +1519,7 @@
|
||||
descriptor = "^17.0.0 || ^18.0.0";
|
||||
};
|
||||
};
|
||||
version = "5.14.5";
|
||||
version = "5.14.7";
|
||||
};
|
||||
};
|
||||
"@next/env" = {
|
||||
@@ -2080,6 +2080,172 @@
|
||||
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" = {
|
||||
"22.0.2" = {
|
||||
depInfo = {
|
||||
@@ -4627,7 +4793,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.12.5";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
cosmiconfig = {
|
||||
@@ -5209,6 +5375,69 @@
|
||||
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 = {
|
||||
"0.0.1" = {
|
||||
fetchInfo = {
|
||||
@@ -5834,7 +6063,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.1.2";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
};
|
||||
@@ -5851,7 +6080,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.8.7";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
csstype = {
|
||||
@@ -7430,7 +7659,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.20.7";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
aria-query = {
|
||||
@@ -9857,6 +10086,54 @@
|
||||
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 = {
|
||||
"5.1.3" = {
|
||||
depInfo = {
|
||||
@@ -10223,6 +10500,19 @@
|
||||
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" = {
|
||||
"4.4.2" = {
|
||||
fetchInfo = {
|
||||
@@ -10402,6 +10692,24 @@
|
||||
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 = {
|
||||
"1.1.1" = {
|
||||
depInfo = {
|
||||
@@ -10584,6 +10892,26 @@
|
||||
pin = "5.14.5";
|
||||
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" = {
|
||||
descriptor = "20.4.7";
|
||||
pin = "20.4.7";
|
||||
@@ -10810,7 +11138,7 @@
|
||||
key = "supports-color/5.5.0";
|
||||
};
|
||||
"node_modules/@babel/runtime" = {
|
||||
key = "@babel/runtime/7.22.10";
|
||||
key = "@babel/runtime/7.22.11";
|
||||
};
|
||||
"node_modules/@babel/types" = {
|
||||
key = "@babel/types/7.22.10";
|
||||
@@ -10956,7 +11284,7 @@
|
||||
key = "@mui/types/7.2.4";
|
||||
};
|
||||
"node_modules/@mui/utils" = {
|
||||
key = "@mui/utils/5.14.5";
|
||||
key = "@mui/utils/5.14.7";
|
||||
};
|
||||
"node_modules/@next/env" = {
|
||||
key = "@next/env/13.4.12";
|
||||
@@ -11049,6 +11377,24 @@
|
||||
"node_modules/@popperjs/core" = {
|
||||
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" = {
|
||||
dev = true;
|
||||
key = "@rollup/plugin-commonjs/22.0.2";
|
||||
@@ -11256,7 +11602,6 @@
|
||||
key = "@types/estree/0.0.39";
|
||||
};
|
||||
"node_modules/@types/json-schema" = {
|
||||
dev = true;
|
||||
key = "@types/json-schema/7.0.12";
|
||||
};
|
||||
"node_modules/@types/json5" = {
|
||||
@@ -11334,15 +11679,12 @@
|
||||
key = "ajv/6.12.6";
|
||||
};
|
||||
"node_modules/ajv-formats" = {
|
||||
dev = true;
|
||||
key = "ajv-formats/2.1.1";
|
||||
};
|
||||
"node_modules/ajv-formats/node_modules/ajv" = {
|
||||
dev = true;
|
||||
key = "ajv/8.12.0";
|
||||
};
|
||||
"node_modules/ajv-formats/node_modules/json-schema-traverse" = {
|
||||
dev = true;
|
||||
key = "json-schema-traverse/1.0.0";
|
||||
};
|
||||
"node_modules/ansi-colors" = {
|
||||
@@ -11542,6 +11884,12 @@
|
||||
dev = true;
|
||||
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" = {
|
||||
key = "concat-map/0.0.1";
|
||||
};
|
||||
@@ -11936,7 +12284,6 @@
|
||||
key = "execa/5.1.1";
|
||||
};
|
||||
"node_modules/fast-deep-equal" = {
|
||||
dev = true;
|
||||
key = "fast-deep-equal/3.1.3";
|
||||
};
|
||||
"node_modules/fast-equals" = {
|
||||
@@ -12323,6 +12670,12 @@
|
||||
"node_modules/json-parse-even-better-errors" = {
|
||||
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" = {
|
||||
dev = true;
|
||||
key = "json-schema-ref-parser/5.1.3";
|
||||
@@ -12364,7 +12717,6 @@
|
||||
key = "jsonpath-plus/7.1.0";
|
||||
};
|
||||
"node_modules/jsonpointer" = {
|
||||
dev = true;
|
||||
key = "jsonpointer/5.0.1";
|
||||
};
|
||||
"node_modules/jsonschema" = {
|
||||
@@ -12404,6 +12756,9 @@
|
||||
"node_modules/lodash" = {
|
||||
key = "lodash/4.17.21";
|
||||
};
|
||||
"node_modules/lodash-es" = {
|
||||
key = "lodash-es/4.17.21";
|
||||
};
|
||||
"node_modules/lodash.get" = {
|
||||
dev = true;
|
||||
key = "lodash.get/4.4.2";
|
||||
@@ -12451,6 +12806,9 @@
|
||||
dev = true;
|
||||
key = "magic-string/0.25.9";
|
||||
};
|
||||
"node_modules/markdown-to-jsx" = {
|
||||
key = "markdown-to-jsx/7.3.2";
|
||||
};
|
||||
"node_modules/matcher" = {
|
||||
dev = true;
|
||||
key = "matcher/1.1.1";
|
||||
@@ -12742,7 +13100,6 @@
|
||||
key = "proxy-from-env/1.1.0";
|
||||
};
|
||||
"node_modules/punycode" = {
|
||||
dev = true;
|
||||
key = "punycode/2.3.0";
|
||||
};
|
||||
"node_modules/queue-microtask" = {
|
||||
@@ -12822,7 +13179,6 @@
|
||||
key = "require-directory/2.1.1";
|
||||
};
|
||||
"node_modules/require-from-string" = {
|
||||
dev = true;
|
||||
key = "require-from-string/2.0.2";
|
||||
};
|
||||
"node_modules/reserved" = {
|
||||
@@ -13100,7 +13456,6 @@
|
||||
key = "update-browserslist-db/1.0.11";
|
||||
};
|
||||
"node_modules/uri-js" = {
|
||||
dev = true;
|
||||
key = "uri-js/4.4.1";
|
||||
};
|
||||
"node_modules/urijs" = {
|
||||
@@ -13121,6 +13476,21 @@
|
||||
dev = true;
|
||||
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" = {
|
||||
dev = true;
|
||||
key = "validator/13.11.0";
|
||||
@@ -15143,7 +15513,7 @@
|
||||
depInfo = {
|
||||
"@babel/runtime" = {
|
||||
descriptor = "^7.5.5";
|
||||
pin = "7.22.10";
|
||||
pin = "7.22.11";
|
||||
runtime = true;
|
||||
};
|
||||
dom-helpers = {
|
||||
@@ -17311,6 +17681,88 @@
|
||||
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 = {
|
||||
"13.11.0" = {
|
||||
fetchInfo = {
|
||||
|
||||
224
pkgs/ui/package-lock.json
generated
224
pkgs/ui/package-lock.json
generated
@@ -12,6 +12,10 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-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",
|
||||
"axios": "^1.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
@@ -353,9 +357,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
|
||||
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
|
||||
"version": "7.22.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
|
||||
"integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@@ -955,11 +959,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "5.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.5.tgz",
|
||||
"integrity": "sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==",
|
||||
"version": "5.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz",
|
||||
"integrity": "sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.22.6",
|
||||
"@babel/runtime": "^7.22.10",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^18.2.1",
|
||||
"prop-types": "^15.8.1",
|
||||
@@ -1277,6 +1281,98 @@
|
||||
"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": {
|
||||
"version": "22.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz",
|
||||
@@ -1987,8 +2083,7 @@
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
|
||||
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
@@ -2218,7 +2313,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
@@ -2235,7 +2329,6 @@
|
||||
"version": "8.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
@@ -2250,8 +2343,7 @@
|
||||
"node_modules/ajv-formats/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==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
@@ -2869,6 +2961,29 @@
|
||||
"integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==",
|
||||
"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": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -4226,8 +4341,7 @@
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"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",
|
||||
"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": {
|
||||
"version": "5.1.3",
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
|
||||
"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -5551,6 +5687,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"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": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
@@ -5637,6 +5778,17 @@
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz",
|
||||
@@ -6688,7 +6840,6 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -6962,7 +7113,6 @@
|
||||
"version": "2.0.2",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -7875,7 +8025,6 @@
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
@@ -7917,6 +8066,43 @@
|
||||
"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": {
|
||||
"version": "13.11.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-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",
|
||||
"axios": "^1.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
|
||||
@@ -1,355 +1,7 @@
|
||||
"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 { buffer } from "stream/consumers";
|
||||
import { CreateMachineForm } from "@/components/createMachineForm";
|
||||
|
||||
type StepId = "select" | "create" | "install";
|
||||
|
||||
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>
|
||||
);
|
||||
export default function CreateMachine() {
|
||||
return <CreateMachineForm />;
|
||||
}
|
||||
|
||||
164
pkgs/ui/src/components/createMachineForm/customConfig.tsx
Normal file
164
pkgs/ui/src/components/createMachineForm/customConfig.tsx
Normal 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>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
160
pkgs/ui/src/components/createMachineForm/index.tsx
Normal file
160
pkgs/ui/src/components/createMachineForm/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
23
pkgs/ui/src/components/createMachineForm/interfaces.ts
Normal file
23
pkgs/ui/src/components/createMachineForm/interfaces.ts
Normal 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>;
|
||||
88
pkgs/ui/src/data/_schema.ts
Normal file
88
pkgs/ui/src/data/_schema.ts
Normal 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",
|
||||
};
|
||||
111
pkgs/ui/src/data/_schema2.ts
Normal file
111
pkgs/ui/src/data/_schema2.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -3,9 +3,21 @@
|
||||
|
||||
inputs.clan-core.url = "git+https://git.clan.lol/clan/clan-core";
|
||||
|
||||
outputs = { clan-core, ... }: {
|
||||
nixosConfigurations = clan-core.lib.buildClan {
|
||||
directory = ./.;
|
||||
outputs = { self, clan-core, ... }:
|
||||
let
|
||||
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
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user