forked from clan/clan-core
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
7365ca0b3c | |||
2538889c04 |
@ -10,7 +10,6 @@ in
|
|||||||
hostPkgs = pkgs;
|
hostPkgs = pkgs;
|
||||||
# speed-up evaluation
|
# speed-up evaluation
|
||||||
defaults = {
|
defaults = {
|
||||||
nix.package = pkgs.nixVersions.latest;
|
|
||||||
documentation.enable = lib.mkDefault false;
|
documentation.enable = lib.mkDefault false;
|
||||||
boot.isContainer = true;
|
boot.isContainer = true;
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ in
|
|||||||
defaults = {
|
defaults = {
|
||||||
documentation.enable = lib.mkDefault false;
|
documentation.enable = lib.mkDefault false;
|
||||||
nix.settings.min-free = 0;
|
nix.settings.min-free = 0;
|
||||||
nix.package = pkgs.nixVersions.latest;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# to accept external dependencies such as disko
|
# to accept external dependencies such as disko
|
||||||
|
@ -6,6 +6,15 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.clan.matrix-synapse;
|
cfg = config.clan.matrix-synapse;
|
||||||
|
nginx-vhost = "matrix.${config.clan.matrix-synapse.domain}";
|
||||||
|
element-web =
|
||||||
|
pkgs.runCommand "element-web-with-config" { nativeBuildInputs = [ pkgs.buildPackages.jq ]; } ''
|
||||||
|
cp -r ${pkgs.element-web} $out
|
||||||
|
chmod -R u+w $out
|
||||||
|
jq '."default_server_config"."m.homeserver" = { "base_url": "https://${nginx-vhost}:443", "server_name": "${config.clan.matrix-synapse.domain}" }' \
|
||||||
|
> $out/config.json < ${pkgs.element-web}/config.json
|
||||||
|
ln -s $out/config.json $out/config.${nginx-vhost}.json
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.clan.matrix-synapse = {
|
options.clan.matrix-synapse = {
|
||||||
@ -13,6 +22,7 @@ in
|
|||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = "The domain name of the matrix server";
|
description = "The domain name of the matrix server";
|
||||||
|
example = "example.com";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
@ -49,14 +59,40 @@ in
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
extraConfigFiles = [ "/var/lib/matrix-synapse/registration_shared_secret.yaml" ];
|
extraConfigFiles = [ "/run/synapse-registration-shared-secret.yaml" ];
|
||||||
};
|
};
|
||||||
|
systemd.tmpfiles.settings."synapse" = {
|
||||||
|
"/run/synapse-registration-shared-secret.yaml" = {
|
||||||
|
C.argument = config.clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path;
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "matrix-synapse";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
systemd.services.matrix-synapse.serviceConfig.ExecStartPre = [
|
systemd.services.matrix-synapse.serviceConfig.ExecStartPre = [
|
||||||
"+${pkgs.writeScript "copy_registration_shared_secret" ''
|
"+${pkgs.writeShellScript "create-matrix-synapse-db" ''
|
||||||
#!/bin/sh
|
export PATH=${
|
||||||
cp ${config.clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path} /var/lib/matrix-synapse/registration_shared_secret.yaml
|
lib.makeBinPath [
|
||||||
chown matrix-synapse:matrix-synapse /var/lib/matrix-synapse/registration_shared_secret.yaml
|
config.services.postgresql.package
|
||||||
chmod 600 /var/lib/matrix-synapse/registration_shared_secret.yaml
|
pkgs.util-linux
|
||||||
|
pkgs.gnugrep
|
||||||
|
]
|
||||||
|
}
|
||||||
|
psql() { runuser -u postgres -- psql "$@"; }
|
||||||
|
# wait for postgres to be ready
|
||||||
|
while ! runuser -u postgres pg_isready; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if ! psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'matrix-synapse'" | grep -q 1; then
|
||||||
|
psql -c "CREATE DATABASE \"matrix-synapse\" TEMPLATE template0 LC_COLLATE = 'C' LC_CTYPE = 'C'"
|
||||||
|
fi
|
||||||
|
# create user if it doesn't exist and make it owner of the database
|
||||||
|
if ! psql -tAc "SELECT 1 FROM pg_user WHERE usename = 'matrix-synapse'" | grep -q 1; then
|
||||||
|
psql -c "CREATE USER \"matrix-synapse\""
|
||||||
|
psql -c "ALTER DATABASE \"matrix-synapse\" OWNER TO \"matrix-synapse\""
|
||||||
|
fi
|
||||||
''}"
|
''}"
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -72,22 +108,6 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.postgresql.enable = true;
|
services.postgresql.enable = true;
|
||||||
# we need to use both ensusureDatabases and initialScript, because the former runs everytime but with the wrong collation
|
|
||||||
services.postgresql = {
|
|
||||||
ensureDatabases = [ "matrix-synapse" ];
|
|
||||||
ensureUsers = [
|
|
||||||
{
|
|
||||||
name = "matrix-synapse";
|
|
||||||
ensureDBOwnership = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
initialScript = pkgs.writeText "synapse-init.sql" ''
|
|
||||||
CREATE DATABASE "matrix-synapse"
|
|
||||||
TEMPLATE template0
|
|
||||||
LC_COLLATE = "C"
|
|
||||||
LC_CTYPE = "C";
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHosts = {
|
virtualHosts = {
|
||||||
@ -102,7 +122,7 @@ in
|
|||||||
return 200 '${
|
return 200 '${
|
||||||
builtins.toJSON {
|
builtins.toJSON {
|
||||||
"m.homeserver" = {
|
"m.homeserver" = {
|
||||||
"base_url" = "https://matrix.${cfg.domain}";
|
"base_url" = "https://${nginx-vhost}";
|
||||||
};
|
};
|
||||||
"m.identity_server" = {
|
"m.identity_server" = {
|
||||||
"base_url" = "https://vector.im";
|
"base_url" = "https://vector.im";
|
||||||
@ -111,15 +131,12 @@ in
|
|||||||
}';
|
}';
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"matrix.${cfg.domain}" = {
|
${nginx-vhost} = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
locations."/_matrix" = {
|
locations."/_matrix".proxyPass = "http://localhost:8008";
|
||||||
proxyPass = "http://localhost:8008";
|
locations."/_synapse".proxyPass = "http://localhost:8008";
|
||||||
};
|
locations."/".root = element-web;
|
||||||
locations."/test".extraConfig = ''
|
|
||||||
return 200 "Hello, world!";
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -3,15 +3,9 @@
|
|||||||
options.clan.static-hosts = {
|
options.clan.static-hosts = {
|
||||||
excludeHosts = lib.mkOption {
|
excludeHosts = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default =
|
default = [ config.clanCore.machineName ];
|
||||||
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clanCore.machineName ];
|
|
||||||
description = "Hosts that should be excluded";
|
description = "Hosts that should be excluded";
|
||||||
};
|
};
|
||||||
topLevelDomain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "";
|
|
||||||
description = "Top level domain to reach hosts";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config.networking.hosts =
|
config.networking.hosts =
|
||||||
@ -30,15 +24,7 @@
|
|||||||
let
|
let
|
||||||
path = zerotierIpMachinePath machine;
|
path = zerotierIpMachinePath machine;
|
||||||
in
|
in
|
||||||
if builtins.pathExists path then
|
if builtins.pathExists path then lib.nameValuePair (builtins.readFile path) [ machine ] else null
|
||||||
lib.nameValuePair (builtins.readFile path) (
|
|
||||||
if (config.clan.static-hosts.topLevelDomain == "") then
|
|
||||||
[ machine ]
|
|
||||||
else
|
|
||||||
[ "${machine}.${config.clan.static-hosts.topLevelDomain}" ]
|
|
||||||
)
|
|
||||||
else
|
|
||||||
null
|
|
||||||
) filteredMachines
|
) filteredMachines
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ let
|
|||||||
if builtins.pathExists fullPath then builtins.readFile fullPath else null
|
if builtins.pathExists fullPath then builtins.readFile fullPath else null
|
||||||
) machines;
|
) machines;
|
||||||
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
|
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
|
||||||
networkId = if builtins.length networkIds == 0 then null else builtins.elemAt networkIds 0;
|
networkId = builtins.elemAt networkIds 0;
|
||||||
in
|
in
|
||||||
#TODO:trace on multiple found network-ids
|
#TODO:trace on multiple found network-ids
|
||||||
#TODO:trace on no single found networkId
|
#TODO:trace on no single found networkId
|
||||||
@ -38,7 +38,7 @@ in
|
|||||||
machines = builtins.readDir machineDir;
|
machines = builtins.readDir machineDir;
|
||||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||||
filteredMachines = lib.filterAttrs (
|
filteredMachines = lib.filterAttrs (
|
||||||
name: _: !(lib.elem name config.clan.zerotier-static-peers.excludeHosts)
|
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
||||||
) machines;
|
) machines;
|
||||||
hosts = lib.mapAttrsToList (host: _: host) (
|
hosts = lib.mapAttrsToList (host: _: host) (
|
||||||
lib.mapAttrs' (
|
lib.mapAttrs' (
|
||||||
|
@ -27,8 +27,7 @@
|
|||||||
packages = [
|
packages = [
|
||||||
select-shell
|
select-shell
|
||||||
pkgs.tea
|
pkgs.tea
|
||||||
# Better error messages than nix 2.18
|
pkgs.nix
|
||||||
pkgs.nixVersions.latest
|
|
||||||
self'.packages.tea-create-pr
|
self'.packages.tea-create-pr
|
||||||
self'.packages.merge-after-ci
|
self'.packages.merge-after-ci
|
||||||
self'.packages.pending-reviews
|
self'.packages.pending-reviews
|
||||||
|
@ -15,74 +15,56 @@ Let's get your development environment up and running:
|
|||||||
|
|
||||||
1. **Install Nix Package Manager**:
|
1. **Install Nix Package Manager**:
|
||||||
|
|
||||||
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
||||||
```bash
|
```bash
|
||||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Install direnv**:
|
2. **Install direnv**:
|
||||||
|
|
||||||
- To automatically setup a devshell on entering the directory
|
- To automatically setup a devshell on entering the directory
|
||||||
```bash
|
```bash
|
||||||
nix profile install nixpkgs#nix-direnv-flakes
|
nix profile install nixpkgs#nix-direnv-flakes
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Add direnv to your shell**:
|
3. **Add direnv to your shell**:
|
||||||
|
|
||||||
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
||||||
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Create a Gitea Account**:
|
4. **Create a Gitea Account**:
|
||||||
- Register an account on https://git.clan.lol
|
- Register an account on https://git.clan.lol
|
||||||
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
||||||
- Clone the repository and navigate to it
|
- Clone the repository and navigate to it
|
||||||
- Add a new remote called upstream:
|
- Add a new remote called upstream:
|
||||||
```bash
|
```bash
|
||||||
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
||||||
```
|
|
||||||
|
|
||||||
5. **Register Your Gitea Account Locally**:
|
|
||||||
|
|
||||||
- Execute the following command to add your Gitea account locally:
|
|
||||||
```bash
|
|
||||||
tea login add
|
|
||||||
```
|
|
||||||
- Fill out the prompt as follows:
|
|
||||||
- URL of Gitea instance: `https://git.clan.lol`
|
|
||||||
- Name of new Login [git.clan.lol]:
|
|
||||||
- Do you have an access token? No
|
|
||||||
- Username: YourUsername
|
|
||||||
- Password: YourPassword
|
|
||||||
- Set Optional settings: No
|
|
||||||
|
|
||||||
|
|
||||||
6. **Allow .envrc**:
|
|
||||||
|
|
||||||
- When you enter the directory, you'll receive an error message like this:
|
|
||||||
```bash
|
|
||||||
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
|
||||||
```
|
|
||||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
|
||||||
|
|
||||||
7. **(Optional) Install Git Hooks**:
|
|
||||||
- To syntax check your code you can run:
|
|
||||||
```bash
|
|
||||||
nix fmt
|
|
||||||
```
|
|
||||||
- To make this automatic install the git hooks
|
|
||||||
```bash
|
|
||||||
./scripts/pre-commit
|
|
||||||
```
|
|
||||||
|
|
||||||
8. **Open a Pull Request**:
|
|
||||||
- To automatically open up a pull request you can use our tool called:
|
|
||||||
```
|
```
|
||||||
merge-after-ci --reviewers Mic92 Lassulus Qubasa
|
|
||||||
|
5. **Allow .envrc**:
|
||||||
|
|
||||||
|
- When you enter the directory, you'll receive an error message like this:
|
||||||
|
```bash
|
||||||
|
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
||||||
|
```
|
||||||
|
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||||
|
|
||||||
|
6. **(Optional) Install Git Hooks**:
|
||||||
|
- To syntax check your code you can run:
|
||||||
|
```bash
|
||||||
|
nix fmt
|
||||||
```
|
```
|
||||||
|
- To make this automatic install the git hooks
|
||||||
|
```bash
|
||||||
|
./scripts/pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Open a Pull Request**:
|
||||||
|
- Go to the webinterface and open up a pull request
|
||||||
|
|
||||||
# Debugging
|
# Debugging
|
||||||
|
|
||||||
|
29
docs/main.py
29
docs/main.py
@ -16,26 +16,15 @@ def define_env(env: Any) -> None:
|
|||||||
@env.macro
|
@env.macro
|
||||||
def asciinema(name: str) -> str:
|
def asciinema(name: str) -> str:
|
||||||
return f"""<div id="{name}">
|
return f"""<div id="{name}">
|
||||||
|
<script src="{asciinema_dir}/asciinema-player.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Function to load the script and then create the Asciinema player
|
AsciinemaPlayer.create('{video_dir + name}',
|
||||||
function loadAsciinemaPlayer() {{
|
document.getElementById("{name}"), {{
|
||||||
var script = document.createElement('script');
|
loop: true,
|
||||||
script.src = "{asciinema_dir}/asciinema-player.min.js";
|
autoPlay: true,
|
||||||
script.onload = function() {{
|
controls: false,
|
||||||
AsciinemaPlayer.create('{video_dir + name}', document.getElementById("{name}"), {{
|
speed: 1.5,
|
||||||
loop: true,
|
theme: "solarized-light"
|
||||||
autoPlay: true,
|
}});
|
||||||
controls: false,
|
|
||||||
speed: 1.5,
|
|
||||||
theme: "solarized-light"
|
|
||||||
}});
|
|
||||||
}};
|
|
||||||
document.head.appendChild(script);
|
|
||||||
}}
|
|
||||||
|
|
||||||
// Load the Asciinema player script
|
|
||||||
loadAsciinemaPlayer();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="{asciinema_dir}/asciinema-player.css" />
|
|
||||||
</div>"""
|
</div>"""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
site_name: Clan Documentation
|
site_name: Clan Docs
|
||||||
site_url: https://docs.clan.lol
|
site_url: https://docs.clan.lol
|
||||||
repo_url: https://git.clan.lol/clan/clan-core/
|
repo_url: https://git.clan.lol/clan/clan-core/
|
||||||
repo_name: clan-core
|
repo_name: clan-core
|
||||||
@ -96,7 +96,7 @@ site_dir: out
|
|||||||
theme:
|
theme:
|
||||||
font: false
|
font: false
|
||||||
logo: https://clan.lol/static/logo/clan-white.png
|
logo: https://clan.lol/static/logo/clan-white.png
|
||||||
favicon: https://clan.lol/static/dark-favicon/128x128.png
|
favicon: https://clan.lol/static/logo/clan-dark.png
|
||||||
name: material
|
name: material
|
||||||
features:
|
features:
|
||||||
- navigation.instant
|
- navigation.instant
|
||||||
@ -105,8 +105,7 @@ theme:
|
|||||||
- content.code.copy
|
- content.code.copy
|
||||||
- content.tabs.link
|
- content.tabs.link
|
||||||
icon:
|
icon:
|
||||||
repo: fontawesome/brands/git-alt
|
repo: fontawesome/brands/git
|
||||||
custom_dir: overrides
|
|
||||||
|
|
||||||
palette:
|
palette:
|
||||||
# Palette toggle for light mode
|
# Palette toggle for light mode
|
||||||
@ -129,6 +128,8 @@ theme:
|
|||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- static/extra.css
|
- static/extra.css
|
||||||
|
- static/asciinema-player/custom-theme.css
|
||||||
|
- static/asciinema-player/asciinema-player.css
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
social:
|
social:
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block extrahead %}
|
|
||||||
<meta property="og:title" content="Clan - Documentation, Blog & Getting Started Guide" />
|
|
||||||
<meta property="og:description" content="Documentation for Clan. The peer-to-peer machine deployment framework." />
|
|
||||||
<meta property="og:image" content="https://clan.lol/static/dark-favicon/128x128.png" />
|
|
||||||
<meta property="og:url" content="https://docs.clan.lol" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:site_name" content="Clan" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -54,9 +54,9 @@ sudo umount /dev/sdb1
|
|||||||
flash-installer
|
flash-installer
|
||||||
```
|
```
|
||||||
|
|
||||||
The `--ssh-pubkey`, `--language` and `--keymap` are optional.
|
The `--ssh-pubkey`, `--language` and `--keymap` are optional.
|
||||||
Replace `$HOME/.ssh/id_ed25519.pub` with a path to your SSH public key.
|
Replace `$HOME/.ssh/id_ed25519.pub` with a path to your SSH public key.
|
||||||
If you do not have an ssh key yet, you can generate one with `ssh-keygen -t ed25519` command.
|
If you do not have an ssh key yet, you can generate one with `ssh-keygen -t ed25519` command.
|
||||||
|
|
||||||
!!! Danger "Specifying the wrong device can lead to unrecoverable data loss."
|
!!! Danger "Specifying the wrong device can lead to unrecoverable data loss."
|
||||||
|
|
||||||
|
42
flake.lock
42
flake.lock
@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717177033,
|
"lastModified": 1716773194,
|
||||||
"narHash": "sha256-G3CZJafCO8WDy3dyA2EhpUJEmzd5gMJ2IdItAg0Hijw=",
|
"narHash": "sha256-rskkGmWlvYFb+CXedBiL8eWEuED0Es0XR4CkJ11RQKY=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "0274af4c92531ebfba4a5bd493251a143bc51f3c",
|
"rev": "10986091e47fb1180620b78438512b294b7e8f67",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -27,11 +27,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717285511,
|
"lastModified": 1715865404,
|
||||||
"narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=",
|
"narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8",
|
"rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -57,11 +57,11 @@
|
|||||||
},
|
},
|
||||||
"nixos-2311": {
|
"nixos-2311": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717017538,
|
"lastModified": 1716767563,
|
||||||
"narHash": "sha256-S5kltvDDfNQM3xx9XcvzKEOyN2qk8Sa+aSOLqZ+1Ujc=",
|
"narHash": "sha256-xaSLDTqKIU55HsCkDnzFKmPiJO2z1xAAvrhUlwlmT2M=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "64e468fd2652105710d86cd2ae3e65a5a6d58dec",
|
"rev": "0c007b36981bdbd69ccf0c7df30a174e63660667",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -100,11 +100,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717040312,
|
"lastModified": 1716786664,
|
||||||
"narHash": "sha256-yI/en4IxuCEClIUpIs3QTyYCCtmSPLOhwLJclfNwdeg=",
|
"narHash": "sha256-iszhOLhxnv+TX/XM2gAX4LhTCoMzLuG51ObZq/eyDx8=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixos-images",
|
"repo": "nixos-images",
|
||||||
"rev": "47bfb55316e105390dd761e0b6e8e0be09462b67",
|
"rev": "2478833ef8cc6de3d9e331f53b6f3682e425f207",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -115,11 +115,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717298511,
|
"lastModified": 1716924492,
|
||||||
"narHash": "sha256-9sXuJn/nL+9ImeYtlspTvjt83z1wIgU+9AwfNbnq+tI=",
|
"narHash": "sha256-9/Ro5/MfI+PNMF8jzh7+gXDPUHeOzL1e/iw3p4z6Ttc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6634a0509e9e81e980b129435fbbec518ab246d0",
|
"rev": "4ae13643e7f2cd4bc6555fce074865d9d14e7c24",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -148,11 +148,11 @@
|
|||||||
"nixpkgs-stable": []
|
"nixpkgs-stable": []
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717297459,
|
"lastModified": 1716692524,
|
||||||
"narHash": "sha256-cZC2f68w5UrJ1f+2NWGV9Gx0dEYmxwomWN2B0lx0QRA=",
|
"narHash": "sha256-sALodaA7Zkp/JD6ehgwc0UCBrSBfB4cX66uFGTsqeFU=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "ab2a43b0d21d1d37d4d5726a892f714eaeb4b075",
|
"rev": "962797a8d7f15ed7033031731d0bb77244839960",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -168,11 +168,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717278143,
|
"lastModified": 1715940852,
|
||||||
"narHash": "sha256-u10aDdYrpiGOLoxzY/mJ9llST9yO8Q7K/UlROoNxzDw=",
|
"narHash": "sha256-wJqHMg/K6X3JGAE9YLM0LsuKrKb4XiBeVaoeMNlReZg=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "3eb96ca1ae9edf792a8e0963cc92fddfa5a87706",
|
"rev": "2fba33a182602b9d49f0b2440513e5ee091d838b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -24,14 +24,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{ flake-parts, self, ... }:
|
inputs@{ flake-parts, ... }:
|
||||||
flake-parts.lib.mkFlake { inherit inputs; } (
|
flake-parts.lib.mkFlake { inherit inputs; } (
|
||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
clan = {
|
|
||||||
# meta.name = "clan-core";
|
|
||||||
directory = self;
|
|
||||||
};
|
|
||||||
systems = [
|
systems = [
|
||||||
"x86_64-linux"
|
"x86_64-linux"
|
||||||
"aarch64-linux"
|
"aarch64-linux"
|
||||||
|
@ -17,33 +17,6 @@ let
|
|||||||
cfg = config.clan;
|
cfg = config.clan;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
|
||||||
# TODO: figure out how to print the deprecation warning
|
|
||||||
# "${inputs.nixpkgs}/nixos/modules/misc/assertions.nix"
|
|
||||||
(lib.mkRenamedOptionModule
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"clanName"
|
|
||||||
]
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"meta"
|
|
||||||
"name"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
(lib.mkRenamedOptionModule
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"clanIcon"
|
|
||||||
]
|
|
||||||
[
|
|
||||||
"clan"
|
|
||||||
"meta"
|
|
||||||
"icon"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
options.clan = {
|
options.clan = {
|
||||||
directory = mkOption {
|
directory = mkOption {
|
||||||
type = types.path;
|
type = types.path;
|
||||||
@ -60,27 +33,15 @@ in
|
|||||||
default = { };
|
default = { };
|
||||||
description = "Allows to include machine-specific modules i.e. machines.\${name} = { ... }";
|
description = "Allows to include machine-specific modules i.e. machines.\${name} = { ... }";
|
||||||
};
|
};
|
||||||
|
clanName = mkOption {
|
||||||
# Checks are performed in 'buildClan'
|
type = types.str;
|
||||||
# Not everyone uses flake-parts
|
description = "Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.";
|
||||||
meta = {
|
};
|
||||||
name = lib.mkOption {
|
clanIcon = mkOption {
|
||||||
type = types.nullOr types.str;
|
type = types.nullOr types.path;
|
||||||
default = null;
|
default = null;
|
||||||
description = "Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.";
|
description = "A path to an icon to be used for the clan, should be the same for all machines";
|
||||||
};
|
|
||||||
icon = mkOption {
|
|
||||||
type = types.nullOr types.path;
|
|
||||||
default = null;
|
|
||||||
description = "A path to an icon to be used for the clan in the GUI";
|
|
||||||
};
|
|
||||||
description = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = "A short description of the clan";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pkgsForSystem = mkOption {
|
pkgsForSystem = mkOption {
|
||||||
type = types.functionTo types.raw;
|
type = types.functionTo types.raw;
|
||||||
default = _system: null;
|
default = _system: null;
|
||||||
@ -91,7 +52,6 @@ in
|
|||||||
clanInternals = lib.mkOption {
|
clanInternals = lib.mkOption {
|
||||||
type = lib.types.submodule {
|
type = lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
meta = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
|
||||||
all-machines-json = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
all-machines-json = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; };
|
||||||
machines = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
machines = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
||||||
machinesFunc = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
machinesFunc = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf lib.types.unspecified); };
|
||||||
@ -105,8 +65,9 @@ in
|
|||||||
directory
|
directory
|
||||||
specialArgs
|
specialArgs
|
||||||
machines
|
machines
|
||||||
|
clanName
|
||||||
|
clanIcon
|
||||||
pkgsForSystem
|
pkgsForSystem
|
||||||
meta
|
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -7,58 +7,16 @@
|
|||||||
directory, # The directory containing the machines subdirectory
|
directory, # The directory containing the machines subdirectory
|
||||||
specialArgs ? { }, # Extra arguments to pass to nixosSystem i.e. useful to make self available
|
specialArgs ? { }, # Extra arguments to pass to nixosSystem i.e. useful to make self available
|
||||||
machines ? { }, # allows to include machine-specific modules i.e. machines.${name} = { ... }
|
machines ? { }, # allows to include machine-specific modules i.e. machines.${name} = { ... }
|
||||||
# DEPRECATED: use meta.name instead
|
clanName, # Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.
|
||||||
clanName ? null, # Needs to be (globally) unique, as this determines the folder name where the flake gets downloaded to.
|
|
||||||
# DEPRECATED: use meta.icon instead
|
|
||||||
clanIcon ? null, # A path to an icon to be used for the clan, should be the same for all machines
|
clanIcon ? null, # A path to an icon to be used for the clan, should be the same for all machines
|
||||||
meta ? { }, # A set containing clan meta: name :: string, icon :: string, description :: string
|
|
||||||
pkgsForSystem ? (_system: null), # A map from arch to pkgs, if specified this nixpkgs will be only imported once for each system.
|
pkgsForSystem ? (_system: null), # A map from arch to pkgs, if specified this nixpkgs will be only imported once for each system.
|
||||||
# This improves performance, but all nipxkgs.* options will be ignored.
|
# This improves performance, but all nipxkgs.* options will be ignored.
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
deprecationWarnings = [
|
|
||||||
(lib.warnIf (
|
|
||||||
clanName != null
|
|
||||||
) "clanName is deprecated, please use meta.name instead. ${clanName}" null)
|
|
||||||
(lib.warnIf (clanIcon != null) "clanIcon is deprecated, please use meta.icon instead" null)
|
|
||||||
];
|
|
||||||
|
|
||||||
machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (
|
machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (
|
||||||
builtins.readDir (directory + /machines)
|
builtins.readDir (directory + /machines)
|
||||||
);
|
);
|
||||||
|
|
||||||
mergedMeta =
|
|
||||||
let
|
|
||||||
metaFromFile =
|
|
||||||
if (builtins.pathExists "${directory}/clan/meta.json") then
|
|
||||||
let
|
|
||||||
settings = builtins.fromJSON (builtins.readFile "${directory}/clan/meta.json");
|
|
||||||
in
|
|
||||||
settings
|
|
||||||
else
|
|
||||||
{ };
|
|
||||||
legacyMeta = lib.filterAttrs (_: v: v != null) {
|
|
||||||
name = clanName;
|
|
||||||
icon = clanIcon;
|
|
||||||
};
|
|
||||||
optionsMeta = lib.filterAttrs (_: v: v != null) meta;
|
|
||||||
|
|
||||||
warnings =
|
|
||||||
builtins.map (
|
|
||||||
name:
|
|
||||||
if
|
|
||||||
metaFromFile.${name} or null != optionsMeta.${name} or null && optionsMeta.${name} or null != null
|
|
||||||
then
|
|
||||||
lib.warn "meta.${name} is set in different places. (exlicit option meta.${name} overrides ${directory}/clan/meta.json)" null
|
|
||||||
else
|
|
||||||
null
|
|
||||||
) (builtins.attrNames metaFromFile)
|
|
||||||
++ [ (if (res.name or null == null) then (throw "meta.name should be set") else null) ];
|
|
||||||
res = metaFromFile // legacyMeta // optionsMeta;
|
|
||||||
in
|
|
||||||
# Print out warnings before returning the merged result
|
|
||||||
builtins.deepSeq warnings res;
|
|
||||||
|
|
||||||
machineSettings =
|
machineSettings =
|
||||||
machineName:
|
machineName:
|
||||||
# CLAN_MACHINE_SETTINGS_FILE allows to override the settings file temporarily
|
# CLAN_MACHINE_SETTINGS_FILE allows to override the settings file temporarily
|
||||||
@ -100,15 +58,11 @@ let
|
|||||||
(machines.${name} or { })
|
(machines.${name} or { })
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
# Settings
|
|
||||||
clanCore.clanDir = directory;
|
|
||||||
# Inherited from clan wide settings
|
|
||||||
clanCore.clanName = meta.name or clanName;
|
|
||||||
clanCore.clanIcon = meta.icon or clanIcon;
|
|
||||||
|
|
||||||
# Machine specific settings
|
|
||||||
clanCore.machineName = name;
|
|
||||||
networking.hostName = lib.mkDefault name;
|
networking.hostName = lib.mkDefault name;
|
||||||
|
clanCore.clanName = clanName;
|
||||||
|
clanCore.clanIcon = clanIcon;
|
||||||
|
clanCore.clanDir = directory;
|
||||||
|
clanCore.machineName = name;
|
||||||
nixpkgs.hostPlatform = lib.mkDefault system;
|
nixpkgs.hostPlatform = lib.mkDefault system;
|
||||||
|
|
||||||
# speeds up nix commands by using the nixpkgs from the host system (especially useful in VMs)
|
# speeds up nix commands by using the nixpkgs from the host system (especially useful in VMs)
|
||||||
@ -173,15 +127,10 @@ let
|
|||||||
) supportedSystems
|
) supportedSystems
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
builtins.deepSeq deprecationWarnings {
|
{
|
||||||
inherit nixosConfigurations;
|
inherit nixosConfigurations;
|
||||||
|
|
||||||
clanInternals = {
|
clanInternals = {
|
||||||
# Evaluated clan meta
|
|
||||||
# Merged /clan/meta.json with overrides from buildClan
|
|
||||||
meta = mergedMeta;
|
|
||||||
|
|
||||||
# machine specifics
|
|
||||||
machines = configsPerSystem;
|
machines = configsPerSystem;
|
||||||
machinesFunc = configsFuncPerSystem;
|
machinesFunc = configsFuncPerSystem;
|
||||||
all-machines-json = lib.mapAttrs (
|
all-machines-json = lib.mapAttrs (
|
||||||
|
@ -152,7 +152,6 @@ For more detailed information, visit: https://docs.clan.lol/getting-started
|
|||||||
),
|
),
|
||||||
formatter_class=argparse.RawTextHelpFormatter,
|
formatter_class=argparse.RawTextHelpFormatter,
|
||||||
)
|
)
|
||||||
|
|
||||||
flakes.register_parser(parser_flake)
|
flakes.register_parser(parser_flake)
|
||||||
|
|
||||||
parser_config = subparsers.add_parser(
|
parser_config = subparsers.add_parser(
|
||||||
|
@ -2,7 +2,6 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
@ -41,10 +40,8 @@ def create_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine", type=str, help="machine in the flake to create backups of"
|
"machine", type=str, help="machine in the flake to create backups of"
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument("--provider", type=str, help="backup provider to use")
|
parser.add_argument("--provider", type=str, help="backup provider to use")
|
||||||
parser.set_defaults(func=create_command)
|
parser.set_defaults(func=create_command)
|
||||||
|
@ -3,7 +3,6 @@ import json
|
|||||||
import subprocess
|
import subprocess
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
@ -58,9 +57,8 @@ def list_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine", type=str, help="machine in the flake to show backups of"
|
"machine", type=str, help="machine in the flake to show backups of"
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
parser.add_argument("--provider", type=str, help="backup provider to filter by")
|
parser.add_argument("--provider", type=str, help="backup provider to filter by")
|
||||||
parser.set_defaults(func=list_command)
|
parser.set_defaults(func=list_command)
|
||||||
|
@ -56,7 +56,7 @@ def handle_output(process: subprocess.Popen, log: Log) -> tuple[str, str]:
|
|||||||
sys.stderr.buffer.write(ret)
|
sys.stderr.buffer.write(ret)
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
stderr_buf += ret
|
stderr_buf += ret
|
||||||
return stdout_buf.decode("utf-8", "replace"), stderr_buf.decode("utf-8", "replace")
|
return stdout_buf.decode("utf-8"), stderr_buf.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
class TimeTable:
|
class TimeTable:
|
||||||
@ -101,19 +101,13 @@ TIME_TABLE = TimeTable()
|
|||||||
def run(
|
def run(
|
||||||
cmd: list[str],
|
cmd: list[str],
|
||||||
*,
|
*,
|
||||||
input: bytes | None = None, # noqa: A002
|
|
||||||
env: dict[str, str] | None = None,
|
env: dict[str, str] | None = None,
|
||||||
cwd: Path = Path.cwd(),
|
cwd: Path = Path.cwd(),
|
||||||
log: Log = Log.STDERR,
|
log: Log = Log.STDERR,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
error_msg: str | None = None,
|
error_msg: str | None = None,
|
||||||
) -> CmdOut:
|
) -> CmdOut:
|
||||||
if input:
|
glog.debug(f"$: {shlex.join(cmd)} \nCaller: {get_caller()}")
|
||||||
glog.debug(
|
|
||||||
f"""$: echo "{input.decode('utf-8', 'replace')}" | {shlex.join(cmd)} \nCaller: {get_caller()}"""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
glog.debug(f"$: {shlex.join(cmd)} \nCaller: {get_caller()}")
|
|
||||||
tstart = datetime.now()
|
tstart = datetime.now()
|
||||||
|
|
||||||
# Start the subprocess
|
# Start the subprocess
|
||||||
@ -126,10 +120,7 @@ def run(
|
|||||||
)
|
)
|
||||||
stdout_buf, stderr_buf = handle_output(process, log)
|
stdout_buf, stderr_buf = handle_output(process, log)
|
||||||
|
|
||||||
if input:
|
rc = process.wait()
|
||||||
process.communicate(input)
|
|
||||||
else:
|
|
||||||
process.wait()
|
|
||||||
tend = datetime.now()
|
tend = datetime.now()
|
||||||
|
|
||||||
global TIME_TABLE
|
global TIME_TABLE
|
||||||
@ -145,7 +136,7 @@ def run(
|
|||||||
msg=error_msg,
|
msg=error_msg,
|
||||||
)
|
)
|
||||||
|
|
||||||
if check and process.returncode != 0:
|
if check and rc != 0:
|
||||||
raise ClanCmdError(cmd_out)
|
raise ClanCmdError(cmd_out)
|
||||||
|
|
||||||
return cmd_out
|
return cmd_out
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
import threading
|
|
||||||
from collections.abc import Callable, Iterable
|
|
||||||
from types import ModuleType
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from .cmd import run
|
|
||||||
from .nix import nix_eval
|
|
||||||
|
|
||||||
"""
|
|
||||||
This module provides dynamic completions.
|
|
||||||
The completions should feel fast.
|
|
||||||
We target a maximum of 1second on our average machine.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
argcomplete: ModuleType | None = None
|
|
||||||
try:
|
|
||||||
import argcomplete # type: ignore[no-redef]
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# The default completion timeout for commands
|
|
||||||
COMPLETION_TIMEOUT: int = 3
|
|
||||||
|
|
||||||
|
|
||||||
def clan_dir(flake: str | None) -> str | None:
|
|
||||||
from .dirs import get_clan_flake_toplevel_or_env
|
|
||||||
|
|
||||||
path_result = get_clan_flake_toplevel_or_env()
|
|
||||||
return str(path_result) if path_result is not None else None
|
|
||||||
|
|
||||||
|
|
||||||
def complete_machines(
|
|
||||||
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
|
|
||||||
) -> Iterable[str]:
|
|
||||||
"""
|
|
||||||
Provides completion functionality for machine names configured in the clan.
|
|
||||||
"""
|
|
||||||
machines: list[str] = []
|
|
||||||
|
|
||||||
def run_cmd() -> None:
|
|
||||||
try:
|
|
||||||
# In my tests this was consistently faster than:
|
|
||||||
# nix eval .#nixosConfigurations --apply builtins.attrNames
|
|
||||||
cmd = ["nix", "flake", "show", "--system", "no-eval", "--json"]
|
|
||||||
if (clan_dir_result := clan_dir(None)) is not None:
|
|
||||||
cmd.append(clan_dir_result)
|
|
||||||
result = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
data = json.loads(result.stdout)
|
|
||||||
try:
|
|
||||||
machines.extend(data.get("nixosConfigurations").keys())
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
thread = threading.Thread(target=run_cmd)
|
|
||||||
thread.start()
|
|
||||||
thread.join(timeout=COMPLETION_TIMEOUT)
|
|
||||||
|
|
||||||
if thread.is_alive():
|
|
||||||
return iter([])
|
|
||||||
|
|
||||||
machines_dict = {name: "machine" for name in machines}
|
|
||||||
return machines_dict
|
|
||||||
|
|
||||||
|
|
||||||
def complete_services_for_machine(
|
|
||||||
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
|
|
||||||
) -> Iterable[str]:
|
|
||||||
"""
|
|
||||||
Provides completion functionality for machine facts generation services.
|
|
||||||
"""
|
|
||||||
services: list[str] = []
|
|
||||||
# TODO: consolidate, if multiple machines are used
|
|
||||||
machines: list[str] = parsed_args.machines
|
|
||||||
|
|
||||||
def run_cmd() -> None:
|
|
||||||
try:
|
|
||||||
if (clan_dir_result := clan_dir(None)) is not None:
|
|
||||||
flake = clan_dir_result
|
|
||||||
else:
|
|
||||||
flake = "."
|
|
||||||
services_result = json.loads(
|
|
||||||
run(
|
|
||||||
nix_eval(
|
|
||||||
flags=[
|
|
||||||
f"{flake}#nixosConfigurations.{machines[0]}.config.clanCore.facts.services",
|
|
||||||
"--apply",
|
|
||||||
"builtins.attrNames",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).stdout.strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
services.extend(services_result)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
thread = threading.Thread(target=run_cmd)
|
|
||||||
thread.start()
|
|
||||||
thread.join(timeout=COMPLETION_TIMEOUT)
|
|
||||||
|
|
||||||
if thread.is_alive():
|
|
||||||
return iter([])
|
|
||||||
|
|
||||||
services_dict = {name: "service" for name in services}
|
|
||||||
return services_dict
|
|
||||||
|
|
||||||
|
|
||||||
def complete_secrets(
|
|
||||||
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
|
|
||||||
) -> Iterable[str]:
|
|
||||||
"""
|
|
||||||
Provides completion functionality for clan secrets
|
|
||||||
"""
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from .secrets.secrets import ListSecretsOptions, list_secrets
|
|
||||||
|
|
||||||
if (clan_dir_result := clan_dir(None)) is not None:
|
|
||||||
flake = clan_dir_result
|
|
||||||
else:
|
|
||||||
flake = "."
|
|
||||||
|
|
||||||
options = ListSecretsOptions(
|
|
||||||
flake=Path(flake),
|
|
||||||
pattern=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
secrets = list_secrets(options.flake, options.pattern)
|
|
||||||
|
|
||||||
secrets_dict = {name: "secret" for name in secrets}
|
|
||||||
return secrets_dict
|
|
||||||
|
|
||||||
|
|
||||||
def add_dynamic_completer(
|
|
||||||
action: argparse.Action,
|
|
||||||
completer: Callable[..., Iterable[str]],
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Add a completion function to an argparse action, this will only be added,
|
|
||||||
if the argcomplete module is loaded.
|
|
||||||
"""
|
|
||||||
if argcomplete:
|
|
||||||
# mypy doesn't check this correctly, so we ignore it
|
|
||||||
action.completer = completer # type: ignore[attr-defined]
|
|
@ -2,7 +2,6 @@ import argparse
|
|||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -55,12 +54,10 @@ def check_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_check_parser(parser: argparse.ArgumentParser) -> None:
|
def register_check_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
help="The machine to check secrets for",
|
help="The machine to check secrets for",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--service",
|
"--service",
|
||||||
help="the service to check",
|
help="the service to check",
|
||||||
|
@ -9,11 +9,6 @@ from tempfile import TemporaryDirectory
|
|||||||
|
|
||||||
from clan_cli.cmd import run
|
from clan_cli.cmd import run
|
||||||
|
|
||||||
from ..completions import (
|
|
||||||
add_dynamic_completer,
|
|
||||||
complete_machines,
|
|
||||||
complete_services_for_machine,
|
|
||||||
)
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..git import commit_files
|
from ..git import commit_files
|
||||||
from ..machines.inventory import get_all_machines, get_selected_machines
|
from ..machines.inventory import get_all_machines, get_selected_machines
|
||||||
@ -32,7 +27,6 @@ def read_multiline_input(prompt: str = "Finish with Ctrl-D") -> str:
|
|||||||
"""
|
"""
|
||||||
print(prompt, flush=True)
|
print(prompt, flush=True)
|
||||||
proc = subprocess.run(["cat"], stdout=subprocess.PIPE, text=True)
|
proc = subprocess.run(["cat"], stdout=subprocess.PIPE, text=True)
|
||||||
log.info("Input received. Processing...")
|
|
||||||
return proc.stdout
|
return proc.stdout
|
||||||
|
|
||||||
|
|
||||||
@ -222,23 +216,19 @@ def generate_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_generate_parser(parser: argparse.ArgumentParser) -> None:
|
def register_generate_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machines",
|
"machines",
|
||||||
type=str,
|
type=str,
|
||||||
help="machine to generate facts for. if empty, generate facts for all machines",
|
help="machine to generate facts for. if empty, generate facts for all machines",
|
||||||
nargs="*",
|
nargs="*",
|
||||||
default=[],
|
default=[],
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
parser.add_argument(
|
||||||
|
|
||||||
service_parser = parser.add_argument(
|
|
||||||
"--service",
|
"--service",
|
||||||
type=str,
|
type=str,
|
||||||
help="service to generate facts for, if empty, generate facts for every service",
|
help="service to generate facts for, if empty, generate facts for every service",
|
||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
add_dynamic_completer(service_parser, complete_services_for_machine)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--regenerate",
|
"--regenerate",
|
||||||
type=bool,
|
type=bool,
|
||||||
|
@ -3,7 +3,6 @@ import importlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -38,10 +37,8 @@ def get_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
help="The machine to print facts for",
|
help="The machine to print facts for",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.set_defaults(func=get_command)
|
parser.set_defaults(func=get_command)
|
||||||
|
@ -2,7 +2,6 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from clan_cli.cmd import Log, run
|
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
from clan_cli.nix import nix_shell
|
from clan_cli.nix import nix_shell
|
||||||
|
|
||||||
@ -16,25 +15,25 @@ class SecretStore(SecretStoreBase):
|
|||||||
def set(
|
def set(
|
||||||
self, service: str, name: str, value: bytes, groups: list[str]
|
self, service: str, name: str, value: bytes, groups: list[str]
|
||||||
) -> Path | None:
|
) -> Path | None:
|
||||||
run(
|
subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#pass"],
|
["nixpkgs#pass"],
|
||||||
["pass", "insert", "-m", f"machines/{self.machine.name}/{name}"],
|
["pass", "insert", "-m", f"machines/{self.machine.name}/{name}"],
|
||||||
),
|
),
|
||||||
input=value,
|
input=value,
|
||||||
log=Log.BOTH,
|
check=True,
|
||||||
error_msg=f"Failed to insert secret {name}",
|
|
||||||
)
|
)
|
||||||
return None # we manage the files outside of the git repo
|
return None # we manage the files outside of the git repo
|
||||||
|
|
||||||
def get(self, service: str, name: str) -> bytes:
|
def get(self, service: str, name: str) -> bytes:
|
||||||
return run(
|
return subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#pass"],
|
["nixpkgs#pass"],
|
||||||
["pass", "show", f"machines/{self.machine.name}/{name}"],
|
["pass", "show", f"machines/{self.machine.name}/{name}"],
|
||||||
),
|
),
|
||||||
error_msg=f"Failed to get secret {name}",
|
check=True,
|
||||||
).stdout.encode("utf-8")
|
stdout=subprocess.PIPE,
|
||||||
|
).stdout
|
||||||
|
|
||||||
def exists(self, service: str, name: str) -> bool:
|
def exists(self, service: str, name: str) -> bool:
|
||||||
password_store = os.environ.get(
|
password_store = os.environ.get(
|
||||||
@ -49,7 +48,7 @@ class SecretStore(SecretStoreBase):
|
|||||||
)
|
)
|
||||||
hashes = []
|
hashes = []
|
||||||
hashes.append(
|
hashes.append(
|
||||||
run(
|
subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#git"],
|
["nixpkgs#git"],
|
||||||
[
|
[
|
||||||
@ -62,15 +61,13 @@ class SecretStore(SecretStoreBase):
|
|||||||
f"machines/{self.machine.name}",
|
f"machines/{self.machine.name}",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
check=False,
|
stdout=subprocess.PIPE,
|
||||||
)
|
).stdout.strip()
|
||||||
.stdout.encode("utf-8")
|
|
||||||
.strip()
|
|
||||||
)
|
)
|
||||||
for symlink in Path(password_store).glob(f"machines/{self.machine.name}/**/*"):
|
for symlink in Path(password_store).glob(f"machines/{self.machine.name}/**/*"):
|
||||||
if symlink.is_symlink():
|
if symlink.is_symlink():
|
||||||
hashes.append(
|
hashes.append(
|
||||||
run(
|
subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#git"],
|
["nixpkgs#git"],
|
||||||
[
|
[
|
||||||
@ -83,10 +80,8 @@ class SecretStore(SecretStoreBase):
|
|||||||
str(symlink),
|
str(symlink),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
check=False,
|
stdout=subprocess.PIPE,
|
||||||
)
|
).stdout.strip()
|
||||||
.stdout.encode("utf-8")
|
|
||||||
.strip()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# we sort the hashes to make sure that the order is always the same
|
# we sort the hashes to make sure that the order is always the same
|
||||||
|
@ -5,7 +5,6 @@ from pathlib import Path
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from ..cmd import Log, run
|
from ..cmd import Log, run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
|
|
||||||
@ -33,8 +32,6 @@ def upload_secrets(machine: Machine) -> None:
|
|||||||
" ".join(["ssh"] + ssh_cmd[2:]),
|
" ".join(["ssh"] + ssh_cmd[2:]),
|
||||||
"-az",
|
"-az",
|
||||||
"--delete",
|
"--delete",
|
||||||
"--chown=root:root",
|
|
||||||
"--chmod=D700,F600",
|
|
||||||
f"{tempdir!s}/",
|
f"{tempdir!s}/",
|
||||||
f"{host.user}@{host.host}:{machine.secrets_upload_directory}/",
|
f"{host.user}@{host.host}:{machine.secrets_upload_directory}/",
|
||||||
],
|
],
|
||||||
@ -49,10 +46,8 @@ def upload_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_upload_parser(parser: argparse.ArgumentParser) -> None:
|
def register_upload_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
help="The machine to upload secrets to",
|
help="The machine to upload secrets to",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.set_defaults(func=upload_command)
|
parser.set_defaults(func=upload_command)
|
||||||
|
@ -12,7 +12,6 @@ from tempfile import TemporaryDirectory
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .cmd import Log, run
|
from .cmd import Log, run
|
||||||
from .completions import add_dynamic_completer, complete_machines
|
|
||||||
from .errors import ClanError
|
from .errors import ClanError
|
||||||
from .facts.secret_modules import SecretStoreBase
|
from .facts.secret_modules import SecretStoreBase
|
||||||
from .machines.machines import Machine
|
from .machines.machines import Machine
|
||||||
@ -174,13 +173,11 @@ def flash_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_parser(parser: argparse.ArgumentParser) -> None:
|
def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
type=str,
|
type=str,
|
||||||
help="machine to install",
|
help="machine to install",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--disk",
|
"--disk",
|
||||||
type=str,
|
type=str,
|
||||||
|
@ -5,7 +5,6 @@ from .create import register_create_parser
|
|||||||
from .delete import register_delete_parser
|
from .delete import register_delete_parser
|
||||||
from .install import register_install_parser
|
from .install import register_install_parser
|
||||||
from .list import register_list_parser
|
from .list import register_list_parser
|
||||||
from .show import register_show_parser
|
|
||||||
from .update import register_update_parser
|
from .update import register_update_parser
|
||||||
|
|
||||||
|
|
||||||
@ -63,17 +62,6 @@ Examples:
|
|||||||
)
|
)
|
||||||
register_list_parser(list_parser)
|
register_list_parser(list_parser)
|
||||||
|
|
||||||
show_parser = subparser.add_parser(
|
|
||||||
"show",
|
|
||||||
help="Show a machine",
|
|
||||||
epilog=(
|
|
||||||
"""
|
|
||||||
This subcommand shows the details of a machine managed by this clan like icon, description, etc
|
|
||||||
"""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
register_show_parser(show_parser)
|
|
||||||
|
|
||||||
install_parser = subparser.add_parser(
|
install_parser = subparser.add_parser(
|
||||||
"install",
|
"install",
|
||||||
help="Install a machine",
|
help="Install a machine",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..dirs import specific_machine_dir
|
from ..dirs import specific_machine_dir
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
|
||||||
@ -15,7 +14,5 @@ def delete_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_delete_parser(parser: argparse.ArgumentParser) -> None:
|
def register_delete_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument("host", type=str)
|
parser.add_argument("host", type=str)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.set_defaults(func=delete_command)
|
parser.set_defaults(func=delete_command)
|
||||||
|
@ -8,7 +8,6 @@ from pathlib import Path
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from ..cmd import Log, run
|
from ..cmd import Log, run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..facts.generate import generate_facts
|
from ..facts.generate import generate_facts
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
@ -189,14 +188,11 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
help="do not ask for confirmation",
|
help="do not ask for confirmation",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
machines_parser = parser.add_argument(
|
|
||||||
"machine",
|
"machine",
|
||||||
type=str,
|
type=str,
|
||||||
help="machine to install",
|
help="machine to install",
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"target_host",
|
"target_host",
|
||||||
type=str,
|
type=str,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -11,15 +12,24 @@ from ..nix import nix_config, nix_eval
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class MachineInfo:
|
||||||
|
machine_name: str
|
||||||
|
machine_description: str | None
|
||||||
|
machine_icon: str | None
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
@API.register
|
||||||
def list_machines(flake_url: str | Path, debug: bool) -> list[str]:
|
def list_machines(flake_url: str | Path, debug: bool) -> dict[str, MachineInfo]:
|
||||||
config = nix_config()
|
config = nix_config()
|
||||||
system = config["system"]
|
system = config["system"]
|
||||||
cmd = nix_eval(
|
cmd = nix_eval(
|
||||||
[
|
[
|
||||||
f"{flake_url}#clanInternals.machines.{system}",
|
f"{flake_url}#clanInternals.machines.{system}",
|
||||||
"--apply",
|
"--apply",
|
||||||
"builtins.attrNames",
|
"""builtins.mapAttrs (name: attrs: {
|
||||||
|
inherit (attrs.config.clanCore) machineDescription machineIcon machineName;
|
||||||
|
})""",
|
||||||
"--json",
|
"--json",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -27,13 +37,27 @@ def list_machines(flake_url: str | Path, debug: bool) -> list[str]:
|
|||||||
proc = run_no_stdout(cmd)
|
proc = run_no_stdout(cmd)
|
||||||
|
|
||||||
res = proc.stdout.strip()
|
res = proc.stdout.strip()
|
||||||
return json.loads(res)
|
machines_dict = json.loads(res)
|
||||||
|
|
||||||
|
return {
|
||||||
|
k: MachineInfo(
|
||||||
|
machine_name=v.get("machineName"),
|
||||||
|
machine_description=v.get("machineDescription", None),
|
||||||
|
machine_icon=v.get("machineIcon", None),
|
||||||
|
)
|
||||||
|
for k, v in machines_dict.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def list_command(args: argparse.Namespace) -> None:
|
def list_command(args: argparse.Namespace) -> None:
|
||||||
flake_path = Path(args.flake).resolve()
|
flake_path = Path(args.flake).resolve()
|
||||||
for name in list_machines(flake_path, args.debug):
|
print("Listing all machines:\n")
|
||||||
print(name)
|
print("Source: ", flake_path)
|
||||||
|
print("-" * 40)
|
||||||
|
for name, machine in list_machines(flake_path, args.debug).items():
|
||||||
|
description = machine.machine_description or "[no description]"
|
||||||
|
print(f"{name}\n: {description}\n")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import dataclasses
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from clan_cli.api import API
|
|
||||||
|
|
||||||
from ..cmd import run_no_stdout
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..nix import nix_config, nix_eval
|
|
||||||
from .types import machine_name_type
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class MachineInfo:
|
|
||||||
machine_name: str
|
|
||||||
machine_description: str | None
|
|
||||||
machine_icon: str | None
|
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
|
||||||
def show_machine(flake_url: str | Path, machine_name: str, debug: bool) -> MachineInfo:
|
|
||||||
config = nix_config()
|
|
||||||
system = config["system"]
|
|
||||||
cmd = nix_eval(
|
|
||||||
[
|
|
||||||
f"{flake_url}#clanInternals.machines.{system}.{machine_name}",
|
|
||||||
"--apply",
|
|
||||||
"machine: { inherit (machine.config.clanCore) machineDescription machineIcon machineName; }",
|
|
||||||
"--json",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
proc = run_no_stdout(cmd)
|
|
||||||
res = proc.stdout.strip()
|
|
||||||
machine = json.loads(res)
|
|
||||||
|
|
||||||
return MachineInfo(
|
|
||||||
machine_name=machine.get("machineName"),
|
|
||||||
machine_description=machine.get("machineDescription", None),
|
|
||||||
machine_icon=machine.get("machineIcon", None),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def show_command(args: argparse.Namespace) -> None:
|
|
||||||
flake_path = Path(args.flake).resolve()
|
|
||||||
machine = show_machine(flake_path, args.machine, args.debug)
|
|
||||||
print(f"Name: {machine.machine_name}")
|
|
||||||
print(f"Description: {machine.machine_description or ''}")
|
|
||||||
print(f"Icon: {machine.machine_icon or ''}")
|
|
||||||
|
|
||||||
|
|
||||||
def register_show_parser(parser: argparse.ArgumentParser) -> None:
|
|
||||||
parser.set_defaults(func=show_command)
|
|
||||||
machine_parser = parser.add_argument(
|
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
|
||||||
)
|
|
||||||
add_dynamic_completer(machine_parser, complete_machines)
|
|
@ -3,10 +3,9 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ..cmd import run
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..facts.generate import generate_facts
|
from ..facts.generate import generate_facts
|
||||||
from ..facts.upload import upload_secrets
|
from ..facts.upload import upload_secrets
|
||||||
@ -54,7 +53,11 @@ def upload_sources(
|
|||||||
path,
|
path,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
run(cmd, env=env, error_msg="failed to upload sources")
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, env=env, check=False)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise ClanError(
|
||||||
|
f"failed to upload sources: {shlex.join(cmd)} failed with {proc.returncode}"
|
||||||
|
)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
# Slow path: we need to upload all sources to the remote machine
|
# Slow path: we need to upload all sources to the remote machine
|
||||||
@ -70,13 +73,16 @@ def upload_sources(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
log.info("run %s", shlex.join(cmd))
|
log.info("run %s", shlex.join(cmd))
|
||||||
proc = run(cmd, error_msg="failed to upload sources")
|
proc = subprocess.run(cmd, stdout=subprocess.PIPE, check=False)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise ClanError(
|
||||||
|
f"failed to upload sources: {shlex.join(cmd)} failed with {proc.returncode}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
return json.loads(proc.stdout)["path"]
|
return json.loads(proc.stdout)["path"]
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
raise ClanError(
|
raise ClanError(
|
||||||
f"failed to parse output of {shlex.join(cmd)}: {e}\nGot: {proc.stdout}"
|
f"failed to parse output of {shlex.join(cmd)}: {e}\nGot: {proc.stdout.decode('utf-8', 'replace')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -174,7 +180,7 @@ def update(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
machines_parser = parser.add_argument(
|
parser.add_argument(
|
||||||
"machines",
|
"machines",
|
||||||
type=str,
|
type=str,
|
||||||
nargs="*",
|
nargs="*",
|
||||||
@ -182,9 +188,6 @@ def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
metavar="MACHINE",
|
metavar="MACHINE",
|
||||||
help="machine to update. If no machine is specified, all machines will be updated.",
|
help="machine to update. If no machine is specified, all machines will be updated.",
|
||||||
)
|
)
|
||||||
|
|
||||||
add_dynamic_completer(machines_parser, complete_machines)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--target-host",
|
"--target-host",
|
||||||
type=str,
|
type=str,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..git import commit_files
|
from ..git import commit_files
|
||||||
from ..machines.types import machine_name_type, validate_hostname
|
from ..machines.types import machine_name_type, validate_hostname
|
||||||
@ -148,28 +147,25 @@ def register_machines_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
|
|
||||||
# Parser
|
# Parser
|
||||||
get_parser = subparser.add_parser("get", help="get a machine public key")
|
get_parser = subparser.add_parser("get", help="get a machine public key")
|
||||||
get_machine_parser = get_parser.add_argument(
|
get_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the machine", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(get_machine_parser, complete_machines)
|
|
||||||
get_parser.set_defaults(func=get_command)
|
get_parser.set_defaults(func=get_command)
|
||||||
|
|
||||||
# Parser
|
# Parser
|
||||||
remove_parser = subparser.add_parser("remove", help="remove a machine")
|
remove_parser = subparser.add_parser("remove", help="remove a machine")
|
||||||
remove_machine_parser = remove_parser.add_argument(
|
remove_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the machine", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(remove_machine_parser, complete_machines)
|
|
||||||
remove_parser.set_defaults(func=remove_command)
|
remove_parser.set_defaults(func=remove_command)
|
||||||
|
|
||||||
# Parser
|
# Parser
|
||||||
add_secret_parser = subparser.add_parser(
|
add_secret_parser = subparser.add_parser(
|
||||||
"add-secret", help="allow a machine to access a secret"
|
"add-secret", help="allow a machine to access a secret"
|
||||||
)
|
)
|
||||||
machine_add_secret_parser = add_secret_parser.add_argument(
|
add_secret_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the machine", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machine_add_secret_parser, complete_machines)
|
|
||||||
add_secret_parser.add_argument(
|
add_secret_parser.add_argument(
|
||||||
"secret", help="the name of the secret", type=secret_name_type
|
"secret", help="the name of the secret", type=secret_name_type
|
||||||
)
|
)
|
||||||
@ -179,10 +175,9 @@ def register_machines_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
remove_secret_parser = subparser.add_parser(
|
remove_secret_parser = subparser.add_parser(
|
||||||
"remove-secret", help="remove a group's access to a secret"
|
"remove-secret", help="remove a group's access to a secret"
|
||||||
)
|
)
|
||||||
machine_remove_parser = remove_secret_parser.add_argument(
|
remove_secret_parser.add_argument(
|
||||||
"machine", help="the name of the machine", type=machine_name_type
|
"machine", help="the name of the group", type=machine_name_type
|
||||||
)
|
)
|
||||||
add_dynamic_completer(machine_remove_parser, complete_machines)
|
|
||||||
remove_secret_parser.add_argument(
|
remove_secret_parser.add_argument(
|
||||||
"secret", help="the name of the secret", type=secret_name_type
|
"secret", help="the name of the secret", type=secret_name_type
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,6 @@ from pathlib import Path
|
|||||||
from typing import IO
|
from typing import IO
|
||||||
|
|
||||||
from .. import tty
|
from .. import tty
|
||||||
from ..completions import add_dynamic_completer, complete_secrets
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..git import commit_files
|
from ..git import commit_files
|
||||||
from .folders import (
|
from .folders import (
|
||||||
@ -154,12 +153,8 @@ def remove_command(args: argparse.Namespace) -> None:
|
|||||||
remove_secret(Path(args.flake), args.secret)
|
remove_secret(Path(args.flake), args.secret)
|
||||||
|
|
||||||
|
|
||||||
def add_secret_argument(parser: argparse.ArgumentParser, autocomplete: bool) -> None:
|
def add_secret_argument(parser: argparse.ArgumentParser) -> None:
|
||||||
secrets_parser = parser.add_argument(
|
parser.add_argument("secret", help="the name of the secret", type=secret_name_type)
|
||||||
"secret", help="the name of the secret", type=secret_name_type
|
|
||||||
)
|
|
||||||
if autocomplete:
|
|
||||||
add_dynamic_completer(secrets_parser, complete_secrets)
|
|
||||||
|
|
||||||
|
|
||||||
def machines_folder(flake_dir: Path, group: str) -> Path:
|
def machines_folder(flake_dir: Path, group: str) -> Path:
|
||||||
@ -328,11 +323,11 @@ def register_secrets_parser(subparser: argparse._SubParsersAction) -> None:
|
|||||||
parser_list.set_defaults(func=list_command)
|
parser_list.set_defaults(func=list_command)
|
||||||
|
|
||||||
parser_get = subparser.add_parser("get", help="get a secret")
|
parser_get = subparser.add_parser("get", help="get a secret")
|
||||||
add_secret_argument(parser_get, True)
|
add_secret_argument(parser_get)
|
||||||
parser_get.set_defaults(func=get_command)
|
parser_get.set_defaults(func=get_command)
|
||||||
|
|
||||||
parser_set = subparser.add_parser("set", help="set a secret")
|
parser_set = subparser.add_parser("set", help="set a secret")
|
||||||
add_secret_argument(parser_set, False)
|
add_secret_argument(parser_set)
|
||||||
parser_set.add_argument(
|
parser_set.add_argument(
|
||||||
"--group",
|
"--group",
|
||||||
type=str,
|
type=str,
|
||||||
@ -364,10 +359,10 @@ def register_secrets_parser(subparser: argparse._SubParsersAction) -> None:
|
|||||||
parser_set.set_defaults(func=set_command)
|
parser_set.set_defaults(func=set_command)
|
||||||
|
|
||||||
parser_rename = subparser.add_parser("rename", help="rename a secret")
|
parser_rename = subparser.add_parser("rename", help="rename a secret")
|
||||||
add_secret_argument(parser_rename, True)
|
add_secret_argument(parser_rename)
|
||||||
parser_rename.add_argument("new_name", type=str, help="the new name of the secret")
|
parser_rename.add_argument("new_name", type=str, help="the new name of the secret")
|
||||||
parser_rename.set_defaults(func=rename_command)
|
parser_rename.set_defaults(func=rename_command)
|
||||||
|
|
||||||
parser_remove = subparser.add_parser("remove", help="remove a secret")
|
parser_remove = subparser.add_parser("remove", help="remove a secret")
|
||||||
add_secret_argument(parser_remove, True)
|
add_secret_argument(parser_remove)
|
||||||
parser_remove.set_defaults(func=remove_command)
|
parser_remove.set_defaults(func=remove_command)
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
mypy,
|
mypy,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
clan-core-path,
|
clan-core-path,
|
||||||
gitMinimal,
|
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
# Dependencies that are directly used in the project
|
# Dependencies that are directly used in the project
|
||||||
@ -114,13 +113,7 @@ python3.pkgs.buildPythonApplication {
|
|||||||
format = "pyproject";
|
format = "pyproject";
|
||||||
|
|
||||||
# Arguments for the wrapper to unset LD_LIBRARY_PATH to avoid glibc version issues
|
# Arguments for the wrapper to unset LD_LIBRARY_PATH to avoid glibc version issues
|
||||||
makeWrapperArgs = [
|
makeWrapperArgs = [ "--unset LD_LIBRARY_PATH" ];
|
||||||
"--unset LD_LIBRARY_PATH"
|
|
||||||
"--suffix"
|
|
||||||
"PATH"
|
|
||||||
":"
|
|
||||||
"${gitMinimal}/bin/git"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Build-time dependencies.
|
# Build-time dependencies.
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
|
@ -17,12 +17,10 @@ def test_create_flake(
|
|||||||
capsys: pytest.CaptureFixture,
|
capsys: pytest.CaptureFixture,
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
cli: Cli,
|
cli: Cli,
|
||||||
clan_core: Path,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
flake_dir = temporary_home / "test-flake"
|
flake_dir = temporary_home / "test-flake"
|
||||||
|
|
||||||
url = f"{clan_core}#default"
|
cli.run(["flakes", "create", str(flake_dir)])
|
||||||
cli.run(["flakes", "create", str(flake_dir), f"--url={url}"])
|
|
||||||
assert (flake_dir / ".clan-flake").exists()
|
assert (flake_dir / ".clan-flake").exists()
|
||||||
monkeypatch.chdir(flake_dir)
|
monkeypatch.chdir(flake_dir)
|
||||||
cli.run(["machines", "create", "machine1"])
|
cli.run(["machines", "create", "machine1"])
|
||||||
@ -49,34 +47,3 @@ def test_create_flake(
|
|||||||
flake_outputs["nixosConfigurations"]["machine1"]
|
flake_outputs["nixosConfigurations"]["machine1"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.impure
|
|
||||||
def test_ui_template(
|
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
|
||||||
capsys: pytest.CaptureFixture,
|
|
||||||
temporary_home: Path,
|
|
||||||
cli: Cli,
|
|
||||||
clan_core: Path,
|
|
||||||
) -> None:
|
|
||||||
flake_dir = temporary_home / "test-flake"
|
|
||||||
url = f"{clan_core}#empty"
|
|
||||||
cli.run(["flakes", "create", str(flake_dir), f"--url={url}"])
|
|
||||||
assert (flake_dir / ".clan-flake").exists()
|
|
||||||
monkeypatch.chdir(flake_dir)
|
|
||||||
cli.run(["machines", "create", "machine1"])
|
|
||||||
capsys.readouterr() # flush cache
|
|
||||||
|
|
||||||
cli.run(["machines", "list"])
|
|
||||||
assert "machine1" in capsys.readouterr().out
|
|
||||||
flake_show = subprocess.run(
|
|
||||||
["nix", "flake", "show", "--json"],
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
flake_outputs = json.loads(flake_show.stdout)
|
|
||||||
try:
|
|
||||||
flake_outputs["nixosConfigurations"]["machine1"]
|
|
||||||
except KeyError:
|
|
||||||
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
|
||||||
|
@ -21,13 +21,6 @@ def test_machine_subcommands(
|
|||||||
assert "vm1" in out.out
|
assert "vm1" in out.out
|
||||||
assert "vm2" in out.out
|
assert "vm2" in out.out
|
||||||
|
|
||||||
capsys.readouterr()
|
|
||||||
cli.run(["machines", "show", "--flake", str(test_flake_with_core.path), "machine1"])
|
|
||||||
out = capsys.readouterr()
|
|
||||||
assert "machine1" in out.out
|
|
||||||
assert "Description" in out.out
|
|
||||||
print(out)
|
|
||||||
|
|
||||||
cli.run(
|
cli.run(
|
||||||
["machines", "delete", "--flake", str(test_flake_with_core.path), "machine1"]
|
["machines", "delete", "--flake", str(test_flake_with_core.path), "machine1"]
|
||||||
)
|
)
|
||||||
|
@ -46,12 +46,6 @@ class WebView:
|
|||||||
self.method_registry: dict[str, Callable] = methods
|
self.method_registry: dict[str, Callable] = methods
|
||||||
|
|
||||||
self.webview = WebKit.WebView()
|
self.webview = WebKit.WebView()
|
||||||
|
|
||||||
settings = self.webview.get_settings()
|
|
||||||
# settings.
|
|
||||||
settings.set_property("enable-developer-extras", True)
|
|
||||||
self.webview.set_settings(settings)
|
|
||||||
|
|
||||||
self.manager = self.webview.get_user_content_manager()
|
self.manager = self.webview.get_user_content_manager()
|
||||||
# Can be called with: window.webkit.messageHandlers.gtk.postMessage("...")
|
# Can be called with: window.webkit.messageHandlers.gtk.postMessage("...")
|
||||||
# Important: it seems postMessage must be given some payload, otherwise it won't trigger the event
|
# Important: it seems postMessage must be given some payload, otherwise it won't trigger the event
|
||||||
|
@ -8,9 +8,8 @@ clean_temp_dir() {
|
|||||||
rm -rf "$temp_dir"
|
rm -rf "$temp_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
is_installed() {
|
is_nix_installed() {
|
||||||
name=$1
|
if [ -n "$(command -v nix)" ]; then
|
||||||
if [ -n "$(command -v "$name")" ]; then
|
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
@ -18,18 +17,9 @@ is_installed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_nix() {
|
install_nix() {
|
||||||
if is_installed curl; then
|
curl --proto '=https' --tlsv1.2 -sSf -L \
|
||||||
curl --proto '=https' --tlsv1.2 -sSf -L \
|
https://install.determinate.systems/nix \
|
||||||
https://install.determinate.systems/nix \
|
> "$temp_dir"/install_nix.sh
|
||||||
> "$temp_dir"/install_nix.sh
|
|
||||||
elif is_installed wget; then
|
|
||||||
wget -qO- \
|
|
||||||
https://install.determinate.systems/nix \
|
|
||||||
> "$temp_dir"/install_nix.sh
|
|
||||||
else
|
|
||||||
echo "Either curl or wget is required to install Nix. Exiting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
NIX_INSTALLER_DIAGNOSTIC_ENDPOINT="" sh "$temp_dir"/install_nix.sh install
|
NIX_INSTALLER_DIAGNOSTIC_ENDPOINT="" sh "$temp_dir"/install_nix.sh install
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,17 +35,15 @@ ask_then_install_nix() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensure_nix_installed() {
|
ensure_nix_installed() {
|
||||||
if ! is_installed nix; then
|
if ! is_nix_installed; then
|
||||||
ask_then_install_nix
|
ask_then_install_nix
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
start_clan_gui() {
|
start_clan_gui() {
|
||||||
PATH="${PATH:+$PATH:}/nix/var/nix/profiles/default/bin" \
|
exec nix run \
|
||||||
exec nix run \
|
https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-vm-manager \
|
||||||
https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-vm-manager \
|
--extra-experimental-features flakes nix-command -- "$@"
|
||||||
--no-accept-flake-config \
|
|
||||||
--extra-experimental-features flakes nix-command -- "$@"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
@ -98,6 +98,9 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
clan = {
|
clan = {
|
||||||
|
clanName = "clan-core";
|
||||||
|
directory = self;
|
||||||
|
|
||||||
# To build a generic installer image (without ssh pubkeys),
|
# To build a generic installer image (without ssh pubkeys),
|
||||||
# use the following command:
|
# use the following command:
|
||||||
# $ nix build .#iso-installer
|
# $ nix build .#iso-installer
|
||||||
|
26
pkgs/merge-after-ci/merge-after-ci.py
Executable file → Normal file
26
pkgs/merge-after-ci/merge-after-ci.py
Executable file → Normal file
@ -1,5 +1,4 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import shlex
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@ -12,17 +11,14 @@ args = parser.parse_args()
|
|||||||
if not args.reviewers and not args.no_review:
|
if not args.reviewers and not args.no_review:
|
||||||
parser.error("either --reviewers or --no-review must be given")
|
parser.error("either --reviewers or --no-review must be given")
|
||||||
|
|
||||||
cmd = [
|
subprocess.run(
|
||||||
"tea-create-pr",
|
[
|
||||||
"origin",
|
"tea-create-pr",
|
||||||
"upstream",
|
"origin",
|
||||||
"main",
|
"main",
|
||||||
"--assignees",
|
"--assignees",
|
||||||
",".join(["clan-bot", *args.reviewers]),
|
",".join(["clan-bot", *args.reviewers]),
|
||||||
*(["--labels", "needs-review"] if not args.no_review else []),
|
*(["--labels", "needs-review"] if not args.no_review else []),
|
||||||
*args.args,
|
*args.args,
|
||||||
]
|
]
|
||||||
|
)
|
||||||
print("Running:", shlex.join(cmd))
|
|
||||||
|
|
||||||
subprocess.run(cmd)
|
|
||||||
|
49
pkgs/tea-create-pr/script.sh
Executable file → Normal file
49
pkgs/tea-create-pr/script.sh
Executable file → Normal file
@ -1,46 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
remoteFork="${1:-origin}"
|
remoteName="${1:-origin}"
|
||||||
remoteUpstream="${2:-upstream}"
|
targetBranch="${2:-main}"
|
||||||
targetBranch="${3:-main}"
|
shift && shift
|
||||||
shift && shift && shift
|
|
||||||
TMPDIR="$(mktemp -d)"
|
TMPDIR="$(mktemp -d)"
|
||||||
currentBranch="$(git rev-parse --abbrev-ref HEAD)"
|
currentBranch="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
user_unparsed="$(tea whoami)"
|
user="$(tea login list -o simple | cut -d" " -f4 | head -n1)"
|
||||||
user="$(echo "$user_unparsed" | tr -d '\n' | cut -f4 -d' ')"
|
|
||||||
tempRemoteBranch="$user-$currentBranch"
|
tempRemoteBranch="$user-$currentBranch"
|
||||||
root_dir=$(git rev-parse --show-toplevel)
|
|
||||||
|
|
||||||
|
nix fmt -- --fail-on-change
|
||||||
|
|
||||||
# Function to check if a remote exists
|
git log --reverse --pretty="format:%s%n%n%b%n%n" "$remoteName/$targetBranch..HEAD" > "$TMPDIR"/commit-msg
|
||||||
check_remote() {
|
|
||||||
if git remote get-url "$1" > /dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if the remote 'upstream' is defined
|
|
||||||
if ! check_remote "$remoteUpstream"; then
|
|
||||||
echo "Error: $remoteUpstream remote is not defined."
|
|
||||||
echo "Please fork the repository and add the $remoteUpstream remote."
|
|
||||||
echo "$ git remote add $remoteUpstream <upstream-url>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
treefmt --fail-on-change -C "$root_dir"
|
|
||||||
|
|
||||||
upstream_url=$(git remote get-url "$remoteUpstream")
|
|
||||||
set -x
|
|
||||||
git fetch "$remoteUpstream" && git rebase "$remoteUpstream"/main --autostash
|
|
||||||
set +x
|
|
||||||
repo=$(echo "$upstream_url" | sed -E 's#.*:([^/]+/[^.]+)\.git#\1#')
|
|
||||||
|
|
||||||
|
|
||||||
git log --reverse --pretty="format:%s%n%n%b%n%n" "$remoteUpstream/$targetBranch..HEAD" > "$TMPDIR"/commit-msg
|
|
||||||
|
|
||||||
|
|
||||||
$EDITOR "$TMPDIR"/commit-msg
|
$EDITOR "$TMPDIR"/commit-msg
|
||||||
|
|
||||||
@ -52,13 +23,11 @@ rest=$(echo "$COMMIT_MSG" | tail -n+2)
|
|||||||
if [[ "$firstLine" == "$rest" ]]; then
|
if [[ "$firstLine" == "$rest" ]]; then
|
||||||
rest=""
|
rest=""
|
||||||
fi
|
fi
|
||||||
|
git push --force -u "$remoteName" HEAD:refs/heads/"$tempRemoteBranch"
|
||||||
git push --force -u "$remoteFork" HEAD:refs/heads/"$tempRemoteBranch"
|
|
||||||
|
|
||||||
|
|
||||||
tea pr create \
|
tea pr create \
|
||||||
--repo "$repo" \
|
|
||||||
--head "$user:$tempRemoteBranch" \
|
|
||||||
--title "$firstLine" \
|
--title "$firstLine" \
|
||||||
--description "$rest" \
|
--description "$rest" \
|
||||||
|
--head "$tempRemoteBranch" \
|
||||||
|
--base "$targetBranch" \
|
||||||
"$@"
|
"$@"
|
||||||
|
@ -19,7 +19,6 @@ export default tseslint.config(
|
|||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
callees: ["cx"],
|
callees: ["cx"],
|
||||||
whitelist: ["material-icons"],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -5,9 +5,6 @@ const distPath = path.resolve(__dirname, "dist");
|
|||||||
const manifestPath = path.join(distPath, ".vite/manifest.json");
|
const manifestPath = path.join(distPath, ".vite/manifest.json");
|
||||||
const outputPath = path.join(distPath, "index.html");
|
const outputPath = path.join(distPath, "index.html");
|
||||||
|
|
||||||
const postcss = require("postcss");
|
|
||||||
const css_url = require("postcss-url");
|
|
||||||
|
|
||||||
fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
|
fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return console.error("Failed to read manifest:", err);
|
return console.error("Failed to read manifest:", err);
|
||||||
@ -28,48 +25,19 @@ fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => {
|
|||||||
|
|
||||||
// Add linked stylesheets
|
// Add linked stylesheets
|
||||||
assets.forEach((asset) => {
|
assets.forEach((asset) => {
|
||||||
// console.log(asset);
|
asset.css.forEach((cssEntry) => {
|
||||||
if (asset.src === "index.html") {
|
htmlContent += `\n <link rel="stylesheet" href="${cssEntry}">`;
|
||||||
asset.css.forEach((cssEntry) => {
|
});
|
||||||
// css to be processed
|
|
||||||
|
|
||||||
const css = fs.readFileSync(`dist/${cssEntry}`, "utf8");
|
|
||||||
|
|
||||||
// process css
|
|
||||||
postcss()
|
|
||||||
.use(
|
|
||||||
css_url({
|
|
||||||
url: (asset, dir) => {
|
|
||||||
const res = path.basename(asset.url);
|
|
||||||
console.log(`Rewriting CSS url(): ${asset.url} to ${res}`);
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.process(css, {
|
|
||||||
from: `dist/${cssEntry}`,
|
|
||||||
to: `dist/${cssEntry}`,
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
fs.writeFileSync(`dist/${cssEntry}`, result.css, "utf8");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extend the HTML content with the linked stylesheet
|
|
||||||
console.log(`Relinking html css stylesheet: ${cssEntry}`);
|
|
||||||
htmlContent += `\n <link rel="stylesheet" href="${cssEntry}">`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
htmlContent += `
|
htmlContent += `
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
`;
|
`;
|
||||||
// Add scripts
|
// Add scripts
|
||||||
assets.forEach((asset) => {
|
assets.forEach((asset) => {
|
||||||
if (asset.file.endsWith(".js")) {
|
if (asset.file.endsWith(".js")) {
|
||||||
console.log(`Relinking js script: ${asset.file}`);
|
|
||||||
htmlContent += `\n <script src="${asset.file}"></script>`;
|
htmlContent += `\n <script src="${asset.file}"></script>`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import { JSONSchemaFaker } from "json-schema-faker";
|
|
||||||
import { schema } from "./api/index";
|
|
||||||
import { OperationNames } from "./src/message";
|
|
||||||
|
|
||||||
const faker = JSONSchemaFaker;
|
|
||||||
|
|
||||||
faker.option({
|
|
||||||
alwaysFakeOptionals: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getFakeResponse = (method: OperationNames, data: any) => {
|
|
||||||
const fakeData = faker.generate(schema.properties[method].properties.return);
|
|
||||||
return fakeData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { getFakeResponse };
|
|
227
pkgs/webview-ui/app/package-lock.json
generated
227
pkgs/webview-ui/app/package-lock.json
generated
@ -9,12 +9,10 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"material-icons": "^1.13.12",
|
|
||||||
"solid-js": "^1.8.11"
|
"solid-js": "^1.8.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.3.0",
|
"@eslint/js": "^9.3.0",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
|
||||||
"@types/node": "^20.12.12",
|
"@types/node": "^20.12.12",
|
||||||
"@typescript-eslint/parser": "^7.10.0",
|
"@typescript-eslint/parser": "^7.10.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
@ -22,10 +20,8 @@
|
|||||||
"daisyui": "^4.11.1",
|
"daisyui": "^4.11.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-tailwindcss": "^3.17.0",
|
"eslint-plugin-tailwindcss": "^3.17.0",
|
||||||
"json-schema-faker": "^0.5.6",
|
|
||||||
"json-schema-to-ts": "^3.1.0",
|
"json-schema-to-ts": "^3.1.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"postcss-url": "^10.1.3",
|
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"solid-devtools": "^0.29.2",
|
"solid-devtools": "^0.29.2",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
@ -1486,34 +1482,6 @@
|
|||||||
"solid-js": "^1.6.12"
|
"solid-js": "^1.6.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/typography": {
|
|
||||||
"version": "0.5.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz",
|
|
||||||
"integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"lodash.castarray": "^4.4.0",
|
|
||||||
"lodash.isplainobject": "^4.0.6",
|
|
||||||
"lodash.merge": "^4.6.2",
|
|
||||||
"postcss-selector-parser": "6.0.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"tailwindcss": ">=3.0.0 || insiders"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
|
||||||
"version": "6.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
|
||||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"cssesc": "^3.0.0",
|
|
||||||
"util-deprecate": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@ -2022,12 +1990,6 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-me-maybe": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
@ -2221,12 +2183,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||||
},
|
},
|
||||||
"node_modules/cuint": {
|
|
||||||
"version": "0.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
|
|
||||||
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/culori": {
|
"node_modules/culori": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
|
||||||
@ -2667,19 +2623,6 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esprima": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"esparse": "bin/esparse.js",
|
|
||||||
"esvalidate": "bin/esvalidate.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/esquery": {
|
"node_modules/esquery": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
|
||||||
@ -2859,12 +2802,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/format-util": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz",
|
|
||||||
"integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||||
@ -3260,53 +3197,6 @@
|
|||||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/json-schema-faker": {
|
|
||||||
"version": "0.5.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.6.tgz",
|
|
||||||
"integrity": "sha512-u/cFC26/GDxh2vPiAC8B8xVvpXAW+QYtG2mijEbKrimCk8IHtiwQBjCE8TwvowdhALWq9IcdIWZ+/8ocXvdL3Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"json-schema-ref-parser": "^6.1.0",
|
|
||||||
"jsonpath-plus": "^7.2.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"jsf": "bin/gen.cjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-ref-parser": {
|
|
||||||
"version": "6.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz",
|
|
||||||
"integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==",
|
|
||||||
"deprecated": "Please switch to @apidevtools/json-schema-ref-parser",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"call-me-maybe": "^1.0.1",
|
|
||||||
"js-yaml": "^3.12.1",
|
|
||||||
"ono": "^4.0.11"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-ref-parser/node_modules/argparse": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"sprintf-js": "~1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-ref-parser/node_modules/js-yaml": {
|
|
||||||
"version": "3.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"argparse": "^1.0.7",
|
|
||||||
"esprima": "^4.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"js-yaml": "bin/js-yaml.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json-schema-to-ts": {
|
"node_modules/json-schema-to-ts": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.0.tgz",
|
||||||
@ -3344,15 +3234,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonpath-plus": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@ -3433,18 +3314,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash.castarray": {
|
|
||||||
"version": "4.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
|
||||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isplainobject": {
|
|
||||||
"version": "4.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
|
||||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -3497,11 +3366,6 @@
|
|||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/material-icons": {
|
|
||||||
"version": "1.13.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.12.tgz",
|
|
||||||
"integrity": "sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA=="
|
|
||||||
},
|
|
||||||
"node_modules/merge-anything": {
|
"node_modules/merge-anything": {
|
||||||
"version": "5.1.7",
|
"version": "5.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
|
||||||
@ -3687,15 +3551,6 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ono": {
|
|
||||||
"version": "4.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz",
|
|
||||||
"integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"format-util": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@ -4012,73 +3867,6 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-url": {
|
|
||||||
"version": "10.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz",
|
|
||||||
"integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"make-dir": "~3.1.0",
|
|
||||||
"mime": "~2.5.2",
|
|
||||||
"minimatch": "~3.0.4",
|
|
||||||
"xxhashjs": "~0.2.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/brace-expansion": {
|
|
||||||
"version": "1.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0",
|
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/make-dir": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"semver": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/mime": {
|
|
||||||
"version": "2.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
|
|
||||||
"integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"mime": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-url/node_modules/minimatch": {
|
|
||||||
"version": "3.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
|
|
||||||
"integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^1.1.7"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss-value-parser": {
|
"node_modules/postcss-value-parser": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
@ -4501,12 +4289,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sprintf-js": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
@ -5226,15 +5008,6 @@
|
|||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/xxhashjs": {
|
|
||||||
"version": "0.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
|
||||||
"integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"cuint": "^0.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
|
@ -13,28 +13,24 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.3.0",
|
"@eslint/js": "^9.3.0",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
|
||||||
"@types/node": "^20.12.12",
|
"@types/node": "^20.12.12",
|
||||||
"@typescript-eslint/parser": "^7.10.0",
|
"@typescript-eslint/parser": "^7.10.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"daisyui": "^4.11.1",
|
"daisyui": "^4.11.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-tailwindcss": "^3.17.0",
|
|
||||||
"json-schema-faker": "^0.5.6",
|
|
||||||
"json-schema-to-ts": "^3.1.0",
|
"json-schema-to-ts": "^3.1.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"postcss-url": "^10.1.3",
|
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"solid-devtools": "^0.29.2",
|
"solid-devtools": "^0.29.2",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"typescript-eslint": "^7.10.0",
|
"typescript-eslint": "^7.10.0",
|
||||||
"vite": "^5.0.11",
|
"vite": "^5.0.11",
|
||||||
"vite-plugin-solid": "^2.8.2"
|
"vite-plugin-solid": "^2.8.2",
|
||||||
|
"eslint-plugin-tailwindcss": "^3.17.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"material-icons": "^1.13.12",
|
|
||||||
"solid-js": "^1.8.11"
|
"solid-js": "^1.8.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,59 @@
|
|||||||
import { createSignal, type Component } from "solid-js";
|
import { Match, Switch, createSignal, type Component } from "solid-js";
|
||||||
import { CountProvider } from "./Config";
|
import { CountProvider } from "./Config";
|
||||||
|
// import { Nested } from "./nested";
|
||||||
import { Layout } from "./layout/layout";
|
import { Layout } from "./layout/layout";
|
||||||
import { Route, Router } from "./Routes";
|
import cx from "classnames";
|
||||||
|
import { Nested } from "./nested";
|
||||||
|
|
||||||
// Global state
|
type Route = "home" | "machines";
|
||||||
const [route, setRoute] = createSignal<Route>("machines");
|
|
||||||
|
|
||||||
export { route, setRoute };
|
|
||||||
|
|
||||||
const App: Component = () => {
|
const App: Component = () => {
|
||||||
|
const [route, setRoute] = createSignal<Route>("home");
|
||||||
return (
|
return (
|
||||||
<CountProvider>
|
<CountProvider>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Router route={route} />
|
<div class="col-span-1">
|
||||||
|
<div class={cx("text-zinc-500")}>Navigation</div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
onClick={() => setRoute("home")}
|
||||||
|
classList={{ "bg-blue-500": route() === "home" }}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{" "}
|
||||||
|
<button
|
||||||
|
onClick={() => setRoute("machines")}
|
||||||
|
classList={{ "bg-blue-500": route() === "machines" }}
|
||||||
|
>
|
||||||
|
Machines
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-7">
|
||||||
|
<div>{route()}</div>
|
||||||
|
<Switch fallback={<p>{route()} not found</p>}>
|
||||||
|
<Match when={route() == "home"}>
|
||||||
|
<Nested />
|
||||||
|
</Match>
|
||||||
|
<Match when={route() == "machines"}>
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
<div class="h-10 w-20 bg-red-500">red</div>
|
||||||
|
<div class="h-10 w-20 bg-green-500">green</div>
|
||||||
|
<div class="h-10 w-20 bg-blue-500">blue</div>
|
||||||
|
<div class="h-10 w-20 bg-yellow-500">yellow</div>
|
||||||
|
<div class="h-10 w-20 bg-purple-500">purple</div>
|
||||||
|
<div class="h-10 w-20 bg-cyan-500">cyan</div>
|
||||||
|
<div class="h-10 w-20 bg-pink-500">pink</div>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
</CountProvider>
|
</CountProvider>
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,7 @@ import { OperationResponse, pyApi } from "./message";
|
|||||||
export const makeCountContext = () => {
|
export const makeCountContext = () => {
|
||||||
const [machines, setMachines] = createSignal<
|
const [machines, setMachines] = createSignal<
|
||||||
OperationResponse<"list_machines">
|
OperationResponse<"list_machines">
|
||||||
>([]);
|
>({});
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
|
|
||||||
pyApi.list_machines.receive((machines) => {
|
pyApi.list_machines.receive((machines) => {
|
||||||
@ -41,7 +41,7 @@ export const CountContext = createContext<CountContextType>([
|
|||||||
loading: () => false,
|
loading: () => false,
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
machines: () => ([]),
|
machines: () => ({}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { Accessor, For, Match, Switch } from "solid-js";
|
|
||||||
import { MachineListView } from "./routes/machines/view";
|
|
||||||
import { colors } from "./routes/colors/view";
|
|
||||||
|
|
||||||
export type Route = keyof typeof routes;
|
|
||||||
|
|
||||||
export const routes = {
|
|
||||||
machines: {
|
|
||||||
child: MachineListView,
|
|
||||||
label: "Machines",
|
|
||||||
icon: "devices_other",
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
child: colors,
|
|
||||||
label: "Colors",
|
|
||||||
icon: "color_lens",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
interface RouterProps {
|
|
||||||
route: Accessor<Route>;
|
|
||||||
}
|
|
||||||
export const Router = (props: RouterProps) => {
|
|
||||||
const { route } = props;
|
|
||||||
return (
|
|
||||||
<Switch fallback={<p>route {route()} not found</p>}>
|
|
||||||
<For each={Object.entries(routes)}>
|
|
||||||
{([key, { child }]) => <Match when={route() === key}>{child}</Match>}
|
|
||||||
</For>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,33 +0,0 @@
|
|||||||
import { Accessor, For, Setter } from "solid-js";
|
|
||||||
import { Route, routes } from "./Routes";
|
|
||||||
|
|
||||||
interface SidebarProps {
|
|
||||||
route: Accessor<Route>;
|
|
||||||
setRoute: Setter<Route>;
|
|
||||||
}
|
|
||||||
export const Sidebar = (props: SidebarProps) => {
|
|
||||||
const { route, setRoute } = props;
|
|
||||||
return (
|
|
||||||
<aside class="min-h-screen w-80 bg-base-100">
|
|
||||||
<div class="sticky top-0 z-20 hidden items-center gap-2 bg-base-100/90 px-4 py-2 shadow-sm backdrop-blur lg:flex">
|
|
||||||
Icon
|
|
||||||
</div>
|
|
||||||
<ul class="menu px-4 py-0">
|
|
||||||
<For each={Object.entries(routes)}>
|
|
||||||
{([key, { label, icon }]) => (
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
onClick={() => setRoute(key as Route)}
|
|
||||||
class="group"
|
|
||||||
classList={{ "bg-blue-500": route() === key }}
|
|
||||||
>
|
|
||||||
<span class="material-icons">{icon}</span>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,5 +1,3 @@
|
|||||||
@import 'material-icons/iconfont/filled.css';
|
|
||||||
/* List of icons: https://marella.me/material-icons/demo/ */
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
@ -3,7 +3,7 @@ import { render } from "solid-js/web";
|
|||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { getFakeResponse } from "../mock";
|
|
||||||
const root = document.getElementById("app");
|
const root = document.getElementById("app");
|
||||||
|
|
||||||
window.clan = window.clan || {};
|
window.clan = window.clan || {};
|
||||||
@ -14,26 +14,5 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(import.meta.env);
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
console.log("Development mode");
|
|
||||||
window.webkit = window.webkit || {
|
|
||||||
messageHandlers: {
|
|
||||||
gtk: {
|
|
||||||
postMessage: (postMessage) => {
|
|
||||||
const { method, data } = postMessage;
|
|
||||||
console.debug("Python API call", { method, data });
|
|
||||||
setTimeout(() => {
|
|
||||||
const mock = getFakeResponse(method, data);
|
|
||||||
console.log("mock", { mock });
|
|
||||||
|
|
||||||
window.clan[method](JSON.stringify(mock));
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
render(() => <App />, root!);
|
render(() => <App />, root!);
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
export const Header = () => {
|
|
||||||
return (
|
|
||||||
<div class="navbar bg-base-100">
|
|
||||||
<div class="flex-none">
|
|
||||||
<button class="btn btn-square btn-ghost">
|
|
||||||
<span class="material-icons">home</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<a class="btn btn-ghost text-xl">Clan</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex-none">
|
|
||||||
<button class="btn btn-square btn-ghost">
|
|
||||||
<span class="material-icons">menu</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,35 +1,9 @@
|
|||||||
import { Component, JSXElement } from "solid-js";
|
import { Component, JSXElement } from "solid-js";
|
||||||
import { Header } from "./header";
|
|
||||||
import { Sidebar } from "../Sidebar";
|
|
||||||
import { route, setRoute } from "../App";
|
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: JSXElement;
|
children: JSXElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Layout: Component<LayoutProps> = (props) => {
|
export const Layout: Component<LayoutProps> = (props) => {
|
||||||
return (
|
return <div class="grid grid-cols-8">{props.children}</div>;
|
||||||
<>
|
|
||||||
<div class="drawer bg-base-100 lg:drawer-open">
|
|
||||||
<input
|
|
||||||
id="toplevel-drawer"
|
|
||||||
type="checkbox"
|
|
||||||
class="drawer-toggle hidden"
|
|
||||||
/>
|
|
||||||
<div class="drawer-content">
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
<div class="drawer-side z-40">
|
|
||||||
<label
|
|
||||||
for="toplevel-drawer"
|
|
||||||
aria-label="close sidebar"
|
|
||||||
class="drawer-overlay"
|
|
||||||
></label>
|
|
||||||
<Sidebar route={route} setRoute={setRoute} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
39
pkgs/webview-ui/app/src/nested.tsx
Normal file
39
pkgs/webview-ui/app/src/nested.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { For, Match, Switch, createEffect, type Component } from "solid-js";
|
||||||
|
import { useCountContext } from "./Config";
|
||||||
|
|
||||||
|
export const Nested: Component = () => {
|
||||||
|
const [{ machines, loading }, { getMachines }] = useCountContext();
|
||||||
|
|
||||||
|
const list = () => Object.values(machines());
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
console.log("1", list());
|
||||||
|
});
|
||||||
|
createEffect(() => {
|
||||||
|
console.log("2", machines());
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => getMachines()} class="btn btn-primary">
|
||||||
|
Get machines
|
||||||
|
</button>
|
||||||
|
<div></div>
|
||||||
|
<Switch>
|
||||||
|
<Match when={loading()}>Loading...</Match>
|
||||||
|
<Match when={!loading() && Object.entries(machines()).length === 0}>
|
||||||
|
No machines found
|
||||||
|
</Match>
|
||||||
|
<Match when={!loading()}>
|
||||||
|
<For each={list()}>
|
||||||
|
{(entry, i) => (
|
||||||
|
<li>
|
||||||
|
{i() + 1}: {entry.machine_name}{" "}
|
||||||
|
{entry.machine_description || "No description"}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,13 +0,0 @@
|
|||||||
export const colors = () => {
|
|
||||||
return (
|
|
||||||
<div class="grid grid-cols-3 gap-2">
|
|
||||||
<div class="h-10 w-20 bg-red-500">red</div>
|
|
||||||
<div class="h-10 w-20 bg-green-500">green</div>
|
|
||||||
<div class="h-10 w-20 bg-blue-500">blue</div>
|
|
||||||
<div class="h-10 w-20 bg-yellow-500">yellow</div>
|
|
||||||
<div class="h-10 w-20 bg-purple-500">purple</div>
|
|
||||||
<div class="h-10 w-20 bg-cyan-500">cyan</div>
|
|
||||||
<div class="h-10 w-20 bg-pink-500">pink</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,78 +0,0 @@
|
|||||||
import { For, Match, Switch, createEffect, type Component } from "solid-js";
|
|
||||||
import { useCountContext } from "../../Config";
|
|
||||||
import { route } from "@/src/App";
|
|
||||||
|
|
||||||
export const MachineListView: Component = () => {
|
|
||||||
const [{ machines, loading }, { getMachines }] = useCountContext();
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (route() === "machines") getMachines();
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div class="max-w-screen-lg">
|
|
||||||
<div class="tooltip" data-tip="Refresh ">
|
|
||||||
<button class="btn btn-ghost" onClick={() => getMachines()}>
|
|
||||||
<span class="material-icons ">refresh</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Switch>
|
|
||||||
<Match when={loading()}>
|
|
||||||
{/* Loading skeleton */}
|
|
||||||
<div>
|
|
||||||
<div class="card card-side m-2 bg-base-100 shadow-lg">
|
|
||||||
<figure class="pl-2">
|
|
||||||
<div class="skeleton size-12"></div>
|
|
||||||
</figure>
|
|
||||||
<div class="card-body">
|
|
||||||
<h2 class="card-title">
|
|
||||||
<div class="skeleton h-12 w-80"></div>
|
|
||||||
</h2>
|
|
||||||
<div class="skeleton h-8 w-72"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
<Match when={!loading() && machines().length === 0}>
|
|
||||||
No machines found
|
|
||||||
</Match>
|
|
||||||
<Match when={!loading()}>
|
|
||||||
<ul>
|
|
||||||
<For each={machines()}>
|
|
||||||
{(entry) => (
|
|
||||||
<li>
|
|
||||||
<div class="card card-side m-2 bg-base-100 shadow-lg">
|
|
||||||
<figure class="pl-2">
|
|
||||||
<span class="material-icons content-center text-5xl">
|
|
||||||
devices_other
|
|
||||||
</span>
|
|
||||||
</figure>
|
|
||||||
<div class="card-body flex-row justify-between">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<h2 class="card-title">{entry}</h2>
|
|
||||||
{/*
|
|
||||||
<p
|
|
||||||
classList={{
|
|
||||||
"text-gray-400": !entry.machine_description,
|
|
||||||
"text-gray-600": !!entry.machine_description,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{entry.machine_description || "No description"}
|
|
||||||
</p>
|
|
||||||
*/}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button class="btn btn-ghost">
|
|
||||||
<span class="material-icons">more_vert</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ul>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,11 +1,9 @@
|
|||||||
const typography = require("@tailwindcss/typography");
|
|
||||||
const daisyui = require("daisyui");
|
const daisyui = require("daisyui");
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [typography, daisyui],
|
plugins: [daisyui],
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
npmDeps = pkgs.fetchNpmDeps {
|
npmDeps = pkgs.fetchNpmDeps {
|
||||||
src = ./app;
|
src = ./app;
|
||||||
hash = "sha256-EadzSkIsV/cJtdxpIUvvpQhu5h3VyF8bLMpwfksNmWQ=";
|
hash = "sha256-E0++hupVKnDqmLk7ljoMcqcI4w+DIMlfRYRPbKUsT2c=";
|
||||||
};
|
};
|
||||||
# The prepack script runs the build script, which we'd rather do in the build phase.
|
# The prepack script runs the build script, which we'd rather do in the build phase.
|
||||||
npmPackFlags = [ "--ignore-scripts" ];
|
npmPackFlags = [ "--ignore-scripts" ];
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
# DO NOT DELETE
|
|
||||||
# This file is used by the clan cli to discover a clan flake
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "My Empty Clan",
|
|
||||||
"description": "some nice description",
|
|
||||||
"icon": "A path to the png"
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
# Clan configuration file
|
|
||||||
# TODO: This file is used as a template for the simple GUI workflow
|
|
||||||
{
|
|
||||||
inputs.clan-core.url = "git+https://git.clan.lol/clan/clan-core";
|
|
||||||
outputs =
|
|
||||||
{ self, clan-core, ... }:
|
|
||||||
let
|
|
||||||
clan = clan-core.lib.buildClan {
|
|
||||||
# This clan builds all its configuration out of the current directory
|
|
||||||
directory = self;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit (clan) nixosConfigurations clanInternals;
|
|
||||||
};
|
|
||||||
}
|
|
@ -5,10 +5,6 @@
|
|||||||
description = "Initialize a new clan flake";
|
description = "Initialize a new clan flake";
|
||||||
path = ./new-clan;
|
path = ./new-clan;
|
||||||
};
|
};
|
||||||
empty = {
|
|
||||||
description = "A empty clan template. Primarily for usage with the clan ui";
|
|
||||||
path = ./empty;
|
|
||||||
};
|
|
||||||
default = self.templates.new-clan;
|
default = self.templates.new-clan;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
description = "<Put your description here>";
|
description = "<Put your description here>";
|
||||||
|
|
||||||
inputs.clan-core.url = "git+https://git.clan.lol/clan/clan-core";
|
inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{ self, clan-core, ... }:
|
{ self, clan-core, ... }:
|
||||||
|
Loading…
Reference in New Issue
Block a user