From db1cf786ac9f982ddebe871c163479719de5f336 Mon Sep 17 00:00:00 2001 From: Maxwell Becker <49575486+mbecker20@users.noreply.github.com> Date: Sun, 23 Mar 2025 16:47:06 -0700 Subject: [PATCH] 1.17.0 (#248) * resolver v3 add new ec2 instance types clean up testing config document the libraries a bit clean up main update sysinfo and otel update client resolver 3.0 resolver v3 prog clean up gitignore implement periphery resolver v3 clean up core read api v3 more prog execute api missing apis compiling 1.16.13 work on more granular traits prog on crud * fmt * format * resource2 not really a benefit * axum to 0.8 * bump aws deps * just make it 1.17.0 * clean up cors * the komodo env file should be highest priority over additional files * add entities / message for test alerter * test alert implementation * rust 1.84.0 * axum update :param to {param} syntax * fix last axum updates * Add test alerter button * higher quality / colored icons * komodo-logo * simplify network stats * rename Test Alerter button * escape incoming sync backslashes (BREAKING) * clean up rust client websocket subscription * finish oidc comment * show update available stack table * update available deployment table * feature: use the repo path instead of name in GetLatestCommit (#282) * Update repo path handling in commit fetching - Changed `name` to `path` for repository identification. - Updated cache update function to use the new path field. - Improved error message for non-directory repo paths. * feat: use optional name and path in GetLatestCommit * review: don't use optional for name * review: use helper * review: remove redundant to_string() * 1.17.0-dev * feature: add post_deploy command (#288) * feature: add post_deploy command * review: do not run post_deploy if deploy failed * feature: interpolate secrets in custom alerter (#289) * feature: interpolate secrets in custom alerter * fix rust warning * review: sanitize errors * review: sanitize error message * Remove .git from remote_url (#299) Remove .git from remote_url Co-authored-by: Deon Marshall * mbecker20 -> moghtech * remove example from cargo toml workspace * dev-1 * fix login screen logo * more legible favicon * fix new compose images * docs new organization * typescript subscribe_to_update_websocket * add donate button docsite * add config save button in desktop sidebar navigator * add save button to config bottom * feature: allow docker image text to overflow in table (#301) * feature: allow docker image text to overflow in table * review: use break-words * wip: revert line break in css file * feature: update devcontainer node release * improve First Login docs * FIx PullStack re #302 and record docker compose config on stack deploy * requery alerts more often * improve update indicator style and also put on home screen * Add all services stack log * 1.17.0-dev-2 * fix api name chnage * choose which stack services to include in logs * feature: improve tables quick actions on mobile (#312) * feature: improve tables quick actions on mobile * review: fix gap4 * review: use flex-wrap * improve pull to git init on existing folder without .git * Fix unclear ComposePull log re #244 * use komodo_client.subscribe_to_update_websocket, and click indicator to reconnect * dev-3 * ServerTemplate description * improve WriteComposeContentsToHost instrument fields * give server stat charts labels * filters wrap * show provider usernames from config file * Stack: Fix git repo new compose file initialization * init sync file new repo * set branch on git init folder * ResourceSync: pending view toggle between "Execute" vs "Commit" sync direction * Improve resource sync Execute / Pending view selector * standardize running commands with interpolation / output sanitizations * fix all clippy lints * fix rand * lock certain users username / password, prevent demo creds from being changed. * revert to login screen whenever the call to check login fails * ResourceSync state resolution refinement * make sure parent directories exist whenever writing files * don't prune images if server not enabled * update most deps * update openidconnect dependency, and use reqwest rustls-tls-native-roots * dev-4 * resource sync only add escaping on toml between the """ * Stacks executions take list of services -- Auto update only redeploys services with update * auto update all service deploy option * dev-5 fix the stack service executions * clean up service_args * rust 1.85 * store sync edits on localstorage * stack edits on localstorage and show last deployed config * add yarn install to runfile * Fix actions when core on https * add update_available query parameter to filter for only stacks /deployments with available update * rust 2024 and fmt * rename test.compose.yaml to dev.compose.yaml, and update runfile * update .devcontainer / dev docs for updated runfile * use png in topbar logo, svg quality sometimes bad * OIDC: Support PKCE auth (secret optional) * update docs on OIDC and client secret * cycle the oidc client on interval to ensure up to date JWKs * add KOMODO_LOCK_LOGIN_CREDENTIALS_FOR in config doc * update deps * resource sync toggle resource / variable / user group inclusion independantly * use jsonwebtoken * improve variable value table overflow * colored tags * fix sync summary count ok * default new tag colors to grey * soften tag opacity a bit * Update config.tsx (#358) * isolate stacks / deployments with pending updates * update some deps * use Tooltip component instead of HoverCard for mobile compatibility * batch Build builds * link to typescript client in the intro * add link to main docs from client docs * doc tweaks * use moghtech/komodo-core and moghtech/komodo-periphery as images * remove unnecessary explicit network * periphery.compose.yaml * clean up periphery compose * add link to config * update periphery container compose config * rust 1.85.1 * update sync docs * 1.17.0 --------- Co-authored-by: unsync <1211591+unsync@users.noreply.github.com> Co-authored-by: Deon Marshall Co-authored-by: komodo Co-authored-by: wlatic --- .devcontainer/dev.compose.yaml | 2 +- .devcontainer/devcontainer.json | 2 +- .dockerignore | 14 - .gitignore | 8 +- Cargo.lock | 1226 ++- Cargo.toml | 85 +- bin/binaries.Dockerfile | 4 +- bin/cli/Cargo.toml | 1 + bin/cli/src/exec.rs | 7 + bin/core/Cargo.toml | 7 +- bin/core/aio.Dockerfile | 4 +- bin/core/multi-arch.Dockerfile | 8 +- bin/core/single-arch.Dockerfile | 6 +- bin/core/src/alert/discord.rs | 38 +- bin/core/src/alert/mod.rs | 122 +- bin/core/src/alert/slack.rs | 50 +- bin/core/src/api/auth.rs | 87 +- bin/core/src/api/execute/action.rs | 94 +- bin/core/src/api/execute/alerter.rs | 73 + bin/core/src/api/execute/build.rs | 106 +- bin/core/src/api/execute/deployment.rs | 192 +- bin/core/src/api/execute/mod.rs | 161 +- bin/core/src/api/execute/procedure.rs | 38 +- bin/core/src/api/execute/repo.rs | 148 +- bin/core/src/api/execute/server.rs | 427 +- bin/core/src/api/execute/server_template.rs | 104 +- bin/core/src/api/execute/stack.rs | 365 +- bin/core/src/api/execute/sync.rs | 378 +- bin/core/src/api/read/action.rs | 85 +- bin/core/src/api/read/alert.rs | 51 +- bin/core/src/api/read/alerter.rs | 74 +- bin/core/src/api/read/build.rs | 145 +- bin/core/src/api/read/builder.rs | 74 +- bin/core/src/api/read/deployment.rs | 184 +- bin/core/src/api/read/mod.rs | 161 +- bin/core/src/api/read/permission.rs | 52 +- bin/core/src/api/read/procedure.rs | 88 +- bin/core/src/api/read/provider.rs | 119 +- bin/core/src/api/read/repo.rs | 98 +- bin/core/src/api/read/search.rs | 82 - bin/core/src/api/read/server.rs | 523 +- bin/core/src/api/read/server_template.rs | 77 +- bin/core/src/api/read/stack.rs | 234 +- bin/core/src/api/read/sync.rs | 139 +- bin/core/src/api/read/tag.rs | 34 +- bin/core/src/api/read/toml.rs | 368 +- bin/core/src/api/read/update.rs | 90 +- bin/core/src/api/read/user.rs | 74 +- bin/core/src/api/read/user_group.rs | 46 +- bin/core/src/api/read/variable.rs | 35 +- bin/core/src/api/user.rs | 110 +- bin/core/src/api/write/action.rs | 90 +- bin/core/src/api/write/alerter.rs | 96 +- bin/core/src/api/write/build.rs | 210 +- bin/core/src/api/write/builder.rs | 96 +- bin/core/src/api/write/deployment.rs | 140 +- bin/core/src/api/write/description.rs | 81 +- bin/core/src/api/write/mod.rs | 43 +- bin/core/src/api/write/permissions.rs | 135 +- bin/core/src/api/write/procedure.rs | 88 +- bin/core/src/api/write/provider.rs | 223 +- bin/core/src/api/write/repo.rs | 213 +- bin/core/src/api/write/server.rs | 82 +- bin/core/src/api/write/server_template.rs | 96 +- bin/core/src/api/write/service_user.rs | 136 +- bin/core/src/api/write/stack.rs | 255 +- bin/core/src/api/write/sync.rs | 910 +- bin/core/src/api/write/tag.rs | 189 +- bin/core/src/api/write/user.rs | 102 +- bin/core/src/api/write/user_group.rs | 140 +- bin/core/src/api/write/variable.rs | 113 +- bin/core/src/auth/github/client.rs | 16 +- bin/core/src/auth/github/mod.rs | 8 +- bin/core/src/auth/google/client.rs | 33 +- bin/core/src/auth/google/mod.rs | 8 +- bin/core/src/auth/jwt.rs | 36 +- bin/core/src/auth/local.rs | 69 +- bin/core/src/auth/mod.rs | 11 +- bin/core/src/auth/oidc/client.rs | 123 +- bin/core/src/auth/oidc/mod.rs | 64 +- bin/core/src/cloud/aws/ec2.rs | 131 +- bin/core/src/cloud/hetzner/client.rs | 4 +- bin/core/src/cloud/hetzner/mod.rs | 2 +- bin/core/src/config.rs | 2 + bin/core/src/db.rs | 4 +- bin/core/src/helpers/action_state.rs | 4 +- bin/core/src/helpers/builder.rs | 21 +- bin/core/src/helpers/cache.rs | 12 +- bin/core/src/helpers/channel.rs | 10 +- bin/core/src/helpers/interpolate.rs | 2 +- bin/core/src/helpers/mod.rs | 85 +- bin/core/src/helpers/procedure.rs | 276 +- bin/core/src/helpers/prune.rs | 7 +- bin/core/src/helpers/query.rs | 12 +- bin/core/src/helpers/update.rs | 52 +- bin/core/src/listener/integrations/github.rs | 2 +- bin/core/src/listener/integrations/gitlab.rs | 2 +- bin/core/src/listener/mod.rs | 2 +- bin/core/src/listener/resources.rs | 81 +- bin/core/src/listener/router.rs | 20 +- bin/core/src/main.rs | 44 +- bin/core/src/monitor/alert/deployment.rs | 2 +- bin/core/src/monitor/alert/mod.rs | 4 +- bin/core/src/monitor/alert/server.rs | 10 +- bin/core/src/monitor/alert/stack.rs | 2 +- bin/core/src/monitor/lists.rs | 2 +- bin/core/src/monitor/mod.rs | 3 +- bin/core/src/monitor/record.rs | 3 +- bin/core/src/monitor/resources.rs | 21 +- bin/core/src/resource/action.rs | 4 +- bin/core/src/resource/alerter.rs | 2 +- bin/core/src/resource/build.rs | 4 +- bin/core/src/resource/builder.rs | 4 +- bin/core/src/resource/deployment.rs | 10 +- bin/core/src/resource/mod.rs | 68 +- bin/core/src/resource/procedure.rs | 18 +- bin/core/src/resource/refresh.rs | 54 +- bin/core/src/resource/repo.rs | 4 +- bin/core/src/resource/server.rs | 5 +- bin/core/src/resource/server_template.rs | 4 +- bin/core/src/resource/stack.rs | 25 +- bin/core/src/resource/sync.rs | 118 +- bin/core/src/stack/execute.rs | 73 +- bin/core/src/stack/mod.rs | 2 +- bin/core/src/stack/remote.rs | 2 +- bin/core/src/state.rs | 16 +- bin/core/src/sync/deploy.rs | 79 +- bin/core/src/sync/execute.rs | 82 +- bin/core/src/sync/file.rs | 9 +- bin/core/src/sync/mod.rs | 65 +- bin/core/src/sync/remote.rs | 16 +- bin/core/src/sync/resources.rs | 35 +- bin/core/src/sync/toml.rs | 10 +- bin/core/src/sync/user_groups.rs | 505 +- bin/core/src/sync/variables.rs | 121 +- bin/core/src/sync/view.rs | 3 +- bin/core/src/ts_client.rs | 7 +- bin/core/src/ws.rs | 28 +- bin/periphery/Cargo.toml | 3 +- bin/periphery/aio.Dockerfile | 4 +- bin/periphery/multi-arch.Dockerfile | 4 +- bin/periphery/single-arch.Dockerfile | 4 +- bin/periphery/src/api/build.rs | 197 +- bin/periphery/src/api/compose.rs | 254 +- bin/periphery/src/api/container.rs | 536 +- bin/periphery/src/api/deploy.rs | 87 +- bin/periphery/src/api/git.rs | 115 +- bin/periphery/src/api/image.rs | 76 +- bin/periphery/src/api/mod.rs | 171 +- bin/periphery/src/api/network.rs | 56 +- bin/periphery/src/api/stats.rs | 64 +- bin/periphery/src/api/volume.rs | 44 +- bin/periphery/src/compose.rs | 458 +- bin/periphery/src/config.rs | 27 - bin/periphery/src/docker.rs | 12 +- bin/periphery/src/helpers.rs | 47 +- bin/periphery/src/main.rs | 4 +- bin/periphery/src/router.rs | 31 +- bin/periphery/src/ssl.rs | 4 +- bin/periphery/src/stats.rs | 63 +- client/core/rs/Cargo.toml | 2 +- client/core/rs/README.md | 4 +- client/core/rs/src/api/auth.rs | 17 +- client/core/rs/src/api/execute/action.rs | 8 +- client/core/rs/src/api/execute/alerter.rs | 29 + client/core/rs/src/api/execute/build.rs | 11 +- client/core/rs/src/api/execute/deployment.rs | 34 +- client/core/rs/src/api/execute/mod.rs | 7 +- client/core/rs/src/api/execute/procedure.rs | 8 +- client/core/rs/src/api/execute/repo.rs | 23 +- client/core/rs/src/api/execute/server.rs | 67 +- .../rs/src/api/execute/server_template.rs | 5 +- client/core/rs/src/api/execute/stack.rs | 86 +- client/core/rs/src/api/execute/sync.rs | 7 +- client/core/rs/src/api/mod.rs | 4 +- client/core/rs/src/api/read/action.rs | 17 +- client/core/rs/src/api/read/alert.rs | 10 +- client/core/rs/src/api/read/alerter.rs | 14 +- client/core/rs/src/api/read/build.rs | 31 +- client/core/rs/src/api/read/builder.rs | 14 +- client/core/rs/src/api/read/deployment.rs | 34 +- client/core/rs/src/api/read/mod.rs | 21 +- client/core/rs/src/api/read/permission.rs | 13 +- client/core/rs/src/api/read/procedure.rs | 17 +- client/core/rs/src/api/read/provider.rs | 14 +- client/core/rs/src/api/read/repo.rs | 20 +- client/core/rs/src/api/read/search.rs | 46 - client/core/rs/src/api/read/server.rs | 79 +- .../core/rs/src/api/read/server_template.rs | 14 +- client/core/rs/src/api/read/stack.rs | 69 +- client/core/rs/src/api/read/sync.rs | 20 +- client/core/rs/src/api/read/tag.rs | 10 +- client/core/rs/src/api/read/toml.rs | 28 +- client/core/rs/src/api/read/update.rs | 10 +- client/core/rs/src/api/read/user.rs | 17 +- client/core/rs/src/api/read/user_group.rs | 8 +- client/core/rs/src/api/read/variable.rs | 8 +- client/core/rs/src/api/user.rs | 16 +- client/core/rs/src/api/write/action.rs | 28 +- client/core/rs/src/api/write/alerter.rs | 20 +- client/core/rs/src/api/write/api_key.rs | 10 +- client/core/rs/src/api/write/build.rs | 31 +- client/core/rs/src/api/write/builder.rs | 18 +- client/core/rs/src/api/write/deployment.rs | 23 +- client/core/rs/src/api/write/description.rs | 5 +- client/core/rs/src/api/write/permissions.rs | 16 +- client/core/rs/src/api/write/procedure.rs | 20 +- client/core/rs/src/api/write/provider.rs | 20 +- client/core/rs/src/api/write/repo.rs | 31 +- client/core/rs/src/api/write/server.rs | 21 +- .../core/rs/src/api/write/server_template.rs | 18 +- client/core/rs/src/api/write/stack.rs | 35 +- client/core/rs/src/api/write/sync.rs | 39 +- client/core/rs/src/api/write/tags.rs | 34 +- client/core/rs/src/api/write/user.rs | 19 +- client/core/rs/src/api/write/user_group.rs | 20 +- client/core/rs/src/api/write/variable.rs | 17 +- .../core/rs/src/deserializers/conversion.rs | 2 +- .../core/rs/src/deserializers/environment.rs | 2 +- .../rs/src/deserializers/file_contents.rs | 6 +- client/core/rs/src/deserializers/labels.rs | 2 +- .../rs/src/deserializers/maybe_string_i64.rs | 6 +- .../core/rs/src/deserializers/string_list.rs | 2 +- .../src/deserializers/term_signal_labels.rs | 2 +- client/core/rs/src/entities/action.rs | 2 +- client/core/rs/src/entities/alert.rs | 15 +- client/core/rs/src/entities/alerter.rs | 5 +- client/core/rs/src/entities/build.rs | 20 +- client/core/rs/src/entities/builder.rs | 2 +- client/core/rs/src/entities/config/core.rs | 20 +- client/core/rs/src/entities/config/mod.rs | 6 +- .../core/rs/src/entities/config/periphery.rs | 6 +- client/core/rs/src/entities/deployment.rs | 14 +- .../core/rs/src/entities/docker/container.rs | 2 +- client/core/rs/src/entities/mod.rs | 11 +- client/core/rs/src/entities/procedure.rs | 2 +- client/core/rs/src/entities/repo.rs | 5 +- client/core/rs/src/entities/resource.rs | 6 +- client/core/rs/src/entities/server.rs | 2 +- .../rs/src/entities/server_template/aws.rs | 2 +- .../src/entities/server_template/hetzner.rs | 2 +- .../rs/src/entities/server_template/mod.rs | 4 +- client/core/rs/src/entities/stack.rs | 40 +- client/core/rs/src/entities/stats.rs | 28 +- client/core/rs/src/entities/sync.rs | 28 +- client/core/rs/src/entities/tag.rs | 82 + client/core/rs/src/entities/toml.rs | 3 +- client/core/rs/src/entities/update.rs | 2 +- client/core/rs/src/entities/user.rs | 4 +- client/core/rs/src/entities/user_group.rs | 2 +- client/core/rs/src/lib.rs | 2 +- client/core/rs/src/request.rs | 91 +- client/core/rs/src/ws.rs | 36 +- client/core/ts/README.md | 2 +- client/core/ts/package.json | 2 +- client/core/ts/src/lib.ts | 92 +- client/core/ts/src/responses.ts | 11 +- client/core/ts/src/types.ts | 317 +- client/periphery/rs/src/api/build.rs | 11 +- client/periphery/rs/src/api/compose.rs | 61 +- client/periphery/rs/src/api/container.rs | 61 +- client/periphery/rs/src/api/git.rs | 23 +- client/periphery/rs/src/api/image.rs | 17 +- client/periphery/rs/src/api/mod.rs | 28 +- client/periphery/rs/src/api/network.rs | 14 +- client/periphery/rs/src/api/stats.rs | 15 +- client/periphery/rs/src/api/volume.rs | 11 +- client/periphery/rs/src/lib.rs | 21 +- compose/compose.env | 9 +- compose/mongo.compose.yaml | 19 +- compose/periphery.compose.yaml | 39 + compose/postgres.compose.yaml | 21 +- compose/sqlite.compose.yaml | 19 +- config/core.config.toml | 21 +- config/periphery.config.toml | 2 +- test.compose.yaml => dev.compose.yaml | 2 +- docsite/docs/build-images/builders.md | 2 +- docsite/docs/build-images/configuration.md | 2 +- docsite/docs/connect-servers.mdx | 81 +- docsite/docs/development.md | 12 +- docsite/docs/intro.md | 2 +- docsite/docs/setup/advanced.mdx | 6 +- docsite/docs/setup/index.mdx | 6 +- docsite/docs/setup/mongo.mdx | 4 +- docsite/docs/setup/postgres.mdx | 4 +- docsite/docs/setup/sqlite.mdx | 4 +- docsite/docs/sync-resources.md | 18 +- docsite/docs/version-upgrades.md | 4 +- docsite/docusaurus.config.ts | 19 +- docsite/src/components/ComposeAndEnv.tsx | 8 +- docsite/src/pages/index.tsx | 4 +- docsite/static/img/favicon.ico | Bin 15406 -> 15086 bytes docsite/static/img/komodo-512x512.png | Bin 0 -> 158194 bytes docsite/static/img/logo512.png | Bin 33400 -> 0 bytes docsite/yarn.lock | 9001 +++++++++++++++++ example/update_logger/src/main.rs | 2 +- frontend/Dockerfile | 2 +- frontend/index.html | 7 +- frontend/package.json | 91 +- frontend/public/apple-touch-icon.png | Bin 15823 -> 33199 bytes frontend/public/client/lib.d.ts | 21 +- frontend/public/client/lib.js | 65 + frontend/public/client/responses.d.ts | 7 +- frontend/public/client/types.d.ts | 323 +- frontend/public/client/types.js | 58 + frontend/public/favicon-16x16.png | Bin 869 -> 0 bytes frontend/public/favicon-32x32.png | Bin 2120 -> 0 bytes frontend/public/favicon-96x96.png | Bin 0 -> 2457 bytes frontend/public/favicon.ico | Bin 15406 -> 15086 bytes frontend/public/favicon.svg | 71 + frontend/public/komodo-192x192.png | Bin 0 -> 36895 bytes frontend/public/komodo-2q2code.png | Bin 0 -> 197511 bytes frontend/public/komodo-512x512.png | Bin 0 -> 158194 bytes frontend/public/komodo-logo.svg | 1505 +++ frontend/public/logo192.png | Bin 17466 -> 0 bytes frontend/public/logo512.png | Bin 33400 -> 0 bytes frontend/public/manifest.json | 26 +- frontend/public/monitor-circle.png | Bin 145517 -> 0 bytes frontend/src/components/alert/topbar.tsx | 2 +- frontend/src/components/config/index.tsx | 111 +- frontend/src/components/config/util.tsx | 5 +- frontend/src/components/export.tsx | 6 +- frontend/src/components/group-actions.tsx | 4 +- frontend/src/components/log.tsx | 171 +- frontend/src/components/monaco.tsx | 4 +- .../components/resources/action/config.tsx | 2 +- .../resources/alerter/config/index.tsx | 2 +- .../components/resources/alerter/index.tsx | 25 +- .../components/resources/build/actions.tsx | 11 +- .../src/components/resources/build/config.tsx | 2 +- .../src/components/resources/build/index.tsx | 14 +- .../components/resources/builder/config.tsx | 6 +- .../resources/deployment/config/index.tsx | 2 +- .../components/resources/deployment/index.tsx | 76 +- .../components/resources/deployment/log.tsx | 140 +- .../components/resources/deployment/table.tsx | 7 +- .../components/resources/procedure/config.tsx | 17 +- .../src/components/resources/repo/config.tsx | 2 +- .../src/components/resources/repo/index.tsx | 38 +- .../resources/resource-sync/actions.tsx | 17 +- .../resources/resource-sync/config.tsx | 46 +- .../resources/resource-sync/index.tsx | 28 +- .../resources/resource-sync/info.tsx | 8 +- .../resources/resource-sync/pending.tsx | 279 +- .../resources/server-template/config/aws.tsx | 2 +- .../server-template/config/hetzner.tsx | 2 +- .../resources/server-template/index.tsx | 2 +- .../components/resources/server/config.tsx | 2 +- .../src/components/resources/server/hooks.ts | 7 - .../resources/server/stat-chart.tsx | 79 +- .../src/components/resources/server/stats.tsx | 221 +- .../components/resources/stack/actions.tsx | 26 +- .../src/components/resources/stack/config.tsx | 31 +- .../src/components/resources/stack/index.tsx | 166 +- .../src/components/resources/stack/info.tsx | 45 +- .../src/components/resources/stack/log.tsx | 166 + .../src/components/resources/stack/table.tsx | 11 +- frontend/src/components/tags/index.tsx | 87 +- frontend/src/components/topbar.tsx | 4 +- .../users/resource-type-permissions.tsx | 5 +- frontend/src/components/util.tsx | 2 +- frontend/src/lib/color.ts | 145 +- frontend/src/lib/hooks.ts | 28 +- frontend/src/lib/socket.tsx | 209 +- frontend/src/main.tsx | 2 +- frontend/src/pages/home/dashboard.tsx | 6 +- frontend/src/pages/login.tsx | 15 +- frontend/src/pages/resources.tsx | 27 +- .../src/pages/server-info/container/log.tsx | 136 +- frontend/src/pages/settings/providers.tsx | 48 +- frontend/src/pages/settings/tags.tsx | 131 +- frontend/src/pages/settings/variables.tsx | 6 +- frontend/src/pages/stack-service/log.tsx | 148 +- frontend/src/router.tsx | 4 +- frontend/src/ui/tooltip.tsx | 166 + frontend/tailwind.config.js | 90 + frontend/yarn.lock | 2531 ++--- lib/cache/README.md | 5 + lib/cache/src/lib.rs | 11 +- lib/command/Cargo.toml | 5 +- lib/command/README.md | 3 + lib/command/src/lib.rs | 88 +- lib/environment_file/README.md | 8 + lib/formatting/README.md | 3 + lib/git/README.md | 3 + lib/git/src/clone.rs | 180 +- lib/git/src/commit.rs | 65 +- lib/git/src/environment.rs | 160 +- lib/git/src/init.rs | 61 + lib/git/src/lib.rs | 28 +- lib/git/src/pull.rs | 227 +- lib/git/src/pull_or_clone.rs | 4 +- lib/logger/README.md | 3 + lib/logger/src/lib.rs | 2 +- lib/logger/src/otel.rs | 52 +- lib/response/Cargo.toml | 15 + lib/response/src/lib.rs | 76 + readme.md | 36 +- runfile.toml | 74 +- scripts/readme.md | 6 +- scripts/setup-periphery.py | 6 +- test.core.config.toml | 43 - test.periphery.config.toml | 24 - 403 files changed, 24493 insertions(+), 11138 deletions(-) delete mode 100644 .dockerignore create mode 100644 bin/core/src/api/execute/alerter.rs delete mode 100644 bin/core/src/api/read/search.rs create mode 100644 client/core/rs/src/api/execute/alerter.rs delete mode 100644 client/core/rs/src/api/read/search.rs create mode 100644 compose/periphery.compose.yaml rename test.compose.yaml => dev.compose.yaml (96%) create mode 100644 docsite/static/img/komodo-512x512.png delete mode 100644 docsite/static/img/logo512.png create mode 100644 docsite/yarn.lock delete mode 100644 frontend/public/favicon-16x16.png delete mode 100644 frontend/public/favicon-32x32.png create mode 100644 frontend/public/favicon-96x96.png create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/public/komodo-192x192.png create mode 100644 frontend/public/komodo-2q2code.png create mode 100644 frontend/public/komodo-512x512.png create mode 100644 frontend/public/komodo-logo.svg delete mode 100644 frontend/public/logo192.png delete mode 100644 frontend/public/logo512.png delete mode 100644 frontend/public/monitor-circle.png create mode 100644 frontend/src/components/resources/stack/log.tsx create mode 100644 frontend/src/ui/tooltip.tsx create mode 100644 lib/cache/README.md create mode 100644 lib/command/README.md create mode 100644 lib/environment_file/README.md create mode 100644 lib/formatting/README.md create mode 100644 lib/git/README.md create mode 100644 lib/git/src/init.rs create mode 100644 lib/logger/README.md create mode 100644 lib/response/Cargo.toml create mode 100644 lib/response/src/lib.rs delete mode 100644 test.core.config.toml delete mode 100644 test.periphery.config.toml diff --git a/.devcontainer/dev.compose.yaml b/.devcontainer/dev.compose.yaml index d9c5c9e16..a3d91f3ef 100644 --- a/.devcontainer/dev.compose.yaml +++ b/.devcontainer/dev.compose.yaml @@ -23,7 +23,7 @@ services: db: extends: - file: ../test.compose.yaml + file: ../dev.compose.yaml service: ferretdb volumes: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d2446671e..93d2f6718 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,7 @@ // Features to add to the dev container. More info: https://containers.dev/features. "features": { "ghcr.io/devcontainers/features/node:1": { - "version": "18.18.0" + "version": "20.12.2" }, "ghcr.io/devcontainers-community/features/deno:1": { diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index c4899da37..000000000 --- a/.dockerignore +++ /dev/null @@ -1,14 +0,0 @@ -/target -readme.md -typeshare.toml -LICENSE -*.code-workspace - -*/node_modules -*/dist - -creds.toml -.core-repos -.repos -.stacks -.ssl \ No newline at end of file diff --git a/.gitignore b/.gitignore index 80333d821..d49f1f2d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,13 @@ target -/frontend/build -/lib/ts_client/build node_modules dist .env .env.development .DS_Store +.idea + +/frontend/build +/lib/ts_client/build creds.toml -.komodo \ No newline at end of file +.dev diff --git a/Cargo.lock b/Cargo.lock index ff5ef93ac..68d0e944a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -24,10 +24,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check 0.9.5", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arc-swap" @@ -126,28 +126,6 @@ dependencies = [ "syn 2.0.89", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.89", -] - [[package]] name = "async-trait" version = "0.1.83" @@ -167,8 +145,8 @@ checksum = "4ecbb56dce3eb772cfb3f5a29e802838b3ba1fdb06303d4f1ff54e0d278dc876" dependencies = [ "serde", "serde_derive", - "strum", - "strum_macros", + "strum 0.26.3", + "strum_macros 0.26.4", "tokio", ] @@ -186,9 +164,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.5.10" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" +checksum = "6a84fe2c5e9965fba0fbc2001db252f1d57527d82a905cca85127df227bca748" dependencies = [ "aws-credential-types", "aws-runtime", @@ -205,8 +183,8 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 0.2.12", - "ring 0.17.8", + "http 1.1.0", + "ring", "time", "tokio", "tracing", @@ -216,9 +194,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +checksum = "4471bef4c22a06d2c7a1b6492493d3fdf24a805323109d6874f9c94d5906ac14" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -228,9 +206,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47bb8cc16b669d267eeccf585aea077d0882f4777b1c1f740217885d6e6e5a3" +checksum = "f409eb70b561706bf8abba8ca9c112729c481595893fd06a2dd9af8ed8441148" dependencies = [ "aws-lc-sys", "paste", @@ -239,24 +217,23 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.23.1" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2101df3813227bbaaaa0b04cd61c534c7954b22bd68d399b440be937dc63ff7" +checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", - "libc", "paste", ] [[package]] name = "aws-runtime" -version = "1.4.3" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +checksum = "0aff45ffe35196e593ea3b9dd65b320e51e2dda95aff4390bc459e461d09c6ad" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -279,9 +256,9 @@ dependencies = [ [[package]] name = "aws-sdk-ec2" -version = "1.93.0" +version = "1.118.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9987ec31fe6dd2d81f2a534de8a752ac9dfc4b87305502807424296e6371af" +checksum = "5bc420e1bdcddf5081cb7e93ca6a524f7a29c6ce05913af574ab30c828472f0d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -303,9 +280,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.49.0" +version = "1.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" +checksum = "1d5330ad4e8a1ff49e9f26b738611caa72b105c41d41733801d1a36e8f9de936" dependencies = [ "aws-credential-types", "aws-runtime", @@ -325,9 +302,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.50.0" +version = "1.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +checksum = "7956b1a85d49082347a7d17daa2e32df191f3e23c03d47294b99f95413026a78" dependencies = [ "aws-credential-types", "aws-runtime", @@ -347,9 +324,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.50.0" +version = "1.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" +checksum = "065c533fbe6f84962af33fcf02b0350b7c1f79285baab5924615d2be3b232855" dependencies = [ "aws-credential-types", "aws-runtime", @@ -370,9 +347,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.5" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" +checksum = "69d03c3c05ff80d54ff860fe38c726f6f494c639ae975203a101335f223386db" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -393,9 +370,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.1" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" dependencies = [ "futures-util", "pin-project-lite", @@ -404,9 +381,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.60.11" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +checksum = "c5949124d11e538ca21142d1fba61ab0a2a2c1bc3ed323cdb3e4b878bfb83166" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -414,6 +391,7 @@ dependencies = [ "bytes-utils", "futures-core", "http 0.2.12", + "http 1.1.0", "http-body 0.4.6", "once_cell", "percent-encoding", @@ -423,10 +401,38 @@ dependencies = [ ] [[package]] -name = "aws-smithy-json" -version = "0.60.7" +name = "aws-smithy-http-client" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +checksum = "0497ef5d53065b7cd6a35e9c1654bd1fefeae5c52900d91d1b188b0af0f29324" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.4.7", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "hyper 0.14.31", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.3", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.25", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower 0.5.2", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" dependencies = [ "aws-smithy-types", ] @@ -443,36 +449,33 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" +checksum = "f6328865e36c6fd970094ead6b05efd047d3a80ec5fc3be5e743910da9f2ebf8" dependencies = [ "aws-smithy-async", "aws-smithy-http", + "aws-smithy-http-client", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", "fastrand", - "h2 0.3.26", "http 0.2.12", + "http 1.1.0", "http-body 0.4.6", "http-body 1.0.1", - "httparse", - "hyper 0.14.31", - "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", - "rustls 0.21.12", "tokio", "tracing", ] [[package]] name = "aws-smithy-runtime-api" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +checksum = "3da37cf5d57011cb1753456518ec76e31691f1f474b73934a284eb2a1c76510f" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -487,9 +490,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.9" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" +checksum = "836155caafba616c0ff9b07944324785de2ab016141c3550bd1c07882f8cee8f" dependencies = [ "base64-simd", "bytes", @@ -522,9 +525,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.3" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +checksum = "3873f8deed8927ce8d04487630dc9ff73193bab64742a61d050e57a68dec4125" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -536,20 +539,20 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.9" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" dependencies = [ - "async-trait", "axum-core", "axum-macros", "base64 0.22.1", "bytes", + "form_urlencoded", "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-util", "itoa", "matchit", @@ -563,10 +566,10 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-tungstenite", - "tower 0.5.1", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -574,11 +577,10 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" dependencies = [ - "async-trait", "bytes", "futures-util", "http 1.1.0", @@ -587,7 +589,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -595,33 +597,31 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b" dependencies = [ "axum", "axum-core", "bytes", - "fastrand", "futures-util", "headers", "http 1.1.0", "http-body 1.0.1", "http-body-util", "mime", - "multer", "pin-project-lite", "serde", - "tower 0.5.1", + "tower 0.5.2", "tower-layer", "tower-service", ] [[package]] name = "axum-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", @@ -630,25 +630,23 @@ dependencies = [ [[package]] name = "axum-server" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56bac90848f6a9393ac03c63c640925c4b7c8ca21654de40d53f55964667c7d8" +checksum = "495c05f60d6df0093e8fb6e74aa5846a0ad06abaf96d76166283720bf740f8ab" dependencies = [ "arc-swap", "bytes", - "futures-util", + "fs-err", "http 1.1.0", "http-body 1.0.1", - "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-util", "pin-project-lite", - "rustls 0.23.18", + "rustls 0.23.25", "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", - "tower 0.4.13", "tower-service", ] @@ -709,13 +707,13 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bcrypt" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b1866ecef4f2d06a0bb77880015fdf2b89e25a1c2e5addacb87e459c86dc67e" +checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f" dependencies = [ "base64 0.22.1", "blowfish", - "getrandom", + "getrandom 0.3.1", "subtle", "zeroize", ] @@ -800,7 +798,7 @@ dependencies = [ "hex", "http 1.1.0", "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-named-pipe", "hyper-util", "hyperlocal", @@ -811,7 +809,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror 2.0.3", + "thiserror 2.0.12", "tokio", "tokio-util", "tower-service", @@ -832,18 +830,20 @@ dependencies = [ [[package]] name = "bson" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068208f2b6fcfa27a7f1ee37488d2bb8ba2640f68f5475d08e1d9130696aba59" +checksum = "af8113ff51309e2779e8785a246c10fb783e8c2452f134d6257fd71cc03ccd6c" dependencies = [ "ahash", - "base64 0.13.1", + "base64 0.22.1", "bitvec", + "getrandom 0.2.15", + "getrandom 0.3.1", "hex", "indexmap 2.6.0", "js-sys", "once_cell", - "rand", + "rand 0.9.0", "serde", "serde_bytes", "serde_json", @@ -865,9 +865,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" dependencies = [ "serde", ] @@ -884,7 +884,7 @@ dependencies = [ [[package]] name = "cache" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", "tokio", @@ -960,9 +960,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -970,9 +970,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -982,9 +982,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -994,9 +994,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmake" @@ -1015,20 +1015,22 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "command" -version = "1.16.12" +version = "1.17.0" dependencies = [ + "anyhow", + "formatting", "komodo_client", "run_command", + "svi", ] [[package]] @@ -1037,6 +1039,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -1103,6 +1125,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1110,7 +1138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1229,14 +1257,25 @@ dependencies = [ ] [[package]] -name = "derivative" -version = "2.2.0" +name = "derive-syn-parse" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.89", +] + +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -1435,7 +1474,7 @@ dependencies = [ "hkdf", "pem-rfc7468", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1464,9 +1503,9 @@ dependencies = [ [[package]] name = "environment_file" -version = "1.16.12" +version = "1.17.0" dependencies = [ - "thiserror 2.0.3", + "thiserror 2.0.12", ] [[package]] @@ -1496,9 +1535,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" @@ -1506,7 +1545,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1533,11 +1572,21 @@ dependencies = [ [[package]] name = "formatting" -version = "1.16.12" +version = "1.17.0" dependencies = [ "serror", ] +[[package]] +name = "fs-err" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa" +dependencies = [ + "autocfg", + "tokio", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -1659,10 +1708,24 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "wasm-bindgen", + "windows-targets 0.52.6", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1671,7 +1734,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", "cache", @@ -1697,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1810,9 +1873,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-proto" -version = "0.24.1" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" dependencies = [ "async-trait", "cfg-if", @@ -1821,10 +1884,10 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.4.0", + "idna", "ipnet", "once_cell", - "rand", + "rand 0.8.5", "thiserror 1.0.69", "tinyvec", "tokio", @@ -1834,9 +1897,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.1" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" dependencies = [ "cfg-if", "futures-util", @@ -1845,7 +1908,7 @@ dependencies = [ "lru-cache", "once_cell", "parking_lot 0.12.3", - "rand", + "rand 0.8.5", "resolv-conf", "smallvec", "thiserror 1.0.69", @@ -1991,9 +2054,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -2017,7 +2080,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", @@ -2049,9 +2112,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-util", - "rustls 0.23.18", + "rustls 0.23.25", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -2066,7 +2129,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.5.1", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", @@ -2084,7 +2147,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.1", + "hyper 1.6.0", "pin-project-lite", "socket2", "tokio", @@ -2100,7 +2163,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" dependencies = [ "hex", "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-util", "pin-project-lite", "tokio", @@ -2254,16 +2317,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.0.3" @@ -2396,45 +2449,32 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "jsonwebtoken" -version = "8.3.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", + "js-sys", "pem", - "ring 0.16.20", + "ring", "serde", "serde_json", "simple_asn1", ] -[[package]] -name = "jwt" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" -dependencies = [ - "base64 0.13.1", - "crypto-common", - "digest", - "hmac", - "serde", - "serde_json", - "sha2", -] - [[package]] name = "komodo_cli" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", "clap", @@ -2450,7 +2490,7 @@ dependencies = [ [[package]] name = "komodo_client" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", "async_timing_util", @@ -2464,13 +2504,13 @@ dependencies = [ "futures", "mongo_indexed", "partial_derive2", - "reqwest 0.12.9", + "reqwest", "resolver_api", "serde", "serde_json", "serror", - "strum", - "thiserror 2.0.3", + "strum 0.27.1", + "thiserror 2.0.12", "tokio", "tokio-tungstenite", "tokio-util", @@ -2481,11 +2521,13 @@ dependencies = [ [[package]] name = "komodo_core" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", + "arc-swap", "async_timing_util", "aws-config", + "aws-credential-types", "aws-sdk-ec2", "axum", "axum-extra", @@ -2504,7 +2546,7 @@ dependencies = [ "git", "hex", "hmac", - "jwt", + "jsonwebtoken", "komodo_client", "logger", "merge_config_files", @@ -2516,11 +2558,12 @@ dependencies = [ "ordered_hash_map", "partial_derive2", "periphery_client", - "rand", + "rand 0.9.0", "regex", - "reqwest 0.12.9", + "reqwest", "resolver_api", - "rustls 0.23.18", + "response", + "rustls 0.23.25", "serde", "serde_json", "serde_yaml", @@ -2542,12 +2585,11 @@ dependencies = [ [[package]] name = "komodo_periphery" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", "async_timing_util", "axum", - "axum-extra", "axum-server", "bollard", "cache", @@ -2565,10 +2607,12 @@ dependencies = [ "merge_config_files", "periphery_client", "resolver_api", + "response", "run_command", - "rustls 0.23.18", + "rustls 0.23.25", "serde", "serde_json", + "serde_yaml", "serror", "svi", "sysinfo", @@ -2583,7 +2627,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -2594,9 +2638,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.166" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -2653,7 +2697,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", "komodo_client", @@ -2675,6 +2719,54 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "macro_magic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "macro_magic_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150" +dependencies = [ + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "macro_magic_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.89", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -2683,9 +2775,9 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" @@ -2754,7 +2846,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2783,16 +2875,16 @@ dependencies = [ [[package]] name = "mongodb" -version = "3.1.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c857d71f918b38221baf2fdff7207fec9984b4504901544772b1edf0302d669f" +checksum = "9a93560fa3ec754ed9aa0954ae8307c5997150dbba7aa735173b514660088475" dependencies = [ "async-trait", "base64 0.13.1", "bitflags 1.3.2", "bson", "chrono", - "derivative", + "derive-where", "derive_more", "futures-core", "futures-executor", @@ -2802,12 +2894,13 @@ dependencies = [ "hickory-proto", "hickory-resolver", "hmac", + "macro_magic", "md-5", "mongodb-internal-macros", "once_cell", "pbkdf2", "percent-encoding", - "rand", + "rand 0.8.5", "rustc_version_runtime", "rustls 0.21.12", "rustls-pemfile 1.0.4", @@ -2831,37 +2924,21 @@ dependencies = [ [[package]] name = "mongodb-internal-macros" -version = "3.1.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6dbc533e93429a71c44a14c04547ac783b56d3f22e6c4f12b1b994cf93844e" +checksum = "79b3dace6c4f33db61d492b3d3b02f4358687a1eb59457ffef6f6cfe461cdb54" dependencies = [ + "macro_magic", "proc-macro2", "quote", "syn 2.0.89", ] -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 1.1.0", - "httparse", - "memchr", - "mime", - "spin 0.9.8", - "version_check 0.9.5", -] - [[package]] name = "mungos" -version = "1.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b0a35310ff16234743e7bc2292c19a537a492c1d7449b1e0254a9ec7db82c6" +checksum = "ef5f7c37daa675e08ac7db72bd9aea2dfe0762845250a650d8676efa2ad92eee" dependencies = [ "anyhow", "envy", @@ -2941,7 +3018,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -2984,16 +3061,16 @@ dependencies = [ [[package]] name = "oauth2" -version = "4.4.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "chrono", - "getrandom", - "http 0.2.12", - "rand", - "reqwest 0.11.27", + "getrandom 0.2.15", + "http 1.1.0", + "rand 0.8.5", + "reqwest", "serde", "serde_json", "serde_path_to_error", @@ -3013,27 +3090,27 @@ dependencies = [ [[package]] name = "octorust" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a776c05d0cdd02480c12cf1484e0f3bf610eab717ba6fb2c51449b60c7a88f" +checksum = "2c488b641cf652f023d371f6d472191bf3b3fd8075a11f274e95d4fe6e5e3878" dependencies = [ "async-recursion", "async-trait", "bytes", "chrono", - "http 0.2.12", + "http 1.1.0", "jsonwebtoken", "log", "mime", "parse_link_header", "pem", "percent-encoding", - "reqwest 0.11.27", + "reqwest", "reqwest-conditional-middleware", "reqwest-middleware", "reqwest-retry", "reqwest-tracing", - "ring 0.16.20", + "ring", "schemars", "serde", "serde_json", @@ -3052,26 +3129,25 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openidconnect" -version = "3.5.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +checksum = "6dd50d4a5e7730e754f94d977efe61f611aadd3131f6a2b464f6e3a4167e8ef7" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "chrono", "dyn-clone", "ed25519-dalek", "hmac", - "http 0.2.12", + "http 1.1.0", "itertools 0.10.5", "log", "oauth2", "p256", "p384", - "rand", + "rand 0.8.5", "rsa", "serde", "serde-value", - "serde_derive", "serde_json", "serde_path_to_error", "serde_plain", @@ -3090,38 +3166,38 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3cebff57f7dbd1255b44d8bddc2cebeb0ea677dbaa2e25a3070a91b318f660" +checksum = "768ee97dc5cd695a4dd4a69a0678fb42789666b5a89e8c0af48bb06c6e427120" dependencies = [ "futures-core", "futures-sink", "js-sys", - "once_cell", "pin-project-lite", - "thiserror 1.0.69", + "thiserror 2.0.12", + "tracing", ] [[package]] name = "opentelemetry-http" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80" +checksum = "46d7ab32b827b5b495bd90fa95a6cb65ccc293555dcc3199ae2937d2d237c8ed" dependencies = [ "async-trait", "bytes", "http 1.1.0", "opentelemetry", - "reqwest 0.12.9", + "reqwest", + "tracing", ] [[package]] name = "opentelemetry-otlp" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" +checksum = "d899720fe06916ccba71c01d04ecd77312734e2de3467fd30d9d580c8ce85656" dependencies = [ - "async-trait", "futures-core", "http 1.1.0", "opentelemetry", @@ -3129,18 +3205,17 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest 0.12.9", - "thiserror 1.0.69", - "tokio", + "reqwest", + "thiserror 2.0.12", "tonic", "tracing", ] [[package]] name = "opentelemetry-proto" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6" +checksum = "8c40da242381435e18570d5b9d50aca2a4f4f4d8e146231adb4e7768023309b3" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -3150,27 +3225,25 @@ dependencies = [ [[package]] name = "opentelemetry-semantic-conventions" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc1b6902ff63b32ef6c489e8048c5e253e2e4a803ea3ea7e783914536eb15c52" +checksum = "84b29a9f89f1a954936d5aa92f19b2feec3c8f3971d3e96206640db7f9706ae3" [[package]] name = "opentelemetry_sdk" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b742c1cae4693792cc564e58d75a2a0ba29421a34a85b50da92efa89ecb2bc" +checksum = "afdefb21d1d47394abc1ba6c57363ab141be19e27cc70d0e422b7f303e4d290b" dependencies = [ - "async-trait", "futures-channel", "futures-executor", "futures-util", "glob", - "once_cell", "opentelemetry", "percent-encoding", - "rand", + "rand 0.9.0", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", "tokio-stream", "tracing", @@ -3327,11 +3400,12 @@ dependencies = [ [[package]] name = "pem" -version = "1.1.1" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", + "serde", ] [[package]] @@ -3351,11 +3425,11 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "periphery_client" -version = "1.16.12" +version = "1.17.0" dependencies = [ "anyhow", "komodo_client", - "reqwest 0.12.9", + "reqwest", "resolver_api", "serde", "serde_json", @@ -3428,7 +3502,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -3499,9 +3573,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.18", + "rustls 0.23.25", "socket2", - "thiserror 2.0.3", + "thiserror 2.0.12", "tokio", "tracing", ] @@ -3513,14 +3587,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", - "getrandom", - "rand", - "ring 0.17.8", + "getrandom 0.2.15", + "rand 0.8.5", + "ring", "rustc-hash 2.0.0", - "rustls 0.23.18", + "rustls 0.23.25", "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror 2.0.12", "tinyvec", "tracing", "web-time", @@ -3562,8 +3636,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.21", ] [[package]] @@ -3573,7 +3658,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -3582,7 +3677,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", ] [[package]] @@ -3660,51 +3764,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.31", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.25.4", - "winreg", -] - -[[package]] -name = "reqwest" -version = "0.12.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64 0.22.1", "bytes", @@ -3716,28 +3778,30 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-rustls 0.27.3", "hyper-util", "ipnet", "js-sys", "log", "mime", + "mime_guess", "once_cell", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.18", + "rustls 0.23.25", "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", - "system-configuration 0.6.1", + "sync_wrapper", + "system-configuration", "tokio", "tokio-rustls 0.26.0", + "tower 0.5.2", "tower-service", "url", "wasm-bindgen", @@ -3749,49 +3813,48 @@ dependencies = [ [[package]] name = "reqwest-conditional-middleware" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e50a2e70970896c99d1b8f20ddc30a70b30d3ac6e619a03a8353b64a49b277" +checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b" dependencies = [ "async-trait", - "reqwest 0.11.27", + "http 1.1.0", + "reqwest", "reqwest-middleware", - "task-local-extensions", ] [[package]] name = "reqwest-middleware" -version = "0.2.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" +checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3" dependencies = [ "anyhow", "async-trait", - "http 0.2.12", - "reqwest 0.11.27", + "http 1.1.0", + "reqwest", "serde", - "task-local-extensions", "thiserror 1.0.69", + "tower-service", ] [[package]] name = "reqwest-retry" -version = "0.2.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6a11c05102e5bec712c0619b8c7b7eda8b21a558a0bd981ceee15c38df8be4" +checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178" dependencies = [ "anyhow", "async-trait", - "chrono", "futures", - "getrandom", - "http 0.2.12", - "hyper 0.14.31", + "getrandom 0.2.15", + "http 1.1.0", + "hyper 1.6.0", "parking_lot 0.11.2", - "reqwest 0.11.27", + "reqwest", "reqwest-middleware", "retry-policies", - "task-local-extensions", + "thiserror 1.0.69", "tokio", "tracing", "wasm-timer", @@ -3799,17 +3862,17 @@ dependencies = [ [[package]] name = "reqwest-tracing" -version = "0.4.8" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190838e54153d7a7e2ea98851304b3ce92daeabf14c54d32b01b84a3e636f683" +checksum = "73e6153390585f6961341b50e5a1931d6be6dee4292283635903c26ef9d980d2" dependencies = [ "anyhow", "async-trait", - "getrandom", + "getrandom 0.2.15", + "http 1.1.0", "matchit", - "reqwest 0.11.27", + "reqwest", "reqwest-middleware", - "task-local-extensions", "tracing", ] @@ -3825,22 +3888,18 @@ dependencies = [ [[package]] name = "resolver_api" -version = "1.1.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86d2e3a6e01f92f8f964bc16a5e579028c02fa625612e5e1f159a6474e47969" +checksum = "3faf1143e798058d8287e5a019fc8a46542c1ef152a29bc3b3d9bf886363016b" dependencies = [ - "anyhow", "resolver_api_derive", - "serde", - "serde_json", - "thiserror 1.0.69", ] [[package]] name = "resolver_api_derive" -version = "1.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35ef0d79735ffcdf944895605c107b7f04ea097ce5a39d3191f9b2d82f73f7b" +checksum = "e45ff368040e7f3787e97e6f3a97718ca08e0553587747bd4d0bf3ca2b899963" dependencies = [ "proc-macro2", "quote", @@ -3848,14 +3907,23 @@ dependencies = [ ] [[package]] -name = "retry-policies" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b" +name = "response" +version = "1.17.0" dependencies = [ "anyhow", - "chrono", - "rand", + "axum", + "serde", + "serde_json", + "serror", +] + +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" +dependencies = [ + "rand 0.8.5", ] [[package]] @@ -3868,21 +3936,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -3891,10 +3944,10 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] @@ -3911,7 +3964,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -3984,23 +4037,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-webpki 0.101.7", "sct", ] [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring 0.17.8", + "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.0", "subtle", "zeroize", ] @@ -4049,9 +4102,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" dependencies = [ "web-time", ] @@ -4062,20 +4115,20 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" dependencies = [ "aws-lc-rs", - "ring 0.17.8", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -4139,8 +4192,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -4201,9 +4254,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -4229,9 +4282,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -4251,9 +4304,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "indexmap 2.6.0", "itoa", @@ -4358,9 +4411,9 @@ dependencies = [ [[package]] name = "serror" -version = "0.4.7" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715a997753611604c722411afbe11f83a89e00e39323dc9016db96a86cc04fc8" +checksum = "2b4167e91cc6ad0f816a0a9297ec203813d426a647c73448b2dad80239e615de" dependencies = [ "anyhow", "axum", @@ -4432,7 +4485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -4463,12 +4516,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7351d61890f030c76e0bebcc203befb73a84cf1888db96597d2f1f6144b693" dependencies = [ "anyhow", - "reqwest 0.12.9", + "reqwest", "serde", "serde_derive", "serde_json", - "strum", - "strum_macros", + "strum 0.26.3", + "strum_macros 0.26.4", ] [[package]] @@ -4487,12 +4540,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -4537,8 +4584,14 @@ name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" dependencies = [ - "strum_macros", + "strum_macros 0.27.1", ] [[package]] @@ -4554,6 +4607,19 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.89", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4591,12 +4657,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4619,9 +4679,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.32.1" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" dependencies = [ "core-foundation-sys", "libc", @@ -4631,17 +4691,6 @@ dependencies = [ "windows", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys 0.5.0", -] - [[package]] name = "system-configuration" version = "0.6.1" @@ -4650,17 +4699,7 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.6.0", "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -4685,15 +4724,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "task-local-extensions" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" -dependencies = [ - "pin-utils", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -4705,11 +4735,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.12", ] [[package]] @@ -4725,9 +4755,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -4775,6 +4805,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -4802,9 +4841,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", @@ -4820,9 +4859,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -4845,7 +4884,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.18", + "rustls 0.23.25", "rustls-pki-types", "tokio", ] @@ -4863,9 +4902,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.24.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", @@ -4875,9 +4914,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -4889,9 +4928,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -4910,9 +4949,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap 2.6.0", "serde", @@ -4939,16 +4978,13 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ - "async-stream", "async-trait", - "axum", "base64 0.22.1", "bytes", - "h2 0.4.7", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -4956,7 +4992,6 @@ dependencies = [ "prost", "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", - "socket2", "tokio", "tokio-rustls 0.26.0", "tokio-stream", @@ -4977,7 +5012,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -4988,14 +5023,14 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 0.1.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -5041,9 +5076,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -5085,9 +5120,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a971f6058498b5c0f1affa23e7ea202057a7301dbff68e968b2d578bcbd053" +checksum = "fd8e764bd6f5813fd8bebc3117875190c5b0415be8f7f8059bffb6ecd979c444" dependencies = [ "js-sys", "once_cell", @@ -5103,9 +5138,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -5113,9 +5148,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "serde", @@ -5136,19 +5171,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.24.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ - "byteorder", "bytes", "data-encoding", "http 1.1.0", "httparse", "log", - "rand", + "rand 0.9.0", "sha1", - "thiserror 1.0.69", + "thiserror 2.0.12", "utf-8", ] @@ -5230,12 +5264,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -5249,7 +5277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 1.0.3", + "idna", "percent-encoding", "serde", ] @@ -5286,13 +5314,15 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom", - "rand", + "getrandom 0.3.1", + "js-sys", + "rand 0.9.0", "serde", + "wasm-bindgen", ] [[package]] @@ -5335,25 +5365,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.95" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.89", @@ -5374,9 +5413,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5384,9 +5423,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -5397,9 +5436,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-timer" @@ -5475,7 +5517,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189" dependencies = [ - "thiserror 2.0.3", + "thiserror 2.0.12", ] [[package]] @@ -5554,14 +5596,20 @@ dependencies = [ ] [[package]] -name = "windows-registry" -version = "0.2.0" +name = "windows-link" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result 0.2.0", + "windows-result 0.3.2", "windows-strings", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] @@ -5575,21 +5623,20 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -5643,13 +5690,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5662,6 +5725,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5674,6 +5743,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5686,12 +5761,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5704,6 +5791,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5716,6 +5809,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5728,6 +5827,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5741,10 +5846,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.6.20" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] @@ -5759,6 +5870,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -5817,7 +5937,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" +dependencies = [ + "zerocopy-derive 0.8.21", ] [[package]] @@ -5831,6 +5960,17 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 91e888963..e94bf2a8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,23 +8,20 @@ members = [ ] [workspace.package] -version = "1.16.12" -edition = "2021" +version = "1.17.0" +edition = "2024" authors = ["mbecker20 "] license = "GPL-3.0-or-later" -repository = "https://github.com/mbecker20/komodo" +repository = "https://github.com/moghtech/komodo" homepage = "https://komo.do" -[patch.crates-io] -# komodo_client = { path = "client/core/rs" } - [workspace.dependencies] # LOCAL -# komodo_client = "1.15.6" komodo_client = { path = "client/core/rs" } periphery_client = { path = "client/periphery/rs" } environment_file = { path = "lib/environment_file" } formatting = { path = "lib/formatting" } +response = { path = "lib/response" } command = { path = "lib/command" } logger = { path = "lib/logger" } cache = { path = "lib/cache" } @@ -32,7 +29,7 @@ git = { path = "lib/git" } # MOGH run_command = { version = "0.0.6", features = ["async_tokio"] } -serror = { version = "0.4.7", default-features = false } +serror = { version = "0.5.0", default-features = false } slack = { version = "0.3.0", package = "slack_client_rs", default-features = false, features = ["rustls"] } derive_default_builder = "0.1.8" derive_empty_traits = "0.1.0" @@ -41,79 +38,81 @@ async_timing_util = "1.0.0" partial_derive2 = "0.4.3" derive_variants = "1.0.0" mongo_indexed = "2.0.1" -resolver_api = "1.1.1" +resolver_api = "3.0.0" toml_pretty = "1.1.2" -mungos = "1.1.0" +mungos = "3.2.0" svi = "1.0.1" # ASYNC -reqwest = { version = "0.12.9", default-features = false, features = ["json", "rustls-tls"] } -tokio = { version = "1.41.1", features = ["full"] } -tokio-util = "0.7.12" +reqwest = { version = "0.12.15", default-features = false, features = ["json", "rustls-tls-native-roots"] } +tokio = { version = "1.44.1", features = ["full"] } +tokio-util = "0.7.14" futures = "0.3.31" futures-util = "0.3.31" +arc-swap = "1.7.1" # SERVER -axum-extra = { version = "0.9.6", features = ["typed-header"] } +axum-extra = { version = "0.10.0", features = ["typed-header"] } tower-http = { version = "0.6.2", features = ["fs", "cors"] } -axum-server = { version = "0.7.1", features = ["tls-rustls"] } -axum = { version = "0.7.9", features = ["ws", "json"] } -tokio-tungstenite = "0.24.0" +axum-server = { version = "0.7.2", features = ["tls-rustls"] } +axum = { version = "0.8.1", features = ["ws", "json", "macros"] } +tokio-tungstenite = "0.26.2" # SER/DE ordered_hash_map = { version = "0.4.0", features = ["serde"] } -serde = { version = "1.0.215", features = ["derive"] } -strum = { version = "0.26.3", features = ["derive"] } -serde_json = "1.0.133" +serde = { version = "1.0.219", features = ["derive"] } +strum = { version = "0.27.1", features = ["derive"] } +serde_json = "1.0.140" serde_yaml = "0.9.34" -toml = "0.8.19" +toml = "0.8.20" # ERROR -anyhow = "1.0.93" -thiserror = "2.0.3" +anyhow = "1.0.97" +thiserror = "2.0.12" # LOGGING -opentelemetry-otlp = { version = "0.27.0", features = ["tls-roots", "reqwest-rustls"] } -opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio"] } -tracing-subscriber = { version = "0.3.18", features = ["json"] } -opentelemetry-semantic-conventions = "0.27.0" -tracing-opentelemetry = "0.28.0" -opentelemetry = "0.27.0" -tracing = "0.1.40" +opentelemetry-otlp = { version = "0.29.0", features = ["tls-roots", "reqwest-rustls"] } +opentelemetry_sdk = { version = "0.29.0", features = ["rt-tokio"] } +tracing-subscriber = { version = "0.3.19", features = ["json"] } +opentelemetry-semantic-conventions = "0.29.0" +tracing-opentelemetry = "0.30.0" +opentelemetry = "0.29.0" +tracing = "0.1.41" # CONFIG -clap = { version = "4.5.21", features = ["derive"] } +clap = { version = "4.5.32", features = ["derive"] } dotenvy = "0.15.7" envy = "0.4.2" # CRYPTO / AUTH -uuid = { version = "1.11.0", features = ["v4", "fast-rng", "serde"] } -openidconnect = "3.5.0" +uuid = { version = "1.16.0", features = ["v4", "fast-rng", "serde"] } +jsonwebtoken = { version = "9.3.1", default-features = false } +openidconnect = "4.0.0" urlencoding = "2.1.3" nom_pem = "4.0.0" -bcrypt = "0.16.0" +bcrypt = "0.17.0" base64 = "0.22.1" -rustls = "0.23.18" +rustls = "0.23.25" hmac = "0.12.1" sha2 = "0.10.8" -rand = "0.8.5" -jwt = "0.16.0" +rand = "0.9.0" hex = "0.4.3" # SYSTEM bollard = "0.18.1" -sysinfo = "0.32.0" +sysinfo = "0.33.1" # CLOUD -aws-config = "1.5.10" -aws-sdk-ec2 = "1.91.0" +aws-config = "1.6.0" +aws-sdk-ec2 = "1.118.1" +aws-credential-types = "1.2.2" # MISC derive_builder = "0.20.2" typeshare = "1.0.4" -octorust = "0.7.0" +octorust = "0.10.0" dashmap = "6.1.0" wildcard = "0.3.0" -colored = "2.1.0" +colored = "3.0.0" regex = "1.11.1" -bson = "2.13.0" +bson = "2.14.0" diff --git a/bin/binaries.Dockerfile b/bin/binaries.Dockerfile index df64093ac..485456007 100644 --- a/bin/binaries.Dockerfile +++ b/bin/binaries.Dockerfile @@ -1,7 +1,7 @@ ## Builds the Komodo Core and Periphery binaries ## for a specific architecture. -FROM rust:1.82.0-bullseye AS builder +FROM rust:1.85.1-bullseye AS builder WORKDIR /builder COPY Cargo.toml Cargo.lock ./ @@ -22,6 +22,6 @@ FROM scratch COPY --from=builder /builder/target/release/core /core COPY --from=builder /builder/target/release/periphery /periphery -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Periphery" LABEL org.opencontainers.image.licenses=GPL-3.0 \ No newline at end of file diff --git a/bin/cli/Cargo.toml b/bin/cli/Cargo.toml index f3f3dd67e..7ab8f3c06 100644 --- a/bin/cli/Cargo.toml +++ b/bin/cli/Cargo.toml @@ -16,6 +16,7 @@ path = "src/main.rs" [dependencies] # local +# komodo_client = "1.16.12" komodo_client.workspace = true # external tracing-subscriber.workspace = true diff --git a/bin/cli/src/exec.rs b/bin/cli/src/exec.rs index 1b20aff6b..9a9cbd577 100644 --- a/bin/cli/src/exec.rs +++ b/bin/cli/src/exec.rs @@ -206,6 +206,9 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::BatchDestroyStack(data) => { println!("{}: {data:?}", "Data".dimmed()) } + Execution::TestAlerter(data) => { + println!("{}: {data:?}", "Data".dimmed()) + } Execution::Sleep(data) => { println!("{}: {data:?}", "Data".dimmed()) } @@ -454,6 +457,10 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { .execute(request) .await .map(ExecutionResult::Batch), + Execution::TestAlerter(request) => komodo_client() + .execute(request) + .await + .map(ExecutionResult::Single), Execution::Sleep(request) => { let duration = Duration::from_millis(request.duration_ms as u64); diff --git a/bin/core/Cargo.toml b/bin/core/Cargo.toml index cec4c2bfa..31607ce29 100644 --- a/bin/core/Cargo.toml +++ b/bin/core/Cargo.toml @@ -19,6 +19,7 @@ komodo_client = { workspace = true, features = ["mongo"] } periphery_client.workspace = true environment_file.workspace = true formatting.workspace = true +response.workspace = true command.workspace = true logger.workspace = true cache.workspace = true @@ -36,9 +37,10 @@ mungos.workspace = true slack.workspace = true svi.workspace = true # external -axum-server.workspace = true +aws-credential-types.workspace = true ordered_hash_map.workspace = true openidconnect.workspace = true +axum-server.workspace = true urlencoding.workspace = true aws-sdk-ec2.workspace = true aws-config.workspace = true @@ -50,6 +52,7 @@ serde_yaml.workspace = true typeshare.workspace = true octorust.workspace = true wildcard.workspace = true +arc-swap.workspace = true dashmap.workspace = true tracing.workspace = true reqwest.workspace = true @@ -70,5 +73,5 @@ envy.workspace = true rand.workspace = true hmac.workspace = true sha2.workspace = true -jwt.workspace = true +jsonwebtoken.workspace = true hex.workspace = true diff --git a/bin/core/aio.Dockerfile b/bin/core/aio.Dockerfile index 2fc5e22bf..952972fc8 100644 --- a/bin/core/aio.Dockerfile +++ b/bin/core/aio.Dockerfile @@ -1,7 +1,7 @@ ## All in one, multi stage compile + runtime Docker build for your architecture. # Build Core -FROM rust:1.82.0-bullseye AS core-builder +FROM rust:1.85.1-bullseye AS core-builder WORKDIR /builder COPY Cargo.toml Cargo.lock ./ @@ -48,7 +48,7 @@ RUN mkdir /action-cache && \ EXPOSE 9120 # Label for Ghcr -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Core" LABEL org.opencontainers.image.licenses=GPL-3.0 diff --git a/bin/core/multi-arch.Dockerfile b/bin/core/multi-arch.Dockerfile index 411cf1538..36c9934a9 100644 --- a/bin/core/multi-arch.Dockerfile +++ b/bin/core/multi-arch.Dockerfile @@ -2,8 +2,8 @@ ## Sets up the necessary runtime container dependencies for Komodo Core. ## Since theres no heavy build here, QEMU multi-arch builds are fine for this image. -ARG BINARIES_IMAGE=ghcr.io/mbecker20/komodo-binaries:latest -ARG FRONTEND_IMAGE=ghcr.io/mbecker20/komodo-frontend:latest +ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest +ARG FRONTEND_IMAGE=ghcr.io/moghtech/komodo-frontend:latest ARG X86_64_BINARIES=${BINARIES_IMAGE}-x86_64 ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64 @@ -43,8 +43,8 @@ RUN mkdir /action-cache && \ EXPOSE 9120 # Label for Ghcr -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Core" LABEL org.opencontainers.image.licenses=GPL-3.0 -ENTRYPOINT [ "core" ] \ No newline at end of file +CMD [ "core" ] \ No newline at end of file diff --git a/bin/core/single-arch.Dockerfile b/bin/core/single-arch.Dockerfile index 8a3a22ba2..e51d10956 100644 --- a/bin/core/single-arch.Dockerfile +++ b/bin/core/single-arch.Dockerfile @@ -1,7 +1,7 @@ ## Assumes the latest binaries for the required arch are already built (by binaries.Dockerfile). ## Sets up the necessary runtime container dependencies for Komodo Core. -ARG BINARIES_IMAGE=ghcr.io/mbecker20/komodo-binaries:latest +ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest # This is required to work with COPY --from FROM ${BINARIES_IMAGE} AS binaries @@ -37,8 +37,8 @@ RUN mkdir /action-cache && \ EXPOSE 9120 # Label for Ghcr -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Core" LABEL org.opencontainers.image.licenses=GPL-3.0 -ENTRYPOINT [ "core" ] \ No newline at end of file +CMD [ "core" ] \ No newline at end of file diff --git a/bin/core/src/alert/discord.rs b/bin/core/src/alert/discord.rs index e627b5f8c..e925d50ff 100644 --- a/bin/core/src/alert/discord.rs +++ b/bin/core/src/alert/discord.rs @@ -11,6 +11,12 @@ pub async fn send_alert( ) -> anyhow::Result<()> { let level = fmt_level(alert.level); let content = match &alert.data { + AlertData::Test { id, name } => { + let link = resource_link(ResourceTargetVariant::Alerter, id); + format!( + "{level} | If you see this message, then Alerter **{name}** is **working**\n{link}" + ) + } AlertData::ServerUnreachable { id, name, @@ -88,7 +94,9 @@ pub async fn send_alert( } => { let link = resource_link(ResourceTargetVariant::Deployment, id); let to = fmt_docker_container_state(to); - format!("📦 Deployment **{name}** is now **{to}**\nserver: **{server_name}**\nprevious: **{from}**\n{link}") + format!( + "📦 Deployment **{name}** is now **{to}**\nserver: **{server_name}**\nprevious: **{from}**\n{link}" + ) } AlertData::DeploymentImageUpdateAvailable { id, @@ -98,7 +106,9 @@ pub async fn send_alert( image, } => { let link = resource_link(ResourceTargetVariant::Deployment, id); - format!("⬆ Deployment **{name}** has an update available\nserver: **{server_name}**\nimage: **{image}**\n{link}") + format!( + "⬆ Deployment **{name}** has an update available\nserver: **{server_name}**\nimage: **{image}**\n{link}" + ) } AlertData::DeploymentAutoUpdated { id, @@ -108,7 +118,9 @@ pub async fn send_alert( image, } => { let link = resource_link(ResourceTargetVariant::Deployment, id); - format!("⬆ Deployment **{name}** was updated automatically ⏫\nserver: **{server_name}**\nimage: **{image}**\n{link}") + format!( + "⬆ Deployment **{name}** was updated automatically ⏫\nserver: **{server_name}**\nimage: **{image}**\n{link}" + ) } AlertData::StackStateChange { id, @@ -120,7 +132,9 @@ pub async fn send_alert( } => { let link = resource_link(ResourceTargetVariant::Stack, id); let to = fmt_stack_state(to); - format!("🥞 Stack **{name}** is now {to}\nserver: **{server_name}**\nprevious: **{from}**\n{link}") + format!( + "🥞 Stack **{name}** is now {to}\nserver: **{server_name}**\nprevious: **{from}**\n{link}" + ) } AlertData::StackImageUpdateAvailable { id, @@ -131,7 +145,9 @@ pub async fn send_alert( image, } => { let link = resource_link(ResourceTargetVariant::Stack, id); - format!("⬆ Stack **{name}** has an update available\nserver: **{server_name}**\nservice: **{service}**\nimage: **{image}**\n{link}") + format!( + "⬆ Stack **{name}** has an update available\nserver: **{server_name}**\nservice: **{service}**\nimage: **{image}**\n{link}" + ) } AlertData::StackAutoUpdated { id, @@ -144,13 +160,17 @@ pub async fn send_alert( let images_label = if images.len() > 1 { "images" } else { "image" }; let images = images.join(", "); - format!("⬆ Stack **{name}** was updated automatically ⏫\nserver: **{server_name}**\n{images_label}: **{images}**\n{link}") + format!( + "⬆ Stack **{name}** was updated automatically ⏫\nserver: **{server_name}**\n{images_label}: **{images}**\n{link}" + ) } AlertData::AwsBuilderTerminationFailed { instance_id, message, } => { - format!("{level} | Failed to terminated AWS builder instance\ninstance id: **{instance_id}**\n{message}") + format!( + "{level} | Failed to terminated AWS builder instance\ninstance id: **{instance_id}**\n{message}" + ) } AlertData::ResourceSyncPendingUpdates { id, name } => { let link = @@ -161,7 +181,9 @@ pub async fn send_alert( } AlertData::BuildFailed { id, name, version } => { let link = resource_link(ResourceTargetVariant::Build, id); - format!("{level} | Build **{name}** failed\nversion: **v{version}**\n{link}") + format!( + "{level} | Build **{name}** failed\nversion: **v{version}**\n{link}" + ) } AlertData::RepoBuildFailed { id, name } => { let link = resource_link(ResourceTargetVariant::Repo, id); diff --git a/bin/core/src/alert/mod.rs b/bin/core/src/alert/mod.rs index aa64d69e5..127cd6c30 100644 --- a/bin/core/src/alert/mod.rs +++ b/bin/core/src/alert/mod.rs @@ -1,22 +1,26 @@ use ::slack::types::Block; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use derive_variants::ExtractVariant; use futures::future::join_all; use komodo_client::entities::{ - alert::{Alert, AlertData, SeverityLevel}, + ResourceTargetVariant, + alert::{Alert, AlertData, AlertDataVariant, SeverityLevel}, alerter::*, deployment::DeploymentState, stack::StackState, - ResourceTargetVariant, }; use mungos::{find::find_collect, mongodb::bson::doc}; +use std::collections::HashSet; use tracing::Instrument; +use crate::helpers::interpolate::interpolate_variables_secrets_into_string; +use crate::helpers::query::get_variables_and_secrets; use crate::{config::core_config, state::db_client}; mod discord; mod slack; +#[instrument(level = "debug")] pub async fn send_alerts(alerts: &[Alert]) { if alerts.is_empty() { return; @@ -54,14 +58,31 @@ async fn send_alert(alerters: &[Alerter], alert: &Alert) { return; } + let handles = alerters + .iter() + .map(|alerter| send_alert_to_alerter(alerter, alert)); + + join_all(handles) + .await + .into_iter() + .filter_map(|res| res.err()) + .for_each(|e| error!("{e:#}")); +} + +pub async fn send_alert_to_alerter( + alerter: &Alerter, + alert: &Alert, +) -> anyhow::Result<()> { + // Don't send if not enabled + if !alerter.config.enabled { + return Ok(()); + } + let alert_type = alert.data.extract_variant(); - let handles = alerters.iter().map(|alerter| async { - // Don't send if not enabled - if !alerter.config.enabled { - return Ok(()); - } - + // In the test case, we don't want the filters inside this + // block to stop the test from being sent to the alerting endpoint. + if alert_type != AlertDataVariant::Test { // Don't send if alert type not configured on the alerter if !alerter.config.alert_types.is_empty() && !alerter.config.alert_types.contains(&alert_type) @@ -80,40 +101,34 @@ async fn send_alert(alerters: &[Alerter], alert: &Alert) { { return Ok(()); } + } - match &alerter.config.endpoint { - AlerterEndpoint::Custom(CustomAlerterEndpoint { url }) => { - send_custom_alert(url, alert).await.with_context(|| { - format!( - "failed to send alert to custom alerter {}", - alerter.name - ) - }) - } - AlerterEndpoint::Slack(SlackAlerterEndpoint { url }) => { - slack::send_alert(url, alert).await.with_context(|| { - format!( - "failed to send alert to slack alerter {}", - alerter.name - ) - }) - } - AlerterEndpoint::Discord(DiscordAlerterEndpoint { url }) => { - discord::send_alert(url, alert).await.with_context(|| { - format!( - "failed to send alert to Discord alerter {}", - alerter.name - ) - }) - } + match &alerter.config.endpoint { + AlerterEndpoint::Custom(CustomAlerterEndpoint { url }) => { + send_custom_alert(url, alert).await.with_context(|| { + format!( + "Failed to send alert to Custom Alerter {}", + alerter.name + ) + }) } - }); - - join_all(handles) - .await - .into_iter() - .filter_map(|res| res.err()) - .for_each(|e| error!("{e:#}")); + AlerterEndpoint::Slack(SlackAlerterEndpoint { url }) => { + slack::send_alert(url, alert).await.with_context(|| { + format!( + "Failed to send alert to Slack Alerter {}", + alerter.name + ) + }) + } + AlerterEndpoint::Discord(DiscordAlerterEndpoint { url }) => { + discord::send_alert(url, alert).await.with_context(|| { + format!( + "Failed to send alert to Discord Alerter {}", + alerter.name + ) + }) + } + } } #[instrument(level = "debug")] @@ -121,11 +136,34 @@ async fn send_custom_alert( url: &str, alert: &Alert, ) -> anyhow::Result<()> { + let vars_and_secrets = get_variables_and_secrets().await?; + let mut global_replacers = HashSet::new(); + let mut secret_replacers = HashSet::new(); + let mut url_interpolated = url.to_string(); + + // interpolate variables and secrets into the url + interpolate_variables_secrets_into_string( + &vars_and_secrets, + &mut url_interpolated, + &mut global_replacers, + &mut secret_replacers, + )?; + let res = reqwest::Client::new() - .post(url) + .post(url_interpolated) .json(alert) .send() .await + .map_err(|e| { + let replacers = + secret_replacers.into_iter().collect::>(); + let sanitized_error = + svi::replace_in_string(&format!("{e:?}"), &replacers); + anyhow::Error::msg(format!( + "Error with request: {}", + sanitized_error + )) + }) .context("failed at post request to alerter")?; let status = res.status(); if !status.is_success() { diff --git a/bin/core/src/alert/slack.rs b/bin/core/src/alert/slack.rs index 69db726fe..4ca91b82a 100644 --- a/bin/core/src/alert/slack.rs +++ b/bin/core/src/alert/slack.rs @@ -7,6 +7,22 @@ pub async fn send_alert( ) -> anyhow::Result<()> { let level = fmt_level(alert.level); let (text, blocks): (_, Option<_>) = match &alert.data { + AlertData::Test { id, name } => { + let text = format!( + "{level} | If you see this message, then Alerter *{name}* is *working*" + ); + let blocks = vec![ + Block::header(level), + Block::section(format!( + "If you see this message, then Alerter *{name}* is *working*" + )), + Block::section(resource_link( + ResourceTargetVariant::Alerter, + id, + )), + ]; + (text, blocks.into()) + } AlertData::ServerUnreachable { id, name, @@ -57,7 +73,9 @@ pub async fn send_alert( let region = fmt_region(region); match alert.level { SeverityLevel::Ok => { - let text = format!("{level} | *{name}*{region} cpu usage at *{percentage:.1}%*"); + let text = format!( + "{level} | *{name}*{region} cpu usage at *{percentage:.1}%*" + ); let blocks = vec![ Block::header(level), Block::section(format!( @@ -71,7 +89,9 @@ pub async fn send_alert( (text, blocks.into()) } _ => { - let text = format!("{level} | *{name}*{region} cpu usage at *{percentage:.1}%* 📈"); + let text = format!( + "{level} | *{name}*{region} cpu usage at *{percentage:.1}%* 📈" + ); let blocks = vec![ Block::header(level), Block::section(format!( @@ -97,7 +117,9 @@ pub async fn send_alert( let percentage = 100.0 * used_gb / total_gb; match alert.level { SeverityLevel::Ok => { - let text = format!("{level} | *{name}*{region} memory usage at *{percentage:.1}%* 💾"); + let text = format!( + "{level} | *{name}*{region} memory usage at *{percentage:.1}%* 💾" + ); let blocks = vec![ Block::header(level), Block::section(format!( @@ -114,7 +136,9 @@ pub async fn send_alert( (text, blocks.into()) } _ => { - let text = format!("{level} | *{name}*{region} memory usage at *{percentage:.1}%* 💾"); + let text = format!( + "{level} | *{name}*{region} memory usage at *{percentage:.1}%* 💾" + ); let blocks = vec![ Block::header(level), Block::section(format!( @@ -144,7 +168,9 @@ pub async fn send_alert( let percentage = 100.0 * used_gb / total_gb; match alert.level { SeverityLevel::Ok => { - let text = format!("{level} | *{name}*{region} disk usage at *{percentage:.1}%* | mount point: *{path:?}* 💿"); + let text = format!( + "{level} | *{name}*{region} disk usage at *{percentage:.1}%* | mount point: *{path:?}* 💿" + ); let blocks = vec![ Block::header(level), Block::section(format!( @@ -153,12 +179,17 @@ pub async fn send_alert( Block::section(format!( "mount point: {path:?} | using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*" )), - Block::section(resource_link(ResourceTargetVariant::Server, id)), + Block::section(resource_link( + ResourceTargetVariant::Server, + id, + )), ]; (text, blocks.into()) } _ => { - let text = format!("{level} | *{name}*{region} disk usage at *{percentage:.1}%* | mount point: *{path:?}* 💿"); + let text = format!( + "{level} | *{name}*{region} disk usage at *{percentage:.1}%* | mount point: *{path:?}* 💿" + ); let blocks = vec![ Block::header(level), Block::section(format!( @@ -167,7 +198,10 @@ pub async fn send_alert( Block::section(format!( "mount point: {path:?} | using *{used_gb:.1} GiB* / *{total_gb:.1} GiB*" )), - Block::section(resource_link(ResourceTargetVariant::Server, id)), + Block::section(resource_link( + ResourceTargetVariant::Server, + id, + )), ]; (text, blocks.into()) } diff --git a/bin/core/src/api/auth.rs b/bin/core/src/api/auth.rs index 91ca0ef4e..61ce46a51 100644 --- a/bin/core/src/api/auth.rs +++ b/bin/core/src/api/auth.rs @@ -1,13 +1,12 @@ use std::{sync::OnceLock, time::Instant}; -use anyhow::anyhow; -use axum::{http::HeaderMap, routing::post, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use axum::{Router, http::HeaderMap, routing::post}; +use derive_variants::{EnumVariants, ExtractVariant}; use komodo_client::{api::auth::*, entities::user::User}; -use reqwest::StatusCode; -use resolver_api::{derive::Resolver, Resolve, Resolver}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; -use serror::{AddStatusCode, Json}; +use serror::Json; use typeshare::typeshare; use uuid::Uuid; @@ -20,13 +19,21 @@ use crate::{ }, config::core_config, helpers::query::get_user, - state::{jwt_client, State}, + state::jwt_client, }; +pub struct AuthArgs { + pub headers: HeaderMap, +} + #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Resolver)] -#[resolver_target(State)] -#[resolver_args(HeaderMap)] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, +)] +#[args(AuthArgs)] +#[response(Response)] +#[error(serror::Error)] +#[variant_derive(Debug)] #[serde(tag = "type", content = "params")] #[allow(clippy::enum_variant_names, clippy::large_enum_variant)] pub enum AuthRequest { @@ -66,27 +73,20 @@ pub fn router() -> Router { async fn handler( headers: HeaderMap, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let timer = Instant::now(); let req_id = Uuid::new_v4(); - debug!("/auth request {req_id} | METHOD: {}", request.req_type()); - let res = State.resolve_request(request, headers).await.map_err( - |e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }, + debug!( + "/auth request {req_id} | METHOD: {:?}", + request.extract_variant() ); + let res = request.resolve(&AuthArgs { headers }).await; if let Err(e) = &res { - debug!("/auth request {req_id} | error: {e:#}"); + debug!("/auth request {req_id} | error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/auth request {req_id} | resolve time: {elapsed:?}"); - Ok(( - TypedHeader(ContentType::json()), - res.status_code(StatusCode::UNAUTHORIZED)?, - )) + res.map(|res| res.0) } fn login_options_reponse() -> &'static GetLoginOptionsResponse { @@ -105,45 +105,40 @@ fn login_options_reponse() -> &'static GetLoginOptionsResponse { && !config.google_oauth.secret.is_empty(), oidc: config.oidc_enabled && !config.oidc_provider.is_empty() - && !config.oidc_client_id.is_empty() - && !config.oidc_client_secret.is_empty(), + && !config.oidc_client_id.is_empty(), registration_disabled: config.disable_user_registration, } }) } -impl Resolve for State { +impl Resolve for GetLoginOptions { #[instrument(name = "GetLoginOptions", level = "debug", skip(self))] async fn resolve( - &self, - _: GetLoginOptions, - _: HeaderMap, - ) -> anyhow::Result { + self, + _: &AuthArgs, + ) -> serror::Result { Ok(*login_options_reponse()) } } -impl Resolve for State { +impl Resolve for ExchangeForJwt { #[instrument(name = "ExchangeForJwt", level = "debug", skip(self))] async fn resolve( - &self, - ExchangeForJwt { token }: ExchangeForJwt, - _: HeaderMap, - ) -> anyhow::Result { - let jwt = jwt_client().redeem_exchange_token(&token).await?; - let res = ExchangeForJwtResponse { jwt }; - Ok(res) + self, + _: &AuthArgs, + ) -> serror::Result { + let jwt = jwt_client().redeem_exchange_token(&self.token).await?; + Ok(ExchangeForJwtResponse { jwt }) } } -impl Resolve for State { +impl Resolve for GetUser { #[instrument(name = "GetUser", level = "debug", skip(self))] async fn resolve( - &self, - GetUser {}: GetUser, - headers: HeaderMap, - ) -> anyhow::Result { - let user_id = get_user_id_from_headers(&headers).await?; - get_user(&user_id).await + self, + AuthArgs { headers }: &AuthArgs, + ) -> serror::Result { + let user_id = get_user_id_from_headers(headers).await?; + Ok(get_user(&user_id).await?) } } diff --git a/bin/core/src/api/execute/action.rs b/bin/core/src/api/execute/action.rs index 05af563cc..1be83f62c 100644 --- a/bin/core/src/api/execute/action.rs +++ b/bin/core/src/api/execute/action.rs @@ -13,11 +13,8 @@ use komodo_client::{ user::{CreateApiKey, CreateApiKeyResponse, DeleteApiKey}, }, entities::{ - action::Action, - config::core::CoreConfig, - permission::PermissionLevel, - update::Update, - user::{action_user, User}, + action::Action, config::core::CoreConfig, + permission::PermissionLevel, update::Update, user::action_user, }, }; use mungos::{by_id::update_one_by_id, mongodb::bson::to_document}; @@ -25,7 +22,7 @@ use resolver_api::Resolve; use tokio::fs; use crate::{ - api::execute::ExecuteRequest, + api::{execute::ExecuteRequest, user::UserArgs}, config::core_config, helpers::{ interpolate::{ @@ -37,9 +34,11 @@ use crate::{ update::update_update, }, resource::{self, refresh_action_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; +use super::ExecuteArgs; + impl super::BatchExecute for BatchRunAction { type Resource = Action; fn single_request(action: String) -> ExecuteRequest { @@ -47,27 +46,28 @@ impl super::BatchExecute for BatchRunAction { } } -impl Resolve for State { +impl Resolve for BatchRunAction { #[instrument(name = "BatchRunAction", skip(self, user), fields(user_id = user.id))] async fn resolve( - &self, - BatchRunAction { pattern }: BatchRunAction, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RunAction", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RunAction { + #[instrument(name = "RunAction", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunAction { action }: RunAction, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut action = resource::get_check_permissions::( - &action, - &user, + &self.action, + user, PermissionLevel::Execute, ) .await?; @@ -83,17 +83,18 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.running = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; - let CreateApiKeyResponse { key, secret } = State - .resolve( - CreateApiKey { - name: update.id.clone(), - expires: 0, - }, - action_user().to_owned(), - ) - .await?; + let CreateApiKeyResponse { key, secret } = CreateApiKey { + name: update.id.clone(), + expires: 0, + } + .resolve(&UserArgs { + user: action_user().to_owned(), + }) + .await?; let contents = &mut action.config.file_contents; @@ -110,19 +111,31 @@ impl Resolve for State { let path = core_config().action_directory.join(&file); if let Some(parent) = path.parent() { - let _ = fs::create_dir_all(parent).await; + fs::create_dir_all(parent) + .await + .with_context(|| format!("Failed to initialize Action file parent directory {parent:?}"))?; } fs::write(&path, contents).await.with_context(|| { format!("Failed to write action file to {path:?}") })?; + let CoreConfig { ssl_enabled, .. } = core_config(); + + let https_cert_flag = if *ssl_enabled { + " --unsafely-ignore-certificate-errors=localhost" + } else { + "" + }; + let mut res = run_komodo_command( // Keep this stage name as is, the UI will find the latest update log by matching the stage name "Execute Action", None, - format!("deno run --allow-all {}", path.display()), - false, + format!( + "deno run --allow-all{https_cert_flag} {}", + path.display() + ), ) .await; @@ -133,12 +146,15 @@ impl Resolve for State { cleanup_run(file + ".js", &path).await; - if let Err(e) = State - .resolve(DeleteApiKey { key }, action_user().to_owned()) + if let Err(e) = (DeleteApiKey { key }) + .resolve(&UserArgs { + user: action_user().to_owned(), + }) .await { warn!( - "Failed to delete API key after action execution | {e:#}" + "Failed to delete API key after action execution | {:#}", + e.error ); }; @@ -171,7 +187,7 @@ async fn interpolate( update: &mut Update, key: String, secret: String, -) -> anyhow::Result> { +) -> serror::Result> { let mut vars_and_secrets = get_variables_and_secrets().await?; vars_and_secrets @@ -301,8 +317,8 @@ fn delete_file( if name == file { if let Err(e) = fs::remove_file(entry.path()).await { warn!( - "Failed to clean up generated file after action execution | {e:#}" - ); + "Failed to clean up generated file after action execution | {e:#}" + ); }; return true; } diff --git a/bin/core/src/api/execute/alerter.rs b/bin/core/src/api/execute/alerter.rs new file mode 100644 index 000000000..96c872c69 --- /dev/null +++ b/bin/core/src/api/execute/alerter.rs @@ -0,0 +1,73 @@ +use formatting::format_serror; +use komodo_client::{ + api::execute::TestAlerter, + entities::{ + alert::{Alert, AlertData, SeverityLevel}, + alerter::Alerter, + komodo_timestamp, + permission::PermissionLevel, + }, +}; +use resolver_api::Resolve; + +use crate::{ + alert::send_alert_to_alerter, helpers::update::update_update, + resource::get_check_permissions, +}; + +use super::ExecuteArgs; + +impl Resolve for TestAlerter { + #[instrument(name = "TestAlerter", skip(user, update), fields(user_id = user.id, update_id = update.id))] + async fn resolve( + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> Result { + let alerter = get_check_permissions::( + &self.alerter, + user, + PermissionLevel::Execute, + ) + .await?; + + let mut update = update.clone(); + + if !alerter.config.enabled { + update.push_error_log( + "Test Alerter", + String::from( + "Alerter is disabled. Enable the Alerter to send alerts.", + ), + ); + update.finalize(); + update_update(update.clone()).await?; + return Ok(update); + } + + let ts = komodo_timestamp(); + + let alert = Alert { + id: Default::default(), + ts, + resolved: true, + level: SeverityLevel::Ok, + target: update.target.clone(), + data: AlertData::Test { + id: alerter.id.clone(), + name: alerter.name.clone(), + }, + resolved_ts: Some(ts), + }; + + if let Err(e) = send_alert_to_alerter(&alerter, &alert).await { + update.push_error_log("Test Alerter", format_serror(&e.into())); + } else { + update.push_simple_log("Test Alerter", String::from("Alert sent successfully. It should be visible at your alerting destination.")); + }; + + update.finalize(); + update_update(update.clone()).await?; + + Ok(update) + } +} diff --git a/bin/core/src/api/execute/build.rs b/bin/core/src/api/execute/build.rs index 6f63ce243..3b92607d7 100644 --- a/bin/core/src/api/execute/build.rs +++ b/bin/core/src/api/execute/build.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, future::IntoFuture, time::Duration}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::format_serror; use futures::future::join_all; use komodo_client::{ @@ -17,7 +17,7 @@ use komodo_client::{ komodo_timestamp, permission::PermissionLevel, update::{Log, Update}, - user::{auto_redeploy_user, User}, + user::auto_redeploy_user, }, }; use mungos::{ @@ -49,10 +49,10 @@ use crate::{ update::{init_execution_update, update_update}, }, resource::{self, refresh_build_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchRunBuild { type Resource = Build; @@ -61,34 +61,35 @@ impl super::BatchExecute for BatchRunBuild { } } -impl Resolve for State { - #[instrument(name = "BatchRunBuild", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchRunBuild { + #[instrument(name = "BatchRunBuild", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchRunBuild { pattern }: BatchRunBuild, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RunBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RunBuild { + #[instrument(name = "RunBuild", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunBuild { build }: RunBuild, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Execute, ) .await?; let mut vars_and_secrets = get_variables_and_secrets().await?; if build.config.builder_id.is_empty() { - return Err(anyhow!("Must attach builder to RunBuild")); + return Err(anyhow!("Must attach builder to RunBuild").into()); } // get the action state for the build (or insert default). @@ -103,6 +104,9 @@ impl Resolve for State { if build.config.auto_increment_version { build.config.version.increment(); } + + let mut update = update.clone(); + update.version = build.config.version; update_update(update.clone()).await?; @@ -408,7 +412,7 @@ impl Resolve for State { }); } - Ok(update) + Ok(update.clone()) } } @@ -418,7 +422,7 @@ async fn handle_early_return( build_id: String, build_name: String, is_cancel: bool, -) -> anyhow::Result { +) -> serror::Result { update.finalize(); // Need to manually update the update before cache refresh, // and before broadcast with add_update. @@ -456,7 +460,7 @@ async fn handle_early_return( send_alerts(&[alert]).await }); } - Ok(update) + Ok(update.clone()) } pub async fn validate_cancel_build( @@ -505,16 +509,15 @@ pub async fn validate_cancel_build( Ok(()) } -impl Resolve for State { - #[instrument(name = "CancelBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for CancelBuild { + #[instrument(name = "CancelBuild", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - CancelBuild { build }: CancelBuild, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Execute, ) .await?; @@ -527,9 +530,11 @@ impl Resolve for State { .and_then(|s| s.get().ok().map(|s| s.building)) .unwrap_or_default() { - return Err(anyhow!("Build is not building.")); + return Err(anyhow!("Build is not building.").into()); } + let mut update = update.clone(); + update.push_simple_log( "cancel triggered", "the build cancel has been triggered", @@ -555,7 +560,9 @@ impl Resolve for State { ) .await { - warn!("failed to set CancelBuild Update status Complete after timeout | {e:#}") + warn!( + "failed to set CancelBuild Update status Complete after timeout | {e:#}" + ) } }); @@ -593,16 +600,13 @@ async fn handle_post_build_redeploy(build_id: &str) { let user = auto_redeploy_user().to_owned(); let res = async { let update = init_execution_update(&req, &user).await?; - State - .resolve( - Deploy { - deployment: deployment.id.clone(), - stop_signal: None, - stop_time: None, - }, - (user, update), - ) - .await + Deploy { + deployment: deployment.id.clone(), + stop_signal: None, + stop_time: None, + } + .resolve(&ExecuteArgs { user, update }) + .await } .await; Some((deployment.id.clone(), res)) @@ -616,7 +620,10 @@ async fn handle_post_build_redeploy(build_id: &str) { continue; }; if let Err(e) = res { - warn!("failed post build redeploy for deployment {id}: {e:#}"); + warn!( + "failed post build redeploy for deployment {id}: {:#}", + e.error + ); } } } @@ -636,14 +643,17 @@ async fn validate_account_extract_registry_token( }, .. }: &Build, -) -> anyhow::Result> { +) -> serror::Result> { if domain.is_empty() { return Ok(None); } if account.is_empty() { - return Err(anyhow!( - "Must attach account to use registry provider {domain}" - )); + return Err( + anyhow!( + "Must attach account to use registry provider {domain}" + ) + .into(), + ); } let registry_token = registry_token(domain, account).await.with_context( diff --git a/bin/core/src/api/execute/deployment.rs b/bin/core/src/api/execute/deployment.rs index cbf8d9a89..e8e7da213 100644 --- a/bin/core/src/api/execute/deployment.rs +++ b/bin/core/src/api/execute/deployment.rs @@ -1,21 +1,21 @@ use std::{collections::HashSet, sync::OnceLock}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use cache::TimeoutCache; use formatting::format_serror; use komodo_client::{ api::execute::*, entities::{ + Version, build::{Build, ImageRegistryConfig}, deployment::{ - extract_registry_domain, Deployment, DeploymentImage, + Deployment, DeploymentImage, extract_registry_domain, }, get_image_name, komodo_timestamp, optional_string, permission::PermissionLevel, server::Server, update::{Log, Update}, user::User, - Version, }, }; use periphery_client::api; @@ -35,10 +35,10 @@ use crate::{ }, monitor::update_cache_for_server, resource, - state::{action_states, State}, + state::action_states, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchDeploy { type Resource = Deployment; @@ -51,14 +51,16 @@ impl super::BatchExecute for BatchDeploy { } } -impl Resolve for State { - #[instrument(name = "BatchDeploy", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDeploy { + #[instrument(name = "BatchDeploy", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDeploy { pattern }: BatchDeploy, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } @@ -87,19 +89,14 @@ async fn setup_deployment_execution( Ok((deployment, server)) } -impl Resolve for State { - #[instrument(name = "Deploy", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for Deploy { + #[instrument(name = "Deploy", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - Deploy { - deployment, - stop_signal, - stop_time, - }: Deploy, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (mut deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -112,6 +109,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.deploying = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -237,8 +236,8 @@ impl Resolve for State { match periphery_client(&server)? .request(api::container::Deploy { deployment, - stop_signal, - stop_time, + stop_signal: self.stop_signal, + stop_time: self.stop_time, registry_token, replacers: secret_replacers.into_iter().collect(), }) @@ -378,14 +377,14 @@ pub async fn pull_deployment_inner( res } -impl Resolve for State { +impl Resolve for PullDeployment { + #[instrument(name = "PullDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PullDeployment { deployment }: PullDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -398,6 +397,7 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pulling = true)?; + let mut update = update.clone(); // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -411,15 +411,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StartDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StartDeployment { + #[instrument(name = "StartDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StartDeployment { deployment }: StartDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -432,6 +431,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.starting = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -457,15 +458,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RestartDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RestartDeployment { + #[instrument(name = "RestartDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RestartDeployment { deployment }: RestartDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -478,6 +478,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.restarting = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -505,15 +507,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PauseDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseDeployment { + #[instrument(name = "PauseDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseDeployment { deployment }: PauseDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -526,6 +527,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pausing = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -551,15 +554,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UnpauseDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseDeployment { + #[instrument(name = "UnpauseDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseDeployment { deployment }: UnpauseDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -572,6 +574,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.unpausing = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -599,19 +603,14 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StopDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopDeployment { + #[instrument(name = "StopDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopDeployment { - deployment, - signal, - time, - }: StopDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -624,16 +623,20 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.stopping = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let log = match periphery_client(&server)? .request(api::container::StopContainer { name: deployment.name, - signal: signal + signal: self + .signal .unwrap_or(deployment.config.termination_signal) .into(), - time: time + time: self + .time .unwrap_or(deployment.config.termination_timeout) .into(), }) @@ -666,31 +669,30 @@ impl super::BatchExecute for BatchDestroyDeployment { } } -impl Resolve for State { - #[instrument(name = "BatchDestroyDeployment", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDestroyDeployment { + #[instrument(name = "BatchDestroyDeployment", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDestroyDeployment { pattern }: BatchDestroyDeployment, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user) - .await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::( + &self.pattern, + user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DestroyDeployment", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DestroyDeployment { + #[instrument(name = "DestroyDeployment", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DestroyDeployment { - deployment, - signal, - time, - }: DestroyDeployment, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (deployment, server) = - setup_deployment_execution(&deployment, &user).await?; + setup_deployment_execution(&self.deployment, user).await?; // get the action state for the deployment (or insert default). let action_state = action_states() @@ -703,16 +705,20 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.destroying = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let log = match periphery_client(&server)? .request(api::container::RemoveContainer { name: deployment.name, - signal: signal + signal: self + .signal .unwrap_or(deployment.config.termination_signal) .into(), - time: time + time: self + .time .unwrap_or(deployment.config.termination_timeout) .into(), }) diff --git a/bin/core/src/api/execute/mod.rs b/bin/core/src/api/execute/mod.rs index 714dd11f2..89d6ad960 100644 --- a/bin/core/src/api/execute/mod.rs +++ b/bin/core/src/api/execute/mod.rs @@ -1,21 +1,22 @@ -use std::time::Instant; +use std::{pin::Pin, time::Instant}; -use anyhow::{anyhow, Context}; -use axum::{middleware, routing::post, Extension, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use anyhow::Context; +use axum::{Extension, Router, middleware, routing::post}; +use axum_extra::{TypedHeader, headers::ContentType}; use derive_variants::{EnumVariants, ExtractVariant}; use formatting::format_serror; use futures::future::join_all; use komodo_client::{ api::execute::*, entities::{ + Operation, update::{Log, Update}, user::User, - Operation, }, }; use mungos::by_id::find_one_by_id; -use resolver_api::{derive::Resolver, Resolver}; +use resolver_api::Resolve; +use response::JsonString; use serde::{Deserialize, Serialize}; use serror::Json; use typeshare::typeshare; @@ -24,11 +25,12 @@ use uuid::Uuid; use crate::{ auth::auth_request, helpers::update::{init_execution_update, update_update}, - resource::{list_full_for_user_using_pattern, KomodoResource}, - state::{db_client, State}, + resource::{KomodoResource, list_full_for_user_using_pattern}, + state::db_client, }; mod action; +mod alerter; mod build; mod deployment; mod procedure; @@ -42,13 +44,19 @@ pub use { deployment::pull_deployment_inner, stack::pull_stack_inner, }; +pub struct ExecuteArgs { + pub user: User, + pub update: Update, +} + #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants, + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, )] #[variant_derive(Debug)] -#[resolver_target(State)] -#[resolver_args((User, Update))] +#[args(ExecuteArgs)] +#[response(JsonString)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] pub enum ExecuteRequest { // ==== SERVER ==== @@ -125,6 +133,9 @@ pub enum ExecuteRequest { // ==== SERVER TEMPLATE ==== LaunchServer(LaunchServer), + // ==== ALERTER ==== + TestAlerter(TestAlerter), + // ==== SYNC ==== RunSync(RunSync), } @@ -153,64 +164,73 @@ pub enum ExecutionResult { Batch(String), } -pub async fn inner_handler( +pub fn inner_handler( request: ExecuteRequest, user: User, -) -> anyhow::Result { - let req_id = Uuid::new_v4(); +) -> Pin< + Box< + dyn std::future::Future> + + Send, + >, +> { + Box::pin(async move { + let req_id = Uuid::new_v4(); - // need to validate no cancel is active before any update is created. - build::validate_cancel_build(&request).await?; + // need to validate no cancel is active before any update is created. + build::validate_cancel_build(&request).await?; - let update = init_execution_update(&request, &user).await?; + let update = init_execution_update(&request, &user).await?; - // This will be the case for the Batch exections, - // they don't have their own updates. - // The batch calls also call "inner_handler" themselves, - // and in their case will spawn tasks, so that isn't necessary - // here either. - if update.operation == Operation::None { - return Ok(ExecutionResult::Batch( - task(req_id, request, user, update).await?, - )); - } - - let handle = - tokio::spawn(task(req_id, request, user, update.clone())); - - tokio::spawn({ - let update_id = update.id.clone(); - async move { - let log = match handle.await { - Ok(Err(e)) => { - warn!("/execute request {req_id} task error: {e:#}",); - Log::error("task error", format_serror(&e.into())) - } - Err(e) => { - warn!("/execute request {req_id} spawn error: {e:?}",); - Log::error("spawn error", format!("{e:#?}")) - } - _ => return, - }; - let res = async { - let mut update = - find_one_by_id(&db_client().updates, &update_id) - .await - .context("failed to query to db")? - .context("no update exists with given id")?; - update.logs.push(log); - update.finalize(); - update_update(update).await - } - .await; - - if let Err(e) = res { - warn!("failed to update update with task error log | {e:#}"); - } + // This will be the case for the Batch exections, + // they don't have their own updates. + // The batch calls also call "inner_handler" themselves, + // and in their case will spawn tasks, so that isn't necessary + // here either. + if update.operation == Operation::None { + return Ok(ExecutionResult::Batch( + task(req_id, request, user, update).await?, + )); } - }); - Ok(ExecutionResult::Single(update)) + let handle = + tokio::spawn(task(req_id, request, user, update.clone())); + + tokio::spawn({ + let update_id = update.id.clone(); + async move { + let log = match handle.await { + Ok(Err(e)) => { + warn!("/execute request {req_id} task error: {e:#}",); + Log::error("task error", format_serror(&e.into())) + } + Err(e) => { + warn!("/execute request {req_id} spawn error: {e:?}",); + Log::error("spawn error", format!("{e:#?}")) + } + _ => return, + }; + let res = async { + let mut update = + find_one_by_id(&db_client().updates, &update_id) + .await + .context("failed to query to db")? + .context("no update exists with given id")?; + update.logs.push(log); + update.finalize(); + update_update(update).await + } + .await; + + if let Err(e) = res { + warn!( + "failed to update update with task error log | {e:#}" + ); + } + } + }); + + Ok(ExecutionResult::Single(update)) + }) } #[instrument( @@ -231,15 +251,14 @@ async fn task( info!("/execute request {req_id} | user: {}", user.username); let timer = Instant::now(); - let res = State - .resolve_request(request, (user, update)) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = match request.resolve(&ExecuteArgs { user, update }).await + { + Err(e) => Err(e.error), + Ok(JsonString::Err(e)) => Err( + anyhow::Error::from(e).context("failed to serialize response"), + ), + Ok(JsonString::Ok(res)) => Ok(res), + }; if let Err(e) = &res { warn!("/execute request {req_id} error: {e:#}"); diff --git a/bin/core/src/api/execute/procedure.rs b/bin/core/src/api/execute/procedure.rs index 28a7d5db5..51c603c15 100644 --- a/bin/core/src/api/execute/procedure.rs +++ b/bin/core/src/api/execute/procedure.rs @@ -1,6 +1,6 @@ use std::pin::Pin; -use formatting::{bold, colored, format_serror, muted, Color}; +use formatting::{Color, bold, colored, format_serror, muted}; use komodo_client::{ api::execute::{ BatchExecutionResponse, BatchRunProcedure, RunProcedure, @@ -17,10 +17,10 @@ use tokio::sync::Mutex; use crate::{ helpers::{procedure::execute_procedure, update::update_update}, resource::{self, refresh_procedure_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchRunProcedure { type Resource = Procedure; @@ -29,25 +29,29 @@ impl super::BatchExecute for BatchRunProcedure { } } -impl Resolve for State { - #[instrument(name = "BatchRunProcedure", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchRunProcedure { + #[instrument(name = "BatchRunProcedure", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchRunProcedure { pattern }: BatchRunProcedure, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RunProcedure", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RunProcedure { + #[instrument(name = "RunProcedure", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunProcedure { procedure }: RunProcedure, - (user, update): (User, Update), - ) -> anyhow::Result { - resolve_inner(procedure, user, update).await + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + Ok( + resolve_inner(self.procedure, user.clone(), update.clone()) + .await?, + ) } } diff --git a/bin/core/src/api/execute/repo.rs b/bin/core/src/api/execute/repo.rs index fd924adce..fc5dd9bd9 100644 --- a/bin/core/src/api/execute/repo.rs +++ b/bin/core/src/api/execute/repo.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, future::IntoFuture, time::Duration}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::format_serror; use komodo_client::{ api::{execute::*, write::RefreshRepoCache}, @@ -12,7 +12,6 @@ use komodo_client::{ repo::Repo, server::Server, update::{Log, Update}, - user::User, }, }; use mungos::{ @@ -28,6 +27,7 @@ use tokio_util::sync::CancellationToken; use crate::{ alert::send_alerts, + api::write::WriteArgs, helpers::{ builder::{cleanup_builder_instance, get_builder_periphery}, channel::repo_cancel_channel, @@ -42,10 +42,10 @@ use crate::{ update::update_update, }, resource::{self, refresh_repo_state_cache}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchCloneRepo { type Resource = Repo; @@ -54,27 +54,28 @@ impl super::BatchExecute for BatchCloneRepo { } } -impl Resolve for State { - #[instrument(name = "BatchCloneRepo", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchCloneRepo { + #[instrument(name = "BatchCloneRepo", skip( user), fields(user_id = user.id))] async fn resolve( - &self, - BatchCloneRepo { pattern }: BatchCloneRepo, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CloneRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for CloneRepo { + #[instrument(name = "CloneRepo", skip( user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - CloneRepo { repo }: CloneRepo, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; @@ -88,10 +89,11 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.cloning = true)?; + let mut update = update.clone(); update_update(update.clone()).await?; if repo.config.server_id.is_empty() { - return Err(anyhow!("repo has no server attached")); + return Err(anyhow!("repo has no server attached").into()); } let git_token = git_token( @@ -141,9 +143,10 @@ impl Resolve for State { update_last_pulled_time(&repo.name).await; } - if let Err(e) = State - .resolve(RefreshRepoCache { repo: repo.id }, user) + if let Err(e) = (RefreshRepoCache { repo: repo.id }) + .resolve(&WriteArgs { user: user.clone() }) .await + .map_err(|e| e.error) .context("Failed to refresh repo cache") { update.push_error_log( @@ -163,27 +166,28 @@ impl super::BatchExecute for BatchPullRepo { } } -impl Resolve for State { - #[instrument(name = "BatchPullRepo", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchPullRepo { + #[instrument(name = "BatchPullRepo", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchPullRepo { pattern }: BatchPullRepo, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "PullRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PullRepo { + #[instrument(name = "PullRepo", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PullRepo { repo }: PullRepo, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; @@ -197,10 +201,12 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pulling = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; if repo.config.server_id.is_empty() { - return Err(anyhow!("repo has no server attached")); + return Err(anyhow!("repo has no server attached").into()); } let git_token = git_token( @@ -254,9 +260,10 @@ impl Resolve for State { update_last_pulled_time(&repo.name).await; } - if let Err(e) = State - .resolve(RefreshRepoCache { repo: repo.id }, user) + if let Err(e) = (RefreshRepoCache { repo: repo.id }) + .resolve(&WriteArgs { user: user.clone() }) .await + .map_err(|e| e.error) .context("Failed to refresh repo cache") { update.push_error_log( @@ -272,7 +279,7 @@ impl Resolve for State { #[instrument(skip_all, fields(update_id = update.id))] async fn handle_server_update_return( update: Update, -) -> anyhow::Result { +) -> serror::Result { // Need to manually update the update before cache refresh, // and before broadcast with add_update. // The Err case of to_document should be unreachable, @@ -314,33 +321,34 @@ impl super::BatchExecute for BatchBuildRepo { } } -impl Resolve for State { - #[instrument(name = "BatchBuildRepo", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchBuildRepo { + #[instrument(name = "BatchBuildRepo", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchBuildRepo { pattern }: BatchBuildRepo, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "BuildRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for BuildRepo { + #[instrument(name = "BuildRepo", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - BuildRepo { repo }: BuildRepo, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let mut repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; if repo.config.builder_id.is_empty() { - return Err(anyhow!("Must attach builder to BuildRepo")); + return Err(anyhow!("Must attach builder to BuildRepo").into()); } // get the action state for the repo (or insert default). @@ -352,6 +360,7 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.building = true)?; + let mut update = update.clone(); update_update(update.clone()).await?; let git_token = git_token( @@ -547,7 +556,7 @@ async fn handle_builder_early_return( repo_id: String, repo_name: String, is_cancel: bool, -) -> anyhow::Result { +) -> serror::Result { update.finalize(); // Need to manually update the update before cache refresh, // and before broadcast with add_update. @@ -635,16 +644,15 @@ pub async fn validate_cancel_repo_build( Ok(()) } -impl Resolve for State { - #[instrument(name = "CancelRepoBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for CancelRepoBuild { + #[instrument(name = "CancelRepoBuild", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - CancelRepoBuild { repo }: CancelRepoBuild, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; @@ -657,9 +665,11 @@ impl Resolve for State { .and_then(|s| s.get().ok().map(|s| s.building)) .unwrap_or_default() { - return Err(anyhow!("Repo is not building.")); + return Err(anyhow!("Repo is not building.").into()); } + let mut update = update.clone(); + update.push_simple_log( "cancel triggered", "the repo build cancel has been triggered", @@ -685,7 +695,9 @@ impl Resolve for State { ) .await { - warn!("failed to set CancelRepoBuild Update status Complete after timeout | {e:#}") + warn!( + "failed to set CancelRepoBuild Update status Complete after timeout | {e:#}" + ) } }); diff --git a/bin/core/src/api/execute/server.rs b/bin/core/src/api/execute/server.rs index c5642400d..2e29c71d1 100644 --- a/bin/core/src/api/execute/server.rs +++ b/bin/core/src/api/execute/server.rs @@ -7,7 +7,6 @@ use komodo_client::{ permission::PermissionLevel, server::Server, update::{Log, Update}, - user::User, }, }; use periphery_client::api; @@ -17,19 +16,20 @@ use crate::{ helpers::{periphery_client, update::update_update}, monitor::update_cache_for_server, resource, - state::{action_states, State}, + state::action_states, }; -impl Resolve for State { +use super::ExecuteArgs; + +impl Resolve for StartContainer { #[instrument(name = "StartContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StartContainer { server, container }: StartContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -45,13 +45,17 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.starting_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::StartContainer { name: container }) + .request(api::container::StartContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -71,16 +75,15 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for RestartContainer { #[instrument(name = "RestartContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RestartContainer { server, container }: RestartContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -96,13 +99,17 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.restarting_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::RestartContainer { name: container }) + .request(api::container::RestartContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -124,16 +131,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PauseContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseContainer { + #[instrument(name = "PauseContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseContainer { server, container }: PauseContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -149,13 +155,17 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pausing_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::PauseContainer { name: container }) + .request(api::container::PauseContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -175,16 +185,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UnpauseContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseContainer { + #[instrument(name = "UnpauseContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseContainer { server, container }: UnpauseContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -200,13 +209,17 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.unpausing_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::container::UnpauseContainer { name: container }) + .request(api::container::UnpauseContainer { + name: self.container, + }) .await { Ok(log) => log, @@ -228,21 +241,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StopContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopContainer { + #[instrument(name = "StopContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopContainer { - server, - container, - signal, - time, - }: StopContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -258,6 +265,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.stopping_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -265,9 +274,9 @@ impl Resolve for State { let log = match periphery .request(api::container::StopContainer { - name: container, - signal, - time, + name: self.container, + signal: self.signal, + time: self.time, }) .await { @@ -288,21 +297,21 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DestroyContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DestroyContainer { + #[instrument(name = "DestroyContainer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DestroyContainer { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + let DestroyContainer { server, container, signal, time, - }: DestroyContainer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Execute, ) .await?; @@ -318,6 +327,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_containers = true)?; + let mut update = update.clone(); + // Send update after setting action state, this way frontend gets correct state. update_update(update.clone()).await?; @@ -348,16 +359,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StartAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StartAllContainers { + #[instrument(name = "StartAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StartAllContainers { server }: StartAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -373,6 +383,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.starting_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -397,16 +409,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RestartAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RestartAllContainers { + #[instrument(name = "RestartAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RestartAllContainers { server }: RestartAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -422,6 +433,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.restarting_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -448,16 +461,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PauseAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseAllContainers { + #[instrument(name = "PauseAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseAllContainers { server }: PauseAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -473,6 +485,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pausing_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -497,16 +511,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UnpauseAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseAllContainers { + #[instrument(name = "UnpauseAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseAllContainers { server }: UnpauseAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -522,6 +535,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.unpausing_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -548,16 +563,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StopAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopAllContainers { + #[instrument(name = "StopAllContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopAllContainers { server }: StopAllContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -573,6 +587,8 @@ impl Resolve for State { let _action_guard = action_state .update(|state| state.stopping_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let logs = periphery_client(&server)? @@ -597,16 +613,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneContainers { + #[instrument(name = "PruneContainers", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneContainers { server }: PruneContainers, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -622,6 +637,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_containers = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -652,37 +669,43 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DeleteNetwork", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeleteNetwork { + #[instrument(name = "DeleteNetwork", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeleteNetwork { server, name }: DeleteNetwork, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::network::DeleteNetwork { name: name.clone() }) + .request(api::network::DeleteNetwork { + name: self.name.clone(), + }) .await .context(format!( - "failed to delete network {name} on server {}", - server.name + "failed to delete network {} on server {}", + self.name, server.name )) { Ok(log) => log, Err(e) => Log::error( "delete network", format_serror( - &e.context(format!("failed to delete network {name}")) - .into(), + &e.context(format!( + "failed to delete network {}", + self.name + )) + .into(), ), ), }; @@ -697,16 +720,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneNetworks", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneNetworks { + #[instrument(name = "PruneNetworks", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneNetworks { server }: PruneNetworks, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -722,6 +744,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_networks = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -750,36 +774,40 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DeleteImage", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeleteImage { + #[instrument(name = "DeleteImage", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeleteImage { server, name }: DeleteImage, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::image::DeleteImage { name: name.clone() }) + .request(api::image::DeleteImage { + name: self.name.clone(), + }) .await .context(format!( - "failed to delete image {name} on server {}", - server.name + "failed to delete image {} on server {}", + self.name, server.name )) { Ok(log) => log, Err(e) => Log::error( "delete image", format_serror( - &e.context(format!("failed to delete image {name}")).into(), + &e.context(format!("failed to delete image {}", self.name)) + .into(), ), ), }; @@ -794,16 +822,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneImages", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneImages { + #[instrument(name = "PruneImages", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneImages { server }: PruneImages, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -819,6 +846,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_images = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -845,37 +874,43 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "DeleteVolume", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeleteVolume { + #[instrument(name = "DeleteVolume", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeleteVolume { server, name }: DeleteVolume, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; let log = match periphery - .request(api::volume::DeleteVolume { name: name.clone() }) + .request(api::volume::DeleteVolume { + name: self.name.clone(), + }) .await .context(format!( - "failed to delete volume {name} on server {}", - server.name + "failed to delete volume {} on server {}", + self.name, server.name )) { Ok(log) => log, Err(e) => Log::error( "delete volume", format_serror( - &e.context(format!("failed to delete volume {name}")) - .into(), + &e.context(format!( + "failed to delete volume {}", + self.name + )) + .into(), ), ), }; @@ -890,16 +925,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneVolumes", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneVolumes { + #[instrument(name = "PruneVolumes", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneVolumes { server }: PruneVolumes, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -915,6 +949,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_volumes = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -941,16 +977,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneDockerBuilders", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneDockerBuilders { + #[instrument(name = "PruneDockerBuilders", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneDockerBuilders { server }: PruneDockerBuilders, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -966,6 +1001,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_builders = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -992,16 +1029,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneBuildx", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneBuildx { + #[instrument(name = "PruneBuildx", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneBuildx { server }: PruneBuildx, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -1017,6 +1053,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_buildx = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; @@ -1043,16 +1081,15 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "PruneSystem", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PruneSystem { + #[instrument(name = "PruneSystem", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PruneSystem { server }: PruneSystem, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Execute, ) .await?; @@ -1068,6 +1105,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pruning_system = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; let periphery = periphery_client(&server)?; diff --git a/bin/core/src/api/execute/server_template.rs b/bin/core/src/api/execute/server_template.rs index 5ef9fadbe..28de0c2fa 100644 --- a/bin/core/src/api/execute/server_template.rs +++ b/bin/core/src/api/execute/server_template.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::format_serror; use komodo_client::{ api::{execute::LaunchServer, write::CreateServer}, @@ -7,51 +7,51 @@ use komodo_client::{ server::PartialServerConfig, server_template::{ServerTemplate, ServerTemplateConfig}, update::Update, - user::User, }, }; use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, cloud::{ aws::ec2::launch_ec2_instance, hetzner::launch_hetzner_server, }, helpers::update::update_update, resource, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { - #[instrument(name = "LaunchServer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +use super::ExecuteArgs; + +impl Resolve for LaunchServer { + #[instrument(name = "LaunchServer", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - LaunchServer { - name, - server_template, - }: LaunchServer, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { // validate name isn't already taken by another server if db_client() .servers .find_one(doc! { - "name": &name + "name": &self.name }) .await .context("failed to query db for servers")? .is_some() { - return Err(anyhow!("name is already taken")); + return Err(anyhow!("name is already taken").into()); } let template = resource::get_check_permissions::( - &server_template, - &user, + &self.server_template, + user, PermissionLevel::Execute, ) .await?; + let mut update = update.clone(); + update.push_simple_log( "launching server", format!("{:#?}", template.config), @@ -63,24 +63,24 @@ impl Resolve for State { let region = config.region.clone(); let use_https = config.use_https; let port = config.port; - let instance = match launch_ec2_instance(&name, config).await - { - Ok(instance) => instance, - Err(e) => { - update.push_error_log( - "launch server", - format!("failed to launch aws instance\n\n{e:#?}"), - ); - update.finalize(); - update_update(update.clone()).await?; - return Ok(update); - } - }; + let instance = + match launch_ec2_instance(&self.name, config).await { + Ok(instance) => instance, + Err(e) => { + update.push_error_log( + "launch server", + format!("failed to launch aws instance\n\n{e:#?}"), + ); + update.finalize(); + update_update(update.clone()).await?; + return Ok(update); + } + }; update.push_simple_log( "launch server", format!( - "successfully launched server {name} on ip {}", - instance.ip + "successfully launched server {} on ip {}", + self.name, instance.ip ), ); let protocol = if use_https { "https" } else { "http" }; @@ -95,24 +95,24 @@ impl Resolve for State { let datacenter = config.datacenter; let use_https = config.use_https; let port = config.port; - let server = match launch_hetzner_server(&name, config).await - { - Ok(server) => server, - Err(e) => { - update.push_error_log( - "launch server", - format!("failed to launch hetzner server\n\n{e:#?}"), - ); - update.finalize(); - update_update(update.clone()).await?; - return Ok(update); - } - }; + let server = + match launch_hetzner_server(&self.name, config).await { + Ok(server) => server, + Err(e) => { + update.push_error_log( + "launch server", + format!("failed to launch hetzner server\n\n{e:#?}"), + ); + update.finalize(); + update_update(update.clone()).await?; + return Ok(update); + } + }; update.push_simple_log( "launch server", format!( - "successfully launched server {name} on ip {}", - server.ip + "successfully launched server {} on ip {}", + self.name, server.ip ), ); let protocol = if use_https { "https" } else { "http" }; @@ -125,7 +125,13 @@ impl Resolve for State { } }; - match self.resolve(CreateServer { name, config }, user).await { + match (CreateServer { + name: self.name, + config, + }) + .resolve(&WriteArgs { user: user.clone() }) + .await + { Ok(server) => { update.push_simple_log( "create server", @@ -136,7 +142,9 @@ impl Resolve for State { Err(e) => { update.push_error_log( "create server", - format_serror(&e.context("failed to create server").into()), + format_serror( + &e.error.context("failed to create server").into(), + ), ); } }; diff --git a/bin/core/src/api/execute/stack.rs b/bin/core/src/api/execute/stack.rs index 01c1a118b..0e82ee3ea 100644 --- a/bin/core/src/api/execute/stack.rs +++ b/bin/core/src/api/execute/stack.rs @@ -9,7 +9,6 @@ use komodo_client::{ server::Server, stack::{Stack, StackInfo}, update::{Log, Update}, - user::User, }, }; use mungos::mongodb::bson::{doc, to_document}; @@ -17,6 +16,7 @@ use periphery_client::api::compose::*; use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, helpers::{ interpolate::{ add_interp_update_log, @@ -31,47 +31,44 @@ use crate::{ monitor::update_cache_for_server, resource, stack::{execute::execute_compose, get_stack_and_server}, - state::{action_states, db_client, State}, + state::{action_states, db_client}, }; -use super::ExecuteRequest; +use super::{ExecuteArgs, ExecuteRequest}; impl super::BatchExecute for BatchDeployStack { type Resource = Stack; fn single_request(stack: String) -> ExecuteRequest { ExecuteRequest::DeployStack(DeployStack { stack, - service: None, + services: Vec::new(), stop_time: None, }) } } -impl Resolve for State { - #[instrument(name = "BatchDeployStack", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDeployStack { + #[instrument(name = "BatchDeployStack", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDeployStack { pattern }: BatchDeployStack, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::(&self.pattern, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeployStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeployStack { + #[instrument(name = "DeployStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DeployStack { - stack, - service, - stop_time, - }: DeployStack, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (mut stack, server) = get_stack_and_server( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, true, ) @@ -86,12 +83,17 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.deploying = true)?; + let mut update = update.clone(); + update_update(update.clone()).await?; - if let Some(service) = &service { + if !self.services.is_empty() { update.logs.push(Log::simple( - &format!("Service: {service}"), - format!("Execution requested for Stack service {service}"), + "Service/s", + format!( + "Execution requested for Stack service/s {}", + self.services.join(", ") + ), )) } @@ -153,6 +155,13 @@ impl Resolve for State { &mut secret_replacers, )?; + interpolate_variables_secrets_into_system_command( + &vars_and_secrets, + &mut stack.config.post_deploy, + &mut global_replacers, + &mut secret_replacers, + )?; + add_interp_update_log( &mut update, &global_replacers, @@ -171,12 +180,13 @@ impl Resolve for State { file_contents, missing_files, remote_errors, + compose_config, commit_hash, commit_message, } = periphery_client(&server)? .request(ComposeUp { stack: stack.clone(), - service, + services: self.services, git_token, registry_token, replacers: secret_replacers.into_iter().collect(), @@ -200,12 +210,14 @@ impl Resolve for State { let ( deployed_services, deployed_contents, + deployed_config, deployed_hash, deployed_message, ) = if deployed { ( Some(latest_services.clone()), Some(file_contents.clone()), + compose_config, commit_hash.clone(), commit_message.clone(), ) @@ -213,6 +225,7 @@ impl Resolve for State { ( stack.info.deployed_services, stack.info.deployed_contents, + stack.info.deployed_config, stack.info.deployed_hash, stack.info.deployed_message, ) @@ -223,6 +236,7 @@ impl Resolve for State { deployed_project_name: project_name.into(), deployed_services, deployed_contents, + deployed_config, deployed_hash, deployed_message, latest_services, @@ -284,39 +298,39 @@ impl super::BatchExecute for BatchDeployStackIfChanged { } } -impl Resolve for State { - #[instrument(name = "BatchDeployStackIfChanged", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDeployStackIfChanged { + #[instrument(name = "BatchDeployStackIfChanged", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDeployStackIfChanged { pattern }: BatchDeployStackIfChanged, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user) - .await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + Ok( + super::batch_execute::( + &self.pattern, + user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeployStackIfChanged", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DeployStackIfChanged { + #[instrument(name = "DeployStackIfChanged", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - DeployStackIfChanged { stack, stop_time }: DeployStackIfChanged, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, ) .await?; - State - .resolve( - RefreshStackCache { - stack: stack.id.clone(), - }, - user.clone(), - ) - .await?; + RefreshStackCache { + stack: stack.id.clone(), + } + .resolve(&WriteArgs { user: user.clone() }) + .await?; let stack = resource::get::(&stack.id).await?; let changed = match ( &stack.info.deployed_contents, @@ -343,6 +357,8 @@ impl Resolve for State { _ => false, }; + let mut update = update.clone(); + if !changed { update.push_simple_log( "Diff compose files", @@ -356,30 +372,35 @@ impl Resolve for State { // This is usually done in crate::helpers::update::init_execution_update. update.id = add_update_without_send(&update).await?; - State - .resolve( - DeployStack { - stack: stack.name, - service: None, - stop_time, - }, - (user, update), - ) - .await + DeployStack { + stack: stack.name, + services: Vec::new(), + stop_time: self.stop_time, + } + .resolve(&ExecuteArgs { + user: user.clone(), + update, + }) + .await } } pub async fn pull_stack_inner( mut stack: Stack, - service: Option, + services: Vec, server: &Server, - update: Option<&mut Update>, + mut update: Option<&mut Update>, ) -> anyhow::Result { - if let (Some(service), Some(update)) = (&service, update) { - update.logs.push(Log::simple( - &format!("Service: {service}"), - format!("Execution requested for Stack service {service}"), - )) + if let Some(update) = update.as_mut() { + if !services.is_empty() { + update.logs.push(Log::simple( + "Service/s", + format!( + "Execution requested for Stack service/s {}", + services.join(", ") + ), + )) + } } let git_token = crate::helpers::git_token( @@ -397,10 +418,40 @@ pub async fn pull_stack_inner( || format!("Failed to get registry token in call to db. Stopping run. | {} | {}", stack.config.registry_provider, stack.config.registry_account), )?; + // interpolate variables / secrets + if !stack.config.skip_secret_interp { + let vars_and_secrets = get_variables_and_secrets().await?; + + let mut global_replacers = HashSet::new(); + let mut secret_replacers = HashSet::new(); + + interpolate_variables_secrets_into_string( + &vars_and_secrets, + &mut stack.config.file_contents, + &mut global_replacers, + &mut secret_replacers, + )?; + + interpolate_variables_secrets_into_string( + &vars_and_secrets, + &mut stack.config.environment, + &mut global_replacers, + &mut secret_replacers, + )?; + + if let Some(update) = update { + add_interp_update_log( + update, + &global_replacers, + &secret_replacers, + ); + } + }; + let res = periphery_client(server)? .request(ComposePull { stack, - service, + services, git_token, registry_token, }) @@ -412,16 +463,15 @@ pub async fn pull_stack_inner( Ok(res) } -impl Resolve for State { - #[instrument(name = "PullStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PullStack { + #[instrument(name = "PullStack", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - PullStack { stack, service }: PullStack, - (user, mut update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { let (stack, server) = get_stack_and_server( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, true, ) @@ -436,11 +486,16 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.pulling = true)?; + let mut update = update.clone(); update_update(update.clone()).await?; - let res = - pull_stack_inner(stack, service, &server, Some(&mut update)) - .await?; + let res = pull_stack_inner( + stack, + self.services, + &server, + Some(&mut update), + ) + .await?; update.logs.extend(res.logs); update.finalize(); @@ -450,104 +505,100 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "StartStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StartStack { + #[instrument(name = "StartStack", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - StartStack { stack, service }: StartStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.services, + user, |state| state.starting = true, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "RestartStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for RestartStack { + #[instrument(name = "RestartStack", skip(user, update), fields(user_id = user.id))] async fn resolve( - &self, - RestartStack { stack, service }: RestartStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.services, + user, |state| { state.restarting = true; }, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "PauseStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for PauseStack { + #[instrument(name = "PauseStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - PauseStack { stack, service }: PauseStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.services, + user, |state| state.pausing = true, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "UnpauseStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for UnpauseStack { + #[instrument(name = "UnpauseStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - UnpauseStack { stack, service }: UnpauseStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.services, + user, |state| state.unpausing = true, - update, + update.clone(), (), ) .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "StopStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for StopStack { + #[instrument(name = "StopStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - StopStack { - stack, - stop_time, - service, - }: StopStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.services, + user, |state| state.stopping = true, - update, - stop_time, + update.clone(), + self.stop_time, ) .await + .map_err(Into::into) } } @@ -556,44 +607,40 @@ impl super::BatchExecute for BatchDestroyStack { fn single_request(stack: String) -> ExecuteRequest { ExecuteRequest::DestroyStack(DestroyStack { stack, - service: None, + services: Vec::new(), remove_orphans: false, stop_time: None, }) } } -impl Resolve for State { - #[instrument(name = "BatchDestroyStack", skip(self, user), fields(user_id = user.id))] +impl Resolve for BatchDestroyStack { + #[instrument(name = "BatchDestroyStack", skip(user), fields(user_id = user.id))] async fn resolve( - &self, - BatchDestroyStack { pattern }: BatchDestroyStack, - (user, _): (User, Update), - ) -> anyhow::Result { - super::batch_execute::(&pattern, &user).await + self, + ExecuteArgs { user, .. }: &ExecuteArgs, + ) -> serror::Result { + super::batch_execute::(&self.pattern, user) + .await + .map_err(Into::into) } } -impl Resolve for State { - #[instrument(name = "DestroyStack", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +impl Resolve for DestroyStack { + #[instrument(name = "DestroyStack", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - DestroyStack { - stack, - service, - remove_orphans, - stop_time, - }: DestroyStack, - (user, update): (User, Update), - ) -> anyhow::Result { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { execute_compose::( - &stack, - service, - &user, + &self.stack, + self.services, + user, |state| state.destroying = true, - update, - (stop_time, remove_orphans), + update.clone(), + (self.stop_time, self.remove_orphans), ) .await + .map_err(Into::into) } } diff --git a/bin/core/src/api/execute/sync.rs b/bin/core/src/api/execute/sync.rs index 60f6176f2..2b5fbead7 100644 --- a/bin/core/src/api/execute/sync.rs +++ b/bin/core/src/api/execute/sync.rs @@ -1,11 +1,11 @@ use std::{collections::HashMap, str::FromStr}; -use anyhow::{anyhow, Context}; -use formatting::{colored, format_serror, Color}; +use anyhow::{Context, anyhow}; +use formatting::{Color, colored, format_serror}; use komodo_client::{ api::{execute::RunSync, write::RefreshResourceSyncPending}, entities::{ - self, + self, ResourceTargetVariant, action::Action, alerter::Alerter, build::Build, @@ -20,45 +20,44 @@ use komodo_client::{ stack::Stack, sync::ResourceSync, update::{Log, Update}, - user::{sync_user, User}, - ResourceTargetVariant, + user::sync_user, }, }; use mongo_indexed::doc; -use mungos::{ - by_id::update_one_by_id, - mongodb::bson::{oid::ObjectId, to_document}, -}; +use mungos::{by_id::update_one_by_id, mongodb::bson::oid::ObjectId}; use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, helpers::{query::get_id_to_tags, update::update_update}, - resource::{self, refresh_resource_sync_state_cache}, - state::{action_states, db_client, State}, + resource, + state::{action_states, db_client}, sync::{ - deploy::{ - build_deploy_cache, deploy_from_cache, SyncDeployParams, - }, - execute::{get_updates_for_execution, ExecuteResourceSync}, - remote::RemoteResources, AllResourcesById, ResourceSyncTrait, + deploy::{ + SyncDeployParams, build_deploy_cache, deploy_from_cache, + }, + execute::{ExecuteResourceSync, get_updates_for_execution}, + remote::RemoteResources, }, }; -impl Resolve for State { - #[instrument(name = "RunSync", skip(self, user, update), fields(user_id = user.id, update_id = update.id))] +use super::ExecuteArgs; + +impl Resolve for RunSync { + #[instrument(name = "RunSync", skip(user, update), fields(user_id = user.id, update_id = update.id))] async fn resolve( - &self, - RunSync { + self, + ExecuteArgs { user, update }: &ExecuteArgs, + ) -> serror::Result { + let RunSync { sync, resource_type: match_resource_type, resources: match_resources, - }: RunSync, - (user, mut update): (User, Update), - ) -> anyhow::Result { + } = self; let sync = resource::get_check_permissions::< entities::sync::ResourceSync, - >(&sync, &user, PermissionLevel::Execute) + >(&sync, user, PermissionLevel::Execute) .await?; // get the action state for the sync (or insert default). @@ -72,6 +71,8 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.syncing = true)?; + let mut update = update.clone(); + // Send update here for FE to recheck action state update_update(update.clone()).await?; @@ -90,7 +91,9 @@ impl Resolve for State { update_update(update.clone()).await?; if !file_errors.is_empty() { - return Err(anyhow!("Found file errors. Cannot execute sync.")); + return Err( + anyhow!("Found file errors. Cannot execute sync.").into(), + ); } let resources = resources?; @@ -203,7 +206,7 @@ impl Resolve for State { let delete = sync.config.managed || sync.config.delete; - let (servers_to_create, servers_to_update, servers_to_delete) = + let server_deltas = if sync.config.include_resources { get_updates_for_execution::( resources.servers, delete, @@ -213,22 +216,11 @@ impl Resolve for State { &id_to_tags, &sync.config.match_tags, ) - .await?; - let ( - deployments_to_create, - deployments_to_update, - deployments_to_delete, - ) = get_updates_for_execution::( - resources.deployments, - delete, - &all_resources, - match_resource_type, - match_resources.as_deref(), - &id_to_tags, - &sync.config.match_tags, - ) - .await?; - let (stacks_to_create, stacks_to_update, stacks_to_delete) = + .await? + } else { + Default::default() + }; + let stack_deltas = if sync.config.include_resources { get_updates_for_execution::( resources.stacks, delete, @@ -238,8 +230,25 @@ impl Resolve for State { &id_to_tags, &sync.config.match_tags, ) - .await?; - let (builds_to_create, builds_to_update, builds_to_delete) = + .await? + } else { + Default::default() + }; + let deployment_deltas = if sync.config.include_resources { + get_updates_for_execution::( + resources.deployments, + delete, + &all_resources, + match_resource_type, + match_resources.as_deref(), + &id_to_tags, + &sync.config.match_tags, + ) + .await? + } else { + Default::default() + }; + let build_deltas = if sync.config.include_resources { get_updates_for_execution::( resources.builds, delete, @@ -249,8 +258,11 @@ impl Resolve for State { &id_to_tags, &sync.config.match_tags, ) - .await?; - let (repos_to_create, repos_to_update, repos_to_delete) = + .await? + } else { + Default::default() + }; + let repo_deltas = if sync.config.include_resources { get_updates_for_execution::( resources.repos, delete, @@ -260,22 +272,25 @@ impl Resolve for State { &id_to_tags, &sync.config.match_tags, ) - .await?; - let ( - procedures_to_create, - procedures_to_update, - procedures_to_delete, - ) = get_updates_for_execution::( - resources.procedures, - delete, - &all_resources, - match_resource_type, - match_resources.as_deref(), - &id_to_tags, - &sync.config.match_tags, - ) - .await?; - let (actions_to_create, actions_to_update, actions_to_delete) = + .await? + } else { + Default::default() + }; + let procedure_deltas = if sync.config.include_resources { + get_updates_for_execution::( + resources.procedures, + delete, + &all_resources, + match_resource_type, + match_resources.as_deref(), + &id_to_tags, + &sync.config.match_tags, + ) + .await? + } else { + Default::default() + }; + let action_deltas = if sync.config.include_resources { get_updates_for_execution::( resources.actions, delete, @@ -285,8 +300,11 @@ impl Resolve for State { &id_to_tags, &sync.config.match_tags, ) - .await?; - let (builders_to_create, builders_to_update, builders_to_delete) = + .await? + } else { + Default::default() + }; + let builder_deltas = if sync.config.include_resources { get_updates_for_execution::( resources.builders, delete, @@ -296,8 +314,11 @@ impl Resolve for State { &id_to_tags, &sync.config.match_tags, ) - .await?; - let (alerters_to_create, alerters_to_update, alerters_to_delete) = + .await? + } else { + Default::default() + }; + let alerter_deltas = if sync.config.include_resources { get_updates_for_execution::( resources.alerters, delete, @@ -307,35 +328,38 @@ impl Resolve for State { &id_to_tags, &sync.config.match_tags, ) - .await?; - let ( - server_templates_to_create, - server_templates_to_update, - server_templates_to_delete, - ) = get_updates_for_execution::( - resources.server_templates, - delete, - &all_resources, - match_resource_type, - match_resources.as_deref(), - &id_to_tags, - &sync.config.match_tags, - ) - .await?; - let ( - resource_syncs_to_create, - resource_syncs_to_update, - resource_syncs_to_delete, - ) = get_updates_for_execution::( - resources.resource_syncs, - delete, - &all_resources, - match_resource_type, - match_resources.as_deref(), - &id_to_tags, - &sync.config.match_tags, - ) - .await?; + .await? + } else { + Default::default() + }; + let server_template_deltas = if sync.config.include_resources { + get_updates_for_execution::( + resources.server_templates, + delete, + &all_resources, + match_resource_type, + match_resources.as_deref(), + &id_to_tags, + &sync.config.match_tags, + ) + .await? + } else { + Default::default() + }; + let resource_sync_deltas = if sync.config.include_resources { + get_updates_for_execution::( + resources.resource_syncs, + delete, + &all_resources, + match_resource_type, + match_resources.as_deref(), + &id_to_tags, + &sync.config.match_tags, + ) + .await? + } else { + Default::default() + }; let ( variables_to_create, @@ -343,12 +367,11 @@ impl Resolve for State { variables_to_delete, ) = if match_resource_type.is_none() && match_resources.is_none() - && sync.config.match_tags.is_empty() + && sync.config.include_variables { crate::sync::variables::get_updates_for_execution( resources.variables, - // Delete doesn't work with variables when match tags are set - sync.config.match_tags.is_empty() && delete, + delete, ) .await? } else { @@ -360,12 +383,11 @@ impl Resolve for State { user_groups_to_delete, ) = if match_resource_type.is_none() && match_resources.is_none() - && sync.config.match_tags.is_empty() + && sync.config.include_user_groups { crate::sync::user_groups::get_updates_for_execution( resources.user_groups, - // Delete doesn't work with user groups when match tags are set - sync.config.match_tags.is_empty() && delete, + delete, &all_resources, ) .await? @@ -374,39 +396,17 @@ impl Resolve for State { }; if deploy_cache.is_empty() - && resource_syncs_to_create.is_empty() - && resource_syncs_to_update.is_empty() - && resource_syncs_to_delete.is_empty() - && server_templates_to_create.is_empty() - && server_templates_to_update.is_empty() - && server_templates_to_delete.is_empty() - && servers_to_create.is_empty() - && servers_to_update.is_empty() - && servers_to_delete.is_empty() - && deployments_to_create.is_empty() - && deployments_to_update.is_empty() - && deployments_to_delete.is_empty() - && stacks_to_create.is_empty() - && stacks_to_update.is_empty() - && stacks_to_delete.is_empty() - && builds_to_create.is_empty() - && builds_to_update.is_empty() - && builds_to_delete.is_empty() - && builders_to_create.is_empty() - && builders_to_update.is_empty() - && builders_to_delete.is_empty() - && alerters_to_create.is_empty() - && alerters_to_update.is_empty() - && alerters_to_delete.is_empty() - && repos_to_create.is_empty() - && repos_to_update.is_empty() - && repos_to_delete.is_empty() - && procedures_to_create.is_empty() - && procedures_to_update.is_empty() - && procedures_to_delete.is_empty() - && actions_to_create.is_empty() - && actions_to_update.is_empty() - && actions_to_delete.is_empty() + && resource_sync_deltas.no_changes() + && server_template_deltas.no_changes() + && server_deltas.no_changes() + && deployment_deltas.no_changes() + && stack_deltas.no_changes() + && build_deltas.no_changes() + && builder_deltas.no_changes() + && alerter_deltas.no_changes() + && repo_deltas.no_changes() + && procedure_deltas.no_changes() + && action_deltas.no_changes() && user_groups_to_create.is_empty() && user_groups_to_update.is_empty() && user_groups_to_delete.is_empty() @@ -449,111 +449,57 @@ impl Resolve for State { ); maybe_extend( &mut update.logs, - ResourceSync::execute_sync_updates( - resource_syncs_to_create, - resource_syncs_to_update, - resource_syncs_to_delete, - ) - .await, + ResourceSync::execute_sync_updates(resource_sync_deltas).await, ); maybe_extend( &mut update.logs, - ServerTemplate::execute_sync_updates( - server_templates_to_create, - server_templates_to_update, - server_templates_to_delete, - ) - .await, + ServerTemplate::execute_sync_updates(server_template_deltas) + .await, ); maybe_extend( &mut update.logs, - Server::execute_sync_updates( - servers_to_create, - servers_to_update, - servers_to_delete, - ) - .await, + Server::execute_sync_updates(server_deltas).await, ); maybe_extend( &mut update.logs, - Alerter::execute_sync_updates( - alerters_to_create, - alerters_to_update, - alerters_to_delete, - ) - .await, + Alerter::execute_sync_updates(alerter_deltas).await, ); maybe_extend( &mut update.logs, - Action::execute_sync_updates( - actions_to_create, - actions_to_update, - actions_to_delete, - ) - .await, + Action::execute_sync_updates(action_deltas).await, ); // Dependent on server maybe_extend( &mut update.logs, - Builder::execute_sync_updates( - builders_to_create, - builders_to_update, - builders_to_delete, - ) - .await, + Builder::execute_sync_updates(builder_deltas).await, ); maybe_extend( &mut update.logs, - Repo::execute_sync_updates( - repos_to_create, - repos_to_update, - repos_to_delete, - ) - .await, + Repo::execute_sync_updates(repo_deltas).await, ); // Dependant on builder maybe_extend( &mut update.logs, - Build::execute_sync_updates( - builds_to_create, - builds_to_update, - builds_to_delete, - ) - .await, + Build::execute_sync_updates(build_deltas).await, ); // Dependant on server / build maybe_extend( &mut update.logs, - Deployment::execute_sync_updates( - deployments_to_create, - deployments_to_update, - deployments_to_delete, - ) - .await, + Deployment::execute_sync_updates(deployment_deltas).await, ); // stack only depends on server, but maybe will depend on build later. maybe_extend( &mut update.logs, - Stack::execute_sync_updates( - stacks_to_create, - stacks_to_update, - stacks_to_delete, - ) - .await, + Stack::execute_sync_updates(stack_deltas).await, ); // Dependant on everything maybe_extend( &mut update.logs, - Procedure::execute_sync_updates( - procedures_to_create, - procedures_to_update, - procedures_to_delete, - ) - .await, + Procedure::execute_sync_updates(procedure_deltas).await, ); // Execute the deploy cache @@ -581,39 +527,27 @@ impl Resolve for State { ) } - if let Err(e) = State - .resolve( - RefreshResourceSyncPending { sync: sync.id }, - sync_user().to_owned(), - ) + if let Err(e) = (RefreshResourceSyncPending { sync: sync.id }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) .await { - warn!("failed to refresh sync {} after run | {e:#}", sync.name); + warn!( + "failed to refresh sync {} after run | {:#}", + sync.name, e.error + ); update.push_error_log( "refresh sync", format_serror( - &e.context("failed to refresh sync pending after run") + &e.error + .context("failed to refresh sync pending after run") .into(), ), ); } update.finalize(); - - // Need to manually update the update before cache refresh, - // and before broadcast with add_update. - // The Err case of to_document should be unreachable, - // but will fail to update cache in that case. - if let Ok(update_doc) = to_document(&update) { - let _ = update_one_by_id( - &db.updates, - &update.id, - mungos::update::Update::Set(update_doc), - None, - ) - .await; - refresh_resource_sync_state_cache().await; - } update_update(update.clone()).await?; Ok(update) diff --git a/bin/core/src/api/read/action.rs b/bin/core/src/api/read/action.rs index 3501333a3..b9428562f 100644 --- a/bin/core/src/api/read/action.rs +++ b/bin/core/src/api/read/action.rs @@ -6,7 +6,6 @@ use komodo_client::{ Action, ActionActionState, ActionListItem, ActionState, }, permission::PermissionLevel, - user::User, }, }; use resolver_api::Resolve; @@ -14,64 +13,71 @@ use resolver_api::Resolve; use crate::{ helpers::query::get_all_tags, resource, - state::{action_state_cache, action_states, State}, + state::{action_state_cache, action_states}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetAction { async fn resolve( - &self, - GetAction { action }: GetAction, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &action, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.action, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListActions { async fn resolve( - &self, - ListActions { query }: ListActions, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullActions { async fn resolve( - &self, - ListFullActions { query }: ListFullActions, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetActionActionState { async fn resolve( - &self, - GetActionActionState { action }: GetActionActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let action = resource::get_check_permissions::( - &action, - &user, + &self.action, + user, PermissionLevel::Read, ) .await?; @@ -85,15 +91,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetActionsSummary { async fn resolve( - &self, - GetActionsSummary {}: GetActionsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let actions = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await diff --git a/bin/core/src/api/read/alert.rs b/bin/core/src/api/read/alert.rs index 2d789545e..574869430 100644 --- a/bin/core/src/api/read/alert.rs +++ b/bin/core/src/api/read/alert.rs @@ -5,7 +5,7 @@ use komodo_client::{ }, entities::{ deployment::Deployment, server::Server, stack::Stack, - sync::ResourceSync, user::User, + sync::ResourceSync, }, }; use mungos::{ @@ -16,29 +16,29 @@ use mungos::{ use resolver_api::Resolve; use crate::{ - config::core_config, - resource::get_resource_ids_for_user, - state::{db_client, State}, + config::core_config, resource::get_resource_ids_for_user, + state::db_client, }; +use super::ReadArgs; + const NUM_ALERTS_PER_PAGE: u64 = 100; -impl Resolve for State { +impl Resolve for ListAlerts { async fn resolve( - &self, - ListAlerts { query, page }: ListAlerts, - user: User, - ) -> anyhow::Result { - let mut query = query.unwrap_or_default(); + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let mut query = self.query.unwrap_or_default(); if !user.admin && !core_config().transparent_mode { let server_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; let stack_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; let deployment_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; let sync_ids = - get_resource_ids_for_user::(&user).await?; + get_resource_ids_for_user::(user).await?; query.extend(doc! { "$or": [ { "target.type": "Server", "target.id": { "$in": &server_ids } }, @@ -55,7 +55,7 @@ impl Resolve for State { FindOptions::builder() .sort(doc! { "ts": -1 }) .limit(NUM_ALERTS_PER_PAGE as i64) - .skip(page * NUM_ALERTS_PER_PAGE) + .skip(self.page * NUM_ALERTS_PER_PAGE) .build(), ) .await @@ -64,7 +64,7 @@ impl Resolve for State { let next_page = if alerts.len() < NUM_ALERTS_PER_PAGE as usize { None } else { - Some((page + 1) as i64) + Some((self.page + 1) as i64) }; let res = ListAlertsResponse { next_page, alerts }; @@ -73,15 +73,16 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetAlert { async fn resolve( - &self, - GetAlert { id }: GetAlert, - _: User, - ) -> anyhow::Result { - find_one_by_id(&db_client().alerts, &id) - .await - .context("failed to query db for alert")? - .context("no alert found with given id") + self, + _: &ReadArgs, + ) -> serror::Result { + Ok( + find_one_by_id(&db_client().alerts, &self.id) + .await + .context("failed to query db for alert")? + .context("no alert found with given id")?, + ) } } diff --git a/bin/core/src/api/read/alerter.rs b/bin/core/src/api/read/alerter.rs index 654d37bc3..1e6b66741 100644 --- a/bin/core/src/api/read/alerter.rs +++ b/bin/core/src/api/read/alerter.rs @@ -4,7 +4,6 @@ use komodo_client::{ entities::{ alerter::{Alerter, AlerterListItem}, permission::PermissionLevel, - user::User, }, }; use mongo_indexed::Document; @@ -12,66 +11,71 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ - helpers::query::get_all_tags, - resource, - state::{db_client, State}, + helpers::query::get_all_tags, resource, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetAlerter { async fn resolve( - &self, - GetAlerter { alerter }: GetAlerter, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &alerter, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.alerter, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListAlerters { async fn resolve( - &self, - ListAlerters { query }: ListAlerters, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullAlerters { async fn resolve( - &self, - ListFullAlerters { query }: ListFullAlerters, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetAlertersSummary { async fn resolve( - &self, - GetAlertersSummary {}: GetAlertersSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = match resource::get_resource_object_ids_for_user::< Alerter, - >(&user) + >(user) .await? { Some(ids) => doc! { diff --git a/bin/core/src/api/read/build.rs b/bin/core/src/api/read/build.rs index 06a64aa02..4fa7ac1f5 100644 --- a/bin/core/src/api/read/build.rs +++ b/bin/core/src/api/read/build.rs @@ -6,12 +6,11 @@ use futures::TryStreamExt; use komodo_client::{ api::read::*, entities::{ + Operation, build::{Build, BuildActionState, BuildListItem, BuildState}, config::core::CoreConfig, permission::PermissionLevel, update::UpdateStatus, - user::User, - Operation, }, }; use mungos::{ @@ -25,65 +24,72 @@ use crate::{ helpers::query::get_all_tags, resource, state::{ - action_states, build_state_cache, db_client, github_client, State, + action_states, build_state_cache, db_client, github_client, }, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetBuild { async fn resolve( - &self, - GetBuild { build }: GetBuild, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &build, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.build, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListBuilds { async fn resolve( - &self, - ListBuilds { query }: ListBuilds, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullBuilds { async fn resolve( - &self, - ListFullBuilds { query }: ListFullBuilds, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetBuildActionState { async fn resolve( - &self, - GetBuildActionState { build }: GetBuildActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Read, ) .await?; @@ -97,15 +103,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetBuildsSummary { async fn resolve( - &self, - GetBuildsSummary {}: GetBuildsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let builds = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -145,16 +150,15 @@ impl Resolve for State { const ONE_DAY_MS: i64 = 86400000; -impl Resolve for State { +impl Resolve for GetBuildMonthlyStats { async fn resolve( - &self, - GetBuildMonthlyStats { page }: GetBuildMonthlyStats, - _: User, - ) -> anyhow::Result { + self, + _: &ReadArgs, + ) -> serror::Result { let curr_ts = unix_timestamp_ms() as i64; let next_day = curr_ts - curr_ts % ONE_DAY_MS + ONE_DAY_MS; - let close_ts = next_day - page as i64 * 30 * ONE_DAY_MS; + let close_ts = next_day - self.page as i64 * 30 * ONE_DAY_MS; let open_ts = close_ts - 30 * ONE_DAY_MS; let mut build_updates = db_client() @@ -202,21 +206,21 @@ fn ms_to_hour(duration: i64) -> f64 { duration as f64 / MS_TO_HOUR_DIVISOR } -impl Resolve for State { +impl Resolve for ListBuildVersions { async fn resolve( - &self, - ListBuildVersions { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let ListBuildVersions { build, major, minor, patch, limit, - }: ListBuildVersions, - user: User, - ) -> anyhow::Result> { + } = self; let build = resource::get_check_permissions::( &build, - &user, + user, PermissionLevel::Read, ) .await?; @@ -259,21 +263,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListCommonBuildExtraArgs { async fn resolve( - &self, - ListCommonBuildExtraArgs { query }: ListCommonBuildExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - let builds = - resource::list_full_for_user::(query, &user, &all_tags) - .await - .context("failed to get resources matching query")?; + let builds = resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -290,12 +294,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetBuildWebhookEnabled { async fn resolve( - &self, - GetBuildWebhookEnabled { build }: GetBuildWebhookEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetBuildWebhookEnabledResponse { managed: false, @@ -304,8 +307,8 @@ impl Resolve for State { }; let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/builder.rs b/bin/core/src/api/read/builder.rs index 469866d37..b387ab8a5 100644 --- a/bin/core/src/api/read/builder.rs +++ b/bin/core/src/api/read/builder.rs @@ -4,7 +4,6 @@ use komodo_client::{ entities::{ builder::{Builder, BuilderListItem}, permission::PermissionLevel, - user::User, }, }; use mongo_indexed::Document; @@ -12,66 +11,71 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ - helpers::query::get_all_tags, - resource, - state::{db_client, State}, + helpers::query::get_all_tags, resource, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetBuilder { async fn resolve( - &self, - GetBuilder { builder }: GetBuilder, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &builder, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.builder, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListBuilders { async fn resolve( - &self, - ListBuilders { query }: ListBuilders, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullBuilders { async fn resolve( - &self, - ListFullBuilders { query }: ListFullBuilders, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetBuildersSummary { async fn resolve( - &self, - GetBuildersSummary {}: GetBuildersSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = match resource::get_resource_object_ids_for_user::< Builder, - >(&user) + >(user) .await? { Some(ids) => doc! { diff --git a/bin/core/src/api/read/deployment.rs b/bin/core/src/api/read/deployment.rs index 697da171f..83f5ec00b 100644 --- a/bin/core/src/api/read/deployment.rs +++ b/bin/core/src/api/read/deployment.rs @@ -1,6 +1,6 @@ use std::{cmp, collections::HashSet}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::read::*, entities::{ @@ -12,7 +12,6 @@ use komodo_client::{ permission::PermissionLevel, server::Server, update::Log, - user::User, }, }; use periphery_client::api; @@ -21,67 +20,81 @@ use resolver_api::Resolve; use crate::{ helpers::{periphery_client, query::get_all_tags}, resource, - state::{action_states, deployment_status_cache, State}, + state::{action_states, deployment_status_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetDeployment { async fn resolve( - &self, - GetDeployment { deployment }: GetDeployment, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &deployment, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.deployment, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListDeployments { async fn resolve( - &self, - ListDeployments { query }: ListDeployments, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + let only_update_available = self.query.specific.update_available; + let deployments = resource::list_for_user::( + self.query, user, &all_tags, + ) + .await?; + let deployments = if only_update_available { + deployments + .into_iter() + .filter(|deployment| deployment.info.update_available) + .collect() + } else { + deployments + }; + Ok(deployments) } } -impl Resolve for State { +impl Resolve for ListFullDeployments { async fn resolve( - &self, - ListFullDeployments { query }: ListFullDeployments, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::( - query, &user, &all_tags, + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for GetDeploymentContainer { async fn resolve( - &self, - GetDeploymentContainer { deployment }: GetDeploymentContainer, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let deployment = resource::get_check_permissions::( - &deployment, - &user, + &self.deployment, + user, PermissionLevel::Read, ) .await?; @@ -99,23 +112,23 @@ impl Resolve for State { const MAX_LOG_LENGTH: u64 = 5000; -impl Resolve for State { +impl Resolve for GetDeploymentLog { async fn resolve( - &self, - GetDeploymentLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetDeploymentLog { deployment, tail, timestamps, - }: GetDeploymentLog, - user: User, - ) -> anyhow::Result { + } = self; let Deployment { name, config: DeploymentConfig { server_id, .. }, .. } = resource::get_check_permissions::( &deployment, - &user, + user, PermissionLevel::Read, ) .await?; @@ -123,36 +136,37 @@ impl Resolve for State { return Ok(Log::default()); } let server = resource::get::(&server_id).await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(api::container::GetContainerLog { name, tail: cmp::min(tail, MAX_LOG_LENGTH), timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SearchDeploymentLog { async fn resolve( - &self, - SearchDeploymentLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let SearchDeploymentLog { deployment, terms, combinator, invert, timestamps, - }: SearchDeploymentLog, - user: User, - ) -> anyhow::Result { + } = self; let Deployment { name, config: DeploymentConfig { server_id, .. }, .. } = resource::get_check_permissions::( &deployment, - &user, + user, PermissionLevel::Read, ) .await?; @@ -160,7 +174,7 @@ impl Resolve for State { return Ok(Log::default()); } let server = resource::get::(&server_id).await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(api::container::GetContainerLogSearch { name, terms, @@ -169,46 +183,48 @@ impl Resolve for State { timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetDeploymentStats { async fn resolve( - &self, - GetDeploymentStats { deployment }: GetDeploymentStats, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Deployment { name, config: DeploymentConfig { server_id, .. }, .. } = resource::get_check_permissions::( - &deployment, - &user, + &self.deployment, + user, PermissionLevel::Read, ) .await?; if server_id.is_empty() { - return Err(anyhow!("deployment has no server attached")); + return Err( + anyhow!("deployment has no server attached").into(), + ); } let server = resource::get::(&server_id).await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(api::container::GetContainerStats { name }) .await - .context("failed to get stats from periphery") + .context("failed to get stats from periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetDeploymentActionState { async fn resolve( - &self, - GetDeploymentActionState { deployment }: GetDeploymentActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let deployment = resource::get_check_permissions::( - &deployment, - &user, + &self.deployment, + user, PermissionLevel::Read, ) .await?; @@ -222,15 +238,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetDeploymentsSummary { async fn resolve( - &self, - GetDeploymentsSummary {}: GetDeploymentsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let deployments = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -263,19 +278,18 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListCommonDeploymentExtraArgs { async fn resolve( - &self, - ListCommonDeploymentExtraArgs { query }: ListCommonDeploymentExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; let deployments = resource::list_full_for_user::( - query, &user, &all_tags, + self.query, user, &all_tags, ) .await .context("failed to get resources matching query")?; diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index 324c3153c..68f058999 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -1,11 +1,11 @@ use std::{collections::HashSet, sync::OnceLock, time::Instant}; -use anyhow::{anyhow, Context}; -use axum::{middleware, routing::post, Extension, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use anyhow::{Context, anyhow}; +use axum::{Extension, Router, middleware, routing::post}; use komodo_client::{ api::read::*, entities::{ + ResourceTarget, build::Build, builder::{Builder, BuilderConfig}, config::{DockerRegistry, GitProvider}, @@ -13,12 +13,10 @@ use komodo_client::{ server::Server, sync::ResourceSync, user::User, - ResourceTarget, }, }; -use resolver_api::{ - derive::Resolver, Resolve, ResolveToString, Resolver, -}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; use serror::Json; use typeshare::typeshare; @@ -26,7 +24,7 @@ use uuid::Uuid; use crate::{ auth::auth_request, config::core_config, helpers::periphery_client, - resource, state::State, + resource, }; mod action; @@ -39,7 +37,6 @@ mod permission; mod procedure; mod provider; mod repo; -mod search; mod server; mod server_template; mod stack; @@ -51,15 +48,18 @@ mod user; mod user_group; mod variable; +pub struct ReadArgs { + pub user: User, +} + #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Resolver)] -#[resolver_target(State)] -#[resolver_args(User)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] +#[args(ReadArgs)] +#[response(Response)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] enum ReadRequest { - #[to_string_resolver] GetVersion(GetVersion), - #[to_string_resolver] GetCoreInfo(GetCoreInfo), ListSecrets(ListSecrets), ListGitProvidersFromConfig(ListGitProvidersFromConfig), @@ -79,9 +79,6 @@ enum ReadRequest { GetUserGroup(GetUserGroup), ListUserGroups(ListUserGroups), - // ==== SEARCH ==== - FindResources(FindResources), - // ==== PROCEDURE ==== GetProceduresSummary(GetProceduresSummary), GetProcedure(GetProcedure), @@ -120,15 +117,10 @@ enum ReadRequest { ListDockerImageHistory(ListDockerImageHistory), InspectDockerVolume(InspectDockerVolume), ListAllDockerContainers(ListAllDockerContainers), - #[to_string_resolver] ListDockerContainers(ListDockerContainers), - #[to_string_resolver] ListDockerNetworks(ListDockerNetworks), - #[to_string_resolver] ListDockerImages(ListDockerImages), - #[to_string_resolver] ListDockerVolumes(ListDockerVolumes), - #[to_string_resolver] ListComposeProjects(ListComposeProjects), // ==== DEPLOYMENT ==== @@ -175,8 +167,8 @@ enum ReadRequest { GetStack(GetStack), GetStackActionState(GetStackActionState), GetStackWebhooksEnabled(GetStackWebhooksEnabled), - GetStackServiceLog(GetStackServiceLog), - SearchStackServiceLog(SearchStackServiceLog), + GetStackLog(GetStackLog), + SearchStackLog(SearchStackLog), ListStacks(ListStacks), ListFullStacks(ListFullStacks), ListStackServices(ListStackServices), @@ -212,11 +204,8 @@ enum ReadRequest { GetAlert(GetAlert), // ==== SERVER STATS ==== - #[to_string_resolver] GetSystemInformation(GetSystemInformation), - #[to_string_resolver] GetSystemStats(GetSystemStats), - #[to_string_resolver] ListSystemProcesses(ListSystemProcesses), // ==== VARIABLE ==== @@ -240,54 +229,35 @@ pub fn router() -> Router { async fn handler( Extension(user): Extension, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let timer = Instant::now(); let req_id = Uuid::new_v4(); debug!("/read request | user: {}", user.username); - let res = - State - .resolve_request(request, user) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&ReadArgs { user }).await; if let Err(e) = &res { - debug!("/read request {req_id} error: {e:#}"); + debug!("/read request {req_id} error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/read request {req_id} | resolve time: {elapsed:?}"); - Ok((TypedHeader(ContentType::json()), res?)) + res.map(|res| res.0) } -fn version() -> &'static String { - static VERSION: OnceLock = OnceLock::new(); - VERSION.get_or_init(|| { - serde_json::to_string(&GetVersionResponse { +impl Resolve for GetVersion { + async fn resolve( + self, + _: &ReadArgs, + ) -> serror::Result { + Ok(GetVersionResponse { version: env!("CARGO_PKG_VERSION").to_string(), }) - .context("failed to serialize GetVersionResponse") - .unwrap() - }) -} - -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetVersion {}: GetVersion, - _: User, - ) -> anyhow::Result { - Ok(version().to_string()) } } -fn core_info() -> &'static String { - static CORE_INFO: OnceLock = OnceLock::new(); +fn core_info() -> &'static GetCoreInfoResponse { + static CORE_INFO: OnceLock = OnceLock::new(); CORE_INFO.get_or_init(|| { let config = core_config(); - let info = GetCoreInfoResponse { + GetCoreInfoResponse { title: config.title.clone(), monitoring_interval: config.monitoring_interval, webhook_base_url: if config.webhook_base_url.is_empty() { @@ -305,36 +275,31 @@ fn core_info() -> &'static String { .iter() .map(|i| i.namespace.to_string()) .collect(), - }; - serde_json::to_string(&info) - .context("failed to serialize GetCoreInfoResponse") - .unwrap() + } }) } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetCoreInfo {}: GetCoreInfo, - _: User, - ) -> anyhow::Result { - Ok(core_info().to_string()) +impl Resolve for GetCoreInfo { + async fn resolve( + self, + _: &ReadArgs, + ) -> serror::Result { + Ok(core_info().clone()) } } -impl Resolve for State { +impl Resolve for ListSecrets { async fn resolve( - &self, - ListSecrets { target }: ListSecrets, - _: User, - ) -> anyhow::Result { + self, + _: &ReadArgs, + ) -> serror::Result { let mut secrets = core_config() .secrets .keys() .cloned() .collect::>(); - if let Some(target) = target { + if let Some(target) = self.target { let server_id = match target { ResourceTarget::Server(id) => Some(id), ResourceTarget::Builder(id) => { @@ -348,7 +313,9 @@ impl Resolve for State { } } _ => { - return Err(anyhow!("target must be `Server` or `Builder`")) + return Err( + anyhow!("target must be `Server` or `Builder`").into(), + ); } }; if let Some(id) = server_id { @@ -373,15 +340,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListGitProvidersFromConfig { async fn resolve( - &self, - ListGitProvidersFromConfig { target }: ListGitProvidersFromConfig, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let mut providers = core_config().git_providers.clone(); - if let Some(target) = target { + if let Some(target) = self.target { match target { ResourceTarget::Server(id) => { merge_git_providers_for_server(&mut providers, &id).await?; @@ -405,7 +371,9 @@ impl Resolve for State { } } _ => { - return Err(anyhow!("target must be `Server` or `Builder`")) + return Err( + anyhow!("target must be `Server` or `Builder`").into(), + ); } } } @@ -413,17 +381,17 @@ impl Resolve for State { let (builds, repos, syncs) = tokio::try_join!( resource::list_full_for_user::( Default::default(), - &user, + user, &[] ), resource::list_full_for_user::( Default::default(), - &user, + user, &[] ), resource::list_full_for_user::( Default::default(), - &user, + user, &[] ), )?; @@ -471,15 +439,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListDockerRegistriesFromConfig { async fn resolve( - &self, - ListDockerRegistriesFromConfig { target }: ListDockerRegistriesFromConfig, - _: User, - ) -> anyhow::Result { + self, + _: &ReadArgs, + ) -> serror::Result { let mut registries = core_config().docker_registries.clone(); - if let Some(target) = target { + if let Some(target) = self.target { match target { ResourceTarget::Server(id) => { merge_docker_registries_for_server(&mut registries, &id) @@ -504,7 +471,9 @@ impl Resolve for State { } } _ => { - return Err(anyhow!("target must be `Server` or `Builder`")) + return Err( + anyhow!("target must be `Server` or `Builder`").into(), + ); } } } @@ -518,7 +487,7 @@ impl Resolve for State { async fn merge_git_providers_for_server( providers: &mut Vec, server_id: &str, -) -> anyhow::Result<()> { +) -> serror::Result<()> { let server = resource::get::(server_id).await?; let more = periphery_client(&server)? .request(periphery_client::api::ListGitProviders {}) @@ -556,7 +525,7 @@ fn merge_git_providers( async fn merge_docker_registries_for_server( registries: &mut Vec, server_id: &str, -) -> anyhow::Result<()> { +) -> serror::Result<()> { let server = resource::get::(server_id).await?; let more = periphery_client(&server)? .request(periphery_client::api::ListDockerRegistries {}) diff --git a/bin/core/src/api/read/permission.rs b/bin/core/src/api/read/permission.rs index e3e493e28..eb934b914 100644 --- a/bin/core/src/api/read/permission.rs +++ b/bin/core/src/api/read/permission.rs @@ -1,27 +1,27 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::read::{ GetPermissionLevel, GetPermissionLevelResponse, ListPermissions, ListPermissionsResponse, ListUserTargetPermissions, ListUserTargetPermissionsResponse, }, - entities::{permission::PermissionLevel, user::User}, + entities::permission::PermissionLevel, }; use mungos::{find::find_collect, mongodb::bson::doc}; use resolver_api::Resolve; use crate::{ - helpers::query::get_user_permission_on_target, - state::{db_client, State}, + helpers::query::get_user_permission_on_target, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for ListPermissions { async fn resolve( - &self, - ListPermissions {}: ListPermissions, - user: User, - ) -> anyhow::Result { - find_collect( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let res = find_collect( &db_client().permissions, doc! { "user_target.type": "User", @@ -30,34 +30,33 @@ impl Resolve for State { None, ) .await - .context("failed to query db for permissions") + .context("failed to query db for permissions")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetPermissionLevel { async fn resolve( - &self, - GetPermissionLevel { target }: GetPermissionLevel, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if user.admin { return Ok(PermissionLevel::Write); } - get_user_permission_on_target(&user, &target).await + Ok(get_user_permission_on_target(user, &self.target).await?) } } -impl Resolve for State { +impl Resolve for ListUserTargetPermissions { async fn resolve( - &self, - ListUserTargetPermissions { user_target }: ListUserTargetPermissions, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } - let (variant, id) = user_target.extract_variant_id(); - find_collect( + let (variant, id) = self.user_target.extract_variant_id(); + let res = find_collect( &db_client().permissions, doc! { "user_target.type": variant.as_ref(), @@ -66,6 +65,7 @@ impl Resolve for State { None, ) .await - .context("failed to query db for permissions") + .context("failed to query db for permissions")?; + Ok(res) } } diff --git a/bin/core/src/api/read/procedure.rs b/bin/core/src/api/read/procedure.rs index ec1e4961d..7ecb044b1 100644 --- a/bin/core/src/api/read/procedure.rs +++ b/bin/core/src/api/read/procedure.rs @@ -4,7 +4,6 @@ use komodo_client::{ entities::{ permission::PermissionLevel, procedure::{Procedure, ProcedureState}, - user::User, }, }; use resolver_api::Resolve; @@ -12,65 +11,73 @@ use resolver_api::Resolve; use crate::{ helpers::query::get_all_tags, resource, - state::{action_states, procedure_state_cache, State}, + state::{action_states, procedure_state_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetProcedure { async fn resolve( - &self, - GetProcedure { procedure }: GetProcedure, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &procedure, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.procedure, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListProcedures { async fn resolve( - &self, - ListProcedures { query }: ListProcedures, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullProcedures { async fn resolve( - &self, - ListFullProcedures { query }: ListFullProcedures, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetProceduresSummary { async fn resolve( - &self, - GetProceduresSummary {}: GetProceduresSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let procedures = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -108,15 +115,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetProcedureActionState { async fn resolve( - &self, - GetProcedureActionState { procedure }: GetProcedureActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let procedure = resource::get_check_permissions::( - &procedure, - &user, + &self.procedure, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/provider.rs b/bin/core/src/api/read/provider.rs index 28d3735df..df416ae3b 100644 --- a/bin/core/src/api/read/provider.rs +++ b/bin/core/src/api/read/provider.rs @@ -1,59 +1,54 @@ -use anyhow::{anyhow, Context}; -use komodo_client::{ - api::read::{ - GetDockerRegistryAccount, GetDockerRegistryAccountResponse, - GetGitProviderAccount, GetGitProviderAccountResponse, - ListDockerRegistryAccounts, ListDockerRegistryAccountsResponse, - ListGitProviderAccounts, ListGitProviderAccountsResponse, - }, - entities::user::User, -}; -use mongo_indexed::{doc, Document}; +use anyhow::{Context, anyhow}; +use komodo_client::api::read::*; +use mongo_indexed::{Document, doc}; use mungos::{ by_id::find_one_by_id, find::find_collect, mongodb::options::FindOptions, }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::state::db_client; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetGitProviderAccount { async fn resolve( - &self, - GetGitProviderAccount { id }: GetGitProviderAccount, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read git provider accounts" - )); + return Err( + anyhow!("Only admins can read git provider accounts").into(), + ); } - find_one_by_id(&db_client().git_accounts, &id) + let res = find_one_by_id(&db_client().git_accounts, &self.id) .await .context("failed to query db for git provider accounts")? - .context("did not find git provider account with the given id") + .context( + "did not find git provider account with the given id", + )?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListGitProviderAccounts { async fn resolve( - &self, - ListGitProviderAccounts { domain, username }: ListGitProviderAccounts, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read git provider accounts" - )); + return Err( + anyhow!("Only admins can read git provider accounts").into(), + ); } let mut filter = Document::new(); - if let Some(domain) = domain { + if let Some(domain) = self.domain { filter.insert("domain", domain); } - if let Some(username) = username { + if let Some(username) = self.username { filter.insert("username", username); } - find_collect( + let res = find_collect( &db_client().git_accounts, filter, FindOptions::builder() @@ -61,49 +56,52 @@ impl Resolve for State { .build(), ) .await - .context("failed to query db for git provider accounts") + .context("failed to query db for git provider accounts")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetDockerRegistryAccount { async fn resolve( - &self, - GetDockerRegistryAccount { id }: GetDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read docker registry accounts" - )); + return Err( + anyhow!("Only admins can read docker registry accounts") + .into(), + ); } - find_one_by_id(&db_client().registry_accounts, &id) - .await - .context("failed to query db for docker registry accounts")? - .context( - "did not find docker registry account with the given id", - ) + let res = + find_one_by_id(&db_client().registry_accounts, &self.id) + .await + .context("failed to query db for docker registry accounts")? + .context( + "did not find docker registry account with the given id", + )?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListDockerRegistryAccounts { async fn resolve( - &self, - ListDockerRegistryAccounts { domain, username }: ListDockerRegistryAccounts, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "Only admins can read docker registry accounts" - )); + return Err( + anyhow!("Only admins can read docker registry accounts") + .into(), + ); } let mut filter = Document::new(); - if let Some(domain) = domain { + if let Some(domain) = self.domain { filter.insert("domain", domain); } - if let Some(username) = username { + if let Some(username) = self.username { filter.insert("username", username); } - find_collect( + let res = find_collect( &db_client().registry_accounts, filter, FindOptions::builder() @@ -111,6 +109,7 @@ impl Resolve for State { .build(), ) .await - .context("failed to query db for docker registry accounts") + .context("failed to query db for docker registry accounts")?; + Ok(res) } } diff --git a/bin/core/src/api/read/repo.rs b/bin/core/src/api/read/repo.rs index 5b8f8c976..ae691c89d 100644 --- a/bin/core/src/api/read/repo.rs +++ b/bin/core/src/api/read/repo.rs @@ -5,7 +5,6 @@ use komodo_client::{ config::core::CoreConfig, permission::PermissionLevel, repo::{Repo, RepoActionState, RepoListItem, RepoState}, - user::User, }, }; use resolver_api::Resolve; @@ -14,64 +13,71 @@ use crate::{ config::core_config, helpers::query::get_all_tags, resource, - state::{action_states, github_client, repo_state_cache, State}, + state::{action_states, github_client, repo_state_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetRepo { async fn resolve( - &self, - GetRepo { repo }: GetRepo, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &repo, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.repo, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListRepos { async fn resolve( - &self, - ListRepos { query }: ListRepos, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullRepos { async fn resolve( - &self, - ListFullRepos { query }: ListFullRepos, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetRepoActionState { async fn resolve( - &self, - GetRepoActionState { repo }: GetRepoActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Read, ) .await?; @@ -85,15 +91,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetReposSummary { async fn resolve( - &self, - GetReposSummary {}: GetReposSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let repos = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -141,12 +146,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetRepoWebhooksEnabled { async fn resolve( - &self, - GetRepoWebhooksEnabled { repo }: GetRepoWebhooksEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetRepoWebhooksEnabledResponse { managed: false, @@ -157,8 +161,8 @@ impl Resolve for State { }; let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/search.rs b/bin/core/src/api/read/search.rs deleted file mode 100644 index 3568ad8ab..000000000 --- a/bin/core/src/api/read/search.rs +++ /dev/null @@ -1,82 +0,0 @@ -use komodo_client::{ - api::read::{FindResources, FindResourcesResponse}, - entities::{ - build::Build, deployment::Deployment, procedure::Procedure, - repo::Repo, server::Server, user::User, ResourceTargetVariant, - }, -}; -use resolver_api::Resolve; - -use crate::{resource, state::State}; - -const FIND_RESOURCE_TYPES: [ResourceTargetVariant; 5] = [ - ResourceTargetVariant::Server, - ResourceTargetVariant::Build, - ResourceTargetVariant::Deployment, - ResourceTargetVariant::Repo, - ResourceTargetVariant::Procedure, -]; - -impl Resolve for State { - async fn resolve( - &self, - FindResources { query, resources }: FindResources, - user: User, - ) -> anyhow::Result { - let mut res = FindResourcesResponse::default(); - let resource_types = if resources.is_empty() { - FIND_RESOURCE_TYPES.to_vec() - } else { - resources - .into_iter() - .filter(|r| { - !matches!( - r, - ResourceTargetVariant::System - | ResourceTargetVariant::Builder - | ResourceTargetVariant::Alerter - ) - }) - .collect() - }; - for resource_type in resource_types { - match resource_type { - ResourceTargetVariant::Server => { - res.servers = resource::list_for_user_using_document::< - Server, - >(query.clone(), &user) - .await?; - } - ResourceTargetVariant::Deployment => { - res.deployments = resource::list_for_user_using_document::< - Deployment, - >(query.clone(), &user) - .await?; - } - ResourceTargetVariant::Build => { - res.builds = - resource::list_for_user_using_document::( - query.clone(), - &user, - ) - .await?; - } - ResourceTargetVariant::Repo => { - res.repos = resource::list_for_user_using_document::( - query.clone(), - &user, - ) - .await?; - } - ResourceTargetVariant::Procedure => { - res.procedures = resource::list_for_user_using_document::< - Procedure, - >(query.clone(), &user) - .await?; - } - _ => {} - } - } - Ok(res) - } -} diff --git a/bin/core/src/api/read/server.rs b/bin/core/src/api/read/server.rs index 1761aca15..c6c5ade25 100644 --- a/bin/core/src/api/read/server.rs +++ b/bin/core/src/api/read/server.rs @@ -4,13 +4,14 @@ use std::{ sync::{Arc, OnceLock}, }; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use async_timing_util::{ - get_timelength_in_ms, unix_timestamp_ms, FIFTEEN_SECONDS_MS, + FIFTEEN_SECONDS_MS, get_timelength_in_ms, unix_timestamp_ms, }; use komodo_client::{ api::read::*, entities::{ + ResourceTarget, deployment::Deployment, docker::{ container::{Container, ContainerListItem}, @@ -23,9 +24,8 @@ use komodo_client::{ Server, ServerActionState, ServerListItem, ServerState, }, stack::{Stack, StackServiceNames}, + stats::{SystemInformation, SystemProcess}, update::Log, - user::User, - ResourceTarget, }, }; use mungos::{ @@ -39,25 +39,26 @@ use periphery_client::api::{ network::InspectNetwork, volume::InspectVolume, }; -use resolver_api::{Resolve, ResolveToString}; +use resolver_api::Resolve; use tokio::sync::Mutex; use crate::{ helpers::{periphery_client, query::get_all_tags}, resource, stack::compose_container_match_regex, - state::{action_states, db_client, server_status_cache, State}, + state::{action_states, db_client, server_status_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetServersSummary { async fn resolve( - &self, - GetServersSummary {}: GetServersSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let servers = resource::list_for_user::( Default::default(), - &user, + user, &[], ) .await?; @@ -80,15 +81,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetPeripheryVersion { async fn resolve( - &self, - req: GetPeripheryVersion, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &req.server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -101,61 +101,66 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetServer { async fn resolve( - &self, - req: GetServer, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &req.server, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.server, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListServers { async fn resolve( - &self, - ListServers { query }: ListServers, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + Ok( + resource::list_for_user::(self.query, user, &all_tags) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullServers { async fn resolve( - &self, - ListFullServers { query }: ListFullServers, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetServerState { async fn resolve( - &self, - GetServerState { server }: GetServerState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -170,15 +175,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetServerActionState { async fn resolve( - &self, - GetServerActionState { server }: GetServerActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -194,22 +198,22 @@ impl Resolve for State { // This protects the peripheries from spam requests const SYSTEM_INFO_EXPIRY: u128 = FIFTEEN_SECONDS_MS; -type SystemInfoCache = Mutex>>; +type SystemInfoCache = + Mutex>>; fn system_info_cache() -> &'static SystemInfoCache { static SYSTEM_INFO_CACHE: OnceLock = OnceLock::new(); SYSTEM_INFO_CACHE.get_or_init(Default::default) } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetSystemInformation { server }: GetSystemInformation, - user: User, - ) -> anyhow::Result { +impl Resolve for GetSystemInformation { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -223,28 +227,26 @@ impl ResolveToString for State { let stats = periphery_client(&server)? .request(periphery::stats::GetSystemInformation {}) .await?; - let res = serde_json::to_string(&stats)?; lock.insert( server.id, - (res.clone(), unix_timestamp_ms() + SYSTEM_INFO_EXPIRY) + (stats.clone(), unix_timestamp_ms() + SYSTEM_INFO_EXPIRY) .into(), ); - res + stats } }; Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - GetSystemStats { server }: GetSystemStats, - user: User, - ) -> anyhow::Result { +impl Resolve for GetSystemStats { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -256,28 +258,27 @@ impl ResolveToString for State { .stats .as_ref() .context("server stats not available")?; - let stats = serde_json::to_string(&stats)?; - Ok(stats) + Ok(stats.clone()) } } // This protects the peripheries from spam requests const PROCESSES_EXPIRY: u128 = FIFTEEN_SECONDS_MS; -type ProcessesCache = Mutex>>; +type ProcessesCache = + Mutex, u128)>>>; fn processes_cache() -> &'static ProcessesCache { static PROCESSES_CACHE: OnceLock = OnceLock::new(); PROCESSES_CACHE.get_or_init(Default::default) } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListSystemProcesses { server }: ListSystemProcesses, - user: User, - ) -> anyhow::Result { +impl Resolve for ListSystemProcesses { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -290,13 +291,12 @@ impl ResolveToString for State { let stats = periphery_client(&server)? .request(periphery::stats::GetSystemProcesses {}) .await?; - let res = serde_json::to_string(&stats)?; lock.insert( server.id, - (res.clone(), unix_timestamp_ms() + PROCESSES_EXPIRY) + (stats.clone(), unix_timestamp_ms() + PROCESSES_EXPIRY) .into(), ); - res + stats } }; Ok(res) @@ -305,19 +305,19 @@ impl ResolveToString for State { const STATS_PER_PAGE: i64 = 200; -impl Resolve for State { +impl Resolve for GetHistoricalServerStats { async fn resolve( - &self, - GetHistoricalServerStats { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetHistoricalServerStats { server, granularity, page, - }: GetHistoricalServerStats, - user: User, - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Read, ) .await?; @@ -358,15 +358,14 @@ impl Resolve for State { } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerContainers { server }: ListDockerContainers, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerContainers { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -374,31 +373,29 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(containers) = &cache.containers { - serde_json::to_string(containers) - .context("failed to serialize response") + Ok(containers.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for ListAllDockerContainers { async fn resolve( - &self, - ListAllDockerContainers { servers }: ListAllDockerContainers, - user: User, - ) -> anyhow::Result> { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let servers = resource::list_for_user::( Default::default(), - &user, + user, &[], ) .await? .into_iter() .filter(|server| { - servers.is_empty() - || servers.contains(&server.id) - || servers.contains(&server.name) + self.servers.is_empty() + || self.servers.contains(&server.id) + || self.servers.contains(&server.name) }); let mut containers = Vec::::new(); @@ -416,15 +413,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for InspectDockerContainer { async fn resolve( - &self, - InspectDockerContainer { server, container }: InspectDockerContainer, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -432,67 +428,74 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect container: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot inspect container: server is {:?}", + cache.state + ) + .into(), + ); } - periphery_client(&server)? - .request(InspectContainer { name: container }) - .await + let res = periphery_client(&server)? + .request(InspectContainer { + name: self.container, + }) + .await?; + Ok(res) } } const MAX_LOG_LENGTH: u64 = 5000; -impl Resolve for State { +impl Resolve for GetContainerLog { async fn resolve( - &self, - GetContainerLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetContainerLog { server, container, tail, timestamps, - }: GetContainerLog, - user: User, - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Read, ) .await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(periphery::container::GetContainerLog { name: container, tail: cmp::min(tail, MAX_LOG_LENGTH), timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SearchContainerLog { async fn resolve( - &self, - SearchContainerLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let SearchContainerLog { server, container, terms, combinator, invert, timestamps, - }: SearchContainerLog, - user: User, - ) -> anyhow::Result { + } = self; let server = resource::get_check_permissions::( &server, - &user, + user, PermissionLevel::Read, ) .await?; - periphery_client(&server)? + let res = periphery_client(&server)? .request(periphery::container::GetContainerLogSearch { name: container, terms, @@ -501,25 +504,25 @@ impl Resolve for State { timestamps, }) .await - .context("failed at call to periphery") + .context("failed at call to periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for GetResourceMatchingContainer { async fn resolve( - &self, - GetResourceMatchingContainer { server, container }: GetResourceMatchingContainer, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; // first check deployments if let Ok(deployment) = - resource::get::(&container).await + resource::get::(&self.container).await { return Ok(GetResourceMatchingContainerResponse { resource: ResourceTarget::Deployment(deployment.id).into(), @@ -530,7 +533,7 @@ impl Resolve for State { let stacks = resource::list_full_for_user_using_document::( doc! { "config.server_id": &server.id }, - &user, + user, ) .await?; @@ -553,7 +556,7 @@ impl Resolve for State { warn!("{e:#}"); continue; } - }.is_match(&container); + }.is_match(&self.container); if is_match { return Ok(GetResourceMatchingContainerResponse { @@ -567,15 +570,14 @@ impl Resolve for State { } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerNetworks { server }: ListDockerNetworks, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerNetworks { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -583,23 +585,21 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(networks) = &cache.networks { - serde_json::to_string(networks) - .context("failed to serialize response") + Ok(networks.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for InspectDockerNetwork { async fn resolve( - &self, - InspectDockerNetwork { server, network }: InspectDockerNetwork, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -607,26 +607,29 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect network: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot inspect network: server is {:?}", + cache.state + ) + .into(), + ); } - periphery_client(&server)? - .request(InspectNetwork { name: network }) - .await + let res = periphery_client(&server)? + .request(InspectNetwork { name: self.network }) + .await?; + Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerImages { server }: ListDockerImages, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerImages { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -634,23 +637,21 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(images) = &cache.images { - serde_json::to_string(images) - .context("failed to serialize response") + Ok(images.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for InspectDockerImage { async fn resolve( - &self, - InspectDockerImage { server, image }: InspectDockerImage, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -658,26 +659,26 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect image: server is {:?}", - cache.state - )); + return Err( + anyhow!("Cannot inspect image: server is {:?}", cache.state) + .into(), + ); } - periphery_client(&server)? - .request(InspectImage { name: image }) - .await + let res = periphery_client(&server)? + .request(InspectImage { name: self.image }) + .await?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListDockerImageHistory { async fn resolve( - &self, - ListDockerImageHistory { server, image }: ListDockerImageHistory, - user: User, - ) -> anyhow::Result> { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -685,26 +686,29 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot get image history: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot get image history: server is {:?}", + cache.state + ) + .into(), + ); } - periphery_client(&server)? - .request(ImageHistory { name: image }) - .await + let res = periphery_client(&server)? + .request(ImageHistory { name: self.image }) + .await?; + Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListDockerVolumes { server }: ListDockerVolumes, - user: User, - ) -> anyhow::Result { +impl Resolve for ListDockerVolumes { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -712,23 +716,21 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(volumes) = &cache.volumes { - serde_json::to_string(volumes) - .context("failed to serialize response") + Ok(volumes.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } -impl Resolve for State { +impl Resolve for InspectDockerVolume { async fn resolve( - &self, - InspectDockerVolume { server, volume }: InspectDockerVolume, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -736,26 +738,26 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect volume: server is {:?}", - cache.state - )); + return Err( + anyhow!("Cannot inspect volume: server is {:?}", cache.state) + .into(), + ); } - periphery_client(&server)? - .request(InspectVolume { name: volume }) - .await + let res = periphery_client(&server)? + .request(InspectVolume { name: self.volume }) + .await?; + Ok(res) } } -impl ResolveToString for State { - async fn resolve_to_string( - &self, - ListComposeProjects { server }: ListComposeProjects, - user: User, - ) -> anyhow::Result { +impl Resolve for ListComposeProjects { + async fn resolve( + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Read, ) .await?; @@ -763,10 +765,9 @@ impl ResolveToString for State { .get_or_insert_default(&server.id) .await; if let Some(projects) = &cache.projects { - serde_json::to_string(projects) - .context("failed to serialize response") + Ok(projects.clone()) } else { - Ok(String::from("[]")) + Ok(Vec::new()) } } } diff --git a/bin/core/src/api/read/server_template.rs b/bin/core/src/api/read/server_template.rs index 503f92540..2fae7752b 100644 --- a/bin/core/src/api/read/server_template.rs +++ b/bin/core/src/api/read/server_template.rs @@ -3,7 +3,6 @@ use komodo_client::{ api::read::*, entities::{ permission::PermissionLevel, server_template::ServerTemplate, - user::User, }, }; use mongo_indexed::Document; @@ -11,69 +10,73 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ - helpers::query::get_all_tags, - resource, - state::{db_client, State}, + helpers::query::get_all_tags, resource, state::db_client, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetServerTemplate { async fn resolve( - &self, - GetServerTemplate { server_template }: GetServerTemplate, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &server_template, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.server_template, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListServerTemplates { async fn resolve( - &self, - ListServerTemplates { query }: ListServerTemplates, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullServerTemplates { async fn resolve( - &self, - ListFullServerTemplates { query }: ListFullServerTemplates, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::( - query, &user, &all_tags, + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for GetServerTemplatesSummary { async fn resolve( - &self, - GetServerTemplatesSummary {}: GetServerTemplatesSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = match resource::get_resource_object_ids_for_user::< ServerTemplate, - >(&user) + >(user) .await? { Some(ids) => doc! { diff --git a/bin/core/src/api/read/stack.rs b/bin/core/src/api/read/stack.rs index 9122236f4..462de4c93 100644 --- a/bin/core/src/api/read/stack.rs +++ b/bin/core/src/api/read/stack.rs @@ -7,11 +7,10 @@ use komodo_client::{ config::core::CoreConfig, permission::PermissionLevel, stack::{Stack, StackActionState, StackListItem, StackState}, - user::User, }, }; use periphery_client::api::compose::{ - GetComposeServiceLog, GetComposeServiceLogSearch, + GetComposeLog, GetComposeLogSearch, }; use resolver_api::Resolve; @@ -20,33 +19,35 @@ use crate::{ helpers::{periphery_client, query::get_all_tags}, resource, stack::get_stack_and_server, - state::{action_states, github_client, stack_status_cache, State}, + state::{action_states, github_client, stack_status_cache}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetStack { async fn resolve( - &self, - GetStack { stack }: GetStack, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &stack, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.stack, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListStackServices { async fn resolve( - &self, - ListStackServices { stack }: ListStackServices, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Read, ) .await?; @@ -63,85 +64,79 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetStackLog { async fn resolve( - &self, - GetStackServiceLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let GetStackLog { stack, - service, + services, tail, timestamps, - }: GetStackServiceLog, - user: User, - ) -> anyhow::Result { - let (stack, server) = get_stack_and_server( - &stack, - &user, - PermissionLevel::Read, - true, - ) - .await?; - periphery_client(&server)? - .request(GetComposeServiceLog { + } = self; + let (stack, server) = + get_stack_and_server(&stack, user, PermissionLevel::Read, true) + .await?; + let res = periphery_client(&server)? + .request(GetComposeLog { project: stack.project_name(false), - service, + services, tail, timestamps, }) .await - .context("failed to get stack service log from periphery") + .context("Failed to get stack log from periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SearchStackLog { async fn resolve( - &self, - SearchStackServiceLog { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let SearchStackLog { stack, - service, + services, terms, combinator, invert, timestamps, - }: SearchStackServiceLog, - user: User, - ) -> anyhow::Result { - let (stack, server) = get_stack_and_server( - &stack, - &user, - PermissionLevel::Read, - true, - ) - .await?; - periphery_client(&server)? - .request(GetComposeServiceLogSearch { + } = self; + let (stack, server) = + get_stack_and_server(&stack, user, PermissionLevel::Read, true) + .await?; + let res = periphery_client(&server)? + .request(GetComposeLogSearch { project: stack.project_name(false), - service, + services, terms, combinator, invert, timestamps, }) .await - .context("failed to get stack service log from periphery") + .context("Failed to search stack log from periphery")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListCommonStackExtraArgs { async fn resolve( - &self, - ListCommonStackExtraArgs { query }: ListCommonStackExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - let stacks = - resource::list_full_for_user::(query, &user, &all_tags) - .await - .context("failed to get resources matching query")?; + let stacks = resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -158,21 +153,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListCommonStackBuildExtraArgs { async fn resolve( - &self, - ListCommonStackBuildExtraArgs { query }: ListCommonStackBuildExtraArgs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - let stacks = - resource::list_full_for_user::(query, &user, &all_tags) - .await - .context("failed to get resources matching query")?; + let stacks = resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -189,46 +184,65 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListStacks { async fn resolve( - &self, - ListStacks { query }: ListStacks, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags).await + let only_update_available = self.query.specific.update_available; + let stacks = + resource::list_for_user::(self.query, user, &all_tags) + .await?; + let stacks = if only_update_available { + stacks + .into_iter() + .filter(|stack| { + stack + .info + .services + .iter() + .any(|service| service.update_available) + }) + .collect() + } else { + stacks + }; + Ok(stacks) } } -impl Resolve for State { +impl Resolve for ListFullStacks { async fn resolve( - &self, - ListFullStacks { query }: ListFullStacks, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for GetStackActionState { async fn resolve( - &self, - GetStackActionState { stack }: GetStackActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Read, ) .await?; @@ -242,15 +256,14 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetStacksSummary { async fn resolve( - &self, - GetStacksSummary {}: GetStacksSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let stacks = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -276,12 +289,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetStackWebhooksEnabled { async fn resolve( - &self, - GetStackWebhooksEnabled { stack }: GetStackWebhooksEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetStackWebhooksEnabledResponse { managed: false, @@ -291,8 +303,8 @@ impl Resolve for State { }; let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/sync.rs b/bin/core/src/api/read/sync.rs index 2606890fa..5db3a987c 100644 --- a/bin/core/src/api/read/sync.rs +++ b/bin/core/src/api/read/sync.rs @@ -6,9 +6,7 @@ use komodo_client::{ permission::PermissionLevel, sync::{ ResourceSync, ResourceSyncActionState, ResourceSyncListItem, - ResourceSyncState, }, - user::User, }, }; use resolver_api::Resolve; @@ -17,69 +15,73 @@ use crate::{ config::core_config, helpers::query::get_all_tags, resource, - state::{ - action_states, github_client, resource_sync_state_cache, State, - }, + state::{action_states, github_client}, }; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetResourceSync { async fn resolve( - &self, - GetResourceSync { sync }: GetResourceSync, - user: User, - ) -> anyhow::Result { - resource::get_check_permissions::( - &sync, - &user, - PermissionLevel::Read, + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + Ok( + resource::get_check_permissions::( + &self.sync, + user, + PermissionLevel::Read, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for ListResourceSyncs { async fn resolve( - &self, - ListResourceSyncs { query }: ListResourceSyncs, - user: User, - ) -> anyhow::Result> { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result> { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_for_user::(query, &user, &all_tags) - .await + Ok( + resource::list_for_user::( + self.query, user, &all_tags, + ) + .await?, + ) } } -impl Resolve for State { +impl Resolve for ListFullResourceSyncs { async fn resolve( - &self, - ListFullResourceSyncs { query }: ListFullResourceSyncs, - user: User, - ) -> anyhow::Result { - let all_tags = if query.tags.is_empty() { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let all_tags = if self.query.tags.is_empty() { vec![] } else { get_all_tags(None).await? }; - resource::list_full_for_user::( - query, &user, &all_tags, + Ok( + resource::list_full_for_user::( + self.query, user, &all_tags, + ) + .await?, ) - .await } } -impl Resolve for State { +impl Resolve for GetResourceSyncActionState { async fn resolve( - &self, - GetResourceSyncActionState { sync }: GetResourceSyncActionState, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Read, ) .await?; @@ -93,16 +95,15 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetResourceSyncsSummary { async fn resolve( - &self, - GetResourceSyncsSummary {}: GetResourceSyncsSummary, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let resource_syncs = resource::list_full_for_user::( Default::default(), - &user, + user, &[], ) .await @@ -110,7 +111,6 @@ impl Resolve for State { let mut res = GetResourceSyncsSummaryResponse::default(); - let cache = resource_sync_state_cache(); let action_states = action_states(); for resource_sync in resource_syncs { @@ -129,42 +129,29 @@ impl Resolve for State { res.failed += 1; continue; } - - match ( - cache.get(&resource_sync.id).await.unwrap_or_default(), - action_states - .resource_sync - .get(&resource_sync.id) - .await - .unwrap_or_default() - .get()?, - ) { - (_, action_states) if action_states.syncing => { - res.syncing += 1; - } - (ResourceSyncState::Ok, _) => res.ok += 1, - (ResourceSyncState::Failed, _) => res.failed += 1, - (ResourceSyncState::Unknown, _) => res.unknown += 1, - // will never come off the cache in the building state, since that comes from action states - (ResourceSyncState::Syncing, _) => { - unreachable!() - } - (ResourceSyncState::Pending, _) => { - unreachable!() - } + if action_states + .resource_sync + .get(&resource_sync.id) + .await + .unwrap_or_default() + .get()? + .syncing + { + res.syncing += 1; + continue; } + res.ok += 1; } Ok(res) } } -impl Resolve for State { +impl Resolve for GetSyncWebhooksEnabled { async fn resolve( - &self, - GetSyncWebhooksEnabled { sync }: GetSyncWebhooksEnabled, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let Some(github) = github_client() else { return Ok(GetSyncWebhooksEnabledResponse { managed: false, @@ -174,8 +161,8 @@ impl Resolve for State { }; let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/tag.rs b/bin/core/src/api/read/tag.rs index be1d032da..6bb849181 100644 --- a/bin/core/src/api/read/tag.rs +++ b/bin/core/src/api/read/tag.rs @@ -1,39 +1,31 @@ use anyhow::Context; use komodo_client::{ api::read::{GetTag, ListTags}, - entities::{tag::Tag, user::User}, + entities::tag::Tag, }; use mongo_indexed::doc; use mungos::{find::find_collect, mongodb::options::FindOptions}; use resolver_api::Resolve; -use crate::{ - helpers::query::get_tag, - state::{db_client, State}, -}; +use crate::{helpers::query::get_tag, state::db_client}; -impl Resolve for State { - async fn resolve( - &self, - GetTag { tag }: GetTag, - _: User, - ) -> anyhow::Result { - get_tag(&tag).await +use super::ReadArgs; + +impl Resolve for GetTag { + async fn resolve(self, _: &ReadArgs) -> serror::Result { + Ok(get_tag(&self.tag).await?) } } -impl Resolve for State { - async fn resolve( - &self, - ListTags { query }: ListTags, - _: User, - ) -> anyhow::Result> { - find_collect( +impl Resolve for ListTags { + async fn resolve(self, _: &ReadArgs) -> serror::Result> { + let res = find_collect( &db_client().tags, - query, + self.query, FindOptions::builder().sort(doc! { "name": 1 }).build(), ) .await - .context("failed to get tags from db") + .context("failed to get tags from db")?; + Ok(res) } } diff --git a/bin/core/src/api/read/toml.rs b/bin/core/src/api/read/toml.rs index 5186e6196..88d0671c7 100644 --- a/bin/core/src/api/read/toml.rs +++ b/bin/core/src/api/read/toml.rs @@ -6,12 +6,12 @@ use komodo_client::{ ListUserGroups, }, entities::{ - action::Action, alerter::Alerter, build::Build, builder::Builder, - deployment::Deployment, permission::PermissionLevel, - procedure::Procedure, repo::Repo, resource::ResourceQuery, - server::Server, server_template::ServerTemplate, stack::Stack, + ResourceTarget, action::Action, alerter::Alerter, build::Build, + builder::Builder, deployment::Deployment, + permission::PermissionLevel, procedure::Procedure, repo::Repo, + resource::ResourceQuery, server::Server, + server_template::ServerTemplate, stack::Stack, sync::ResourceSync, toml::ResourcesToml, user::User, - ResourceTarget, }, }; use mungos::find::find_collect; @@ -22,183 +22,196 @@ use crate::{ get_all_tags, get_id_to_tags, get_user_user_group_ids, }, resource, - state::{db_client, State}, + state::db_client, sync::{ - toml::{convert_resource, ToToml, TOML_PRETTY_OPTIONS}, - user_groups::convert_user_groups, AllResourcesById, + toml::{TOML_PRETTY_OPTIONS, ToToml, convert_resource}, + user_groups::convert_user_groups, }, }; -impl Resolve for State { - async fn resolve( - &self, - ExportAllResourcesToToml { tags }: ExportAllResourcesToToml, - user: User, - ) -> anyhow::Result { - let mut targets = Vec::::new(); +use super::ReadArgs; - let all_tags = if tags.is_empty() { - vec![] - } else { - get_all_tags(None).await? - }; - - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Alerter(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Builder(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Server(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Deployment(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Stack(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Build(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Repo(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Procedure(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::Action(resource.id)), - ); - targets.extend( - resource::list_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - .map(|resource| ResourceTarget::ServerTemplate(resource.id)), - ); - targets.extend( - resource::list_full_for_user::( - ResourceQuery::builder().tags(tags.clone()).build(), - &user, - &all_tags, - ) - .await? - .into_iter() - // These will already be filtered by [ExportResourcesToToml] - .map(|resource| ResourceTarget::ResourceSync(resource.id)), - ); - - let user_groups = if user.admin && tags.is_empty() { - find_collect(&db_client().user_groups, None, None) - .await - .context("failed to query db for user groups")? - .into_iter() - .map(|user_group| user_group.id) - .collect() - } else { - get_user_user_group_ids(&user.id).await? - }; - - self - .resolve( - ExportResourcesToToml { - targets, - user_groups, - include_variables: tags.is_empty(), - }, - user, - ) - .await - } +async fn get_all_targets( + tags: &[String], + user: &User, +) -> anyhow::Result> { + let mut targets = Vec::::new(); + let all_tags = if tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Alerter(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Builder(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Server(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Stack(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Deployment(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Build(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Repo(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Procedure(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::Action(resource.id)), + ); + targets.extend( + resource::list_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + .map(|resource| ResourceTarget::ServerTemplate(resource.id)), + ); + targets.extend( + resource::list_full_for_user::( + ResourceQuery::builder().tags(tags).build(), + user, + &all_tags, + ) + .await? + .into_iter() + // These will already be filtered by [ExportResourcesToToml] + .map(|resource| ResourceTarget::ResourceSync(resource.id)), + ); + Ok(targets) } -impl Resolve for State { +impl Resolve for ExportAllResourcesToToml { async fn resolve( - &self, + self, + args: &ReadArgs, + ) -> serror::Result { + let targets = if self.include_resources { + get_all_targets(&self.tags, &args.user).await? + } else { + Vec::new() + }; + + let user_groups = if self.include_user_groups { + if args.user.admin { + find_collect(&db_client().user_groups, None, None) + .await + .context("failed to query db for user groups")? + .into_iter() + .map(|user_group| user_group.id) + .collect() + } else { + get_user_user_group_ids(&args.user.id).await? + } + } else { + Vec::new() + }; + ExportResourcesToToml { + targets, + user_groups, + include_variables: self.include_variables, + } + .resolve(args) + .await + } +} + +impl Resolve for ExportResourcesToToml { + async fn resolve( + self, + args: &ReadArgs, + ) -> serror::Result { + let ExportResourcesToToml { targets, user_groups, include_variables, - }: ExportResourcesToToml, - user: User, - ) -> anyhow::Result { + } = self; let mut res = ResourcesToml::default(); let all = AllResourcesById::load().await?; let id_to_tags = get_id_to_tags(None).await?; + let ReadArgs { user } = args; for target in targets { match target { ResourceTarget::Alerter(id) => { let alerter = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -212,7 +225,7 @@ impl Resolve for State { ResourceTarget::ResourceSync(id) => { let sync = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -231,9 +244,7 @@ impl Resolve for State { ResourceTarget::ServerTemplate(id) => { let template = resource::get_check_permissions::< ServerTemplate, - >( - &id, &user, PermissionLevel::Read - ) + >(&id, user, PermissionLevel::Read) .await?; res.server_templates.push( convert_resource::( @@ -247,7 +258,7 @@ impl Resolve for State { ResourceTarget::Server(id) => { let server = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -262,7 +273,7 @@ impl Resolve for State { let mut builder = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -277,7 +288,7 @@ impl Resolve for State { ResourceTarget::Build(id) => { let mut build = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -293,7 +304,7 @@ impl Resolve for State { let mut deployment = resource::get_check_permissions::< Deployment, >( - &id, &user, PermissionLevel::Read + &id, user, PermissionLevel::Read ) .await?; Deployment::replace_ids(&mut deployment, &all); @@ -307,7 +318,7 @@ impl Resolve for State { ResourceTarget::Repo(id) => { let mut repo = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -322,7 +333,7 @@ impl Resolve for State { ResourceTarget::Stack(id) => { let mut stack = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -338,7 +349,7 @@ impl Resolve for State { let mut procedure = resource::get_check_permissions::< Procedure, >( - &id, &user, PermissionLevel::Read + &id, user, PermissionLevel::Read ) .await?; Procedure::replace_ids(&mut procedure, &all); @@ -352,7 +363,7 @@ impl Resolve for State { ResourceTarget::Action(id) => { let mut action = resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -368,7 +379,7 @@ impl Resolve for State { }; } - add_user_groups(user_groups, &mut res, &all, &user) + add_user_groups(user_groups, &mut res, &all, args) .await .context("failed to add user groups")?; @@ -398,11 +409,12 @@ async fn add_user_groups( user_groups: Vec, res: &mut ResourcesToml, all: &AllResourcesById, - user: &User, + args: &ReadArgs, ) -> anyhow::Result<()> { - let user_groups = State - .resolve(ListUserGroups {}, user.clone()) - .await? + let user_groups = ListUserGroups {} + .resolve(args) + .await + .map_err(|e| e.error)? .into_iter() .filter(|ug| { user_groups.contains(&ug.name) || user_groups.contains(&ug.id) diff --git a/bin/core/src/api/read/update.rs b/bin/core/src/api/read/update.rs index 89d59440d..10d9ccca2 100644 --- a/bin/core/src/api/read/update.rs +++ b/bin/core/src/api/read/update.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::read::{GetUpdate, ListUpdates, ListUpdatesResponse}, entities::{ + ResourceTarget, action::Action, alerter::Alerter, build::Build, @@ -18,7 +19,6 @@ use komodo_client::{ sync::ResourceSync, update::{Update, UpdateListItem}, user::User, - ResourceTarget, }, }; use mungos::{ @@ -28,25 +28,22 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::{ - config::core_config, - resource, - state::{db_client, State}, -}; +use crate::{config::core_config, resource, state::db_client}; + +use super::ReadArgs; const UPDATES_PER_PAGE: i64 = 100; -impl Resolve for State { +impl Resolve for ListUpdates { async fn resolve( - &self, - ListUpdates { query, page }: ListUpdates, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let query = if user.admin || core_config().transparent_mode { - query + self.query } else { let server_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -56,7 +53,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Server" }); let deployment_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -66,7 +63,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Deployment" }); let stack_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -76,7 +73,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Stack" }); let build_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -86,7 +83,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Build" }); let repo_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -96,7 +93,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Repo" }); let procedure_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -106,7 +103,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Procedure" }); let action_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -116,7 +113,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Action" }); let builder_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -126,7 +123,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Builder" }); let alerter_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -136,7 +133,7 @@ impl Resolve for State { .unwrap_or_else(|| doc! { "target.type": "Alerter" }); let server_template_query = - resource::get_resource_ids_for_user::(&user) + resource::get_resource_ids_for_user::(user) .await? .map(|ids| { doc! { @@ -147,7 +144,7 @@ impl Resolve for State { let resource_sync_query = resource::get_resource_ids_for_user::( - &user, + user, ) .await? .map(|ids| { @@ -157,7 +154,7 @@ impl Resolve for State { }) .unwrap_or_else(|| doc! { "target.type": "ResourceSync" }); - let mut query = query.unwrap_or_default(); + let mut query = self.query.unwrap_or_default(); query.extend(doc! { "$or": [ server_query, @@ -188,7 +185,7 @@ impl Resolve for State { query, FindOptions::builder() .sort(doc! { "start_ts": -1 }) - .skip(page as u64 * UPDATES_PER_PAGE as u64) + .skip(self.page as u64 * UPDATES_PER_PAGE as u64) .limit(UPDATES_PER_PAGE) .build(), ) @@ -220,7 +217,7 @@ impl Resolve for State { .collect::>(); let next_page = if updates.len() == UPDATES_PER_PAGE as usize { - Some(page + 1) + Some(self.page + 1) } else { None }; @@ -229,13 +226,12 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for GetUpdate { async fn resolve( - &self, - GetUpdate { id }: GetUpdate, - user: User, - ) -> anyhow::Result { - let update = find_one_by_id(&db_client().updates, &id) + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let update = find_one_by_id(&db_client().updates, &self.id) .await .context("failed to query to db")? .context("no update exists with given id")?; @@ -244,14 +240,14 @@ impl Resolve for State { } match &update.target { ResourceTarget::System(_) => { - return Err(anyhow!( - "user must be admin to view system updates" - )) + return Err( + anyhow!("user must be admin to view system updates").into(), + ); } ResourceTarget::Server(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -259,7 +255,7 @@ impl Resolve for State { ResourceTarget::Deployment(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -267,7 +263,7 @@ impl Resolve for State { ResourceTarget::Build(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -275,7 +271,7 @@ impl Resolve for State { ResourceTarget::Repo(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -283,7 +279,7 @@ impl Resolve for State { ResourceTarget::Builder(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -291,7 +287,7 @@ impl Resolve for State { ResourceTarget::Alerter(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -299,7 +295,7 @@ impl Resolve for State { ResourceTarget::Procedure(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -307,7 +303,7 @@ impl Resolve for State { ResourceTarget::Action(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -315,7 +311,7 @@ impl Resolve for State { ResourceTarget::ServerTemplate(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -323,7 +319,7 @@ impl Resolve for State { ResourceTarget::ResourceSync(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; @@ -331,7 +327,7 @@ impl Resolve for State { ResourceTarget::Stack(id) => { resource::get_check_permissions::( id, - &user, + user, PermissionLevel::Read, ) .await?; diff --git a/bin/core/src/api/read/user.rs b/bin/core/src/api/read/user.rs index d120e5bbb..5a9395045 100644 --- a/bin/core/src/api/read/user.rs +++ b/bin/core/src/api/read/user.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::read::{ FindUser, FindUserResponse, GetUsername, GetUsernameResponse, @@ -6,7 +6,7 @@ use komodo_client::{ ListApiKeysForServiceUserResponse, ListApiKeysResponse, ListUsers, ListUsersResponse, }, - entities::user::{admin_service_user, User, UserConfig}, + entities::user::{UserConfig, admin_service_user}, }; use mungos::{ by_id::find_one_by_id, @@ -15,25 +15,23 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::{ - helpers::query::get_user, - state::{db_client, State}, -}; +use crate::{helpers::query::get_user, state::db_client}; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetUsername { async fn resolve( - &self, - GetUsername { user_id }: GetUsername, - _: User, - ) -> anyhow::Result { - if let Some(user) = admin_service_user(&user_id) { + self, + _: &ReadArgs, + ) -> serror::Result { + if let Some(user) = admin_service_user(&self.user_id) { return Ok(GetUsernameResponse { username: user.username, avatar: None, }); } - let user = find_one_by_id(&db_client().users, &user_id) + let user = find_one_by_id(&db_client().users, &self.user_id) .await .context("failed at mongo query for user")? .context("no user found with id")?; @@ -51,27 +49,27 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for FindUser { async fn resolve( - &self, - FindUser { user }: FindUser, - admin: User, - ) -> anyhow::Result { + self, + ReadArgs { user: admin }: &ReadArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This method is admin only.")); + return Err(anyhow!("This method is admin only.").into()); } - get_user(&user).await + Ok(get_user(&self.user).await?) } } -impl Resolve for State { +impl Resolve for ListUsers { async fn resolve( - &self, - ListUsers {}: ListUsers, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("this route is only accessable by admins")); + return Err( + anyhow!("this route is only accessable by admins").into(), + ); } let mut users = find_collect( &db_client().users, @@ -85,12 +83,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListApiKeys { async fn resolve( - &self, - ListApiKeys {}: ListApiKeys, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let api_keys = find_collect( &db_client().api_keys, doc! { "user_id": &user.id }, @@ -108,20 +105,19 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListApiKeysForServiceUser { async fn resolve( - &self, - ListApiKeysForServiceUser { user }: ListApiKeysForServiceUser, - admin: User, - ) -> anyhow::Result { + self, + ReadArgs { user: admin }: &ReadArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This method is admin only.")); + return Err(anyhow!("This method is admin only.").into()); } - let user = get_user(&user).await?; + let user = get_user(&self.user).await?; let UserConfig::Service { .. } = user.config else { - return Err(anyhow!("Given user is not service user")); + return Err(anyhow!("Given user is not service user").into()); }; let api_keys = find_collect( &db_client().api_keys, diff --git a/bin/core/src/api/read/user_group.rs b/bin/core/src/api/read/user_group.rs index 363332eaa..611bd9de4 100644 --- a/bin/core/src/api/read/user_group.rs +++ b/bin/core/src/api/read/user_group.rs @@ -1,64 +1,60 @@ use std::str::FromStr; use anyhow::Context; -use komodo_client::{ - api::read::{ - GetUserGroup, GetUserGroupResponse, ListUserGroups, - ListUserGroupsResponse, - }, - entities::user::User, -}; +use komodo_client::api::read::*; use mungos::{ find::find_collect, mongodb::{ - bson::{doc, oid::ObjectId, Document}, + bson::{Document, doc, oid::ObjectId}, options::FindOptions, }, }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::state::db_client; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetUserGroup { async fn resolve( - &self, - GetUserGroup { user_group }: GetUserGroup, - user: User, - ) -> anyhow::Result { - let mut filter = match ObjectId::from_str(&user_group) { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let mut filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; // Don't allow non admin users to get UserGroups they aren't a part of. if !user.admin { // Filter for only UserGroups which contain the users id filter.insert("users", &user.id); } - db_client() + let res = db_client() .user_groups .find_one(filter) .await .context("failed to query db for user groups")? - .context("no UserGroup found with given name or id") + .context("no UserGroup found with given name or id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for ListUserGroups { async fn resolve( - &self, - ListUserGroups {}: ListUserGroups, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let mut filter = Document::new(); if !user.admin { filter.insert("users", &user.id); } - find_collect( + let res = find_collect( &db_client().user_groups, filter, FindOptions::builder().sort(doc! { "name": 1 }).build(), ) .await - .context("failed to query db for UserGroups") + .context("failed to query db for UserGroups")?; + Ok(res) } } diff --git a/bin/core/src/api/read/variable.rs b/bin/core/src/api/read/variable.rs index 9fa42c517..f196c1df2 100644 --- a/bin/core/src/api/read/variable.rs +++ b/bin/core/src/api/read/variable.rs @@ -1,27 +1,19 @@ use anyhow::Context; -use komodo_client::{ - api::read::{ - GetVariable, GetVariableResponse, ListVariables, - ListVariablesResponse, - }, - entities::user::User, -}; +use komodo_client::api::read::*; use mongo_indexed::doc; use mungos::{find::find_collect, mongodb::options::FindOptions}; use resolver_api::Resolve; -use crate::{ - helpers::query::get_variable, - state::{db_client, State}, -}; +use crate::{helpers::query::get_variable, state::db_client}; -impl Resolve for State { +use super::ReadArgs; + +impl Resolve for GetVariable { async fn resolve( - &self, - GetVariable { name }: GetVariable, - user: User, - ) -> anyhow::Result { - let mut variable = get_variable(&name).await?; + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { + let mut variable = get_variable(&self.name).await?; if !variable.is_secret || user.admin { return Ok(variable); } @@ -30,12 +22,11 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for ListVariables { async fn resolve( - &self, - ListVariables {}: ListVariables, - user: User, - ) -> anyhow::Result { + self, + ReadArgs { user }: &ReadArgs, + ) -> serror::Result { let variables = find_collect( &db_client().variables, None, diff --git a/bin/core/src/api/user.rs b/bin/core/src/api/user.rs index 8d6bd527c..95a605b07 100644 --- a/bin/core/src/api/user.rs +++ b/bin/core/src/api/user.rs @@ -1,20 +1,16 @@ use std::{collections::VecDeque, time::Instant}; -use anyhow::{anyhow, Context}; -use axum::{middleware, routing::post, Extension, Json, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use anyhow::{Context, anyhow}; +use axum::{Extension, Json, Router, middleware, routing::post}; +use derive_variants::EnumVariants; use komodo_client::{ - api::user::{ - CreateApiKey, CreateApiKeyResponse, DeleteApiKey, - DeleteApiKeyResponse, PushRecentlyViewed, - PushRecentlyViewedResponse, SetLastSeenUpdate, - SetLastSeenUpdateResponse, - }, + api::user::*, entities::{api_key::ApiKey, komodo_timestamp, user::User}, }; use mongo_indexed::doc; use mungos::{by_id::update_one_by_id, mongodb::bson::to_bson}; -use resolver_api::{derive::Resolver, Resolve, Resolver}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use uuid::Uuid; @@ -22,13 +18,20 @@ use uuid::Uuid; use crate::{ auth::auth_request, helpers::{query::get_user, random_string}, - state::{db_client, State}, + state::db_client, }; +pub struct UserArgs { + pub user: User, +} + #[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Resolver)] -#[resolver_target(State)] -#[resolver_args(User)] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, +)] +#[args(UserArgs)] +#[response(Response)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] enum UserRequest { PushRecentlyViewed(PushRecentlyViewed), @@ -47,47 +50,37 @@ pub fn router() -> Router { async fn handler( Extension(user): Extension, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let timer = Instant::now(); let req_id = Uuid::new_v4(); debug!( "/user request {req_id} | user: {} ({})", user.username, user.id ); - let res = - State - .resolve_request(request, user) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&UserArgs { user }).await; if let Err(e) = &res { - warn!("/user request {req_id} error: {e:#}"); + warn!("/user request {req_id} error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/user request {req_id} | resolve time: {elapsed:?}"); - Ok((TypedHeader(ContentType::json()), res?)) + res.map(|res| res.0) } const RECENTLY_VIEWED_MAX: usize = 10; -impl Resolve for State { +impl Resolve for PushRecentlyViewed { #[instrument( name = "PushRecentlyViewed", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - PushRecentlyViewed { resource }: PushRecentlyViewed, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { let user = get_user(&user.id).await?; - let (resource_type, id) = resource.extract_variant_id(); + let (resource_type, id) = self.resource.extract_variant_id(); let update = match user.recents.get(&resource_type) { Some(recents) => { let mut recents = recents @@ -117,17 +110,16 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for SetLastSeenUpdate { #[instrument( name = "SetLastSeenUpdate", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - SetLastSeenUpdate {}: SetLastSeenUpdate, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { update_one_by_id( &db_client().users, &user.id, @@ -145,17 +137,12 @@ impl Resolve for State { const SECRET_LENGTH: usize = 40; const BCRYPT_COST: u32 = 10; -impl Resolve for State { - #[instrument( - name = "CreateApiKey", - level = "debug", - skip(self, user) - )] +impl Resolve for CreateApiKey { + #[instrument(name = "CreateApiKey", level = "debug", skip(user))] async fn resolve( - &self, - CreateApiKey { name, expires }: CreateApiKey, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { let user = get_user(&user.id).await?; let key = format!("K-{}", random_string(SECRET_LENGTH)); @@ -164,12 +151,12 @@ impl Resolve for State { .context("failed at hashing secret string")?; let api_key = ApiKey { - name, + name: self.name, key: key.clone(), secret: secret_hash, user_id: user.id.clone(), created_at: komodo_timestamp(), - expires, + expires: self.expires, }; db_client() .api_keys @@ -180,26 +167,21 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument( - name = "DeleteApiKey", - level = "debug", - skip(self, user) - )] +impl Resolve for DeleteApiKey { + #[instrument(name = "DeleteApiKey", level = "debug", skip(user))] async fn resolve( - &self, - DeleteApiKey { key }: DeleteApiKey, - user: User, - ) -> anyhow::Result { + self, + UserArgs { user }: &UserArgs, + ) -> serror::Result { let client = db_client(); let key = client .api_keys - .find_one(doc! { "key": &key }) + .find_one(doc! { "key": &self.key }) .await .context("failed at db query")? .context("no api key with key found")?; if user.id != key.user_id { - return Err(anyhow!("api key does not belong to user")); + return Err(anyhow!("api key does not belong to user").into()); } client .api_keys diff --git a/bin/core/src/api/write/action.rs b/bin/core/src/api/write/action.rs index f18c3a6b9..c262e754b 100644 --- a/bin/core/src/api/write/action.rs +++ b/bin/core/src/api/write/action.rs @@ -2,70 +2,70 @@ use komodo_client::{ api::write::*, entities::{ action::Action, permission::PermissionLevel, update::Update, - user::User, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateAction", skip(self, user))] - async fn resolve( - &self, - CreateAction { name, config }: CreateAction, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await - } -} +use super::WriteArgs; -impl Resolve for State { - #[instrument(name = "CopyAction", skip(self, user))] +impl Resolve for CreateAction { + #[instrument(name = "CreateAction", skip(user))] async fn resolve( - &self, - CopyAction { name, id }: CopyAction, - user: User, - ) -> anyhow::Result { - let Action { config, .. } = resource::get_check_permissions::< - Action, - >( - &id, &user, PermissionLevel::Write + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, ) - .await?; - resource::create::(&name, config.into(), &user).await } } -impl Resolve for State { - #[instrument(name = "UpdateAction", skip(self, user))] +impl Resolve for CopyAction { + #[instrument(name = "CopyAction", skip(user))] async fn resolve( - &self, - UpdateAction { id, config }: UpdateAction, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let Action { config, .. } = + resource::get_check_permissions::( + &self.id, + user, + PermissionLevel::Write, + ) + .await?; + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameAction", skip(self, user))] +impl Resolve for UpdateAction { + #[instrument(name = "UpdateAction", skip(user))] async fn resolve( - &self, - RenameAction { id, name }: RenameAction, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "DeleteAction", skip(self, user))] +impl Resolve for RenameAction { + #[instrument(name = "RenameAction", skip(user))] async fn resolve( - &self, - DeleteAction { id }: DeleteAction, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) + } +} + +impl Resolve for DeleteAction { + #[instrument(name = "DeleteAction", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } diff --git a/bin/core/src/api/write/alerter.rs b/bin/core/src/api/write/alerter.rs index 2047f4872..e3c1deee5 100644 --- a/bin/core/src/api/write/alerter.rs +++ b/bin/core/src/api/write/alerter.rs @@ -2,70 +2,76 @@ use komodo_client::{ api::write::*, entities::{ alerter::Alerter, permission::PermissionLevel, update::Update, - user::User, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateAlerter", skip(self, user))] - async fn resolve( - &self, - CreateAlerter { name, config }: CreateAlerter, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await - } -} +use super::WriteArgs; -impl Resolve for State { - #[instrument(name = "CopyAlerter", skip(self, user))] +impl Resolve for CreateAlerter { + #[instrument(name = "CreateAlerter", skip(user))] async fn resolve( - &self, - CopyAlerter { name, id }: CopyAlerter, - user: User, - ) -> anyhow::Result { - let Alerter { config, .. } = resource::get_check_permissions::< - Alerter, - >( - &id, &user, PermissionLevel::Write + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, ) - .await?; - resource::create::(&name, config.into(), &user).await } } -impl Resolve for State { - #[instrument(name = "DeleteAlerter", skip(self, user))] +impl Resolve for CopyAlerter { + #[instrument(name = "CopyAlerter", skip(user))] async fn resolve( - &self, - DeleteAlerter { id }: DeleteAlerter, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let Alerter { config, .. } = + resource::get_check_permissions::( + &self.id, + user, + PermissionLevel::Write, + ) + .await?; + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "UpdateAlerter", skip(self, user))] +impl Resolve for DeleteAlerter { + #[instrument(name = "DeleteAlerter", skip(args))] async fn resolve( - &self, - UpdateAlerter { id, config }: UpdateAlerter, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "RenameAlerter", skip(self, user))] +impl Resolve for UpdateAlerter { + #[instrument(name = "UpdateAlerter", skip(user))] async fn resolve( - &self, - RenameAlerter { id, name }: RenameAlerter, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) + } +} + +impl Resolve for RenameAlerter { + #[instrument(name = "RenameAlerter", skip(user))] + async fn resolve( + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } diff --git a/bin/core/src/api/write/build.rs b/bin/core/src/api/write/build.rs index e1ee86abd..b2b22909a 100644 --- a/bin/core/src/api/write/build.rs +++ b/bin/core/src/api/write/build.rs @@ -1,14 +1,13 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use git::GitRes; use komodo_client::{ api::write::*, entities::{ + CloneArgs, NoData, build::{Build, BuildInfo, PartialBuildConfig}, config::core::CoreConfig, permission::PermissionLevel, update::Update, - user::User, - CloneArgs, NoData, }, }; use mongo_indexed::doc; @@ -22,89 +21,88 @@ use crate::{ config::core_config, helpers::git_token, resource, - state::{db_client, github_client, State}, + state::{db_client, github_client}, }; -impl Resolve for State { - #[instrument(name = "CreateBuild", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateBuild { + #[instrument(name = "CreateBuild", skip(user))] async fn resolve( - &self, - CreateBuild { name, config }: CreateBuild, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyBuild", skip(self, user))] +impl Resolve for CopyBuild { + #[instrument(name = "CopyBuild", skip(user))] async fn resolve( - &self, - CopyBuild { name, id }: CopyBuild, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Build { mut config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; // reset version to 0.0.0 config.version = Default::default(); - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteBuild", skip(self, user))] +impl Resolve for DeleteBuild { + #[instrument(name = "DeleteBuild", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) + } +} + +impl Resolve for UpdateBuild { + #[instrument(name = "UpdateBuild", skip(user))] async fn resolve( - &self, - DeleteBuild { id }: DeleteBuild, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateBuild", skip(self, user))] +impl Resolve for RenameBuild { + #[instrument(name = "RenameBuild", skip(user))] async fn resolve( - &self, - UpdateBuild { id, config }: UpdateBuild, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameBuild", skip(self, user))] - async fn resolve( - &self, - RenameBuild { id, name }: RenameBuild, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await - } -} - -impl Resolve for State { +impl Resolve for RefreshBuildCache { #[instrument( name = "RefreshBuildCache", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshBuildCache { build }: RefreshBuildCache, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // build should be able to do this. let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Execute, ) .await?; @@ -178,39 +176,44 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "CreateBuildWebhook", skip(self, user))] +impl Resolve for CreateBuildWebhook { + #[instrument(name = "CreateBuildWebhook", skip(args))] async fn resolve( - &self, - CreateBuildWebhook { build }: CreateBuildWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; + let WriteArgs { user } = args; + let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Write, ) .await?; if build.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = build.config.repo.split('/'); let owner = split.next().context("Build repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -271,64 +274,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !build.config.webhook_enabled { - self - .resolve( - UpdateBuild { - id: build.id, - config: PartialBuildConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update build to enable webhook")?; + UpdateBuild { + id: build.id, + config: PartialBuildConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update build to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteBuildWebhook", skip(self, user))] +impl Resolve for DeleteBuildWebhook { + #[instrument(name = "DeleteBuildWebhook", skip(user))] async fn resolve( - &self, - DeleteBuildWebhook { build }: DeleteBuildWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let build = resource::get_check_permissions::( - &build, - &user, + &self.build, + user, PermissionLevel::Write, ) .await?; if build.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if build.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't delete webhook" - )); + return Err( + anyhow!("No repo configured, can't delete webhook").into(), + ); } let mut split = build.config.repo.split('/'); let owner = split.next().context("Build repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = diff --git a/bin/core/src/api/write/builder.rs b/bin/core/src/api/write/builder.rs index d99fabe0d..337d98506 100644 --- a/bin/core/src/api/write/builder.rs +++ b/bin/core/src/api/write/builder.rs @@ -2,70 +2,76 @@ use komodo_client::{ api::write::*, entities::{ builder::Builder, permission::PermissionLevel, update::Update, - user::User, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateBuilder", skip(self, user))] - async fn resolve( - &self, - CreateBuilder { name, config }: CreateBuilder, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await - } -} +use super::WriteArgs; -impl Resolve for State { - #[instrument(name = "CopyBuilder", skip(self, user))] +impl Resolve for CreateBuilder { + #[instrument(name = "CreateBuilder", skip(user))] async fn resolve( - &self, - CopyBuilder { name, id }: CopyBuilder, - user: User, - ) -> anyhow::Result { - let Builder { config, .. } = resource::get_check_permissions::< - Builder, - >( - &id, &user, PermissionLevel::Write + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, ) - .await?; - resource::create::(&name, config.into(), &user).await } } -impl Resolve for State { - #[instrument(name = "DeleteBuilder", skip(self, user))] +impl Resolve for CopyBuilder { + #[instrument(name = "CopyBuilder", skip(user))] async fn resolve( - &self, - DeleteBuilder { id }: DeleteBuilder, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let Builder { config, .. } = + resource::get_check_permissions::( + &self.id, + user, + PermissionLevel::Write, + ) + .await?; + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "UpdateBuilder", skip(self, user))] +impl Resolve for DeleteBuilder { + #[instrument(name = "DeleteBuilder", skip(args))] async fn resolve( - &self, - UpdateBuilder { id, config }: UpdateBuilder, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "RenameBuilder", skip(self, user))] +impl Resolve for UpdateBuilder { + #[instrument(name = "UpdateBuilder", skip(user))] async fn resolve( - &self, - RenameBuilder { id, name }: RenameBuilder, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) + } +} + +impl Resolve for RenameBuilder { + #[instrument(name = "RenameBuilder", skip(user))] + async fn resolve( + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } diff --git a/bin/core/src/api/write/deployment.rs b/bin/core/src/api/write/deployment.rs index dd4ccca51..69a02726a 100644 --- a/bin/core/src/api/write/deployment.rs +++ b/bin/core/src/api/write/deployment.rs @@ -1,7 +1,8 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::write::*, entities::{ + Operation, deployment::{ Deployment, DeploymentImage, DeploymentState, PartialDeploymentConfig, RestartMode, @@ -12,8 +13,6 @@ use komodo_client::{ server::{Server, ServerState}, to_komodo_name, update::Update, - user::User, - Operation, }, }; use mungos::{by_id::update_one_by_id, mongodb::bson::doc}; @@ -27,51 +26,53 @@ use crate::{ update::{add_update, make_update}, }, resource, - state::{action_states, db_client, server_status_cache, State}, + state::{action_states, db_client, server_status_cache}, }; -impl Resolve for State { - #[instrument(name = "CreateDeployment", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateDeployment { + #[instrument(name = "CreateDeployment", skip(user))] async fn resolve( - &self, - CreateDeployment { name, config }: CreateDeployment, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyDeployment", skip(self, user))] +impl Resolve for CopyDeployment { + #[instrument(name = "CopyDeployment", skip(user))] async fn resolve( - &self, - CopyDeployment { name, id }: CopyDeployment, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Deployment { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument( - name = "CreateDeploymentFromContainer", - skip(self, user) - )] +impl Resolve for CreateDeploymentFromContainer { + #[instrument(name = "CreateDeploymentFromContainer", skip(user))] async fn resolve( - &self, - CreateDeploymentFromContainer { name, server }: CreateDeploymentFromContainer, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Write, ) .await?; @@ -79,13 +80,18 @@ impl Resolve for State { .get_or_insert_default(&server.id) .await; if cache.state != ServerState::Ok { - return Err(anyhow!( - "Cannot inspect container: server is {:?}", - cache.state - )); + return Err( + anyhow!( + "Cannot inspect container: server is {:?}", + cache.state + ) + .into(), + ); } let container = periphery_client(&server)? - .request(InspectContainer { name: name.clone() }) + .request(InspectContainer { + name: self.name.clone(), + }) .await .context("Failed to inspect container")?; @@ -146,42 +152,45 @@ impl Resolve for State { }); } - resource::create::(&name, config, &user).await + Ok( + resource::create::(&self.name, config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteDeployment", skip(self, user))] +impl Resolve for DeleteDeployment { + #[instrument(name = "DeleteDeployment", skip(args))] async fn resolve( - &self, - DeleteDeployment { id }: DeleteDeployment, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateDeployment", skip(self, user))] +impl Resolve for UpdateDeployment { + #[instrument(name = "UpdateDeployment", skip(user))] async fn resolve( - &self, - UpdateDeployment { id, config }: UpdateDeployment, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameDeployment", skip(self, user))] +impl Resolve for RenameDeployment { + #[instrument(name = "RenameDeployment", skip(user))] async fn resolve( - &self, - RenameDeployment { id, name }: RenameDeployment, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let deployment = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; @@ -197,18 +206,21 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.renaming = true)?; - let name = to_komodo_name(&name); + let name = to_komodo_name(&self.name); let container_state = get_deployment_state(&deployment).await?; if container_state == DeploymentState::Unknown { - return Err(anyhow!( - "Cannot rename Deployment when container status is unknown" - )); + return Err( + anyhow!( + "Cannot rename Deployment when container status is unknown" + ) + .into(), + ); } let mut update = - make_update(&deployment, Operation::RenameDeployment, &user); + make_update(&deployment, Operation::RenameDeployment, user); update_one_by_id( &db_client().deployments, diff --git a/bin/core/src/api/write/description.rs b/bin/core/src/api/write/description.rs index 2740e722b..a30709e05 100644 --- a/bin/core/src/api/write/description.rs +++ b/bin/core/src/api/write/description.rs @@ -2,117 +2,118 @@ use anyhow::anyhow; use komodo_client::{ api::write::{UpdateDescription, UpdateDescriptionResponse}, entities::{ - action::Action, alerter::Alerter, build::Build, builder::Builder, - deployment::Deployment, procedure::Procedure, repo::Repo, - server::Server, server_template::ServerTemplate, stack::Stack, - sync::ResourceSync, user::User, ResourceTarget, + ResourceTarget, action::Action, alerter::Alerter, build::Build, + builder::Builder, deployment::Deployment, procedure::Procedure, + repo::Repo, server::Server, server_template::ServerTemplate, + stack::Stack, sync::ResourceSync, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "UpdateDescription", skip(self, user))] +use super::WriteArgs; + +impl Resolve for UpdateDescription { + #[instrument(name = "UpdateDescription", skip(user))] async fn resolve( - &self, - UpdateDescription { - target, - description, - }: UpdateDescription, - user: User, - ) -> anyhow::Result { - match target { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + match self.target { ResourceTarget::System(_) => { - return Err(anyhow!( - "cannot update description of System resource target" - )) + return Err( + anyhow!( + "cannot update description of System resource target" + ) + .into(), + ); } ResourceTarget::Server(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Deployment(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Build(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Repo(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Builder(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Alerter(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Procedure(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Action(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::ServerTemplate(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::ResourceSync(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } ResourceTarget::Stack(id) => { resource::update_description::( &id, - &description, - &user, + &self.description, + user, ) .await?; } diff --git a/bin/core/src/api/write/mod.rs b/bin/core/src/api/write/mod.rs index f2d14fce1..4926184d3 100644 --- a/bin/core/src/api/write/mod.rs +++ b/bin/core/src/api/write/mod.rs @@ -1,17 +1,17 @@ use std::time::Instant; -use anyhow::{anyhow, Context}; -use axum::{middleware, routing::post, Extension, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use anyhow::Context; +use axum::{Extension, Router, middleware, routing::post}; use derive_variants::{EnumVariants, ExtractVariant}; use komodo_client::{api::write::*, entities::user::User}; -use resolver_api::{derive::Resolver, Resolver}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; use serror::Json; use typeshare::typeshare; use uuid::Uuid; -use crate::{auth::auth_request, state::State}; +use crate::auth::auth_request; mod action; mod alerter; @@ -33,13 +33,18 @@ mod user; mod user_group; mod variable; +pub struct WriteArgs { + pub user: User, +} + #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants, + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, )] #[variant_derive(Debug)] -#[resolver_target(State)] -#[resolver_args(User)] +#[args(WriteArgs)] +#[response(Response)] +#[error(serror::Error)] #[serde(tag = "type", content = "params")] pub enum WriteRequest { // ==== USER ==== @@ -167,6 +172,7 @@ pub enum WriteRequest { CreateTag(CreateTag), DeleteTag(DeleteTag), RenameTag(RenameTag), + UpdateTagColor(UpdateTagColor), UpdateTagsOnResource(UpdateTagsOnResource), // ==== VARIABLE ==== @@ -194,7 +200,7 @@ pub fn router() -> Router { async fn handler( Extension(user): Extension, Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let req_id = Uuid::new_v4(); let res = tokio::spawn(task(req_id, request, user)) @@ -205,7 +211,7 @@ async fn handler( warn!("/write request {req_id} spawn error: {e:#}"); } - Ok((TypedHeader(ContentType::json()), res??)) + res? } #[instrument( @@ -220,28 +226,19 @@ async fn task( req_id: Uuid, request: WriteRequest, user: User, -) -> anyhow::Result { +) -> serror::Result { info!("/write request | user: {}", user.username); let timer = Instant::now(); - let res = - State - .resolve_request(request, user) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&WriteArgs { user }).await; if let Err(e) = &res { - warn!("/write request {req_id} error: {e:#}"); + warn!("/write request {req_id} error: {:#}", e.error); } let elapsed = timer.elapsed(); debug!("/write request {req_id} | resolve time: {elapsed:?}"); - res + res.map(|res| res.0) } diff --git a/bin/core/src/api/write/permissions.rs b/bin/core/src/api/write/permissions.rs index 65e96655b..d987bbe05 100644 --- a/bin/core/src/api/write/permissions.rs +++ b/bin/core/src/api/write/permissions.rs @@ -1,60 +1,56 @@ use std::str::FromStr; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ - api::write::{ - UpdatePermissionOnResourceType, - UpdatePermissionOnResourceTypeResponse, UpdatePermissionOnTarget, - UpdatePermissionOnTargetResponse, UpdateUserAdmin, - UpdateUserAdminResponse, UpdateUserBasePermissions, - UpdateUserBasePermissionsResponse, - }, + api::write::*, entities::{ - permission::{UserTarget, UserTargetVariant}, - user::User, ResourceTarget, ResourceTargetVariant, + permission::{UserTarget, UserTargetVariant}, }, }; use mungos::{ by_id::{find_one_by_id, update_one_by_id}, mongodb::{ - bson::{doc, oid::ObjectId, Document}, + bson::{Document, doc, oid::ObjectId}, options::UpdateOptions, }, }; use resolver_api::Resolve; -use crate::{ - helpers::query::get_user, - state::{db_client, State}, -}; +use crate::{helpers::query::get_user, state::db_client}; -impl Resolve for State { +use super::WriteArgs; + +impl Resolve for UpdateUserAdmin { + #[instrument(name = "UpdateUserAdmin", skip(super_admin))] async fn resolve( - &self, - UpdateUserAdmin { user_id, admin }: UpdateUserAdmin, - super_admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: super_admin }: &WriteArgs, + ) -> serror::Result { if !super_admin.super_admin { - return Err(anyhow!("Only super admins can call this method.")); + return Err( + anyhow!("Only super admins can call this method.").into(), + ); } - let user = find_one_by_id(&db_client().users, &user_id) + let user = find_one_by_id(&db_client().users, &self.user_id) .await .context("failed to query mongo for user")? .context("did not find user with given id")?; if !user.enabled { - return Err(anyhow!("User is disabled. Enable user first.")); + return Err( + anyhow!("User is disabled. Enable user first.").into(), + ); } if user.super_admin { - return Err(anyhow!("Cannot update other super admins")); + return Err(anyhow!("Cannot update other super admins").into()); } update_one_by_id( &db_client().users, - &user_id, - doc! { "$set": { "admin": admin } }, + &self.user_id, + doc! { "$set": { "admin": self.admin } }, None, ) .await?; @@ -63,20 +59,21 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UpdateUserBasePermissions", skip(self, admin))] +impl Resolve for UpdateUserBasePermissions { + #[instrument(name = "UpdateUserBasePermissions", skip(admin))] async fn resolve( - &self, - UpdateUserBasePermissions { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { + let UpdateUserBasePermissions { user_id, enabled, create_servers, create_builds, - }: UpdateUserBasePermissions, - admin: User, - ) -> anyhow::Result { + } = self; + if !admin.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } let user = find_one_by_id(&db_client().users, &user_id) @@ -84,14 +81,17 @@ impl Resolve for State { .context("failed to query mongo for user")? .context("did not find user with given id")?; if user.super_admin { - return Err(anyhow!( - "Cannot use this method to update super admins permissions" - )); + return Err( + anyhow!( + "Cannot use this method to update super admins permissions" + ) + .into(), + ); } if user.admin && !admin.super_admin { return Err(anyhow!( "Only super admins can use this method to update other admins permissions" - )); + ).into()); } let mut update_doc = Document::new(); if let Some(enabled) = enabled { @@ -116,34 +116,35 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument( - name = "UpdatePermissionOnResourceType", - skip(self, admin) - )] +impl Resolve for UpdatePermissionOnResourceType { + #[instrument(name = "UpdatePermissionOnResourceType", skip(admin))] async fn resolve( - &self, - UpdatePermissionOnResourceType { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { + let UpdatePermissionOnResourceType { user_target, resource_type, permission, - }: UpdatePermissionOnResourceType, - admin: User, - ) -> anyhow::Result { + } = self; + if !admin.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } // Some extra checks if user target is an actual User if let UserTarget::User(user_id) = &user_target { let user = get_user(user_id).await?; if user.admin { - return Err(anyhow!( + return Err( + anyhow!( "cannot use this method to update other admins permissions" - )); + ) + .into(), + ); } if !user.enabled { - return Err(anyhow!("user not enabled")); + return Err(anyhow!("user not enabled").into()); } } @@ -181,31 +182,35 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "UpdatePermissionOnTarget", skip(self, admin))] +impl Resolve for UpdatePermissionOnTarget { + #[instrument(name = "UpdatePermissionOnTarget", skip(admin))] async fn resolve( - &self, - UpdatePermissionOnTarget { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { + let UpdatePermissionOnTarget { user_target, resource_target, permission, - }: UpdatePermissionOnTarget, - admin: User, - ) -> anyhow::Result { + } = self; + if !admin.admin { - return Err(anyhow!("this method is admin only")); + return Err(anyhow!("this method is admin only").into()); } // Some extra checks if user target is an actual User if let UserTarget::User(user_id) = &user_target { let user = get_user(user_id).await?; if user.admin { - return Err(anyhow!( + return Err( + anyhow!( "cannot use this method to update other admins permissions" - )); + ) + .into(), + ); } if !user.enabled { - return Err(anyhow!("user not enabled")); + return Err(anyhow!("user not enabled").into()); } } @@ -247,7 +252,7 @@ impl Resolve for State { /// checks if inner id is actually a `name`, and replaces it with id if so. async fn extract_user_target_with_validation( user_target: &UserTarget, -) -> anyhow::Result<(UserTargetVariant, String)> { +) -> serror::Result<(UserTargetVariant, String)> { match user_target { UserTarget::User(ident) => { let filter = match ObjectId::from_str(ident) { @@ -283,7 +288,7 @@ async fn extract_user_target_with_validation( /// checks if inner id is actually a `name`, and replaces it with id if so. async fn extract_resource_target_with_validation( resource_target: &ResourceTarget, -) -> anyhow::Result<(ResourceTargetVariant, String)> { +) -> serror::Result<(ResourceTargetVariant, String)> { match resource_target { ResourceTarget::System(_) => { let res = resource_target.extract_variant_id(); diff --git a/bin/core/src/api/write/procedure.rs b/bin/core/src/api/write/procedure.rs index 393c6d820..89c6539e1 100644 --- a/bin/core/src/api/write/procedure.rs +++ b/bin/core/src/api/write/procedure.rs @@ -1,72 +1,80 @@ use komodo_client::{ api::write::*, entities::{ - permission::PermissionLevel, procedure::Procedure, - update::Update, user::User, + permission::PermissionLevel, procedure::Procedure, update::Update, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateProcedure", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateProcedure { + #[instrument(name = "CreateProcedure", skip(user))] async fn resolve( - &self, - CreateProcedure { name, config }: CreateProcedure, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyProcedure", skip(self, user))] +impl Resolve for CopyProcedure { + #[instrument(name = "CopyProcedure", skip(user))] async fn resolve( - &self, - CopyProcedure { name, id }: CopyProcedure, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Procedure { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "UpdateProcedure", skip(self, user))] +impl Resolve for UpdateProcedure { + #[instrument(name = "UpdateProcedure", skip(user))] async fn resolve( - &self, - UpdateProcedure { id, config }: UpdateProcedure, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameProcedure", skip(self, user))] +impl Resolve for RenameProcedure { + #[instrument(name = "RenameProcedure", skip(user))] async fn resolve( - &self, - RenameProcedure { id, name }: RenameProcedure, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::rename::(&self.id, &self.name, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteProcedure", skip(self, user))] +impl Resolve for DeleteProcedure { + #[instrument(name = "DeleteProcedure", skip(args))] async fn resolve( - &self, - DeleteProcedure { id }: DeleteProcedure, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } diff --git a/bin/core/src/api/write/provider.rs b/bin/core/src/api/write/provider.rs index 1b5f20f19..07d7bd0b7 100644 --- a/bin/core/src/api/write/provider.rs +++ b/bin/core/src/api/write/provider.rs @@ -1,10 +1,9 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::write::*, entities::{ - provider::{DockerRegistryAccount, GitProviderAccount}, - user::User, Operation, ResourceTarget, + provider::{DockerRegistryAccount, GitProviderAccount}, }, }; use mungos::{ @@ -15,35 +14,37 @@ use resolver_api::Resolve; use crate::{ helpers::update::{add_update, make_update}, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { +use super::WriteArgs; + +impl Resolve for CreateGitProviderAccount { async fn resolve( - &self, - CreateGitProviderAccount { account }: CreateGitProviderAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can create git provider accounts" - )); + return Err( + anyhow!("only admins can create git provider accounts") + .into(), + ); } - let mut account: GitProviderAccount = account.into(); + let mut account: GitProviderAccount = self.account.into(); if account.domain.is_empty() { - return Err(anyhow!("domain cannot be empty string.")); + return Err(anyhow!("domain cannot be empty string.").into()); } if account.username.is_empty() { - return Err(anyhow!("username cannot be empty string.")); + return Err(anyhow!("username cannot be empty string.").into()); } let mut update = make_update( ResourceTarget::system(), Operation::CreateGitProviderAccount, - &user, + user, ); account.id = db_client() @@ -77,62 +78,63 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for UpdateGitProviderAccount { async fn resolve( - &self, - UpdateGitProviderAccount { id, mut account }: UpdateGitProviderAccount, - user: User, - ) -> anyhow::Result { + mut self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can update git provider accounts" - )); + return Err( + anyhow!("only admins can update git provider accounts") + .into(), + ); } - if let Some(domain) = &account.domain { + if let Some(domain) = &self.account.domain { if domain.is_empty() { - return Err(anyhow!( - "cannot update git provider with empty domain" - )); + return Err( + anyhow!("cannot update git provider with empty domain") + .into(), + ); } } - if let Some(username) = &account.username { + if let Some(username) = &self.account.username { if username.is_empty() { - return Err(anyhow!( - "cannot update git provider with empty username" - )); + return Err( + anyhow!("cannot update git provider with empty username") + .into(), + ); } } // Ensure update does not change id - account.id = None; + self.account.id = None; let mut update = make_update( ResourceTarget::system(), Operation::UpdateGitProviderAccount, - &user, + user, ); - let account = to_document(&account).context( + let account = to_document(&self.account).context( "failed to serialize partial git provider account to bson", )?; let db = db_client(); update_one_by_id( &db.git_accounts, - &id, + &self.id, doc! { "$set": account }, None, ) .await .context("failed to update git provider account on db")?; - let Some(account) = - find_one_by_id(&db.git_accounts, &id) - .await - .context("failed to query db for git accounts")? + let Some(account) = find_one_by_id(&db.git_accounts, &self.id) + .await + .context("failed to query db for git accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; update.push_simple_log( @@ -156,33 +158,32 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for DeleteGitProviderAccount { async fn resolve( - &self, - DeleteGitProviderAccount { id }: DeleteGitProviderAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can delete git provider accounts" - )); + return Err( + anyhow!("only admins can delete git provider accounts") + .into(), + ); } let mut update = make_update( ResourceTarget::system(), Operation::UpdateGitProviderAccount, - &user, + user, ); let db = db_client(); - let Some(account) = - find_one_by_id(&db.git_accounts, &id) - .await - .context("failed to query db for git accounts")? + let Some(account) = find_one_by_id(&db.git_accounts, &self.id) + .await + .context("failed to query db for git accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; - delete_one_by_id(&db.git_accounts, &id, None) + delete_one_by_id(&db.git_accounts, &self.id, None) .await .context("failed to delete git account on db")?; @@ -207,32 +208,34 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for CreateDockerRegistryAccount { async fn resolve( - &self, - CreateDockerRegistryAccount { account }: CreateDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can create docker registry account accounts" - )); + return Err( + anyhow!( + "only admins can create docker registry account accounts" + ) + .into(), + ); } - let mut account: DockerRegistryAccount = account.into(); + let mut account: DockerRegistryAccount = self.account.into(); if account.domain.is_empty() { - return Err(anyhow!("domain cannot be empty string.")); + return Err(anyhow!("domain cannot be empty string.").into()); } if account.username.is_empty() { - return Err(anyhow!("username cannot be empty string.")); + return Err(anyhow!("username cannot be empty string.").into()); } let mut update = make_update( ResourceTarget::system(), Operation::CreateDockerRegistryAccount, - &user, + user, ); account.id = db_client() @@ -268,50 +271,56 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for UpdateDockerRegistryAccount { async fn resolve( - &self, - UpdateDockerRegistryAccount { id, mut account }: UpdateDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + mut self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can update docker registry accounts" - )); + return Err( + anyhow!("only admins can update docker registry accounts") + .into(), + ); } - if let Some(domain) = &account.domain { + if let Some(domain) = &self.account.domain { if domain.is_empty() { - return Err(anyhow!( - "cannot update docker registry account with empty domain" - )); + return Err( + anyhow!( + "cannot update docker registry account with empty domain" + ) + .into(), + ); } } - if let Some(username) = &account.username { + if let Some(username) = &self.account.username { if username.is_empty() { - return Err(anyhow!( - "cannot update docker registry account with empty username" - )); + return Err( + anyhow!( + "cannot update docker registry account with empty username" + ) + .into(), + ); } } - account.id = None; + self.account.id = None; let mut update = make_update( ResourceTarget::system(), Operation::UpdateDockerRegistryAccount, - &user, + user, ); - let account = to_document(&account).context( + let account = to_document(&self.account).context( "failed to serialize partial docker registry account account to bson", )?; let db = db_client(); update_one_by_id( &db.registry_accounts, - &id, + &self.id, doc! { "$set": account }, None, ) @@ -320,11 +329,12 @@ impl Resolve for State { "failed to update docker registry account account on db", )?; - let Some(account) = find_one_by_id(&db.registry_accounts, &id) - .await - .context("failed to query db for registry accounts")? + let Some(account) = + find_one_by_id(&db.registry_accounts, &self.id) + .await + .context("failed to query db for registry accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; update.push_simple_log( @@ -348,32 +358,33 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for DeleteDockerRegistryAccount { async fn resolve( - &self, - DeleteDockerRegistryAccount { id }: DeleteDockerRegistryAccount, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!( - "only admins can delete docker registry accounts" - )); + return Err( + anyhow!("only admins can delete docker registry accounts") + .into(), + ); } let mut update = make_update( ResourceTarget::system(), Operation::UpdateDockerRegistryAccount, - &user, + user, ); let db = db_client(); - let Some(account) = find_one_by_id(&db.registry_accounts, &id) - .await - .context("failed to query db for git accounts")? + let Some(account) = + find_one_by_id(&db.registry_accounts, &self.id) + .await + .context("failed to query db for git accounts")? else { - return Err(anyhow!("no account found with given id")); + return Err(anyhow!("no account found with given id").into()); }; - delete_one_by_id(&db.registry_accounts, &id, None) + delete_one_by_id(&db.registry_accounts, &self.id, None) .await .context("failed to delete registry account on db")?; diff --git a/bin/core/src/api/write/repo.rs b/bin/core/src/api/write/repo.rs index 11def0dc9..99f271848 100644 --- a/bin/core/src/api/write/repo.rs +++ b/bin/core/src/api/write/repo.rs @@ -1,9 +1,10 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::format_serror; use git::GitRes; use komodo_client::{ api::write::*, entities::{ + CloneArgs, NoData, Operation, config::core::CoreConfig, komodo_timestamp, permission::PermissionLevel, @@ -11,8 +12,6 @@ use komodo_client::{ server::Server, to_komodo_name, update::{Log, Update}, - user::User, - CloneArgs, NoData, Operation, }, }; use mongo_indexed::doc; @@ -30,70 +29,67 @@ use crate::{ update::{add_update, make_update}, }, resource, - state::{action_states, db_client, github_client, State}, + state::{action_states, db_client, github_client}, }; -impl Resolve for State { - #[instrument(name = "CreateRepo", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateRepo { + #[instrument(name = "CreateRepo", skip(user))] async fn resolve( - &self, - CreateRepo { name, config }: CreateRepo, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::create::(&self.name, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "CopyRepo", skip(self, user))] +impl Resolve for CopyRepo { + #[instrument(name = "CopyRepo", skip(user))] async fn resolve( - &self, - CopyRepo { name, id }: CopyRepo, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Repo { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteRepo", skip(self, user))] - async fn resolve( - &self, - DeleteRepo { id }: DeleteRepo, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await +impl Resolve for DeleteRepo { + #[instrument(name = "DeleteRepo", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateRepo", skip(self, user))] +impl Resolve for UpdateRepo { + #[instrument(name = "UpdateRepo", skip(user))] async fn resolve( - &self, - UpdateRepo { id, config }: UpdateRepo, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameRepo", skip(self, user))] +impl Resolve for RenameRepo { + #[instrument(name = "RenameRepo", skip(user))] async fn resolve( - &self, - RenameRepo { id, name }: RenameRepo, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let repo = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; @@ -101,7 +97,9 @@ impl Resolve for State { if repo.config.server_id.is_empty() || !repo.config.path.is_empty() { - return resource::rename::(&repo.id, &name, &user).await; + return Ok( + resource::rename::(&repo.id, &self.name, user).await?, + ); } // get the action state for the repo (or insert default). @@ -113,9 +111,9 @@ impl Resolve for State { let _action_guard = action_state.update(|state| state.renaming = true)?; - let name = to_komodo_name(&name); + let name = to_komodo_name(&self.name); - let mut update = make_update(&repo, Operation::RenameRepo, &user); + let mut update = make_update(&repo, Operation::RenameRepo, user); update_one_by_id( &db_client().repos, @@ -159,22 +157,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for RefreshRepoCache { #[instrument( name = "RefreshRepoCache", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshRepoCache { repo }: RefreshRepoCache, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // repo should be able to do this. let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Execute, ) .await?; @@ -245,39 +242,42 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "CreateRepoWebhook", skip(self, user))] +impl Resolve for CreateRepoWebhook { + #[instrument(name = "CreateRepoWebhook", skip(args))] async fn resolve( - &self, - CreateRepoWebhook { repo, action }: CreateRepoWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + &args.user, PermissionLevel::Write, ) .await?; if repo.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = repo.config.repo.split('/'); let owner = split.next().context("Repo repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo_name = @@ -310,7 +310,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { RepoWebhookAction::Clone => { format!("{host}/listener/github/repo/{}/clone", repo.id) } @@ -348,64 +348,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !repo.config.webhook_enabled { - self - .resolve( - UpdateRepo { - id: repo.id, - config: PartialRepoConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update repo to enable webhook")?; + UpdateRepo { + id: repo.id, + config: PartialRepoConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update repo to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteRepoWebhook", skip(self, user))] +impl Resolve for DeleteRepoWebhook { + #[instrument(name = "DeleteRepoWebhook", skip(user))] async fn resolve( - &self, - DeleteRepoWebhook { repo, action }: DeleteRepoWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let repo = resource::get_check_permissions::( - &repo, - &user, + &self.repo, + user, PermissionLevel::Write, ) .await?; if repo.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if repo.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = repo.config.repo.split('/'); let owner = split.next().context("Repo repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo_name = @@ -431,7 +432,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { RepoWebhookAction::Clone => { format!("{host}/listener/github/repo/{}/clone", repo.id) } diff --git a/bin/core/src/api/write/server.rs b/bin/core/src/api/write/server.rs index d9a60abc7..2e4babee5 100644 --- a/bin/core/src/api/write/server.rs +++ b/bin/core/src/api/write/server.rs @@ -2,11 +2,10 @@ use formatting::format_serror; use komodo_client::{ api::write::*, entities::{ + Operation, permission::PermissionLevel, server::Server, update::{Update, UpdateStatus}, - user::User, - Operation, }, }; use periphery_client::api; @@ -18,63 +17,59 @@ use crate::{ update::{add_update, make_update, update_update}, }, resource, - state::State, }; -impl Resolve for State { - #[instrument(name = "CreateServer", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateServer { + #[instrument(name = "CreateServer", skip(user))] async fn resolve( - &self, - CreateServer { name, config }: CreateServer, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteServer", skip(self, user))] - async fn resolve( - &self, - DeleteServer { id }: DeleteServer, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await +impl Resolve for DeleteServer { + #[instrument(name = "DeleteServer", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateServer", skip(self, user))] +impl Resolve for UpdateServer { + #[instrument(name = "UpdateServer", skip(user))] async fn resolve( - &self, - UpdateServer { id, config }: UpdateServer, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameServer", skip(self, user))] +impl Resolve for RenameServer { + #[instrument(name = "RenameServer", skip(user))] async fn resolve( - &self, - RenameServer { id, name }: RenameServer, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } -impl Resolve for State { - #[instrument(name = "CreateNetwork", skip(self, user))] +impl Resolve for CreateNetwork { + #[instrument(name = "CreateNetwork", skip(user))] async fn resolve( - &self, - CreateNetwork { server, name }: CreateNetwork, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let server = resource::get_check_permissions::( - &server, - &user, + &self.server, + user, PermissionLevel::Write, ) .await?; @@ -82,12 +77,15 @@ impl Resolve for State { let periphery = periphery_client(&server)?; let mut update = - make_update(&server, Operation::CreateNetwork, &user); + make_update(&server, Operation::CreateNetwork, user); update.status = UpdateStatus::InProgress; update.id = add_update(update.clone()).await?; match periphery - .request(api::network::CreateNetwork { name, driver: None }) + .request(api::network::CreateNetwork { + name: self.name, + driver: None, + }) .await { Ok(log) => update.logs.push(log), diff --git a/bin/core/src/api/write/server_template.rs b/bin/core/src/api/write/server_template.rs index 0a873349d..9058986e4 100644 --- a/bin/core/src/api/write/server_template.rs +++ b/bin/core/src/api/write/server_template.rs @@ -5,72 +5,88 @@ use komodo_client::{ }, entities::{ permission::PermissionLevel, server_template::ServerTemplate, - update::Update, user::User, + update::Update, }, }; use resolver_api::Resolve; -use crate::{resource, state::State}; +use crate::resource; -impl Resolve for State { - #[instrument(name = "CreateServerTemplate", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateServerTemplate { + #[instrument(name = "CreateServerTemplate", skip(user))] async fn resolve( - &self, - CreateServerTemplate { name, config }: CreateServerTemplate, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::( + &self.name, + self.config, + user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyServerTemplate", skip(self, user))] +impl Resolve for CopyServerTemplate { + #[instrument(name = "CopyServerTemplate", skip(user))] async fn resolve( - &self, - CopyServerTemplate { name, id }: CopyServerTemplate, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let ServerTemplate { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user) - .await + Ok( + resource::create::( + &self.name, + config.into(), + user, + ) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteServerTemplate", skip(self, user))] +impl Resolve for DeleteServerTemplate { + #[instrument(name = "DeleteServerTemplate", skip(args))] async fn resolve( - &self, - DeleteServerTemplate { id }: DeleteServerTemplate, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateServerTemplate", skip(self, user))] +impl Resolve for UpdateServerTemplate { + #[instrument(name = "UpdateServerTemplate", skip(user))] async fn resolve( - &self, - UpdateServerTemplate { id, config }: UpdateServerTemplate, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "RenameServerTemplate", skip(self, user))] +impl Resolve for RenameServerTemplate { + #[instrument(name = "RenameServerTemplate", skip(user))] async fn resolve( - &self, - RenameServerTemplate { id, name }: RenameServerTemplate, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::rename::(&self.id, &self.name, user) + .await?, + ) } } diff --git a/bin/core/src/api/write/service_user.rs b/bin/core/src/api/write/service_user.rs index e23884656..a825254d6 100644 --- a/bin/core/src/api/write/service_user.rs +++ b/bin/core/src/api/write/service_user.rs @@ -1,17 +1,8 @@ use std::str::FromStr; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ - api::{ - user::CreateApiKey, - write::{ - CreateApiKeyForServiceUser, CreateApiKeyForServiceUserResponse, - CreateServiceUser, CreateServiceUserResponse, - DeleteApiKeyForServiceUser, DeleteApiKeyForServiceUserResponse, - UpdateServiceUserDescription, - UpdateServiceUserDescriptionResponse, - }, - }, + api::{user::CreateApiKey, write::*}, entities::{ komodo_timestamp, user::{User, UserConfig}, @@ -23,28 +14,30 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::{api::user::UserArgs, state::db_client}; -impl Resolve for State { - #[instrument(name = "CreateServiceUser", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateServiceUser { + #[instrument(name = "CreateServiceUser", skip(user))] async fn resolve( - &self, - CreateServiceUser { - username, - description, - }: CreateServiceUser, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } - if ObjectId::from_str(&username).is_ok() { - return Err(anyhow!("username cannot be valid ObjectId")); + if ObjectId::from_str(&self.username).is_ok() { + return Err( + anyhow!("username cannot be valid ObjectId").into(), + ); } - let config = UserConfig::Service { description }; + let config = UserConfig::Service { + description: self.description, + }; let mut user = User { id: Default::default(), - username, + username: self.username, config, enabled: true, admin: false, @@ -69,88 +62,81 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument( - name = "UpdateServiceUserDescription", - skip(self, user) - )] +impl Resolve for UpdateServiceUserDescription { + #[instrument(name = "UpdateServiceUserDescription", skip(user))] async fn resolve( - &self, - UpdateServiceUserDescription { - username, - description, - }: UpdateServiceUserDescription, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } let db = db_client(); let service_user = db .users - .find_one(doc! { "username": &username }) + .find_one(doc! { "username": &self.username }) .await .context("failed to query db for user")? .context("no user with given username")?; let UserConfig::Service { .. } = &service_user.config else { - return Err(anyhow!("user is not service user")); + return Err(anyhow!("user is not service user").into()); }; db.users .update_one( - doc! { "username": &username }, - doc! { "$set": { "config.data.description": description } }, + doc! { "username": &self.username }, + doc! { "$set": { "config.data.description": self.description } }, ) .await .context("failed to update user on db")?; - db.users - .find_one(doc! { "username": &username }) + let res = db + .users + .find_one(doc! { "username": &self.username }) .await .context("failed to query db for user")? - .context("user with username not found") + .context("user with username not found")?; + Ok(res) } } -impl Resolve for State { - #[instrument(name = "CreateApiKeyForServiceUser", skip(self, user))] +impl Resolve for CreateApiKeyForServiceUser { + #[instrument(name = "CreateApiKeyForServiceUser", skip(user))] async fn resolve( - &self, - CreateApiKeyForServiceUser { - user_id, - name, - expires, - }: CreateApiKeyForServiceUser, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } - let service_user = find_one_by_id(&db_client().users, &user_id) - .await - .context("failed to query db for user")? - .context("no user found with id")?; + let service_user = + find_one_by_id(&db_client().users, &self.user_id) + .await + .context("failed to query db for user")? + .context("no user found with id")?; let UserConfig::Service { .. } = &service_user.config else { - return Err(anyhow!("user is not service user")); + return Err(anyhow!("user is not service user").into()); }; - self - .resolve(CreateApiKey { name, expires }, service_user) - .await + CreateApiKey { + name: self.name, + expires: self.expires, + } + .resolve(&UserArgs { user: service_user }) + .await } } -impl Resolve for State { - #[instrument(name = "DeleteApiKeyForServiceUser", skip(self, user))] +impl Resolve for DeleteApiKeyForServiceUser { + #[instrument(name = "DeleteApiKeyForServiceUser", skip(user))] async fn resolve( - &self, - DeleteApiKeyForServiceUser { key }: DeleteApiKeyForServiceUser, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("user not admin")); + return Err(anyhow!("user not admin").into()); } let db = db_client(); let api_key = db .api_keys - .find_one(doc! { "key": &key }) + .find_one(doc! { "key": &self.key }) .await .context("failed to query db for api key")? .context("did not find matching api key")?; @@ -160,10 +146,10 @@ impl Resolve for State { .context("failed to query db for user")? .context("no user found with id")?; let UserConfig::Service { .. } = &service_user.config else { - return Err(anyhow!("user is not service user")); + return Err(anyhow!("user is not service user").into()); }; db.api_keys - .delete_one(doc! { "key": key }) + .delete_one(doc! { "key": self.key }) .await .context("failed to delete api key on db")?; Ok(DeleteApiKeyForServiceUserResponse {}) diff --git a/bin/core/src/api/write/stack.rs b/bin/core/src/api/write/stack.rs index 4c14764b0..6cf7b2103 100644 --- a/bin/core/src/api/write/stack.rs +++ b/bin/core/src/api/write/stack.rs @@ -1,15 +1,15 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::format_serror; use komodo_client::{ api::write::*, entities::{ + FileContents, NoData, Operation, config::core::CoreConfig, permission::PermissionLevel, server::ServerState, stack::{PartialStackConfig, Stack, StackInfo}, update::Update, - user::{stack_user, User}, - FileContents, NoData, Operation, + user::stack_user, }, }; use mungos::mongodb::bson::{doc, to_document}; @@ -33,87 +33,88 @@ use crate::{ resource, stack::{ get_stack_and_server, - remote::{get_repo_compose_contents, RemoteComposeContents}, + remote::{RemoteComposeContents, get_repo_compose_contents}, services::extract_services_into_res, }, - state::{db_client, github_client, State}, + state::{db_client, github_client}, }; -impl Resolve for State { - #[instrument(name = "CreateStack", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateStack { + #[instrument(name = "CreateStack", skip(user))] async fn resolve( - &self, - CreateStack { name, config }: CreateStack, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyStack", skip(self, user))] +impl Resolve for CopyStack { + #[instrument(name = "CopyStack", skip(user))] async fn resolve( - &self, - CopyStack { name, id }: CopyStack, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Stack { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user).await + Ok( + resource::create::(&self.name, config.into(), user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "DeleteStack", skip(self, user))] - async fn resolve( - &self, - DeleteStack { id }: DeleteStack, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await +impl Resolve for DeleteStack { + #[instrument(name = "DeleteStack", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateStack", skip(self, user))] +impl Resolve for UpdateStack { + #[instrument(name = "UpdateStack", skip(user))] async fn resolve( - &self, - UpdateStack { id, config }: UpdateStack, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::update::(&self.id, self.config, user).await?) } } -impl Resolve for State { - #[instrument(name = "RenameStack", skip(self, user))] +impl Resolve for RenameStack { + #[instrument(name = "RenameStack", skip(user))] async fn resolve( - &self, - RenameStack { id, name }: RenameStack, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok(resource::rename::(&self.id, &self.name, user).await?) } } -impl Resolve for State { +impl Resolve for WriteStackFileContents { + #[instrument(name = "WriteStackFileContents", skip(user))] async fn resolve( - &self, - WriteStackFileContents { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let WriteStackFileContents { stack, file_path, contents, - }: WriteStackFileContents, - user: User, - ) -> anyhow::Result { + } = self; let (mut stack, server) = get_stack_and_server( &stack, - &user, + user, PermissionLevel::Write, true, ) @@ -122,11 +123,11 @@ impl Resolve for State { if !stack.config.files_on_host && stack.config.repo.is_empty() { return Err(anyhow!( "Stack is not configured to use Files on Host or Git Repo, can't write file contents" - )); + ).into()); } let mut update = - make_update(&stack, Operation::WriteStackContents, &user); + make_update(&stack, Operation::WriteStackContents, user); update.push_simple_log("File contents to write", &contents); @@ -148,7 +149,7 @@ impl Resolve for State { } Err(e) => { update.push_error_log( - "Write file contents", + "Write File Contents", format_serror(&e.into()), ); } @@ -173,7 +174,7 @@ impl Resolve for State { match periphery_client(&server)? .request(WriteCommitComposeContents { stack, - username: Some(user.username), + username: Some(user.username.clone()), file_path, contents, git_token, @@ -186,19 +187,19 @@ impl Resolve for State { } Err(e) => { update.push_error_log( - "Write file contents", + "Write File Contents", format_serror(&e.into()), ); } }; } - if let Err(e) = State - .resolve( - RefreshStackCache { stack: stack_id }, - stack_user().to_owned(), - ) + if let Err(e) = (RefreshStackCache { stack: stack_id }) + .resolve(&WriteArgs { + user: stack_user().to_owned(), + }) .await + .map_err(|e| e.error) .context( "Failed to refresh stack cache after writing file contents", ) @@ -216,22 +217,21 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for RefreshStackCache { #[instrument( name = "RefreshStackCache", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshStackCache { stack }: RefreshStackCache, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // stack should be able to do this. let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Execute, ) .await?; @@ -300,9 +300,9 @@ impl Resolve for State { &mut services, ) { warn!( - "failed to extract stack services, things won't works correctly. stack: {} | {e:#}", - stack.name - ); + "failed to extract stack services, things won't works correctly. stack: {} | {e:#}", + stack.name + ); } } @@ -372,6 +372,7 @@ impl Resolve for State { deployed_services: stack.info.deployed_services.clone(), deployed_project_name: stack.info.deployed_project_name.clone(), deployed_contents: stack.info.deployed_contents.clone(), + deployed_config: stack.info.deployed_config.clone(), deployed_hash: stack.info.deployed_hash.clone(), deployed_message: stack.info.deployed_message.clone(), latest_services, @@ -401,7 +402,7 @@ impl Resolve for State { if state == ServerState::Ok { let name = stack.name.clone(); if let Err(e) = - pull_stack_inner(stack, None, &server, None).await + pull_stack_inner(stack, Vec::new(), &server, None).await { warn!( "Failed to pull latest images for Stack {name} | {e:#}", @@ -414,39 +415,44 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "CreateStackWebhook", skip(self, user))] +impl Resolve for CreateStackWebhook { + #[instrument(name = "CreateStackWebhook", skip(args))] async fn resolve( - &self, - CreateStackWebhook { stack, action }: CreateStackWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { + let WriteArgs { user } = args; + let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Write, ) .await?; if stack.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = stack.config.repo.split('/'); let owner = split.next().context("Stack repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -479,7 +485,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { StackWebhookAction::Refresh => { format!("{host}/listener/github/stack/{}/refresh", stack.id) } @@ -514,64 +520,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !stack.config.webhook_enabled { - self - .resolve( - UpdateStack { - id: stack.id, - config: PartialStackConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update stack to enable webhook")?; + UpdateStack { + id: stack.id, + config: PartialStackConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update stack to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteStackWebhook", skip(self, user))] +impl Resolve for DeleteStackWebhook { + #[instrument(name = "DeleteStackWebhook", skip(user))] async fn resolve( - &self, - DeleteStackWebhook { stack, action }: DeleteStackWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let stack = resource::get_check_permissions::( - &stack, - &user, + &self.stack, + user, PermissionLevel::Write, ) .await?; if stack.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if stack.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = stack.config.repo.split('/'); let owner = split.next().context("Stack repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -597,7 +604,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { StackWebhookAction::Refresh => { format!("{host}/listener/github/stack/{}/refresh", stack.id) } diff --git a/bin/core/src/api/write/sync.rs b/bin/core/src/api/write/sync.rs index 7d522c657..04c4b4f60 100644 --- a/bin/core/src/api/write/sync.rs +++ b/bin/core/src/api/write/sync.rs @@ -1,11 +1,11 @@ use std::{collections::HashMap, path::PathBuf}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::format_serror; use komodo_client::{ api::{read::ExportAllResourcesToToml, write::*}, entities::{ - self, + self, CloneArgs, NoData, Operation, ResourceTarget, action::Action, alert::{Alert, AlertData, SeverityLevel}, alerter::Alerter, @@ -23,11 +23,11 @@ use komodo_client::{ stack::Stack, sync::{ PartialResourceSyncConfig, ResourceSync, ResourceSyncInfo, + SyncDeployUpdate, }, to_komodo_name, update::{Log, Update}, - user::{sync_user, User}, - CloneArgs, NoData, Operation, ResourceTarget, + user::sync_user, }, }; use mungos::{ @@ -42,142 +42,216 @@ use tokio::fs; use crate::{ alert::send_alerts, + api::read::ReadArgs, config::core_config, helpers::{ + git_token, query::get_id_to_tags, update::{add_update, make_update, update_update}, }, - resource::{self, refresh_resource_sync_state_cache}, - state::{db_client, github_client, State}, + resource, + state::{db_client, github_client}, sync::{ - deploy::SyncDeployParams, remote::RemoteResources, - view::push_updates_for_view, AllResourcesById, + AllResourcesById, deploy::SyncDeployParams, + remote::RemoteResources, view::push_updates_for_view, }, }; -impl Resolve for State { - #[instrument(name = "CreateResourceSync", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateResourceSync { + #[instrument(name = "CreateResourceSync", skip(user))] async fn resolve( - &self, - CreateResourceSync { name, config }: CreateResourceSync, - user: User, - ) -> anyhow::Result { - resource::create::(&name, config, &user).await + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::create::(&self.name, self.config, user) + .await?, + ) } } -impl Resolve for State { - #[instrument(name = "CopyResourceSync", skip(self, user))] +impl Resolve for CopyResourceSync { + #[instrument(name = "CopyResourceSync", skip(user))] async fn resolve( - &self, - CopyResourceSync { name, id }: CopyResourceSync, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let ResourceSync { config, .. } = resource::get_check_permissions::( - &id, - &user, + &self.id, + user, PermissionLevel::Write, ) .await?; - resource::create::(&name, config.into(), &user) - .await - } -} - -impl Resolve for State { - #[instrument(name = "DeleteResourceSync", skip(self, user))] - async fn resolve( - &self, - DeleteResourceSync { id }: DeleteResourceSync, - user: User, - ) -> anyhow::Result { - resource::delete::(&id, &user).await - } -} - -impl Resolve for State { - #[instrument(name = "UpdateResourceSync", skip(self, user))] - async fn resolve( - &self, - UpdateResourceSync { id, config }: UpdateResourceSync, - user: User, - ) -> anyhow::Result { - resource::update::(&id, config, &user).await - } -} - -impl Resolve for State { - #[instrument(name = "RenameResourceSync", skip(self, user))] - async fn resolve( - &self, - RenameResourceSync { id, name }: RenameResourceSync, - user: User, - ) -> anyhow::Result { - resource::rename::(&id, &name, &user).await - } -} - -impl Resolve for State { - async fn resolve( - &self, - WriteSyncFileContents { - sync, - resource_path, - file_path, - contents, - }: WriteSyncFileContents, - user: User, - ) -> anyhow::Result { - let sync = resource::get_check_permissions::( - &sync, - &user, - PermissionLevel::Write, + Ok( + resource::create::( + &self.name, + config.into(), + user, + ) + .await?, ) - .await?; + } +} - if !sync.config.files_on_host && sync.config.repo.is_empty() { - return Err(anyhow!( - "This method is only for files on host, or repo based syncs." - )); - } +impl Resolve for DeleteResourceSync { + #[instrument(name = "DeleteResourceSync", skip(args))] + async fn resolve( + self, + args: &WriteArgs, + ) -> serror::Result { + Ok(resource::delete::(&self.id, args).await?) + } +} - let mut update = - make_update(&sync, Operation::WriteSyncContents, &user); +impl Resolve for UpdateResourceSync { + #[instrument(name = "UpdateResourceSync", skip(user))] + async fn resolve( + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::update::(&self.id, self.config, user) + .await?, + ) + } +} - update.push_simple_log("File contents", &contents); +impl Resolve for RenameResourceSync { + #[instrument(name = "RenameResourceSync", skip(user))] + async fn resolve( + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + Ok( + resource::rename::(&self.id, &self.name, user) + .await?, + ) + } +} - let root = if sync.config.files_on_host { - core_config() - .sync_directory - .join(to_komodo_name(&sync.name)) +async fn write_sync_file_contents_on_host( + req: WriteSyncFileContents, + args: &WriteArgs, + sync: ResourceSync, + mut update: Update, +) -> serror::Result { + let WriteSyncFileContents { + sync: _, + resource_path, + file_path, + contents, + } = req; + + let root = core_config() + .sync_directory + .join(to_komodo_name(&sync.name)); + let file_path = + file_path.parse::().context("Invalid file path")?; + let resource_path = resource_path + .parse::() + .context("Invalid resource path")?; + let full_path = root.join(&resource_path).join(&file_path); + + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).await.with_context(|| { + format!( + "Failed to initialize resource file parent directory {parent:?}" + ) + })?; + } + + if let Err(e) = + fs::write(&full_path, &contents).await.with_context(|| { + format!( + "Failed to write resource file contents to {full_path:?}" + ) + }) + { + update.push_error_log("Write File", format_serror(&e.into())); + } else { + update.push_simple_log( + "Write File", + format!("File written to {full_path:?}"), + ); + }; + + if !all_logs_success(&update.logs) { + update.finalize(); + update.id = add_update(update.clone()).await?; + + return Ok(update); + } + + if let Err(e) = (RefreshResourceSyncPending { sync: sync.name }) + .resolve(args) + .await + { + update.push_error_log( + "Refresh failed", + format_serror(&e.error.into()), + ); + } + + update.finalize(); + update.id = add_update(update.clone()).await?; + + Ok(update) +} + +async fn write_sync_file_contents_git( + req: WriteSyncFileContents, + args: &WriteArgs, + sync: ResourceSync, + mut update: Update, +) -> serror::Result { + let WriteSyncFileContents { + sync: _, + resource_path, + file_path, + contents, + } = req; + + let mut clone_args: CloneArgs = (&sync).into(); + let root = clone_args.unique_path(&core_config().repo_directory)?; + + let file_path = + file_path.parse::().context("Invalid file path")?; + let resource_path = resource_path + .parse::() + .context("Invalid resource path")?; + let full_path = root.join(&resource_path).join(&file_path); + + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).await.with_context(|| { + format!( + "Failed to initialize resource file parent directory {parent:?}" + ) + })?; + } + + // Ensure the folder is initialized as git repo. + // This allows a new file to be committed on a branch that may not exist. + if !root.join(".git").exists() { + let access_token = if let Some(account) = &clone_args.account { + git_token(&clone_args.provider, account, |https| clone_args.https = https) + .await + .with_context( + || format!("Failed to get git token in call to db. Stopping run. | {} | {account}", clone_args.provider), + )? } else { - let clone_args: CloneArgs = (&sync).into(); - clone_args.unique_path(&core_config().repo_directory)? + None }; - let file_path = - file_path.parse::().context("Invalid file path")?; - let resource_path = resource_path - .parse::() - .context("Invalid resource path")?; - let full_path = root.join(&resource_path).join(&file_path); - if let Some(parent) = full_path.parent() { - let _ = fs::create_dir_all(parent).await; - } - - if let Err(e) = - fs::write(&full_path, &contents).await.with_context(|| { - format!("Failed to write file contents to {full_path:?}") - }) - { - update.push_error_log("Write file", format_serror(&e.into())); - } else { - update.push_simple_log( - "Write file", - format!("File written to {full_path:?}"), - ); - }; + git::init_folder_as_repo( + &root, + &clone_args, + access_token.as_deref(), + &mut update.logs, + ) + .await; if !all_logs_success(&update.logs) { update.finalize(); @@ -185,56 +259,95 @@ impl Resolve for State { return Ok(update); } + } - if sync.config.files_on_host { - if let Err(e) = State - .resolve(RefreshResourceSyncPending { sync: sync.name }, user) - .await - { - update - .push_error_log("Refresh failed", format_serror(&e.into())); - } - - update.finalize(); - update.id = add_update(update.clone()).await?; - - return Ok(update); - } - - let commit_res = git::commit_file( - &format!("{}: Commit Resource File", user.username), - &root, - &resource_path.join(&file_path), - ) - .await; - - update.logs.extend(commit_res.logs); - - if let Err(e) = State - .resolve(RefreshResourceSyncPending { sync: sync.name }, user) - .await - { - update - .push_error_log("Refresh failed", format_serror(&e.into())); - } + if let Err(e) = + fs::write(&full_path, &contents).await.with_context(|| { + format!( + "Failed to write resource file contents to {full_path:?}" + ) + }) + { + update.push_error_log("Write File", format_serror(&e.into())); + } else { + update.push_simple_log( + "Write File", + format!("File written to {full_path:?}"), + ); + }; + if !all_logs_success(&update.logs) { update.finalize(); update.id = add_update(update.clone()).await?; - Ok(update) + return Ok(update); + } + + let commit_res = git::commit_file( + &format!("{}: Commit Resource File", args.user.username), + &root, + &resource_path.join(&file_path), + &sync.config.branch, + ) + .await; + + update.logs.extend(commit_res.logs); + + if let Err(e) = (RefreshResourceSyncPending { sync: sync.name }) + .resolve(args) + .await + { + update.push_error_log( + "Refresh failed", + format_serror(&e.error.into()), + ); + } + + update.finalize(); + update.id = add_update(update.clone()).await?; + + Ok(update) +} + +impl Resolve for WriteSyncFileContents { + async fn resolve(self, args: &WriteArgs) -> serror::Result { + let sync = resource::get_check_permissions::( + &self.sync, + &args.user, + PermissionLevel::Write, + ) + .await?; + + if !sync.config.files_on_host && sync.config.repo.is_empty() { + return Err( + anyhow!( + "This method is only for 'files on host' or 'repo' based syncs." + ) + .into(), + ); + } + + let mut update = + make_update(&sync, Operation::WriteSyncContents, &args.user); + + update.push_simple_log("File contents", &self.contents); + + if sync.config.files_on_host { + write_sync_file_contents_on_host(self, args, sync, update).await + } else { + write_sync_file_contents_git(self, args, sync, update).await + } } } -impl Resolve for State { - #[instrument(name = "CommitSync", skip(self, user))] - async fn resolve( - &self, - CommitSync { sync }: CommitSync, - user: User, - ) -> anyhow::Result { +impl Resolve for CommitSync { + #[instrument(name = "CommitSync", skip(args))] + async fn resolve(self, args: &WriteArgs) -> serror::Result { + let WriteArgs { user } = args; + let sync = resource::get_check_permissions::< entities::sync::ResourceSync, - >(&sync, &user, PermissionLevel::Write) + >(&self.sync, user, PermissionLevel::Write) .await?; let file_contents_empty = sync.config.file_contents_empty(); @@ -244,9 +357,10 @@ impl Resolve for State { && file_contents_empty; if !sync.config.managed && !fresh_sync { - return Err(anyhow!( - "Cannot commit to sync. Enabled 'managed' mode." - )); + return Err( + anyhow!("Cannot commit to sync. Enabled 'managed' mode.") + .into(), + ); } // Get this here so it can fail before update created. @@ -265,25 +379,27 @@ impl Resolve for State { .context("Resource path missing '.toml' extension")? != "toml" { - return Err(anyhow!( - "Resource path missing '.toml' extension" - )); + return Err( + anyhow!("Resource path missing '.toml' extension").into(), + ); } Some(resource_path) } else { None }; - let res = State - .resolve( - ExportAllResourcesToToml { - tags: sync.config.match_tags.clone(), - }, - sync_user().to_owned(), - ) - .await?; + let res = ExportAllResourcesToToml { + include_resources: sync.config.include_resources, + tags: sync.config.match_tags.clone(), + include_variables: sync.config.include_variables, + include_user_groups: sync.config.include_user_groups, + } + .resolve(&ReadArgs { + user: sync_user().to_owned(), + }) + .await?; - let mut update = make_update(&sync, Operation::CommitSync, &user); + let mut update = make_update(&sync, Operation::CommitSync, user); update.id = add_update(update.clone()).await?; update.logs.push(Log::simple("Resources", res.toml.clone())); @@ -298,7 +414,9 @@ impl Resolve for State { .join(to_komodo_name(&sync.name)) .join(&resource_path); if let Some(parent) = file_path.parent() { - let _ = tokio::fs::create_dir_all(&parent).await; + fs::create_dir_all(parent) + .await + .with_context(|| format!("Failed to initialize resource file parent directory {parent:?}"))?; }; if let Err(e) = tokio::fs::write(&file_path, &res.toml) .await @@ -332,6 +450,7 @@ impl Resolve for State { &root, &resource_path, &res.toml, + &sync.config.branch, ) .await { @@ -366,54 +485,38 @@ impl Resolve for State { return Ok(update); } - if let Err(e) = State - .resolve(RefreshResourceSyncPending { sync: sync.name }, user) + if let Err(e) = (RefreshResourceSyncPending { sync: sync.name }) + .resolve(args) .await { update.push_error_log( "Refresh sync pending", - format_serror(&(&e).into()), + format_serror(&e.error.into()), ); }; update.finalize(); - - // Need to manually update the update before cache refresh, - // and before broadcast with add_update. - // The Err case of to_document should be unreachable, - // but will fail to update cache in that case. - if let Ok(update_doc) = to_document(&update) { - let _ = update_one_by_id( - &db_client().updates, - &update.id, - mungos::update::Update::Set(update_doc), - None, - ) - .await; - refresh_resource_sync_state_cache().await; - } update_update(update.clone()).await?; Ok(update) } } -impl Resolve for State { +impl Resolve for RefreshResourceSyncPending { #[instrument( name = "RefreshResourceSyncPending", level = "debug", - skip(self, user) + skip(user) )] async fn resolve( - &self, - RefreshResourceSyncPending { sync }: RefreshResourceSyncPending, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { // Even though this is a write request, this doesn't change any config. Anyone that can execute the // sync should be able to do this. let mut sync = resource::get_check_permissions::< entities::sync::ResourceSync, - >(&sync, &user, PermissionLevel::Execute) + >(&self.sync, user, PermissionLevel::Execute) .await?; if !sync.config.managed @@ -449,162 +552,168 @@ impl Resolve for State { } let resources = resources?; - - let id_to_tags = get_id_to_tags(None).await?; + let delete = sync.config.managed || sync.config.delete; let all_resources = AllResourcesById::load().await?; - let deployments_by_name = all_resources - .deployments - .values() - .map(|deployment| { - (deployment.name.clone(), deployment.clone()) - }) - .collect::>(); - let stacks_by_name = all_resources - .stacks - .values() - .map(|stack| (stack.name.clone(), stack.clone())) - .collect::>(); + let (resource_updates, deploy_updates) = + if sync.config.include_resources { + let id_to_tags = get_id_to_tags(None).await?; - let deploy_updates = - crate::sync::deploy::get_updates_for_view(SyncDeployParams { - deployments: &resources.deployments, - deployment_map: &deployments_by_name, - stacks: &resources.stacks, - stack_map: &stacks_by_name, - all_resources: &all_resources, - }) - .await; + let deployments_by_name = all_resources + .deployments + .values() + .map(|deployment| { + (deployment.name.clone(), deployment.clone()) + }) + .collect::>(); + let stacks_by_name = all_resources + .stacks + .values() + .map(|stack| (stack.name.clone(), stack.clone())) + .collect::>(); - let delete = sync.config.managed || sync.config.delete; + let deploy_updates = + crate::sync::deploy::get_updates_for_view( + SyncDeployParams { + deployments: &resources.deployments, + deployment_map: &deployments_by_name, + stacks: &resources.stacks, + stack_map: &stacks_by_name, + all_resources: &all_resources, + }, + ) + .await; - let mut diffs = Vec::new(); + let mut diffs = Vec::new(); - { - push_updates_for_view::( - resources.servers, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.stacks, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.deployments, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.builds, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.repos, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.procedures, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.actions, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.builders, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.alerters, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.server_templates, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - push_updates_for_view::( - resources.resource_syncs, - delete, - &all_resources, - None, - None, - &id_to_tags, - &sync.config.match_tags, - &mut diffs, - ) - .await?; - } + push_updates_for_view::( + resources.servers, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.stacks, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.deployments, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.builds, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.repos, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.procedures, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.actions, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.builders, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.alerters, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.server_templates, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; + push_updates_for_view::( + resources.resource_syncs, + delete, + &all_resources, + None, + None, + &id_to_tags, + &sync.config.match_tags, + &mut diffs, + ) + .await?; - let variable_updates = if sync.config.match_tags.is_empty() { + (diffs, deploy_updates) + } else { + (Vec::new(), SyncDeployUpdate::default()) + }; + + let variable_updates = if sync.config.include_variables { crate::sync::variables::get_updates_for_view( &resources.variables, delete, @@ -614,7 +723,7 @@ impl Resolve for State { Default::default() }; - let user_group_updates = if sync.config.match_tags.is_empty() { + let user_group_updates = if sync.config.include_user_groups { crate::sync::user_groups::get_updates_for_view( resources.user_groups, delete, @@ -626,7 +735,7 @@ impl Resolve for State { }; anyhow::Ok(( - diffs, + resource_updates, deploy_updates, variable_updates, user_group_updates, @@ -744,43 +853,47 @@ impl Resolve for State { } }); - crate::resource::get::(&sync.id).await + Ok(crate::resource::get::(&sync.id).await?) } } -impl Resolve for State { - #[instrument(name = "CreateSyncWebhook", skip(self, user))] +impl Resolve for CreateSyncWebhook { + #[instrument(name = "CreateSyncWebhook", skip(args))] async fn resolve( - &self, - CreateSyncWebhook { sync, action }: CreateSyncWebhook, - user: User, - ) -> anyhow::Result { + self, + args: &WriteArgs, + ) -> serror::Result { + let WriteArgs { user } = args; let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Write, ) .await?; if sync.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = sync.config.repo.split('/'); let owner = split.next().context("Sync repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -813,7 +926,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { SyncWebhookAction::Refresh => { format!("{host}/listener/github/sync/{}/refresh", sync.id) } @@ -848,64 +961,65 @@ impl Resolve for State { .context("failed to create webhook")?; if !sync.config.webhook_enabled { - self - .resolve( - UpdateResourceSync { - id: sync.id, - config: PartialResourceSyncConfig { - webhook_enabled: Some(true), - ..Default::default() - }, - }, - user, - ) - .await - .context("failed to update sync to enable webhook")?; + UpdateResourceSync { + id: sync.id, + config: PartialResourceSyncConfig { + webhook_enabled: Some(true), + ..Default::default() + }, + } + .resolve(args) + .await + .map_err(|e| e.error) + .context("failed to update sync to enable webhook")?; } Ok(NoData {}) } } -impl Resolve for State { - #[instrument(name = "DeleteSyncWebhook", skip(self, user))] +impl Resolve for DeleteSyncWebhook { + #[instrument(name = "DeleteSyncWebhook", skip(user))] async fn resolve( - &self, - DeleteSyncWebhook { sync, action }: DeleteSyncWebhook, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { let Some(github) = github_client() else { - return Err(anyhow!( - "github_webhook_app is not configured in core config toml" - )); + return Err( + anyhow!( + "github_webhook_app is not configured in core config toml" + ) + .into(), + ); }; let sync = resource::get_check_permissions::( - &sync, - &user, + &self.sync, + user, PermissionLevel::Write, ) .await?; if sync.config.git_provider != "github.com" { - return Err(anyhow!( - "Can only manage github.com repo webhooks" - )); + return Err( + anyhow!("Can only manage github.com repo webhooks").into(), + ); } if sync.config.repo.is_empty() { - return Err(anyhow!( - "No repo configured, can't create webhook" - )); + return Err( + anyhow!("No repo configured, can't create webhook").into(), + ); } let mut split = sync.config.repo.split('/'); let owner = split.next().context("Sync repo has no owner")?; let Some(github) = github.get(owner) else { - return Err(anyhow!( - "Cannot manage repo webhooks under owner {owner}" - )); + return Err( + anyhow!("Cannot manage repo webhooks under owner {owner}") + .into(), + ); }; let repo = @@ -931,7 +1045,7 @@ impl Resolve for State { } else { webhook_base_url }; - let url = match action { + let url = match self.action { SyncWebhookAction::Refresh => { format!("{host}/listener/github/sync/{}/refresh", sync.id) } diff --git a/bin/core/src/api/write/tag.rs b/bin/core/src/api/write/tag.rs index a45b37850..d627012d8 100644 --- a/bin/core/src/api/write/tag.rs +++ b/bin/core/src/api/write/tag.rs @@ -1,17 +1,26 @@ use std::str::FromStr; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::write::{ - CreateTag, DeleteTag, RenameTag, UpdateTagsOnResource, - UpdateTagsOnResourceResponse, + CreateTag, DeleteTag, RenameTag, UpdateTagColor, + UpdateTagsOnResource, UpdateTagsOnResourceResponse, }, entities::{ - action::Action, alerter::Alerter, build::Build, builder::Builder, - deployment::Deployment, permission::PermissionLevel, - procedure::Procedure, repo::Repo, server::Server, - server_template::ServerTemplate, stack::Stack, - sync::ResourceSync, tag::Tag, user::User, ResourceTarget, + ResourceTarget, + action::Action, + alerter::Alerter, + build::Build, + builder::Builder, + deployment::Deployment, + permission::PermissionLevel, + procedure::Procedure, + repo::Repo, + server::Server, + server_template::ServerTemplate, + stack::Stack, + sync::ResourceSync, + tag::{Tag, TagColor}, }, }; use mungos::{ @@ -23,23 +32,25 @@ use resolver_api::Resolve; use crate::{ helpers::query::{get_tag, get_tag_check_owner}, resource, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { - #[instrument(name = "CreateTag", skip(self, user))] +use super::WriteArgs; + +impl Resolve for CreateTag { + #[instrument(name = "CreateTag", skip(user))] async fn resolve( - &self, - CreateTag { name }: CreateTag, - user: User, - ) -> anyhow::Result { - if ObjectId::from_str(&name).is_ok() { - return Err(anyhow!("tag name cannot be ObjectId")); + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + if ObjectId::from_str(&self.name).is_ok() { + return Err(anyhow!("tag name cannot be ObjectId").into()); } let mut tag = Tag { id: Default::default(), - name, + name: self.name, + color: TagColor::Slate, owner: user.id.clone(), }; @@ -57,167 +68,191 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RenameTag", skip(self, user))] +impl Resolve for RenameTag { + #[instrument(name = "RenameTag", skip(user))] async fn resolve( - &self, - RenameTag { id, name }: RenameTag, - user: User, - ) -> anyhow::Result { - if ObjectId::from_str(&name).is_ok() { - return Err(anyhow!("tag name cannot be ObjectId")); + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + if ObjectId::from_str(&self.name).is_ok() { + return Err(anyhow!("tag name cannot be ObjectId").into()); } - get_tag_check_owner(&id, &user).await?; + get_tag_check_owner(&self.id, user).await?; update_one_by_id( &db_client().tags, - &id, - doc! { "$set": { "name": name } }, + &self.id, + doc! { "$set": { "name": self.name } }, None, ) .await .context("failed to rename tag on db")?; - get_tag(&id).await + Ok(get_tag(&self.id).await?) } } -impl Resolve for State { - #[instrument(name = "DeleteTag", skip(self, user))] +impl Resolve for UpdateTagColor { + #[instrument(name = "UpdateTagColor", skip(user))] async fn resolve( - &self, - DeleteTag { id }: DeleteTag, - user: User, - ) -> anyhow::Result { - let tag = get_tag_check_owner(&id, &user).await?; + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let tag = get_tag_check_owner(&self.tag, user).await?; + + update_one_by_id( + &db_client().tags, + &tag.id, + doc! { "$set": { "color": self.color.as_ref() } }, + None, + ) + .await + .context("failed to rename tag on db")?; + + Ok(get_tag(&self.tag).await?) + } +} + +impl Resolve for DeleteTag { + #[instrument(name = "DeleteTag", skip(user))] + async fn resolve( + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let tag = get_tag_check_owner(&self.id, user).await?; tokio::try_join!( - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), - resource::remove_tag_from_all::(&id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), + resource::remove_tag_from_all::(&self.id), )?; - delete_one_by_id(&db_client().tags, &id, None).await?; + delete_one_by_id(&db_client().tags, &self.id, None).await?; Ok(tag) } } -impl Resolve for State { - #[instrument(name = "UpdateTagsOnResource", skip(self, user))] +impl Resolve for UpdateTagsOnResource { + #[instrument(name = "UpdateTagsOnResource", skip(args))] async fn resolve( - &self, - UpdateTagsOnResource { target, tags }: UpdateTagsOnResource, - user: User, - ) -> anyhow::Result { - match target { - ResourceTarget::System(_) => return Err(anyhow!("")), + self, + args: &WriteArgs, + ) -> serror::Result { + let WriteArgs { user } = args; + match self.target { + ResourceTarget::System(_) => { + return Err(anyhow!("Invalid target type: System").into()); + } ResourceTarget::Build(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await?; + resource::update_tags::(&id, self.tags, args).await?; } ResourceTarget::Builder(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Deployment(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args) + .await? } ResourceTarget::Server(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Repo(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Alerter(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::Procedure(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args) + .await? } ResourceTarget::Action(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } ResourceTarget::ServerTemplate(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user) + resource::update_tags::(&id, self.tags, args) .await? } ResourceTarget::ResourceSync(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args) + .await? } ResourceTarget::Stack(id) => { resource::get_check_permissions::( &id, - &user, + user, PermissionLevel::Write, ) .await?; - resource::update_tags::(&id, tags, user).await? + resource::update_tags::(&id, self.tags, args).await? } }; Ok(UpdateTagsOnResourceResponse {}) diff --git a/bin/core/src/api/write/user.rs b/bin/core/src/api/write/user.rs index 764a435b6..7afdf9744 100644 --- a/bin/core/src/api/write/user.rs +++ b/bin/core/src/api/write/user.rs @@ -1,52 +1,59 @@ use std::str::FromStr; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::write::{ DeleteUser, DeleteUserResponse, UpdateUserPassword, UpdateUserPasswordResponse, UpdateUserUsername, UpdateUserUsernameResponse, }, - entities::{ - user::{User, UserConfig}, - NoData, - }, + entities::{NoData, user::UserConfig}, }; use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; use crate::{ - helpers::hash_password, - state::{db_client, State}, + config::core_config, helpers::hash_password, state::db_client, }; +use super::WriteArgs; + // -impl Resolve for State { +impl Resolve for UpdateUserUsername { async fn resolve( - &self, - UpdateUserUsername { username }: UpdateUserUsername, - user: User, - ) -> anyhow::Result { - if username.is_empty() { - return Err(anyhow!("Username cannot be empty.")); + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + for locked_username in &core_config().lock_login_credentials_for { + if locked_username == "__ALL__" + || *locked_username == user.username + { + return Err( + anyhow!("User not allowed to update their username.") + .into(), + ); + } + } + if self.username.is_empty() { + return Err(anyhow!("Username cannot be empty.").into()); } let db = db_client(); if db .users - .find_one(doc! { "username": &username }) + .find_one(doc! { "username": &self.username }) .await .context("Failed to query for existing users")? .is_some() { - return Err(anyhow!("Username already taken.")); + return Err(anyhow!("Username already taken.").into()); } let id = ObjectId::from_str(&user.id) .context("User id not valid ObjectId.")?; db.users .update_one( doc! { "_id": id }, - doc! { "$set": { "username": username } }, + doc! { "$set": { "username": self.username } }, ) .await .context("Failed to update user username on database.")?; @@ -56,21 +63,30 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for UpdateUserPassword { async fn resolve( - &self, - UpdateUserPassword { password }: UpdateUserPassword, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + for locked_username in &core_config().lock_login_credentials_for { + if locked_username == "__ALL__" + || *locked_username == user.username + { + return Err( + anyhow!("User not allowed to update their password.") + .into(), + ); + } + } let UserConfig::Local { .. } = user.config else { - return Err(anyhow!("User is not local user")); + return Err(anyhow!("User is not local user").into()); }; - if password.is_empty() { - return Err(anyhow!("Password cannot be empty.")); + if self.password.is_empty() { + return Err(anyhow!("Password cannot be empty.").into()); } let id = ObjectId::from_str(&user.id) .context("User id not valid ObjectId.")?; - let hashed_password = hash_password(password)?; + let hashed_password = hash_password(self.password)?; db_client() .users .update_one( @@ -87,22 +103,21 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for DeleteUser { async fn resolve( - &self, - DeleteUser { user }: DeleteUser, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("Calling user is not admin.")); + return Err(anyhow!("Calling user is not admin.").into()); } - if admin.username == user || admin.id == user { - return Err(anyhow!("User cannot delete themselves.")); + if admin.username == self.user || admin.id == self.user { + return Err(anyhow!("User cannot delete themselves.").into()); } - let query = if let Ok(id) = ObjectId::from_str(&user) { + let query = if let Ok(id) = ObjectId::from_str(&self.user) { doc! { "_id": id } } else { - doc! { "username": user } + doc! { "username": self.user } }; let db = db_client(); let Some(user) = db @@ -111,15 +126,20 @@ impl Resolve for State { .await .context("Failed to query database for users.")? else { - return Err(anyhow!("No user found with given id / username")); + return Err( + anyhow!("No user found with given id / username").into(), + ); }; if user.super_admin { - return Err(anyhow!("Cannot delete a super admin user.")); + return Err( + anyhow!("Cannot delete a super admin user.").into(), + ); } if user.admin && !admin.super_admin { - return Err(anyhow!( - "Only a Super Admin can delete an admin user." - )); + return Err( + anyhow!("Only a Super Admin can delete an admin user.") + .into(), + ); } db.users .delete_one(query) diff --git a/bin/core/src/api/write/user_group.rs b/bin/core/src/api/write/user_group.rs index 95f34170a..7b982a6e4 100644 --- a/bin/core/src/api/write/user_group.rs +++ b/bin/core/src/api/write/user_group.rs @@ -1,12 +1,12 @@ use std::{collections::HashMap, str::FromStr}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::write::{ AddUserToUserGroup, CreateUserGroup, DeleteUserGroup, RemoveUserFromUserGroup, RenameUserGroup, SetUsersInUserGroup, }, - entities::{komodo_timestamp, user::User, user_group::UserGroup}, + entities::{komodo_timestamp, user_group::UserGroup}, }; use mungos::{ by_id::{delete_one_by_id, find_one_by_id, update_one_by_id}, @@ -15,23 +15,24 @@ use mungos::{ }; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::state::db_client; -impl Resolve for State { +use super::WriteArgs; + +impl Resolve for CreateUserGroup { async fn resolve( - &self, - CreateUserGroup { name }: CreateUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let user_group = UserGroup { id: Default::default(), users: Default::default(), all: Default::default(), updated_at: komodo_timestamp(), - name, + name: self.name, }; let db = db_client(); let id = db @@ -43,63 +44,63 @@ impl Resolve for State { .as_object_id() .context("inserted id is not ObjectId")? .to_string(); - find_one_by_id(&db.user_groups, &id) + let res = find_one_by_id(&db.user_groups, &id) .await .context("failed to query db for user groups")? - .context("user group at id not found") + .context("user group at id not found")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for RenameUserGroup { async fn resolve( - &self, - RenameUserGroup { id, name }: RenameUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); update_one_by_id( &db.user_groups, - &id, - doc! { "$set": { "name": name } }, + &self.id, + doc! { "$set": { "name": self.name } }, None, ) .await .context("failed to rename UserGroup on db")?; - find_one_by_id(&db.user_groups, &id) + let res = find_one_by_id(&db.user_groups, &self.id) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for DeleteUserGroup { async fn resolve( - &self, - DeleteUserGroup { id }: DeleteUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); - let ug = find_one_by_id(&db.user_groups, &id) + let ug = find_one_by_id(&db.user_groups, &self.id) .await .context("failed to query db for UserGroups")? .context("no UserGroup found with given id")?; - delete_one_by_id(&db.user_groups, &id, None) + delete_one_by_id(&db.user_groups, &self.id, None) .await .context("failed to delete UserGroup from db")?; db.permissions .delete_many(doc! { "user_target.type": "UserGroup", - "user_target.id": id, + "user_target.id": self.id, }) .await .context("failed to clean up UserGroups permissions. User Group has been deleted")?; @@ -108,21 +109,20 @@ impl Resolve for State { } } -impl Resolve for State { +impl Resolve for AddUserToUserGroup { async fn resolve( - &self, - AddUserToUserGroup { user_group, user }: AddUserToUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); - let filter = match ObjectId::from_str(&user) { + let filter = match ObjectId::from_str(&self.user) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "username": &user }, + Err(_) => doc! { "username": &self.user }, }; let user = db .users @@ -131,9 +131,9 @@ impl Resolve for State { .context("failed to query mongo for users")? .context("no matching user found")?; - let filter = match ObjectId::from_str(&user_group) { + let filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; db.user_groups .update_one( @@ -142,32 +142,30 @@ impl Resolve for State { ) .await .context("failed to add user to group on db")?; - db.user_groups + let res = db + .user_groups .find_one(filter) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for RemoveUserFromUserGroup { async fn resolve( - &self, - RemoveUserFromUserGroup { - user_group, - user, - }: RemoveUserFromUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); - let filter = match ObjectId::from_str(&user) { + let filter = match ObjectId::from_str(&self.user) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "username": &user }, + Err(_) => doc! { "username": &self.user }, }; let user = db .users @@ -176,9 +174,9 @@ impl Resolve for State { .context("failed to query mongo for users")? .context("no matching user found")?; - let filter = match ObjectId::from_str(&user_group) { + let filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; db.user_groups .update_one( @@ -187,22 +185,23 @@ impl Resolve for State { ) .await .context("failed to add user to group on db")?; - db.user_groups + let res = db + .user_groups .find_one(filter) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } -impl Resolve for State { +impl Resolve for SetUsersInUserGroup { async fn resolve( - &self, - SetUsersInUserGroup { user_group, users }: SetUsersInUserGroup, - admin: User, - ) -> anyhow::Result { + self, + WriteArgs { user: admin }: &WriteArgs, + ) -> serror::Result { if !admin.admin { - return Err(anyhow!("This call is admin-only")); + return Err(anyhow!("This call is admin-only").into()); } let db = db_client(); @@ -215,7 +214,8 @@ impl Resolve for State { .collect::>(); // Make sure all users are user ids - let users = users + let users = self + .users .into_iter() .filter_map(|user| match ObjectId::from_str(&user) { Ok(_) => Some(user), @@ -223,18 +223,20 @@ impl Resolve for State { }) .collect::>(); - let filter = match ObjectId::from_str(&user_group) { + let filter = match ObjectId::from_str(&self.user_group) { Ok(id) => doc! { "_id": id }, - Err(_) => doc! { "name": &user_group }, + Err(_) => doc! { "name": &self.user_group }, }; db.user_groups .update_one(filter.clone(), doc! { "$set": { "users": users } }) .await .context("failed to set users on user group")?; - db.user_groups + let res = db + .user_groups .find_one(filter) .await .context("failed to query db for UserGroups")? - .context("no user group with given id") + .context("no user group with given id")?; + Ok(res) } } diff --git a/bin/core/src/api/write/variable.rs b/bin/core/src/api/write/variable.rs index aef49517c..b3d1ef6b2 100644 --- a/bin/core/src/api/write/variable.rs +++ b/bin/core/src/api/write/variable.rs @@ -1,15 +1,7 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ - api::write::{ - CreateVariable, CreateVariableResponse, DeleteVariable, - DeleteVariableResponse, UpdateVariableDescription, - UpdateVariableDescriptionResponse, UpdateVariableIsSecret, - UpdateVariableIsSecretResponse, UpdateVariableValue, - UpdateVariableValueResponse, - }, - entities::{ - user::User, variable::Variable, Operation, ResourceTarget, - }, + api::write::*, + entities::{Operation, ResourceTarget, variable::Variable}, }; use mungos::mongodb::bson::doc; use resolver_api::Resolve; @@ -19,23 +11,26 @@ use crate::{ query::get_variable, update::{add_update, make_update}, }, - state::{db_client, State}, + state::db_client, }; -impl Resolve for State { - #[instrument(name = "CreateVariable", skip(self, user, value))] +use super::WriteArgs; + +impl Resolve for CreateVariable { + #[instrument(name = "CreateVariable", skip(user, self), fields(name = &self.name))] async fn resolve( - &self, - CreateVariable { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { + let CreateVariable { name, value, description, is_secret, - }: CreateVariable, - user: User, - ) -> anyhow::Result { + } = self; + if !user.admin { - return Err(anyhow!("only admins can create variables")); + return Err(anyhow!("only admins can create variables").into()); } let variable = Variable { @@ -54,7 +49,7 @@ impl Resolve for State { let mut update = make_update( ResourceTarget::system(), Operation::CreateVariable, - &user, + user, ); update @@ -63,21 +58,22 @@ impl Resolve for State { add_update(update).await?; - get_variable(&variable.name).await + Ok(get_variable(&variable.name).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateVariableValue", skip(self, user, value))] +impl Resolve for UpdateVariableValue { + #[instrument(name = "UpdateVariableValue", skip(user, self), fields(name = &self.name))] async fn resolve( - &self, - UpdateVariableValue { name, value }: UpdateVariableValue, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can update variables")); + return Err(anyhow!("only admins can update variables").into()); } + let UpdateVariableValue { name, value } = self; + let variable = get_variable(&name).await?; if value == variable.value { @@ -96,7 +92,7 @@ impl Resolve for State { let mut update = make_update( ResourceTarget::system(), Operation::UpdateVariableValue, - &user, + user, ); let log = if variable.is_secret { @@ -116,74 +112,71 @@ impl Resolve for State { add_update(update).await?; - get_variable(&name).await + Ok(get_variable(&name).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateVariableDescription", skip(self, user))] +impl Resolve for UpdateVariableDescription { + #[instrument(name = "UpdateVariableDescription", skip(user))] async fn resolve( - &self, - UpdateVariableDescription { name, description }: UpdateVariableDescription, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can update variables")); + return Err(anyhow!("only admins can update variables").into()); } db_client() .variables .update_one( - doc! { "name": &name }, - doc! { "$set": { "description": &description } }, + doc! { "name": &self.name }, + doc! { "$set": { "description": &self.description } }, ) .await .context("failed to update variable description on db")?; - get_variable(&name).await + Ok(get_variable(&self.name).await?) } } -impl Resolve for State { - #[instrument(name = "UpdateVariableIsSecret", skip(self, user))] +impl Resolve for UpdateVariableIsSecret { + #[instrument(name = "UpdateVariableIsSecret", skip(user))] async fn resolve( - &self, - UpdateVariableIsSecret { name, is_secret }: UpdateVariableIsSecret, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can update variables")); + return Err(anyhow!("only admins can update variables").into()); } db_client() .variables .update_one( - doc! { "name": &name }, - doc! { "$set": { "is_secret": is_secret } }, + doc! { "name": &self.name }, + doc! { "$set": { "is_secret": self.is_secret } }, ) .await .context("failed to update variable is secret on db")?; - get_variable(&name).await + Ok(get_variable(&self.name).await?) } } -impl Resolve for State { +impl Resolve for DeleteVariable { async fn resolve( - &self, - DeleteVariable { name }: DeleteVariable, - user: User, - ) -> anyhow::Result { + self, + WriteArgs { user }: &WriteArgs, + ) -> serror::Result { if !user.admin { - return Err(anyhow!("only admins can delete variables")); + return Err(anyhow!("only admins can delete variables").into()); } - let variable = get_variable(&name).await?; + let variable = get_variable(&self.name).await?; db_client() .variables - .delete_one(doc! { "name": &name }) + .delete_one(doc! { "name": &self.name }) .await .context("failed to delete variable on db")?; let mut update = make_update( ResourceTarget::system(), Operation::DeleteVariable, - &user, + user, ); update diff --git a/bin/core/src/auth/github/client.rs b/bin/core/src/auth/github/client.rs index 00620e27c..c3e69fb3e 100644 --- a/bin/core/src/auth/github/client.rs +++ b/bin/core/src/auth/github/client.rs @@ -1,11 +1,11 @@ use std::sync::OnceLock; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::entities::config::core::{ CoreConfig, OauthCredentials, }; use reqwest::StatusCode; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use tokio::sync::Mutex; use crate::{ @@ -47,15 +47,21 @@ impl GithubOauthClient { return None; } if host.is_empty() { - warn!("github oauth is enabled, but 'config.host' is not configured"); + warn!( + "github oauth is enabled, but 'config.host' is not configured" + ); return None; } if id.is_empty() { - warn!("github oauth is enabled, but 'config.github_oauth.id' is not configured"); + warn!( + "github oauth is enabled, but 'config.github_oauth.id' is not configured" + ); return None; } if secret.is_empty() { - warn!("github oauth is enabled, but 'config.github_oauth.secret' is not configured"); + warn!( + "github oauth is enabled, but 'config.github_oauth.secret' is not configured" + ); return None; } GithubOauthClient { diff --git a/bin/core/src/auth/github/mod.rs b/bin/core/src/auth/github/mod.rs index 0fa619c30..b397ba7ec 100644 --- a/bin/core/src/auth/github/mod.rs +++ b/bin/core/src/auth/github/mod.rs @@ -1,6 +1,6 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use axum::{ - extract::Query, response::Redirect, routing::get, Router, + Router, extract::Query, response::Redirect, routing::get, }; use komodo_client::entities::{ komodo_timestamp, @@ -72,7 +72,7 @@ async fn callback( .context("failed at find user query from database")?; let jwt = match user { Some(user) => jwt_client() - .generate(user.id) + .encode(user.id) .context("failed to generate jwt")?, None => { let ts = komodo_timestamp(); @@ -109,7 +109,7 @@ async fn callback( .context("inserted_id is not ObjectId")? .to_string(); jwt_client() - .generate(user_id) + .encode(user_id) .context("failed to generate jwt")? } }; diff --git a/bin/core/src/auth/google/client.rs b/bin/core/src/auth/google/client.rs index 725fed665..ddda8ae5f 100644 --- a/bin/core/src/auth/google/client.rs +++ b/bin/core/src/auth/google/client.rs @@ -1,13 +1,12 @@ use std::sync::OnceLock; -use anyhow::{anyhow, Context}; -use jwt::Token; +use anyhow::{Context, anyhow}; +use jsonwebtoken::{DecodingKey, Validation, decode}; use komodo_client::entities::config::core::{ CoreConfig, OauthCredentials, }; use reqwest::StatusCode; -use serde::{de::DeserializeOwned, Deserialize}; -use serde_json::Value; +use serde::{Deserialize, de::DeserializeOwned}; use tokio::sync::Mutex; use crate::{ @@ -49,15 +48,21 @@ impl GoogleOauthClient { return None; } if host.is_empty() { - warn!("google oauth is enabled, but 'config.host' is not configured"); + warn!( + "google oauth is enabled, but 'config.host' is not configured" + ); return None; } if id.is_empty() { - warn!("google oauth is enabled, but 'config.google_oauth.id' is not configured"); + warn!( + "google oauth is enabled, but 'config.google_oauth.id' is not configured" + ); return None; } if secret.is_empty() { - warn!("google oauth is enabled, but 'config.google_oauth.secret' is not configured"); + warn!( + "google oauth is enabled, but 'config.google_oauth.secret' is not configured" + ); return None; } let scopes = urlencoding::encode( @@ -139,10 +144,16 @@ impl GoogleOauthClient { &self, id_token: &str, ) -> anyhow::Result { - let t: Token = - Token::parse_unverified(id_token) - .context("failed to parse id_token")?; - Ok(t.claims().to_owned()) + let mut v = Validation::new(Default::default()); + v.insecure_disable_signature_validation(); + v.validate_aud = false; + let res = decode::( + id_token, + &DecodingKey::from_secret(b""), + &v, + ) + .context("failed to decode google id token")?; + Ok(res.claims) } #[instrument(level = "debug", skip(self))] diff --git a/bin/core/src/auth/google/mod.rs b/bin/core/src/auth/google/mod.rs index 45f6f50df..fd27b81a3 100644 --- a/bin/core/src/auth/google/mod.rs +++ b/bin/core/src/auth/google/mod.rs @@ -1,7 +1,7 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use async_timing_util::unix_timestamp_ms; use axum::{ - extract::Query, response::Redirect, routing::get, Router, + Router, extract::Query, response::Redirect, routing::get, }; use komodo_client::entities::user::{User, UserConfig}; use mongo_indexed::Document; @@ -81,7 +81,7 @@ async fn callback( .context("failed at find user query from mongo")?; let jwt = match user { Some(user) => jwt_client() - .generate(user.id) + .encode(user.id) .context("failed to generate jwt")?, None => { let ts = unix_timestamp_ms() as i64; @@ -124,7 +124,7 @@ async fn callback( .context("inserted_id is not ObjectId")? .to_string(); jwt_client() - .generate(user_id) + .encode(user_id) .context("failed to generate jwt")? } }; diff --git a/bin/core/src/auth/jwt.rs b/bin/core/src/auth/jwt.rs index 1b4a51db3..8cf7d9fb4 100644 --- a/bin/core/src/auth/jwt.rs +++ b/bin/core/src/auth/jwt.rs @@ -1,15 +1,15 @@ use std::collections::HashMap; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use async_timing_util::{ - get_timelength_in_ms, unix_timestamp_ms, Timelength, + Timelength, get_timelength_in_ms, unix_timestamp_ms, +}; +use jsonwebtoken::{ + DecodingKey, EncodingKey, Header, Validation, decode, encode, }; -use hmac::{Hmac, Mac}; -use jwt::SignWithKey; use komodo_client::entities::config::core::CoreConfig; use mungos::mongodb::bson::doc; use serde::{Deserialize, Serialize}; -use sha2::Sha256; use tokio::sync::Mutex; use crate::helpers::random_string; @@ -24,7 +24,10 @@ pub struct JwtClaims { } pub struct JwtClient { - pub key: Hmac, + header: Header, + validation: Validation, + encoding_key: EncodingKey, + decoding_key: DecodingKey, ttl_ms: u128, exchange_tokens: ExchangeTokenMap, } @@ -36,10 +39,11 @@ impl JwtClient { } else { config.jwt_secret.clone() }; - let key = Hmac::new_from_slice(secret.as_bytes()) - .context("failed at taking HmacSha256 of jwt secret")?; Ok(JwtClient { - key, + header: Header::default(), + validation: Validation::new(Default::default()), + encoding_key: EncodingKey::from_secret(secret.as_bytes()), + decoding_key: DecodingKey::from_secret(secret.as_bytes()), ttl_ms: get_timelength_in_ms( config.jwt_ttl.to_string().parse()?, ), @@ -47,7 +51,7 @@ impl JwtClient { }) } - pub fn generate(&self, user_id: String) -> anyhow::Result { + pub fn encode(&self, user_id: String) -> anyhow::Result { let iat = unix_timestamp_ms(); let exp = iat + self.ttl_ms; let claims = JwtClaims { @@ -55,10 +59,14 @@ impl JwtClient { iat, exp, }; - let jwt = claims - .sign_with_key(&self.key) - .context("failed at signing claim")?; - Ok(jwt) + encode(&self.header, &claims, &self.encoding_key) + .context("failed at signing claim") + } + + pub fn decode(&self, jwt: &str) -> anyhow::Result { + decode::(jwt, &self.decoding_key, &self.validation) + .map(|res| res.claims) + .context("failed to decode token claims") } #[instrument(level = "debug", skip_all)] diff --git a/bin/core/src/auth/local.rs b/bin/core/src/auth/local.rs index 619d90e93..ecc16456c 100644 --- a/bin/core/src/auth/local.rs +++ b/bin/core/src/auth/local.rs @@ -1,8 +1,7 @@ use std::str::FromStr; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use async_timing_util::unix_timestamp_ms; -use axum::http::HeaderMap; use komodo_client::{ api::auth::{ CreateLocalUser, CreateLocalUserResponse, LoginLocalUser, @@ -15,50 +14,52 @@ use mungos::mongodb::bson::{doc, oid::ObjectId}; use resolver_api::Resolve; use crate::{ + api::auth::AuthArgs, config::core_config, helpers::hash_password, - state::{db_client, jwt_client, State}, + state::{db_client, jwt_client}, }; -impl Resolve for State { +impl Resolve for CreateLocalUser { #[instrument(name = "CreateLocalUser", skip(self))] async fn resolve( - &self, - CreateLocalUser { username, password }: CreateLocalUser, - _: HeaderMap, - ) -> anyhow::Result { + self, + _: &AuthArgs, + ) -> serror::Result { let core_config = core_config(); if !core_config.local_auth { - return Err(anyhow!("Local auth is not enabled")); + return Err(anyhow!("Local auth is not enabled").into()); } - if username.is_empty() { - return Err(anyhow!("Username cannot be empty string")); + if self.username.is_empty() { + return Err(anyhow!("Username cannot be empty string").into()); } - if ObjectId::from_str(&username).is_ok() { - return Err(anyhow!("Username cannot be valid ObjectId")); + if ObjectId::from_str(&self.username).is_ok() { + return Err( + anyhow!("Username cannot be valid ObjectId").into(), + ); } - if password.is_empty() { - return Err(anyhow!("Password cannot be empty string")); + if self.password.is_empty() { + return Err(anyhow!("Password cannot be empty string").into()); } - let hashed_password = hash_password(password)?; + let hashed_password = hash_password(self.password)?; let no_users_exist = db_client().users.find_one(Document::new()).await?.is_none(); if !no_users_exist && core_config.disable_user_registration { - return Err(anyhow!("User registration is disabled")); + return Err(anyhow!("User registration is disabled").into()); } let ts = unix_timestamp_ms() as i64; let user = User { id: Default::default(), - username, + username: self.username, enabled: no_users_exist || core_config.enable_new_users, admin: no_users_exist, super_admin: no_users_exist, @@ -84,51 +85,53 @@ impl Resolve for State { .to_string(); let jwt = jwt_client() - .generate(user_id) + .encode(user_id) .context("failed to generate jwt for user")?; Ok(CreateLocalUserResponse { jwt }) } } -impl Resolve for State { +impl Resolve for LoginLocalUser { #[instrument(name = "LoginLocalUser", level = "debug", skip(self))] async fn resolve( - &self, - LoginLocalUser { username, password }: LoginLocalUser, - _: HeaderMap, - ) -> anyhow::Result { + self, + _: &AuthArgs, + ) -> serror::Result { if !core_config().local_auth { - return Err(anyhow!("local auth is not enabled")); + return Err(anyhow!("local auth is not enabled").into()); } let user = db_client() .users - .find_one(doc! { "username": &username }) + .find_one(doc! { "username": &self.username }) .await .context("failed at db query for users")? .with_context(|| { - format!("did not find user with username {username}") + format!("did not find user with username {}", self.username) })?; let UserConfig::Local { password: user_pw_hash, } = user.config else { - return Err(anyhow!( - "non-local auth users can not log in with a password" - )); + return Err( + anyhow!( + "non-local auth users can not log in with a password" + ) + .into(), + ); }; - let verified = bcrypt::verify(password, &user_pw_hash) + let verified = bcrypt::verify(self.password, &user_pw_hash) .context("failed at verify password")?; if !verified { - return Err(anyhow!("invalid credentials")); + return Err(anyhow!("invalid credentials").into()); } let jwt = jwt_client() - .generate(user.id) + .encode(user.id) .context("failed at generating jwt for user")?; Ok(LoginLocalUserResponse { jwt }) diff --git a/bin/core/src/auth/mod.rs b/bin/core/src/auth/mod.rs index d7c6d3441..a91f046b5 100644 --- a/bin/core/src/auth/mod.rs +++ b/bin/core/src/auth/mod.rs @@ -1,5 +1,4 @@ -use ::jwt::VerifyWithKey; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use async_timing_util::unix_timestamp_ms; use axum::{ extract::Request, http::HeaderMap, middleware::Next, @@ -71,7 +70,9 @@ pub async fn get_user_id_from_headers( } _ => { // AUTH FAIL - Err(anyhow!("must attach either AUTHORIZATION header with jwt OR pass X-API-KEY and X-API-SECRET")) + Err(anyhow!( + "must attach either AUTHORIZATION header with jwt OR pass X-API-KEY and X-API-SECRET" + )) } } } @@ -93,9 +94,7 @@ pub async fn authenticate_check_enabled( pub async fn auth_jwt_get_user_id( jwt: &str, ) -> anyhow::Result { - let claims: JwtClaims = jwt - .verify_with_key(&jwt_client().key) - .context("failed to verify claims")?; + let claims: JwtClaims = jwt_client().decode(jwt)?; if claims.exp > unix_timestamp_ms() { Ok(claims.id) } else { diff --git a/bin/core/src/auth/oidc/client.rs b/bin/core/src/auth/oidc/client.rs index 140036b42..c6b773070 100644 --- a/bin/core/src/auth/oidc/client.rs +++ b/bin/core/src/auth/oidc/client.rs @@ -1,67 +1,94 @@ -use std::sync::OnceLock; +use std::{sync::OnceLock, time::Duration}; use anyhow::Context; +use arc_swap::ArcSwapOption; use openidconnect::{ - core::{CoreClient, CoreProviderMetadata}, - reqwest::async_http_client, - ClientId, ClientSecret, IssuerUrl, RedirectUrl, + Client, ClientId, ClientSecret, EmptyAdditionalClaims, + EndpointMaybeSet, EndpointNotSet, EndpointSet, IssuerUrl, + RedirectUrl, StandardErrorResponse, core::*, }; use crate::config::core_config; -static DEFAULT_OIDC_CLIENT: OnceLock> = - OnceLock::new(); +type OidcClient = Client< + EmptyAdditionalClaims, + CoreAuthDisplay, + CoreGenderClaim, + CoreJweContentEncryptionAlgorithm, + CoreJsonWebKey, + CoreAuthPrompt, + StandardErrorResponse, + CoreTokenResponse, + CoreTokenIntrospectionResponse, + CoreRevocableToken, + CoreRevocationErrorResponse, + EndpointSet, + EndpointNotSet, + EndpointNotSet, + EndpointNotSet, + EndpointMaybeSet, + EndpointMaybeSet, +>; -pub fn default_oidc_client() -> Option<&'static CoreClient> { - DEFAULT_OIDC_CLIENT - .get() - .expect("OIDC client get before init") - .as_ref() +pub fn oidc_client() -> &'static ArcSwapOption { + static OIDC_CLIENT: OnceLock> = + OnceLock::new(); + OIDC_CLIENT.get_or_init(Default::default) } -pub async fn init_default_oidc_client() { +/// The OIDC client must be reinitialized to +/// pick up the latest provider JWKs. This +/// function spawns a management thread to do this +/// on a loop. +pub async fn spawn_oidc_client_management() { let config = core_config(); if !config.oidc_enabled || config.oidc_provider.is_empty() || config.oidc_client_id.is_empty() - || config.oidc_client_secret.is_empty() { - DEFAULT_OIDC_CLIENT - .set(None) - .expect("Default OIDC client initialized twice"); return; } - async { - // Use OpenID Connect Discovery to fetch the provider metadata. - let provider_metadata = CoreProviderMetadata::discover_async( - IssuerUrl::new(config.oidc_provider.clone())?, - async_http_client, - ) + reset_oidc_client() .await - .context( - "Failed to get OIDC /.well-known/openid-configuration", - )?; - - // Create an OpenID Connect client by specifying the client ID, client secret, authorization URL - // and token URL. - let client = CoreClient::from_provider_metadata( - provider_metadata, - ClientId::new(config.oidc_client_id.to_string()), - Some(ClientSecret::new(config.oidc_client_secret.to_string())), - ) - // Set the URL the user will be redirected to after the authorization process. - .set_redirect_uri(RedirectUrl::new(format!( - "{}/auth/oidc/callback", - core_config().host - ))?); - - DEFAULT_OIDC_CLIENT - .set(Some(client)) - .expect("Default OIDC client initialized twice"); - - anyhow::Ok(()) - } - .await - .context("Failed to init default OIDC client") - .unwrap(); + .context("Failed to initialize OIDC client.") + .unwrap(); + tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_secs(60)).await; + if let Err(e) = reset_oidc_client().await { + warn!("Failed to reinitialize OIDC client | {e:#}"); + } + } + }); +} + +async fn reset_oidc_client() -> anyhow::Result<()> { + let config = core_config(); + // Use OpenID Connect Discovery to fetch the provider metadata. + let provider_metadata = CoreProviderMetadata::discover_async( + IssuerUrl::new(config.oidc_provider.clone())?, + super::reqwest_client(), + ) + .await + .context("Failed to get OIDC /.well-known/openid-configuration")?; + + let client = CoreClient::from_provider_metadata( + provider_metadata, + ClientId::new(config.oidc_client_id.to_string()), + // The secret may be empty / ommitted if auth provider supports PKCE + if config.oidc_client_secret.is_empty() { + None + } else { + Some(ClientSecret::new(config.oidc_client_secret.to_string())) + }, + ) + // Set the URL the user will be redirected to after the authorization process. + .set_redirect_uri(RedirectUrl::new(format!( + "{}/auth/oidc/callback", + core_config().host + ))?); + + oidc_client().store(Some(client.into())); + + Ok(()) } diff --git a/bin/core/src/auth/oidc/mod.rs b/bin/core/src/auth/oidc/mod.rs index 28822616a..70987560a 100644 --- a/bin/core/src/auth/oidc/mod.rs +++ b/bin/core/src/auth/oidc/mod.rs @@ -1,20 +1,20 @@ use std::sync::OnceLock; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use axum::{ - extract::Query, response::Redirect, routing::get, Router, + Router, extract::Query, response::Redirect, routing::get, }; -use client::default_oidc_client; +use client::oidc_client; use dashmap::DashMap; use komodo_client::entities::{ komodo_timestamp, user::{User, UserConfig}, }; -use mungos::mongodb::bson::{doc, Document}; +use mungos::mongodb::bson::{Document, doc}; use openidconnect::{ - core::CoreAuthenticationFlow, AccessTokenHash, AuthorizationCode, - CsrfToken, Nonce, OAuth2TokenResponse, PkceCodeChallenge, - PkceCodeVerifier, Scope, TokenResponse, + AccessTokenHash, AuthorizationCode, CsrfToken, Nonce, + OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, Scope, + TokenResponse, core::CoreAuthenticationFlow, }; use reqwest::StatusCode; use serde::Deserialize; @@ -29,16 +29,28 @@ use super::RedirectQuery; pub mod client; +fn reqwest_client() -> &'static reqwest::Client { + static REQWEST: OnceLock = OnceLock::new(); + REQWEST.get_or_init(|| { + reqwest::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .expect("Invalid OIDC reqwest client") + }) +} + /// CSRF tokens can only be used once from the callback, /// and must be used within this timeframe const CSRF_VALID_FOR_MS: i64 = 120_000; // 2 minutes for user to log in. type RedirectUrl = Option; -type CsrfMap = +/// Maps the csrf secrets to other information added in the "login" method (before auth provider redirect). +/// This information is retrieved in the "callback" method (after auth provider redirect). +type VerifierMap = DashMap; -fn csrf_verifier_tokens() -> &'static CsrfMap { - static CSRF: OnceLock = OnceLock::new(); - CSRF.get_or_init(Default::default) +fn verifier_tokens() -> &'static VerifierMap { + static VERIFIERS: OnceLock = OnceLock::new(); + VERIFIERS.get_or_init(Default::default) } pub fn router() -> Router { @@ -61,10 +73,10 @@ pub fn router() -> Router { async fn login( Query(RedirectQuery { redirect }): Query, ) -> anyhow::Result { + let client = oidc_client().load(); let client = - default_oidc_client().context("OIDC Client not configured")?; + client.as_ref().context("OIDC Client not configured")?; - // Generate a PKCE challenge. let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); @@ -75,13 +87,13 @@ async fn login( CsrfToken::new_random, Nonce::new_random, ) + .set_pkce_challenge(pkce_challenge) .add_scope(Scope::new("openid".to_string())) .add_scope(Scope::new("email".to_string())) - .set_pkce_challenge(pkce_challenge) .url(); // Data inserted here will be matched on callback side for csrf protection. - csrf_verifier_tokens().insert( + verifier_tokens().insert( csrf_token.secret().clone(), ( pkce_verifier, @@ -123,8 +135,9 @@ struct CallbackQuery { async fn callback( Query(query): Query, ) -> anyhow::Result { + let client = oidc_client().load(); let client = - default_oidc_client().context("OIDC Client not configured")?; + client.as_ref().context("OIDC Client not configured")?; if let Some(e) = query.error { return Err(anyhow!("Provider returned error: {e}")); @@ -136,21 +149,21 @@ async fn callback( ); let (_, (pkce_verifier, nonce, redirect, valid_until)) = - csrf_verifier_tokens() + verifier_tokens() .remove(state.secret()) - .context("CSRF Token invalid")?; + .context("CSRF token invalid")?; if komodo_timestamp() > valid_until { return Err(anyhow!( - "CSRF token invalid (Timed out). The token must be " + "CSRF token invalid (Timed out). The token must be used within 2 minutes." )); } let token_response = client .exchange_code(AuthorizationCode::new(code)) - // Set the PKCE code verifier. + .context("Failed to get Oauth token at exchange code")? .set_pkce_verifier(pkce_verifier) - .request_async(openidconnect::reqwest::async_http_client) + .request_async(reqwest_client()) .await .context("Failed to get Oauth token")?; @@ -173,7 +186,7 @@ async fn callback( let claims = id_token .claims(&verifier, &nonce) - .context("Failed to verify token claims")?; + .context("Failed to verify token claims. This issue may be temporary (60 seconds max).")?; // Verify the access token hash to ensure that the access token hasn't been substituted for // another user's. @@ -181,7 +194,8 @@ async fn callback( { let actual_access_token_hash = AccessTokenHash::from_token( token_response.access_token(), - &id_token.signing_alg()?, + id_token.signing_alg()?, + id_token.signing_key(&verifier)?, )?; if actual_access_token_hash != *expected_access_token_hash { return Err(anyhow!("Invalid access token")); @@ -202,7 +216,7 @@ async fn callback( let jwt = match user { Some(user) => jwt_client() - .generate(user.id) + .encode(user.id) .context("failed to generate jwt")?, None => { let ts = komodo_timestamp(); @@ -258,7 +272,7 @@ async fn callback( .context("inserted_id is not ObjectId")? .to_string(); jwt_client() - .generate(user_id) + .encode(user_id) .context("failed to generate jwt")? } }; diff --git a/bin/core/src/cloud/aws/ec2.rs b/bin/core/src/cloud/aws/ec2.rs index 03770c34d..d2bf36302 100644 --- a/bin/core/src/cloud/aws/ec2.rs +++ b/bin/core/src/cloud/aws/ec2.rs @@ -1,22 +1,22 @@ use std::{str::FromStr, time::Duration}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use aws_config::{BehaviorVersion, Region}; use aws_sdk_ec2::{ + Client, types::{ BlockDeviceMapping, EbsBlockDevice, InstanceNetworkInterfaceSpecification, InstanceStateChange, InstanceStateName, InstanceStatus, InstanceType, ResourceType, Tag, TagSpecification, VolumeType, }, - Client, }; use base64::Engine; use komodo_client::entities::{ + ResourceTarget, alert::{Alert, AlertData, SeverityLevel}, komodo_timestamp, server_template::aws::AwsServerTemplateConfig, - ResourceTarget, }; use crate::{alert::send_alerts, config::core_config}; @@ -29,20 +29,40 @@ pub struct Ec2Instance { pub ip: String, } +/// Provides credentials in the core config file to the AWS client +#[derive(Debug)] +struct CredentialsFromConfig; + +impl aws_credential_types::provider::ProvideCredentials + for CredentialsFromConfig +{ + fn provide_credentials<'a>( + &'a self, + ) -> aws_credential_types::provider::future::ProvideCredentials<'a> + where + Self: 'a, + { + aws_credential_types::provider::future::ProvideCredentials::new( + async { + let config = core_config(); + Ok(aws_credential_types::Credentials::new( + &config.aws.access_key_id, + &config.aws.secret_access_key, + None, + None, + "komodo-config", + )) + }, + ) + } +} + #[instrument] async fn create_ec2_client(region: String) -> Client { - // There may be a better way to pass these keys to client - std::env::set_var( - "AWS_ACCESS_KEY_ID", - &core_config().aws.access_key_id, - ); - std::env::set_var( - "AWS_SECRET_ACCESS_KEY", - &core_config().aws.secret_access_key, - ); let region = Region::new(region); - let config = aws_config::defaults(BehaviorVersion::v2024_03_28()) + let config = aws_config::defaults(BehaviorVersion::latest()) .region(region) + .credentials_provider(CredentialsFromConfig) .load() .await; Client::new(&config) @@ -1087,7 +1107,90 @@ fn handle_unknown_instance_type( | InstanceType::Z1d6xlarge | InstanceType::Z1dLarge | InstanceType::Z1dMetal - | InstanceType::Z1dXlarge => Ok(instance_type), + | InstanceType::Z1dXlarge + | InstanceType::C7gdMetal + | InstanceType::C7gnMetal + | InstanceType::C7iFlex2xlarge + | InstanceType::C7iFlex4xlarge + | InstanceType::C7iFlex8xlarge + | InstanceType::C7iFlexLarge + | InstanceType::C7iFlexXlarge + | InstanceType::C8g12xlarge + | InstanceType::C8g16xlarge + | InstanceType::C8g24xlarge + | InstanceType::C8g2xlarge + | InstanceType::C8g48xlarge + | InstanceType::C8g4xlarge + | InstanceType::C8g8xlarge + | InstanceType::C8gLarge + | InstanceType::C8gMedium + | InstanceType::C8gMetal24xl + | InstanceType::C8gMetal48xl + | InstanceType::C8gXlarge + | InstanceType::G612xlarge + | InstanceType::G616xlarge + | InstanceType::G624xlarge + | InstanceType::G62xlarge + | InstanceType::G648xlarge + | InstanceType::G64xlarge + | InstanceType::G68xlarge + | InstanceType::G6Xlarge + | InstanceType::G6e12xlarge + | InstanceType::G6e16xlarge + | InstanceType::G6e24xlarge + | InstanceType::G6e2xlarge + | InstanceType::G6e48xlarge + | InstanceType::G6e4xlarge + | InstanceType::G6e8xlarge + | InstanceType::G6eXlarge + | InstanceType::Gr64xlarge + | InstanceType::Gr68xlarge + | InstanceType::M7gdMetal + | InstanceType::M8g12xlarge + | InstanceType::M8g16xlarge + | InstanceType::M8g24xlarge + | InstanceType::M8g2xlarge + | InstanceType::M8g48xlarge + | InstanceType::M8g4xlarge + | InstanceType::M8g8xlarge + | InstanceType::M8gLarge + | InstanceType::M8gMedium + | InstanceType::M8gMetal24xl + | InstanceType::M8gMetal48xl + | InstanceType::M8gXlarge + | InstanceType::Mac2M1ultraMetal + | InstanceType::R7gdMetal + | InstanceType::R7izMetal16xl + | InstanceType::R7izMetal32xl + | InstanceType::R8g12xlarge + | InstanceType::R8g16xlarge + | InstanceType::R8g24xlarge + | InstanceType::R8g2xlarge + | InstanceType::R8g48xlarge + | InstanceType::R8g4xlarge + | InstanceType::R8g8xlarge + | InstanceType::R8gLarge + | InstanceType::R8gMedium + | InstanceType::R8gMetal24xl + | InstanceType::R8gMetal48xl + | InstanceType::R8gXlarge + | InstanceType::U7i12tb224xlarge + | InstanceType::U7ib12tb224xlarge + | InstanceType::U7in16tb224xlarge + | InstanceType::U7in24tb224xlarge + | InstanceType::U7in32tb224xlarge + | InstanceType::X8g12xlarge + | InstanceType::X8g16xlarge + | InstanceType::X8g24xlarge + | InstanceType::X8g2xlarge + | InstanceType::X8g48xlarge + | InstanceType::X8g4xlarge + | InstanceType::X8g8xlarge + | InstanceType::X8gLarge + | InstanceType::X8gMedium + | InstanceType::X8gMetal24xl + | InstanceType::X8gMetal48xl + | InstanceType::X8gXlarge => Ok(instance_type), other => Err(anyhow!("unknown InstanceType: {other:?}")), } } diff --git a/bin/core/src/cloud/hetzner/client.rs b/bin/core/src/cloud/hetzner/client.rs index c4d1712de..79de50ac4 100644 --- a/bin/core/src/cloud/hetzner/client.rs +++ b/bin/core/src/cloud/hetzner/client.rs @@ -1,7 +1,7 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use axum::http::{HeaderName, HeaderValue}; use reqwest::{RequestBuilder, StatusCode}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; use super::{ common::{ diff --git a/bin/core/src/cloud/hetzner/mod.rs b/bin/core/src/cloud/hetzner/mod.rs index aed19e0b5..e235cd887 100644 --- a/bin/core/src/cloud/hetzner/mod.rs +++ b/bin/core/src/cloud/hetzner/mod.rs @@ -3,7 +3,7 @@ use std::{ time::Duration, }; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use futures::future::join_all; use komodo_client::entities::server_template::hetzner::{ HetznerDatacenter, HetznerServerTemplateConfig, HetznerServerType, diff --git a/bin/core/src/config.rs b/bin/core/src/config.rs index fa2c2fcbd..6865ac97c 100644 --- a/bin/core/src/config.rs +++ b/bin/core/src/config.rs @@ -182,6 +182,8 @@ pub fn core_config() -> &'static CoreConfig { .unwrap_or(config.disable_user_registration), disable_non_admin_create: env.komodo_disable_non_admin_create .unwrap_or(config.disable_non_admin_create), + lock_login_credentials_for: env.komodo_lock_login_credentials_for + .unwrap_or(config.lock_login_credentials_for), local_auth: env.komodo_local_auth .unwrap_or(config.local_auth), logging: LogConfig { diff --git a/bin/core/src/db.rs b/bin/core/src/db.rs index d372ed3c7..8c138f0f3 100644 --- a/bin/core/src/db.rs +++ b/bin/core/src/db.rs @@ -89,7 +89,9 @@ impl DbClient { client = client.address(address); } _ => { - error!("config.mongo not configured correctly. must pass either config.mongo.uri, or config.mongo.address + config.mongo.username? + config.mongo.password?"); + error!( + "config.mongo not configured correctly. must pass either config.mongo.uri, or config.mongo.address + config.mongo.username? + config.mongo.password?" + ); std::process::exit(1) } } diff --git a/bin/core/src/helpers/action_state.rs b/bin/core/src/helpers/action_state.rs index 4582bc319..e103f5dd2 100644 --- a/bin/core/src/helpers/action_state.rs +++ b/bin/core/src/helpers/action_state.rs @@ -84,8 +84,8 @@ pub struct UpdateGuard<'a, States: Default + Send + 'static>( &'a Mutex, ); -impl<'a, States: Default + Send + 'static> Drop - for UpdateGuard<'a, States> +impl Drop + for UpdateGuard<'_, States> { fn drop(&mut self) { let mut lock = match self.0.lock() { diff --git a/bin/core/src/helpers/builder.rs b/bin/core/src/helpers/builder.rs index 8d3cd0684..fd539f06a 100644 --- a/bin/core/src/helpers/builder.rs +++ b/bin/core/src/helpers/builder.rs @@ -1,27 +1,27 @@ use std::time::Duration; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::muted; use komodo_client::entities::{ + Version, builder::{AwsBuilderConfig, Builder, BuilderConfig}, komodo_timestamp, server::Server, server_template::aws::AwsServerTemplateConfig, update::{Log, Update}, - Version, }; use periphery_client::{ - api::{self, GetVersionResponse}, PeripheryClient, + api::{self, GetVersionResponse}, }; use crate::{ cloud::{ - aws::ec2::{ - launch_ec2_instance, terminate_ec2_instance_with_retry, - Ec2Instance, - }, BuildCleanupData, + aws::ec2::{ + Ec2Instance, launch_ec2_instance, + terminate_ec2_instance_with_retry, + }, }, config::core_config, helpers::update::update_update, @@ -122,8 +122,11 @@ async fn get_aws_builder( let protocol = if config.use_https { "https" } else { "http" }; let periphery_address = format!("{protocol}://{ip}:{}", config.port); - let periphery = - PeripheryClient::new(&periphery_address, &core_config().passkey, Duration::from_secs(3)); + let periphery = PeripheryClient::new( + &periphery_address, + &core_config().passkey, + Duration::from_secs(3), + ); let start_connect_ts = komodo_timestamp(); let mut res = Ok(GetVersionResponse { diff --git a/bin/core/src/helpers/cache.rs b/bin/core/src/helpers/cache.rs index 01416c692..a9ae902d1 100644 --- a/bin/core/src/helpers/cache.rs +++ b/bin/core/src/helpers/cache.rs @@ -9,9 +9,9 @@ pub struct Cache { } impl< - K: PartialEq + Eq + Hash + std::fmt::Debug + Clone, - T: Clone + Default, - > Cache + K: PartialEq + Eq + Hash + std::fmt::Debug + Clone, + T: Clone + Default, +> Cache { #[instrument(level = "debug", skip(self))] pub async fn get(&self, key: &K) -> Option { @@ -70,9 +70,9 @@ impl< } impl< - K: PartialEq + Eq + Hash + std::fmt::Debug + Clone, - T: Clone + Default + Busy, - > Cache + K: PartialEq + Eq + Hash + std::fmt::Debug + Clone, + T: Clone + Default + Busy, +> Cache { #[instrument(level = "debug", skip(self))] pub async fn busy(&self, id: &K) -> bool { diff --git a/bin/core/src/helpers/channel.rs b/bin/core/src/helpers/channel.rs index 3e591bba6..79a5b8914 100644 --- a/bin/core/src/helpers/channel.rs +++ b/bin/core/src/helpers/channel.rs @@ -1,11 +1,11 @@ use std::sync::OnceLock; use komodo_client::entities::update::{Update, UpdateListItem}; -use tokio::sync::{broadcast, Mutex}; +use tokio::sync::{Mutex, broadcast}; /// A channel sending (build_id, update_id) -pub fn build_cancel_channel( -) -> &'static BroadcastChannel<(String, Update)> { +pub fn build_cancel_channel() +-> &'static BroadcastChannel<(String, Update)> { static BUILD_CANCEL_CHANNEL: OnceLock< BroadcastChannel<(String, Update)>, > = OnceLock::new(); @@ -13,8 +13,8 @@ pub fn build_cancel_channel( } /// A channel sending (repo_id, update_id) -pub fn repo_cancel_channel( -) -> &'static BroadcastChannel<(String, Update)> { +pub fn repo_cancel_channel() +-> &'static BroadcastChannel<(String, Update)> { static REPO_CANCEL_CHANNEL: OnceLock< BroadcastChannel<(String, Update)>, > = OnceLock::new(); diff --git a/bin/core/src/helpers/interpolate.rs b/bin/core/src/helpers/interpolate.rs index 7154ad90e..b6e6df4bd 100644 --- a/bin/core/src/helpers/interpolate.rs +++ b/bin/core/src/helpers/interpolate.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use anyhow::Context; -use komodo_client::entities::{update::Update, SystemCommand}; +use komodo_client::entities::{SystemCommand, update::Update}; use super::query::VariablesAndSecrets; diff --git a/bin/core/src/helpers/mod.rs b/bin/core/src/helpers/mod.rs index 086f3e290..2cd7c458b 100644 --- a/bin/core/src/helpers/mod.rs +++ b/bin/core/src/helpers/mod.rs @@ -1,33 +1,32 @@ use std::{str::FromStr, time::Duration}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use futures::future::join_all; use komodo_client::{ api::write::{CreateBuilder, CreateServer}, entities::{ + ResourceTarget, builder::{PartialBuilderConfig, PartialServerBuilderConfig}, komodo_timestamp, permission::{Permission, PermissionLevel, UserTarget}, server::{PartialServerConfig, Server}, sync::ResourceSync, update::Log, - user::{system_user, User}, - ResourceTarget, + user::{User, system_user}, }, }; use mongo_indexed::Document; use mungos::{ find::find_collect, - mongodb::bson::{doc, oid::ObjectId, to_document, Bson}, + mongodb::bson::{Bson, doc, oid::ObjectId, to_document}, }; use periphery_client::PeripheryClient; -use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use rand::Rng; use resolver_api::Resolve; use crate::{ - config::core_config, - resource, - state::{db_client, State}, + api::write::WriteArgs, config::core_config, resource, + state::db_client, }; pub mod action_state; @@ -55,8 +54,8 @@ pub fn empty_or_only_spaces(word: &str) -> bool { } pub fn random_string(length: usize) -> String { - thread_rng() - .sample_iter(&Alphanumeric) + rand::rng() + .sample_iter(&rand::distr::Alphanumeric) .take(length) .map(char::from) .collect() @@ -209,7 +208,9 @@ pub async fn startup_cleanup() { async fn startup_in_progress_update_cleanup() { let log = Log::error( "Komodo shutdown", - String::from("Komodo shutdown during execution. If this is a build, the builder may not have been terminated.") + String::from( + "Komodo shutdown during execution. If this is a build, the builder may not have been terminated.", + ), ); // This static log won't fail to serialize, unwrap ok. let log = to_document(&log).unwrap(); @@ -305,46 +306,50 @@ pub async fn ensure_first_server_and_builder() { let server = if let Some(server) = server { server } else { - match State - .resolve( - CreateServer { - name: format!("server-{}", random_string(5)), - config: PartialServerConfig { - address: Some(first_server.to_string()), - enabled: Some(true), - ..Default::default() - }, - }, - system_user().to_owned(), - ) - .await + match (CreateServer { + name: format!("server-{}", random_string(5)), + config: PartialServerConfig { + address: Some(first_server.to_string()), + enabled: Some(true), + ..Default::default() + }, + }) + .resolve(&WriteArgs { + user: system_user().to_owned(), + }) + .await { Ok(server) => server, Err(e) => { - error!("Failed to initialize 'first_server'. Failed to CreateServer. {e:?}"); + error!( + "Failed to initialize 'first_server'. Failed to CreateServer. {:#}", + e.error + ); return; } } }; let Ok(None) = db.builders .find_one(Document::new()).await - .inspect_err(|e| error!("Failed to initialize 'first_builder'. Failed to query db. {e:?}")) else { + .inspect_err(|e| error!("Failed to initialize 'first_builder' | Failed to query db | {e:?}")) else { return; }; - if let Err(e) = State - .resolve( - CreateBuilder { - name: String::from("local"), - config: PartialBuilderConfig::Server( - PartialServerBuilderConfig { - server_id: Some(server.id), - }, - ), + if let Err(e) = (CreateBuilder { + name: String::from("local"), + config: PartialBuilderConfig::Server( + PartialServerBuilderConfig { + server_id: Some(server.id), }, - system_user().to_owned(), - ) - .await + ), + }) + .resolve(&WriteArgs { + user: system_user().to_owned(), + }) + .await { - error!("Failed to initialize 'first_builder'. Failed to CreateBuilder. {e:?}"); + error!( + "Failed to initialize 'first_builder' | Failed to CreateBuilder | {:#}", + e.error + ); } -} +} \ No newline at end of file diff --git a/bin/core/src/helpers/procedure.rs b/bin/core/src/helpers/procedure.rs index a55a6fb10..071108c95 100644 --- a/bin/core/src/helpers/procedure.rs +++ b/bin/core/src/helpers/procedure.rs @@ -1,7 +1,7 @@ use std::time::{Duration, Instant}; -use anyhow::{anyhow, Context}; -use formatting::{bold, colored, format_serror, muted, Color}; +use anyhow::{Context, anyhow}; +use formatting::{Color, bold, colored, format_serror, muted}; use futures::future::join_all; use komodo_client::{ api::execute::*, @@ -21,9 +21,12 @@ use resolver_api::Resolve; use tokio::sync::Mutex; use crate::{ - api::execute::ExecuteRequest, - resource::{list_full_for_user_using_pattern, KomodoResource}, - state::{db_client, State}, + api::{ + execute::{ExecuteArgs, ExecuteRequest}, + write::WriteArgs, + }, + resource::{KomodoResource, list_full_for_user_using_pattern}, + state::db_client, }; use super::update::{init_execution_update, update_update}; @@ -203,7 +206,7 @@ async fn execute_stage( join_all(futures) .await .into_iter() - .collect::>()?; + .collect::>>()?; Ok(()) } @@ -227,9 +230,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunProcedure"), &update_id, ) @@ -249,9 +253,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunAction"), &update_id, ) @@ -271,9 +276,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunBuild"), &update_id, ) @@ -293,9 +299,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at CancelBuild"), &update_id, ) @@ -309,9 +316,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at Deploy"), &update_id, ) @@ -331,9 +339,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PullDeployment"), &update_id, ) @@ -347,9 +356,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartDeployment"), &update_id, ) @@ -363,9 +373,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartDeployment"), &update_id, ) @@ -379,9 +390,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseDeployment"), &update_id, ) @@ -395,9 +407,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseDeployment"), &update_id, ) @@ -411,9 +424,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopDeployment"), &update_id, ) @@ -427,9 +441,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RemoveDeployment"), &update_id, ) @@ -449,9 +464,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at CloneRepo"), &update_id, ) @@ -471,9 +487,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PullRepo"), &update_id, ) @@ -493,9 +510,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at BuildRepo"), &update_id, ) @@ -515,9 +533,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at CancelRepoBuild"), &update_id, ) @@ -531,9 +550,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartContainer"), &update_id, ) @@ -547,9 +567,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartContainer"), &update_id, ) @@ -563,9 +584,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseContainer"), &update_id, ) @@ -579,9 +601,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseContainer"), &update_id, ) @@ -595,9 +618,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopContainer"), &update_id, ) @@ -611,9 +635,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RemoveContainer"), &update_id, ) @@ -627,9 +652,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartAllContainers"), &update_id, ) @@ -643,9 +669,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartAllContainers"), &update_id, ) @@ -659,9 +686,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseAllContainers"), &update_id, ) @@ -675,9 +703,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseAllContainers"), &update_id, ) @@ -691,9 +720,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopAllContainers"), &update_id, ) @@ -707,9 +737,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneContainers"), &update_id, ) @@ -723,9 +754,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeleteNetwork"), &update_id, ) @@ -739,9 +771,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneNetworks"), &update_id, ) @@ -755,9 +788,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeleteImage"), &update_id, ) @@ -771,9 +805,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneImages"), &update_id, ) @@ -787,9 +822,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeleteVolume"), &update_id, ) @@ -803,9 +839,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneVolumes"), &update_id, ) @@ -819,9 +856,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneDockerBuilders"), &update_id, ) @@ -835,9 +873,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneBuildx"), &update_id, ) @@ -851,9 +890,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PruneSystem"), &update_id, ) @@ -867,18 +907,20 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RunSync"), &update_id, ) .await? } // Exception: This is a write operation. - Execution::CommitSync(req) => State - .resolve(req, user) + Execution::CommitSync(req) => req + .resolve(&WriteArgs { user }) .await + .map_err(|e| e.error) .context("Failed at CommitSync")?, Execution::DeployStack(req) => { let req = ExecuteRequest::DeployStack(req); @@ -888,9 +930,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeployStack"), &update_id, ) @@ -910,9 +953,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DeployStackIfChanged"), &update_id, ) @@ -932,9 +976,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PullStack"), &update_id, ) @@ -948,9 +993,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StartStack"), &update_id, ) @@ -964,9 +1010,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at RestartStack"), &update_id, ) @@ -980,9 +1027,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at PauseStack"), &update_id, ) @@ -996,9 +1044,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at UnpauseStack"), &update_id, ) @@ -1012,9 +1061,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at StopStack"), &update_id, ) @@ -1028,9 +1078,10 @@ async fn execute_execution( }; let update_id = update.id.clone(); handle_resolve_result( - State - .resolve(req, (user, update)) + req + .resolve(&ExecuteArgs { user, update }) .await + .map_err(|e| e.error) .context("Failed at DestroyStack"), &update_id, ) @@ -1042,6 +1093,23 @@ async fn execute_execution( "Batch method BatchDestroyStack not implemented correctly" )); } + Execution::TestAlerter(req) => { + let req = ExecuteRequest::TestAlerter(req); + let update = init_execution_update(&req, &user).await?; + let ExecuteRequest::TestAlerter(req) = req else { + unreachable!() + }; + let update_id = update.id.clone(); + handle_resolve_result( + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error) + .context("Failed at TestAlerter"), + &update_id, + ) + .await? + } Execution::Sleep(req) => { let duration = Duration::from_millis(req.duration_ms as u64); tokio::time::sleep(duration).await; @@ -1191,7 +1259,7 @@ impl ExtendBatch for BatchDeployStack { fn single_execution(stack: String) -> Execution { Execution::DeployStack(DeployStack { stack, - service: None, + services: Vec::new(), stop_time: None, }) } @@ -1212,7 +1280,7 @@ impl ExtendBatch for BatchDestroyStack { fn single_execution(stack: String) -> Execution { Execution::DestroyStack(DestroyStack { stack, - service: None, + services: Vec::new(), remove_orphans: false, stop_time: None, }) diff --git a/bin/core/src/helpers/prune.rs b/bin/core/src/helpers/prune.rs index fda502f3b..fce9a7d3a 100644 --- a/bin/core/src/helpers/prune.rs +++ b/bin/core/src/helpers/prune.rs @@ -1,6 +1,6 @@ use anyhow::Context; use async_timing_util::{ - unix_timestamp_ms, wait_until_timelength, Timelength, ONE_DAY_MS, + ONE_DAY_MS, Timelength, unix_timestamp_ms, wait_until_timelength, }; use futures::future::join_all; use mungos::{find::find_collect, mongodb::bson::doc}; @@ -34,8 +34,9 @@ async fn prune_images() -> anyhow::Result<()> { .await .context("failed to get servers from db")? .into_iter() - // This could be done in the mongo query, but rather have rust type system guarantee this. - .filter(|server| server.config.auto_prune) + .filter(|server| { + server.config.enabled && server.config.auto_prune + }) .map(|server| async move { ( async { diff --git a/bin/core/src/helpers/query.rs b/bin/core/src/helpers/query.rs index 39e637982..62d3ba840 100644 --- a/bin/core/src/helpers/query.rs +++ b/bin/core/src/helpers/query.rs @@ -1,7 +1,8 @@ use std::{collections::HashMap, str::FromStr}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::entities::{ + Operation, ResourceTarget, ResourceTargetVariant, action::Action, alerter::Alerter, build::Build, @@ -17,15 +18,14 @@ use komodo_client::entities::{ sync::ResourceSync, tag::Tag, update::Update, - user::{admin_service_user, User}, + user::{User, admin_service_user}, user_group::UserGroup, variable::Variable, - Operation, ResourceTarget, ResourceTargetVariant, }; use mungos::{ find::find_collect, mongodb::{ - bson::{doc, oid::ObjectId, Document}, + bson::{Document, doc, oid::ObjectId}, options::FindOneOptions, }, }; @@ -359,8 +359,8 @@ pub struct VariablesAndSecrets { pub secrets: HashMap, } -pub async fn get_variables_and_secrets( -) -> anyhow::Result { +pub async fn get_variables_and_secrets() +-> anyhow::Result { let variables = find_collect(&db_client().variables, None, None) .await .context("failed to get all variables from db")?; diff --git a/bin/core/src/helpers/update.rs b/bin/core/src/helpers/update.rs index 536be25c5..45b6059dd 100644 --- a/bin/core/src/helpers/update.rs +++ b/bin/core/src/helpers/update.rs @@ -1,6 +1,8 @@ use anyhow::Context; use komodo_client::entities::{ + Operation, ResourceTarget, action::Action, + alerter::Alerter, build::Build, deployment::Deployment, komodo_timestamp, @@ -12,7 +14,6 @@ use komodo_client::entities::{ sync::ResourceSync, update::{Update, UpdateListItem}, user::User, - Operation, ResourceTarget, }; use mungos::{ by_id::{find_one_by_id, update_one_by_id}, @@ -262,7 +263,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchDeploy(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } ExecuteRequest::PullDeployment(data) => ( Operation::PullDeployment, @@ -307,7 +308,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchDestroyDeployment(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } // Build @@ -318,7 +319,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchRunBuild(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } ExecuteRequest::CancelBuild(data) => ( Operation::CancelBuild, @@ -335,7 +336,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchCloneRepo(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } ExecuteRequest::PullRepo(data) => ( Operation::PullRepo, @@ -344,7 +345,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchPullRepo(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } ExecuteRequest::BuildRepo(data) => ( Operation::BuildRepo, @@ -353,7 +354,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchBuildRepo(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } ExecuteRequest::CancelRepoBuild(data) => ( Operation::CancelRepoBuild, @@ -370,7 +371,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchRunProcedure(_) => { - return Ok(Default::default()) + return Ok(Default::default()); } // Action @@ -381,7 +382,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchRunAction(_) => { - return Ok(Default::default()) + return Ok(Default::default()); } // Server template @@ -404,7 +405,7 @@ pub async fn init_execution_update( // Stack ExecuteRequest::DeployStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::DeployStackService } else { Operation::DeployStack @@ -414,7 +415,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchDeployStack(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } ExecuteRequest::DeployStackIfChanged(data) => ( Operation::DeployStack, @@ -423,10 +424,10 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchDeployStackIfChanged(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } ExecuteRequest::StartStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::StartStackService } else { Operation::StartStack @@ -436,7 +437,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::PullStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::PullStackService } else { Operation::PullStack @@ -446,7 +447,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::RestartStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::RestartStackService } else { Operation::RestartStack @@ -456,7 +457,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::PauseStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::PauseStackService } else { Operation::PauseStack @@ -466,7 +467,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::UnpauseStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::UnpauseStackService } else { Operation::UnpauseStack @@ -476,7 +477,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::StopStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::StopStackService } else { Operation::StopStack @@ -486,7 +487,7 @@ pub async fn init_execution_update( ), ), ExecuteRequest::DestroyStack(data) => ( - if data.service.is_some() { + if !data.services.is_empty() { Operation::DestroyStackService } else { Operation::DestroyStack @@ -496,15 +497,26 @@ pub async fn init_execution_update( ), ), ExecuteRequest::BatchDestroyStack(_data) => { - return Ok(Default::default()) + return Ok(Default::default()); } + + // Alerter + ExecuteRequest::TestAlerter(data) => ( + Operation::TestAlerter, + ResourceTarget::Alerter( + resource::get::(&data.alerter).await?.id, + ), + ), }; + let mut update = make_update(target, operation, user); update.in_progress(); + // Hold off on even adding update for DeployStackIfChanged if !matches!(&request, ExecuteRequest::DeployStackIfChanged(_)) { // Don't actually send it here, let the handlers send it after they can set action state. update.id = add_update_without_send(&update).await?; } + Ok(update) } diff --git a/bin/core/src/listener/integrations/github.rs b/bin/core/src/listener/integrations/github.rs index 62f2a9f5f..ce155f2d0 100644 --- a/bin/core/src/listener/integrations/github.rs +++ b/bin/core/src/listener/integrations/github.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use axum::http::HeaderMap; use hex::ToHex; use hmac::{Hmac, Mac}; diff --git a/bin/core/src/listener/integrations/gitlab.rs b/bin/core/src/listener/integrations/gitlab.rs index c5a27df99..b8b3520f5 100644 --- a/bin/core/src/listener/integrations/gitlab.rs +++ b/bin/core/src/listener/integrations/gitlab.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use serde::Deserialize; use crate::{ diff --git a/bin/core/src/listener/mod.rs b/bin/core/src/listener/mod.rs index 9adbce3f2..97f72d1ad 100644 --- a/bin/core/src/listener/mod.rs +++ b/bin/core/src/listener/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use axum::{http::HeaderMap, Router}; +use axum::{Router, http::HeaderMap}; use komodo_client::entities::resource::Resource; use tokio::sync::Mutex; diff --git a/bin/core/src/listener/resources.rs b/bin/core/src/listener/resources.rs index b37a300dd..ea767b7ad 100644 --- a/bin/core/src/listener/resources.rs +++ b/bin/core/src/listener/resources.rs @@ -15,11 +15,14 @@ use resolver_api::Resolve; use serde::Deserialize; use crate::{ - api::execute::ExecuteRequest, - helpers::update::init_execution_update, state::State, + api::{ + execute::{ExecuteArgs, ExecuteRequest}, + write::WriteArgs, + }, + helpers::update::init_execution_update, }; -use super::{ListenerLockCache, ANY_BRANCH}; +use super::{ANY_BRANCH, ListenerLockCache}; // ======= // BUILD @@ -58,7 +61,10 @@ pub async fn handle_build_webhook( let ExecuteRequest::RunBuild(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } @@ -93,7 +99,10 @@ impl RepoExecution for CloneRepo { else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -110,7 +119,10 @@ impl RepoExecution for PullRepo { else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -127,7 +139,10 @@ impl RepoExecution for BuildRepo { else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -196,33 +211,37 @@ fn stack_locks() -> &'static ListenerLockCache { } pub trait StackExecution { - async fn resolve(stack: Stack) -> anyhow::Result<()>; + async fn resolve(stack: Stack) -> serror::Result<()>; } impl StackExecution for RefreshStackCache { - async fn resolve(stack: Stack) -> anyhow::Result<()> { - let user = git_webhook_user().to_owned(); - State - .resolve(RefreshStackCache { stack: stack.id }, user) + async fn resolve(stack: Stack) -> serror::Result<()> { + RefreshStackCache { stack: stack.id } + .resolve(&WriteArgs { + user: git_webhook_user().to_owned(), + }) .await?; Ok(()) } } impl StackExecution for DeployStack { - async fn resolve(stack: Stack) -> anyhow::Result<()> { + async fn resolve(stack: Stack) -> serror::Result<()> { let user = git_webhook_user().to_owned(); if stack.config.webhook_force_deploy { let req = ExecuteRequest::DeployStack(DeployStack { stack: stack.id, - service: None, + services: Vec::new(), stop_time: None, }); let update = init_execution_update(&req, &user).await?; let ExecuteRequest::DeployStack(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; } else { let req = ExecuteRequest::DeployStackIfChanged(DeployStackIfChanged { @@ -233,7 +252,10 @@ impl StackExecution for DeployStack { let ExecuteRequest::DeployStackIfChanged(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; } Ok(()) @@ -282,7 +304,7 @@ pub async fn handle_stack_webhook_inner< B::verify_branch(&body, &stack.config.branch)?; - E::resolve(stack).await + E::resolve(stack).await.map_err(|e| e.error) } // ====== @@ -306,10 +328,12 @@ pub trait SyncExecution { impl SyncExecution for RefreshResourceSyncPending { async fn resolve(sync: ResourceSync) -> anyhow::Result<()> { - let user = git_webhook_user().to_owned(); - State - .resolve(RefreshResourceSyncPending { sync: sync.id }, user) - .await?; + RefreshResourceSyncPending { sync: sync.id } + .resolve(&WriteArgs { + user: git_webhook_user().to_owned(), + }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -326,7 +350,10 @@ impl SyncExecution for RunSync { let ExecuteRequest::RunSync(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } } @@ -422,7 +449,10 @@ pub async fn handle_procedure_webhook( let ExecuteRequest::RunProcedure(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } @@ -467,6 +497,9 @@ pub async fn handle_action_webhook( let ExecuteRequest::RunAction(req) = req else { unreachable!() }; - State.resolve(req, (user, update)).await?; + req + .resolve(&ExecuteArgs { user, update }) + .await + .map_err(|e| e.error)?; Ok(()) } diff --git a/bin/core/src/listener/router.rs b/bin/core/src/listener/router.rs index 491b69bf9..a69124d32 100644 --- a/bin/core/src/listener/router.rs +++ b/bin/core/src/listener/router.rs @@ -1,4 +1,4 @@ -use axum::{extract::Path, http::HeaderMap, routing::post, Router}; +use axum::{Router, extract::Path, http::HeaderMap, routing::post}; use komodo_client::entities::{ action::Action, build::Build, procedure::Procedure, repo::Repo, resource::Resource, stack::Stack, sync::ResourceSync, @@ -11,13 +11,13 @@ use tracing::Instrument; use crate::resource::KomodoResource; use super::{ + CustomSecret, VerifyBranch, VerifySecret, resources::{ + RepoWebhookOption, StackWebhookOption, SyncWebhookOption, handle_action_webhook, handle_build_webhook, handle_procedure_webhook, handle_repo_webhook, - handle_stack_webhook, handle_sync_webhook, RepoWebhookOption, - StackWebhookOption, SyncWebhookOption, + handle_stack_webhook, handle_sync_webhook, }, - CustomSecret, VerifyBranch, VerifySecret, }; #[derive(Deserialize)] @@ -45,7 +45,7 @@ fn default_branch() -> String { pub fn router() -> Router { Router::new() .route( - "/build/:id", + "/build/{id}", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { let build = @@ -71,7 +71,7 @@ pub fn router() -> Router { ), ) .route( - "/repo/:id/:option", + "/repo/{id}/{option}", post( |Path(IdAndOption:: { id, option }), headers: HeaderMap, body: String| async move { let repo = @@ -97,7 +97,7 @@ pub fn router() -> Router { ), ) .route( - "/stack/:id/:option", + "/stack/{id}/{option}", post( |Path(IdAndOption:: { id, option }), headers: HeaderMap, body: String| async move { let stack = @@ -123,7 +123,7 @@ pub fn router() -> Router { ), ) .route( - "/sync/:id/:option", + "/sync/{id}/{option}", post( |Path(IdAndOption:: { id, option }), headers: HeaderMap, body: String| async move { let sync = @@ -149,7 +149,7 @@ pub fn router() -> Router { ), ) .route( - "/procedure/:id/:branch", + "/procedure/{id}/{branch}", post( |Path(IdAndBranch { id, branch }), headers: HeaderMap, body: String| async move { let procedure = @@ -175,7 +175,7 @@ pub fn router() -> Router { ), ) .route( - "/action/:id/:branch", + "/action/{id}/{branch}", post( |Path(IdAndBranch { id, branch }), headers: HeaderMap, body: String| async move { let action = diff --git a/bin/core/src/main.rs b/bin/core/src/main.rs index 608c21f4e..eda17d22f 100644 --- a/bin/core/src/main.rs +++ b/bin/core/src/main.rs @@ -40,8 +40,8 @@ async fn app() -> anyhow::Result<()> { tokio::join!( // Init db_client check to crash on db init failure state::init_db_client(), - // Init default OIDC client (defined in config / env vars / compose secret file) - auth::oidc::client::init_default_oidc_client() + // Manage OIDC client (defined in config / env vars / compose secret file) + auth::oidc::client::spawn_oidc_client_management() ); tokio::join!( // Maybe initialize first server @@ -59,14 +59,13 @@ async fn app() -> anyhow::Result<()> { resource::spawn_repo_state_refresh_loop(); resource::spawn_procedure_state_refresh_loop(); resource::spawn_action_state_refresh_loop(); - resource::spawn_resource_sync_state_refresh_loop(); helpers::prune::spawn_prune_loop(); // Setup static frontend services let frontend_path = &config.frontend_path; let frontend_index = ServeFile::new(format!("{frontend_path}/index.html")); - let serve_dir = ServeDir::new(frontend_path) + let serve_frontend = ServeDir::new(frontend_path) .not_found_service(frontend_index.clone()); let app = Router::new() @@ -78,9 +77,13 @@ async fn app() -> anyhow::Result<()> { .nest("/listener", listener::router()) .nest("/ws", ws::router()) .nest("/client", ts_client::router()) - .nest_service("/", serve_dir) - .fallback_service(frontend_index) - .layer(cors()?) + .fallback_service(serve_frontend) + .layer( + CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any), + ) .into_make_service(); let socket_addr = @@ -101,14 +104,16 @@ async fn app() -> anyhow::Result<()> { .context("Invalid ssl cert / key")?; axum_server::bind_rustls(socket_addr, ssl_config) .serve(app) - .await? + .await + .context("failed to start https server") } else { info!("🔓 Core SSL Disabled"); info!("Komodo Core starting on http://{socket_addr}"); - axum_server::bind(socket_addr).serve(app).await? + axum_server::bind(socket_addr) + .serve(app) + .await + .context("failed to start http server") } - - Ok(()) } #[tokio::main] @@ -116,21 +121,8 @@ async fn main() -> anyhow::Result<()> { let mut term_signal = tokio::signal::unix::signal( tokio::signal::unix::SignalKind::terminate(), )?; - - let app = tokio::spawn(app()); - tokio::select! { - res = app => return res?, - _ = term_signal.recv() => {}, + res = tokio::spawn(app()) => res?, + _ = term_signal.recv() => Ok(()), } - - Ok(()) -} - -fn cors() -> anyhow::Result { - let cors = CorsLayer::new() - .allow_origin(Any) - .allow_methods(Any) - .allow_headers(Any); - Ok(cors) } diff --git a/bin/core/src/monitor/alert/deployment.rs b/bin/core/src/monitor/alert/deployment.rs index bf296a01d..9b153628d 100644 --- a/bin/core/src/monitor/alert/deployment.rs +++ b/bin/core/src/monitor/alert/deployment.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use komodo_client::entities::{ + ResourceTarget, alert::{Alert, AlertData, SeverityLevel}, deployment::{Deployment, DeploymentState}, - ResourceTarget, }; use crate::{ diff --git a/bin/core/src/monitor/alert/mod.rs b/bin/core/src/monitor/alert/mod.rs index d370becd2..8a851d6e2 100644 --- a/bin/core/src/monitor/alert/mod.rs +++ b/bin/core/src/monitor/alert/mod.rs @@ -30,8 +30,8 @@ pub async fn check_alerts(ts: i64) { } #[instrument(level = "debug")] -async fn get_all_servers_map( -) -> anyhow::Result<(HashMap, HashMap)> +async fn get_all_servers_map() +-> anyhow::Result<(HashMap, HashMap)> { let servers = resource::list_full_for_user::( ResourceQuery::default(), diff --git a/bin/core/src/monitor/alert/server.rs b/bin/core/src/monitor/alert/server.rs index 7448932eb..2e9305ae6 100644 --- a/bin/core/src/monitor/alert/server.rs +++ b/bin/core/src/monitor/alert/server.rs @@ -3,10 +3,10 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use anyhow::Context; use derive_variants::ExtractVariant; use komodo_client::entities::{ + ResourceTarget, alert::{Alert, AlertData, AlertDataVariant, SeverityLevel}, komodo_timestamp, optional_string, server::{Server, ServerState}, - ResourceTarget, }; use mongo_indexed::Indexed; use mungos::{ @@ -85,7 +85,9 @@ pub async fn alert_servers( id, name, region, .. } => (id, name, region), data => { - error!("got incorrect alert data in ServerStatus handler. got {data:?}"); + error!( + "got incorrect alert data in ServerStatus handler. got {data:?}" + ); continue; } }; @@ -530,8 +532,8 @@ async fn resolve_alerts(alerts: &[(Alert, SendAlerts)]) { } #[instrument(level = "debug")] -async fn get_open_alerts( -) -> anyhow::Result<(OpenAlertMap, OpenDiskAlertMap)> { +async fn get_open_alerts() +-> anyhow::Result<(OpenAlertMap, OpenDiskAlertMap)> { let alerts = find_collect( &db_client().alerts, doc! { "resolved": false }, diff --git a/bin/core/src/monitor/alert/stack.rs b/bin/core/src/monitor/alert/stack.rs index 6fa488e4f..98c73ee68 100644 --- a/bin/core/src/monitor/alert/stack.rs +++ b/bin/core/src/monitor/alert/stack.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use komodo_client::entities::{ + ResourceTarget, alert::{Alert, AlertData, SeverityLevel}, stack::{Stack, StackState}, - ResourceTarget, }; use crate::{ diff --git a/bin/core/src/monitor/lists.rs b/bin/core/src/monitor/lists.rs index 7eace0ec0..ffd5fe42f 100644 --- a/bin/core/src/monitor/lists.rs +++ b/bin/core/src/monitor/lists.rs @@ -6,8 +6,8 @@ use komodo_client::entities::{ stack::ComposeProject, }; use periphery_client::{ - api::{GetDockerLists, GetDockerListsResponse}, PeripheryClient, + api::{GetDockerLists, GetDockerListsResponse}, }; pub async fn get_docker_lists( diff --git a/bin/core/src/monitor/mod.rs b/bin/core/src/monitor/mod.rs index 00fcb139e..77c1966b4 100644 --- a/bin/core/src/monitor/mod.rs +++ b/bin/core/src/monitor/mod.rs @@ -7,7 +7,7 @@ use komodo_client::entities::{ container::ContainerListItem, image::ImageListItem, network::NetworkListItem, volume::VolumeListItem, }, - komodo_timestamp, + komodo_timestamp, optional_string, server::{Server, ServerHealth, ServerState}, stack::{ComposeProject, StackService, StackState}, stats::SystemStats, @@ -264,6 +264,7 @@ pub async fn update_cache_for_server(server: &Server) { let (latest_hash, latest_message) = periphery .request(GetLatestCommit { name: repo.name.clone(), + path: optional_string(&repo.config.path), }) .await .map(|r| (r.hash, r.message)) diff --git a/bin/core/src/monitor/record.rs b/bin/core/src/monitor/record.rs index 213f92b05..d4660e4eb 100644 --- a/bin/core/src/monitor/record.rs +++ b/bin/core/src/monitor/record.rs @@ -1,5 +1,5 @@ use komodo_client::entities::stats::{ - sum_disk_usage, SystemStatsRecord, TotalDiskUsage, + SystemStatsRecord, TotalDiskUsage, sum_disk_usage, }; use crate::state::{db_client, server_status_cache}; @@ -28,7 +28,6 @@ pub async fn record_server_stats(ts: i64) { disks: stats.disks.clone(), network_ingress_bytes: stats.network_ingress_bytes, network_egress_bytes: stats.network_egress_bytes, - network_usage_interface: stats.network_usage_interface.clone(), }) }) .collect::>(); diff --git a/bin/core/src/monitor/resources.rs b/bin/core/src/monitor/resources.rs index f2b3ea065..bd8e5c63f 100644 --- a/bin/core/src/monitor/resources.rs +++ b/bin/core/src/monitor/resources.rs @@ -7,6 +7,7 @@ use anyhow::Context; use komodo_client::{ api::execute::{Deploy, DeployStack}, entities::{ + ResourceTarget, alert::{Alert, AlertData, SeverityLevel}, build::Build, deployment::{Deployment, DeploymentImage, DeploymentState}, @@ -17,7 +18,6 @@ use komodo_client::{ komodo_timestamp, stack::{Stack, StackService, StackServiceNames, StackState}, user::auto_redeploy_user, - ResourceTarget, }, }; @@ -222,8 +222,8 @@ pub async fn update_deployment_cache( } /// (StackId, Service) -fn stack_alert_sent_cache( -) -> &'static Mutex> { +fn stack_alert_sent_cache() +-> &'static Mutex> { static CACHE: OnceLock>> = OnceLock::new(); CACHE.get_or_init(Default::default) @@ -322,8 +322,8 @@ pub async fn update_stack_cache( } }).collect::>(); - let mut update_available = false; let mut images_with_update = Vec::new(); + let mut services_to_update = Vec::new(); for service in services_with_containers.iter() { if service.update_available { @@ -336,7 +336,7 @@ pub async fn update_stack_cache( .map(|c| c.state == ContainerStateStatusEnum::Running) .unwrap_or_default() { - update_available = true + services_to_update.push(service.service.clone()); } } } @@ -346,7 +346,7 @@ pub async fn update_stack_cache( &services, containers, ); - if update_available + if !services_to_update.is_empty() && stack.config.auto_update && state == StackState::Running && !action_states() @@ -358,11 +358,16 @@ pub async fn update_stack_cache( { let id = stack.id.clone(); let server_name = server_name.clone(); + let services = if stack.config.auto_update_all_services { + Vec::new() + } else { + services_to_update + }; tokio::spawn(async move { match execute::inner_handler( ExecuteRequest::DeployStack(DeployStack { stack: stack.name.clone(), - service: None, + services, stop_time: None, }), auto_redeploy_user().to_owned(), @@ -395,7 +400,7 @@ pub async fn update_stack_cache( send_alerts(&[alert]).await; } Err(e) => { - warn!("Failed auto update Stack {} | {e:#}", stack.name) + warn!("Failed auto update Stack {} | {e:#}", stack.name,) } } }); diff --git a/bin/core/src/resource/action.rs b/bin/core/src/resource/action.rs index 72688a20a..bd9691e91 100644 --- a/bin/core/src/resource/action.rs +++ b/bin/core/src/resource/action.rs @@ -2,6 +2,7 @@ use std::time::Duration; use anyhow::Context; use komodo_client::entities::{ + Operation, ResourceTargetVariant, action::{ Action, ActionConfig, ActionConfigDiff, ActionInfo, ActionListItem, ActionListItemInfo, ActionQuerySpecifics, @@ -10,11 +11,10 @@ use komodo_client::entities::{ resource::Resource, update::Update, user::User, - Operation, ResourceTargetVariant, }; use mungos::{ find::find_collect, - mongodb::{bson::doc, options::FindOneOptions, Collection}, + mongodb::{Collection, bson::doc, options::FindOneOptions}, }; use crate::state::{action_state_cache, action_states, db_client}; diff --git a/bin/core/src/resource/alerter.rs b/bin/core/src/resource/alerter.rs index c884eb2be..736be84db 100644 --- a/bin/core/src/resource/alerter.rs +++ b/bin/core/src/resource/alerter.rs @@ -1,5 +1,6 @@ use derive_variants::ExtractVariant; use komodo_client::entities::{ + Operation, ResourceTargetVariant, alerter::{ Alerter, AlerterConfig, AlerterConfigDiff, AlerterListItem, AlerterListItemInfo, AlerterQuerySpecifics, PartialAlerterConfig, @@ -7,7 +8,6 @@ use komodo_client::entities::{ resource::Resource, update::Update, user::User, - Operation, ResourceTargetVariant, }; use mungos::mongodb::Collection; diff --git a/bin/core/src/resource/build.rs b/bin/core/src/resource/build.rs index d6c39dc14..1e778e9e0 100644 --- a/bin/core/src/resource/build.rs +++ b/bin/core/src/resource/build.rs @@ -2,6 +2,7 @@ use std::time::Duration; use anyhow::Context; use komodo_client::entities::{ + Operation, ResourceTargetVariant, build::{ Build, BuildConfig, BuildConfigDiff, BuildInfo, BuildListItem, BuildListItemInfo, BuildQuerySpecifics, BuildState, @@ -13,11 +14,10 @@ use komodo_client::entities::{ resource::Resource, update::Update, user::User, - Operation, ResourceTargetVariant, }; use mungos::{ find::find_collect, - mongodb::{bson::doc, options::FindOptions, Collection}, + mongodb::{Collection, bson::doc, options::FindOptions}, }; use crate::{ diff --git a/bin/core/src/resource/builder.rs b/bin/core/src/resource/builder.rs index 0494bd37a..c3135dd89 100644 --- a/bin/core/src/resource/builder.rs +++ b/bin/core/src/resource/builder.rs @@ -1,5 +1,6 @@ use anyhow::Context; use komodo_client::entities::{ + MergePartial, Operation, ResourceTargetVariant, builder::{ Builder, BuilderConfig, BuilderConfigDiff, BuilderConfigVariant, BuilderListItem, BuilderListItemInfo, BuilderQuerySpecifics, @@ -10,11 +11,10 @@ use komodo_client::entities::{ server::Server, update::Update, user::User, - MergePartial, Operation, ResourceTargetVariant, }; use mungos::mongodb::{ - bson::{doc, to_document, Document}, Collection, + bson::{Document, doc, to_document}, }; use crate::state::db_client; diff --git a/bin/core/src/resource/deployment.rs b/bin/core/src/resource/deployment.rs index 19c92ce40..7884cdec9 100644 --- a/bin/core/src/resource/deployment.rs +++ b/bin/core/src/resource/deployment.rs @@ -1,12 +1,13 @@ use anyhow::Context; use formatting::format_serror; use komodo_client::entities::{ + Operation, ResourceTargetVariant, build::Build, deployment::{ - conversions_from_str, Deployment, DeploymentConfig, - DeploymentConfigDiff, DeploymentImage, DeploymentListItem, - DeploymentListItemInfo, DeploymentQuerySpecifics, - DeploymentState, PartialDeploymentConfig, + Deployment, DeploymentConfig, DeploymentConfigDiff, + DeploymentImage, DeploymentListItem, DeploymentListItemInfo, + DeploymentQuerySpecifics, DeploymentState, + PartialDeploymentConfig, conversions_from_str, }, environment_vars_from_str, permission::PermissionLevel, @@ -14,7 +15,6 @@ use komodo_client::entities::{ server::Server, update::Update, user::User, - Operation, ResourceTargetVariant, }; use mungos::mongodb::Collection; use periphery_client::api::container::RemoveContainer; diff --git a/bin/core/src/resource/mod.rs b/bin/core/src/resource/mod.rs index 289b1a786..33807f81f 100644 --- a/bin/core/src/resource/mod.rs +++ b/bin/core/src/resource/mod.rs @@ -3,20 +3,20 @@ use std::{ str::FromStr, }; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use formatting::format_serror; -use futures::{future::join_all, FutureExt}; +use futures::{FutureExt, future::join_all}; use komodo_client::{ api::{read::ExportResourcesToToml, write::CreateTag}, entities::{ + Operation, ResourceTarget, ResourceTargetVariant, komodo_timestamp, permission::PermissionLevel, resource::{AddFilters, Resource, ResourceQuery}, tag::Tag, to_komodo_name, update::Update, - user::{system_user, User}, - Operation, ResourceTarget, ResourceTargetVariant, + user::{User, system_user}, }, parsers::parse_string_list, }; @@ -24,16 +24,17 @@ use mungos::{ by_id::{delete_one_by_id, update_one_by_id}, find::find_collect, mongodb::{ - bson::{doc, oid::ObjectId, to_document, Document}, - options::FindOptions, Collection, + bson::{Document, doc, oid::ObjectId, to_document}, + options::FindOptions, }, }; use partial_derive2::{Diff, FieldDiff, MaybeNone, PartialDiff}; use resolver_api::Resolve; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; use crate::{ + api::{read::ReadArgs, write::WriteArgs}, config::core_config, helpers::{ create_permission, flatten_document, @@ -43,7 +44,7 @@ use crate::{ }, update::{add_update, make_update}, }, - state::{db_client, State}, + state::db_client, }; mod action; @@ -72,10 +73,6 @@ pub use refresh::spawn_resource_refresh_loop; pub use repo::{ refresh_repo_state_cache, spawn_repo_state_refresh_loop, }; -pub use sync::{ - refresh_resource_sync_state_cache, - spawn_resource_sync_state_refresh_loop, -}; /// Implement on each Komodo resource for common methods pub trait KomodoResource { @@ -783,20 +780,17 @@ pub async fn update_description( pub async fn update_tags( id_or_name: &str, tags: Vec, - user: User, + args: &WriteArgs, ) -> anyhow::Result<()> { let futures = tags.iter().map(|tag| async { match get_tag(tag).await { Ok(tag) => Ok(tag.id), - Err(_) => State - .resolve( - CreateTag { - name: tag.to_string(), - }, - user.clone(), - ) - .await - .map(|tag| tag.id), + Err(_) => CreateTag { + name: tag.to_string(), + } + .resolve(args) + .await + .map(|tag| tag.id), } }); let tags = join_all(futures) @@ -884,11 +878,11 @@ pub async fn rename( pub async fn delete( id_or_name: &str, - user: &User, + args: &WriteArgs, ) -> anyhow::Result> { let resource = get_check_permissions::( id_or_name, - user, + &args.user, PermissionLevel::Write, ) .await?; @@ -898,19 +892,19 @@ pub async fn delete( } let target = resource_target::(resource.id.clone()); - let toml = State - .resolve( - ExportResourcesToToml { - targets: vec![target.clone()], - ..Default::default() - }, - user.clone(), - ) - .await? - .toml; + let toml = ExportResourcesToToml { + targets: vec![target.clone()], + ..Default::default() + } + .resolve(&ReadArgs { + user: args.user.clone(), + }) + .await + .map_err(|e| e.error)? + .toml; let mut update = - make_update(target.clone(), T::delete_operation(), user); + make_update(target.clone(), T::delete_operation(), &args.user); T::pre_delete(&resource, &mut update).await?; @@ -977,7 +971,9 @@ where }) .await { - warn!("failed to delete_many permissions matching target {target:?} | {e:#}"); + warn!( + "failed to delete_many permissions matching target {target:?} | {e:#}" + ); } } diff --git a/bin/core/src/resource/procedure.rs b/bin/core/src/resource/procedure.rs index 4b95da075..5a70dbd5f 100644 --- a/bin/core/src/resource/procedure.rs +++ b/bin/core/src/resource/procedure.rs @@ -1,10 +1,12 @@ use std::time::Duration; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ api::execute::Execution, entities::{ + Operation, ResourceTargetVariant, action::Action, + alerter::Alerter, build::Build, deployment::Deployment, permission::PermissionLevel, @@ -20,12 +22,11 @@ use komodo_client::{ sync::ResourceSync, update::Update, user::User, - Operation, ResourceTargetVariant, }, }; use mungos::{ find::find_collect, - mongodb::{bson::doc, options::FindOneOptions, Collection}, + mongodb::{Collection, bson::doc, options::FindOneOptions}, }; use crate::{ @@ -172,7 +173,7 @@ async fn validate_config( Some(id) if procedure.id == id => { return Err(anyhow!( "Cannot have self-referential procedure" - )) + )); } _ => {} } @@ -687,6 +688,15 @@ async fn validate_config( )); } } + Execution::TestAlerter(params) => { + let alerter = super::get_check_permissions::( + ¶ms.alerter, + user, + PermissionLevel::Execute, + ) + .await?; + params.alerter = alerter.id; + } Execution::Sleep(_) => {} } } diff --git a/bin/core/src/resource/refresh.rs b/bin/core/src/resource/refresh.rs index 85da9807d..ef4421128 100644 --- a/bin/core/src/resource/refresh.rs +++ b/bin/core/src/resource/refresh.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use async_timing_util::{get_timelength_in_ms, Timelength}; +use async_timing_util::{Timelength, get_timelength_in_ms}; use komodo_client::{ api::write::{ RefreshBuildCache, RefreshRepoCache, RefreshResourceSyncPending, @@ -12,9 +12,9 @@ use mungos::find::find_collect; use resolver_api::Resolve; use crate::{ - api::execute::pull_deployment_inner, + api::{execute::pull_deployment_inner, write::WriteArgs}, config::core_config, - state::{db_client, State}, + state::db_client, }; pub fn spawn_resource_refresh_loop() { @@ -53,14 +53,13 @@ async fn refresh_stacks() { return; }; for stack in stacks { - State + RefreshStackCache { stack: stack.id } .resolve( - RefreshStackCache { stack: stack.id }, - stack_user().clone(), + &WriteArgs { user: stack_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh Stack cache in refresh task | Stack: {} | {e:#}", stack.name) + warn!("Failed to refresh Stack cache in refresh task | Stack: {} | {:#}", stack.name, e.error) }) .ok(); } @@ -96,7 +95,9 @@ async fn refresh_deployments() { if let Err(e) = pull_deployment_inner(deployment, server).await { - warn!("Failed to pull latest image for Deployment {name} | {e:#}"); + warn!( + "Failed to pull latest image for Deployment {name} | {e:#}" + ); } } } @@ -115,14 +116,13 @@ async fn refresh_builds() { return; }; for build in builds { - State + RefreshBuildCache { build: build.id } .resolve( - RefreshBuildCache { build: build.id }, - build_user().clone(), + &WriteArgs { user: build_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh Build cache in refresh task | Build: {} | {e:#}", build.name) + warn!("Failed to refresh Build cache in refresh task | Build: {} | {:#}", build.name, e.error) }) .ok(); } @@ -140,43 +140,43 @@ async fn refresh_repos() { return; }; for repo in repos { - State + RefreshRepoCache { repo: repo.id } .resolve( - RefreshRepoCache { repo: repo.id }, - repo_user().clone(), + &WriteArgs { user: repo_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh Repo cache in refresh task | Repo: {} | {e:#}", repo.name) + warn!("Failed to refresh Repo cache in refresh task | Repo: {} | {:#}", repo.name, e.error) }) .ok(); } } async fn refresh_syncs() { - let Ok(syncs) = - find_collect(&db_client().resource_syncs, None, None) - .await - .inspect_err(|e| { - warn!( + let Ok(syncs) = find_collect( + &db_client().resource_syncs, + None, + None, + ) + .await + .inspect_err(|e| { + warn!( "failed to get resource syncs from db in refresh task | {e:#}" ) - }) - else { + }) else { return; }; for sync in syncs { if sync.config.repo.is_empty() { continue; } - State + RefreshResourceSyncPending { sync: sync.id } .resolve( - RefreshResourceSyncPending { sync: sync.id }, - sync_user().clone(), + &WriteArgs { user: sync_user().clone() }, ) .await .inspect_err(|e| { - warn!("Failed to refresh ResourceSync in refresh task | Sync: {} | {e:#}", sync.name) + warn!("Failed to refresh ResourceSync in refresh task | Sync: {} | {:#}", sync.name, e.error) }) .ok(); } diff --git a/bin/core/src/resource/repo.rs b/bin/core/src/resource/repo.rs index 69b910eb2..4c589f5ac 100644 --- a/bin/core/src/resource/repo.rs +++ b/bin/core/src/resource/repo.rs @@ -3,6 +3,7 @@ use std::time::Duration; use anyhow::Context; use formatting::format_serror; use komodo_client::entities::{ + Operation, ResourceTargetVariant, builder::Builder, permission::PermissionLevel, repo::{ @@ -14,11 +15,10 @@ use komodo_client::entities::{ to_komodo_name, update::Update, user::User, - Operation, ResourceTargetVariant, }; use mungos::{ find::find_collect, - mongodb::{bson::doc, options::FindOneOptions, Collection}, + mongodb::{Collection, bson::doc, options::FindOneOptions}, }; use periphery_client::api::git::DeleteRepo; diff --git a/bin/core/src/resource/server.rs b/bin/core/src/resource/server.rs index 68170bd81..457ce077a 100644 --- a/bin/core/src/resource/server.rs +++ b/bin/core/src/resource/server.rs @@ -1,6 +1,6 @@ use anyhow::Context; use komodo_client::entities::{ - komodo_timestamp, + Operation, ResourceTargetVariant, komodo_timestamp, resource::Resource, server::{ PartialServerConfig, Server, ServerConfig, ServerConfigDiff, @@ -8,9 +8,8 @@ use komodo_client::entities::{ }, update::Update, user::User, - Operation, ResourceTargetVariant, }; -use mungos::mongodb::{bson::doc, Collection}; +use mungos::mongodb::{Collection, bson::doc}; use crate::{ config::core_config, diff --git a/bin/core/src/resource/server_template.rs b/bin/core/src/resource/server_template.rs index c54bfef69..bb98ee418 100644 --- a/bin/core/src/resource/server_template.rs +++ b/bin/core/src/resource/server_template.rs @@ -1,4 +1,5 @@ use komodo_client::entities::{ + MergePartial, Operation, ResourceTargetVariant, resource::Resource, server_template::{ PartialServerTemplateConfig, ServerTemplate, @@ -8,11 +9,10 @@ use komodo_client::entities::{ }, update::Update, user::User, - MergePartial, Operation, ResourceTargetVariant, }; use mungos::mongodb::{ - bson::{to_document, Document}, Collection, + bson::{Document, to_document}, }; use crate::state::db_client; diff --git a/bin/core/src/resource/stack.rs b/bin/core/src/resource/stack.rs index 0de89ad9e..a54c18f3f 100644 --- a/bin/core/src/resource/stack.rs +++ b/bin/core/src/resource/stack.rs @@ -3,6 +3,7 @@ use formatting::format_serror; use komodo_client::{ api::write::RefreshStackCache, entities::{ + Operation, ResourceTargetVariant, permission::PermissionLevel, resource::Resource, server::Server, @@ -12,8 +13,7 @@ use komodo_client::{ StackQuerySpecifics, StackServiceWithUpdate, StackState, }, update::Update, - user::{stack_user, User}, - Operation, ResourceTargetVariant, + user::{User, stack_user}, }, }; use mungos::mongodb::Collection; @@ -21,12 +21,12 @@ use periphery_client::api::compose::ComposeExecution; use resolver_api::Resolve; use crate::{ + api::write::WriteArgs, config::core_config, helpers::{periphery_client, query::get_stack_state}, monitor::update_cache_for_server, state::{ - action_states, db_client, server_status_cache, - stack_status_cache, State, + action_states, db_client, server_status_cache, stack_status_cache, }, }; @@ -152,18 +152,17 @@ impl super::KomodoResource for Stack { created: &Resource, update: &mut Update, ) -> anyhow::Result<()> { - if let Err(e) = State - .resolve( - RefreshStackCache { - stack: created.name.clone(), - }, - stack_user().to_owned(), - ) - .await + if let Err(e) = (RefreshStackCache { + stack: created.name.clone(), + }) + .resolve(&WriteArgs { + user: stack_user().to_owned(), + }) + .await { update.push_error_log( "Refresh stack cache", - format_serror(&e.context("The stack cache has failed to refresh. This is likely due to a misconfiguration of the Stack").into()) + format_serror(&e.error.context("The stack cache has failed to refresh. This is likely due to a misconfiguration of the Stack").into()) ); }; if created.config.server_id.is_empty() { diff --git a/bin/core/src/resource/sync.rs b/bin/core/src/resource/sync.rs index b7b856647..b0edfb14b 100644 --- a/bin/core/src/resource/sync.rs +++ b/bin/core/src/resource/sync.rs @@ -1,11 +1,9 @@ -use std::time::Duration; - use anyhow::Context; use formatting::format_serror; use komodo_client::{ api::write::RefreshResourceSyncPending, entities::{ - komodo_timestamp, + Operation, ResourceTargetVariant, komodo_timestamp, resource::Resource, sync::{ PartialResourceSyncConfig, ResourceSync, ResourceSyncConfig, @@ -14,19 +12,16 @@ use komodo_client::{ ResourceSyncState, }, update::Update, - user::{sync_user, User}, - Operation, ResourceTargetVariant, + user::{User, sync_user}, }, }; use mongo_indexed::doc; -use mungos::{ - find::find_collect, - mongodb::{options::FindOneOptions, Collection}, -}; +use mungos::mongodb::Collection; use resolver_api::Resolve; -use crate::state::{ - action_states, db_client, resource_sync_state_cache, State, +use crate::{ + api::write::WriteArgs, + state::{action_states, db_client}, }; impl super::KomodoResource for ResourceSync { @@ -103,21 +98,19 @@ impl super::KomodoResource for ResourceSync { created: &Resource, update: &mut Update, ) -> anyhow::Result<()> { - if let Err(e) = State - .resolve( - RefreshResourceSyncPending { - sync: created.id.clone(), - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (RefreshResourceSyncPending { + sync: created.id.clone(), + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { update.push_error_log( "Refresh sync pending", - format_serror(&e.context("The sync pending cache has failed to refresh. This is likely due to a misconfiguration of the sync").into()) + format_serror(&e.error.context("The sync pending cache has failed to refresh. This is likely due to a misconfiguration of the sync").into()) ); }; - refresh_resource_sync_state_cache().await; Ok(()) } @@ -180,35 +173,6 @@ impl super::KomodoResource for ResourceSync { } } -pub fn spawn_resource_sync_state_refresh_loop() { - tokio::spawn(async move { - loop { - refresh_resource_sync_state_cache().await; - tokio::time::sleep(Duration::from_secs(60)).await; - } - }); -} - -pub async fn refresh_resource_sync_state_cache() { - let _ = async { - let resource_syncs = - find_collect(&db_client().resource_syncs, None, None) - .await - .context("failed to get resource_syncs from db")?; - let cache = resource_sync_state_cache(); - for resource_sync in resource_syncs { - let state = - get_resource_sync_state_from_db(&resource_sync.id).await; - cache.insert(resource_sync.id, state).await; - } - anyhow::Ok(()) - } - .await - .inspect_err(|e| { - error!("failed to refresh resource_sync state cache | {e:#}") - }); -} - async fn get_resource_sync_state( id: &String, data: &ResourceSyncInfo, @@ -232,57 +196,15 @@ async fn get_resource_sync_state( { return state; } - if data.pending_error.is_some() { - return ResourceSyncState::Failed; - } - if !data.resource_updates.is_empty() + if data.pending_error.is_some() || !data.remote_errors.is_empty() { + ResourceSyncState::Failed + } else if !data.resource_updates.is_empty() || !data.variable_updates.is_empty() || !data.user_group_updates.is_empty() || data.pending_deploy.to_deploy > 0 { - return ResourceSyncState::Pending; + ResourceSyncState::Pending + } else { + ResourceSyncState::Ok } - resource_sync_state_cache() - .get(id) - .await - .unwrap_or_default() -} - -async fn get_resource_sync_state_from_db( - id: &str, -) -> ResourceSyncState { - async { - let state = db_client() - .updates - .find_one(doc! { - "target.type": "ResourceSync", - "target.id": id, - "$or": [ - { "operation": "RunSync" }, - { "operation": "CommitSync" }, - ], - }) - .with_options( - FindOneOptions::builder() - .sort(doc! { "start_ts": -1 }) - .build(), - ) - .await? - .map(|u| { - if u.success { - ResourceSyncState::Ok - } else { - ResourceSyncState::Failed - } - }) - .unwrap_or(ResourceSyncState::Ok); - anyhow::Ok(state) - } - .await - .inspect_err(|e| { - warn!( - "failed to get resource sync state from db for {id} | {e:#}" - ) - }) - .unwrap_or(ResourceSyncState::Unknown) } diff --git a/bin/core/src/stack/execute.rs b/bin/core/src/stack/execute.rs index 6be90327e..2b16e3c09 100644 --- a/bin/core/src/stack/execute.rs +++ b/bin/core/src/stack/execute.rs @@ -7,7 +7,7 @@ use komodo_client::{ user::User, }, }; -use periphery_client::{api::compose::*, PeripheryClient}; +use periphery_client::{PeripheryClient, api::compose::*}; use crate::{ helpers::{periphery_client, update::update_update}, @@ -23,14 +23,14 @@ pub trait ExecuteCompose { async fn execute( periphery: PeripheryClient, stack: Stack, - service: Option, + services: Vec, extras: Self::Extras, ) -> anyhow::Result; } pub async fn execute_compose( stack: &str, - service: Option, + services: Vec, user: &User, set_in_progress: impl Fn(&mut StackActionState), mut update: Update, @@ -53,16 +53,19 @@ pub async fn execute_compose( let periphery = periphery_client(&server)?; - if let Some(service) = &service { + if !services.is_empty() { update.logs.push(Log::simple( - &format!("Service: {service}"), - format!("Execution requested for Stack service {service}"), + "Service/s", + format!( + "Execution requested for Stack service/s {}", + services.join(", ") + ), )) } update .logs - .push(T::execute(periphery, stack, service, extras).await?); + .push(T::execute(periphery, stack, services, extras).await?); // Ensure cached stack state up to date by updating server cache update_cache_for_server(&server).await; @@ -73,21 +76,27 @@ pub async fn execute_compose( Ok(update) } +fn service_args(services: &[String]) -> String { + if !services.is_empty() { + format!(" {}", services.join(" ")) + } else { + String::new() + } +} + impl ExecuteCompose for StartStack { type Extras = (); async fn execute( periphery: PeripheryClient, stack: Stack, - service: Option, + services: Vec, _: Self::Extras, ) -> anyhow::Result { - let service = service - .map(|service| format!(" {service}")) - .unwrap_or_default(); + let service_args = service_args(&services); periphery .request(ComposeExecution { project: stack.project_name(false), - command: format!("start{service}"), + command: format!("start{service_args}"), }) .await } @@ -98,16 +107,14 @@ impl ExecuteCompose for RestartStack { async fn execute( periphery: PeripheryClient, stack: Stack, - service: Option, + services: Vec, _: Self::Extras, ) -> anyhow::Result { - let service = service - .map(|service| format!(" {service}")) - .unwrap_or_default(); + let service_args = service_args(&services); periphery .request(ComposeExecution { project: stack.project_name(false), - command: format!("restart{service}"), + command: format!("restart{service_args}"), }) .await } @@ -118,16 +125,14 @@ impl ExecuteCompose for PauseStack { async fn execute( periphery: PeripheryClient, stack: Stack, - service: Option, + services: Vec, _: Self::Extras, ) -> anyhow::Result { - let service = service - .map(|service| format!(" {service}")) - .unwrap_or_default(); + let service_args = service_args(&services); periphery .request(ComposeExecution { project: stack.project_name(false), - command: format!("pause{service}"), + command: format!("pause{service_args}"), }) .await } @@ -138,16 +143,14 @@ impl ExecuteCompose for UnpauseStack { async fn execute( periphery: PeripheryClient, stack: Stack, - service: Option, + services: Vec, _: Self::Extras, ) -> anyhow::Result { - let service = service - .map(|service| format!(" {service}")) - .unwrap_or_default(); + let service_args = service_args(&services); periphery .request(ComposeExecution { project: stack.project_name(false), - command: format!("unpause{service}"), + command: format!("unpause{service_args}"), }) .await } @@ -158,17 +161,15 @@ impl ExecuteCompose for StopStack { async fn execute( periphery: PeripheryClient, stack: Stack, - service: Option, + services: Vec, timeout: Self::Extras, ) -> anyhow::Result { - let service = service - .map(|service| format!(" {service}")) - .unwrap_or_default(); + let service_args = service_args(&services); let maybe_timeout = maybe_timeout(timeout); periphery .request(ComposeExecution { project: stack.project_name(false), - command: format!("stop{maybe_timeout}{service}"), + command: format!("stop{maybe_timeout}{service_args}"), }) .await } @@ -179,12 +180,10 @@ impl ExecuteCompose for DestroyStack { async fn execute( periphery: PeripheryClient, stack: Stack, - service: Option, + services: Vec, (timeout, remove_orphans): Self::Extras, ) -> anyhow::Result { - let service = service - .map(|service| format!(" {service}")) - .unwrap_or_default(); + let service_args = service_args(&services); let maybe_timeout = maybe_timeout(timeout); let maybe_remove_orphans = if remove_orphans { " --remove-orphans" @@ -195,7 +194,7 @@ impl ExecuteCompose for DestroyStack { .request(ComposeExecution { project: stack.project_name(false), command: format!( - "down{maybe_timeout}{maybe_remove_orphans}{service}" + "down{maybe_timeout}{maybe_remove_orphans}{service_args}" ), }) .await diff --git a/bin/core/src/stack/mod.rs b/bin/core/src/stack/mod.rs index 71d526f0a..c7c73bf0d 100644 --- a/bin/core/src/stack/mod.rs +++ b/bin/core/src/stack/mod.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::entities::{ permission::PermissionLevel, server::{Server, ServerState}, diff --git a/bin/core/src/stack/remote.rs b/bin/core/src/stack/remote.rs index 3500c0832..593b93468 100644 --- a/bin/core/src/stack/remote.rs +++ b/bin/core/src/stack/remote.rs @@ -3,7 +3,7 @@ use std::{fs, path::PathBuf}; use anyhow::Context; use formatting::format_serror; use komodo_client::entities::{ - stack::Stack, update::Log, CloneArgs, FileContents, + CloneArgs, FileContents, stack::Stack, update::Log, }; use crate::{config::core_config, helpers::git_token}; diff --git a/bin/core/src/state.rs b/bin/core/src/state.rs index df9eefd19..304abe076 100644 --- a/bin/core/src/state.rs +++ b/bin/core/src/state.rs @@ -12,7 +12,6 @@ use komodo_client::entities::{ procedure::ProcedureState, repo::RepoState, stack::StackState, - sync::ResourceSyncState, }; use octorust::auth::{ Credentials, InstallationTokenGenerator, JWTCredentials, @@ -29,8 +28,6 @@ use crate::{ }, }; -pub struct State; - static DB_CLIENT: OnceLock = OnceLock::new(); pub fn db_client() -> &'static DbClient { @@ -58,8 +55,8 @@ pub fn jwt_client() -> &'static JwtClient { }) } -pub fn github_client( -) -> Option<&'static HashMap> { +pub fn github_client() +-> Option<&'static HashMap> { static GITHUB_CLIENT: OnceLock< Option>, > = OnceLock::new(); @@ -199,12 +196,3 @@ pub fn action_state_cache() -> &'static ActionStateCache { OnceLock::new(); ACTION_STATE_CACHE.get_or_init(Default::default) } - -pub type ResourceSyncStateCache = Cache; - -pub fn resource_sync_state_cache() -> &'static ResourceSyncStateCache -{ - static RESOURCE_SYNC_STATE_CACHE: OnceLock = - OnceLock::new(); - RESOURCE_SYNC_STATE_CACHE.get_or_init(Default::default) -} diff --git a/bin/core/src/sync/deploy.rs b/bin/core/src/sync/deploy.rs index 29fbf5422..9fa8cd16c 100644 --- a/bin/core/src/sync/deploy.rs +++ b/bin/core/src/sync/deploy.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, time::Duration}; -use anyhow::{anyhow, Context}; -use formatting::{bold, colored, format_serror, muted, Color}; +use anyhow::{Context, anyhow}; +use formatting::{Color, bold, colored, format_serror, muted}; use futures::future::join_all; use komodo_client::{ api::{ @@ -9,6 +9,7 @@ use komodo_client::{ read::ListBuildVersions, }, entities::{ + FileContents, ResourceTarget, deployment::{ Deployment, DeploymentConfig, DeploymentImage, DeploymentState, PartialDeploymentConfig, @@ -18,15 +19,17 @@ use komodo_client::{ toml::ResourceToml, update::Log, user::sync_user, - FileContents, ResourceTarget, }, }; use resolver_api::Resolve; use crate::{ - api::execute::ExecuteRequest, + api::{ + execute::{ExecuteArgs, ExecuteRequest}, + read::ReadArgs, + }, helpers::update::init_execution_update, - state::{deployment_status_cache, stack_status_cache, State}, + state::{deployment_status_cache, stack_status_cache}, }; use super::{AllResourcesById, ResourceSyncTrait}; @@ -92,12 +95,17 @@ pub async fn deploy_from_cache( let ExecuteRequest::Deploy(req) = req else { unreachable!() }; - State.resolve(req, (user.to_owned(), update)).await + req + .resolve(&ExecuteArgs { + user: user.to_owned(), + update, + }) + .await } ResourceTarget::Stack(name) => { let req = ExecuteRequest::DeployStack(DeployStack { stack: name.to_string(), - service: None, + services: Vec::new(), stop_time: None, }); @@ -105,7 +113,12 @@ pub async fn deploy_from_cache( let ExecuteRequest::DeployStack(req) = req else { unreachable!() }; - State.resolve(req, (user.to_owned(), update)).await + req + .resolve(&ExecuteArgs { + user: user.to_owned(), + update, + }) + .await } _ => unreachable!(), } @@ -124,10 +137,11 @@ pub async fn deploy_from_cache( if let Err(e) = res { has_error = true; log.push_str(&format!( - "\n{}: failed to deploy {resource} '{}' in round {} | {e:#}", + "\n{}: failed to deploy {resource} '{}' in round {} | {:#}", colored("ERROR", Color::Red), bold(name), - bold(round) + bold(round), + e.error )); } else { log.push_str(&format!( @@ -426,19 +440,18 @@ fn build_cache_for_deployment<'a>( // Build version is the same, still need to check 'after' Some(_) => {} None => { - let Some(version) = State - .resolve( - ListBuildVersions { - build: build_id.to_string(), - limit: Some(1), - ..Default::default() - }, - sync_user().to_owned(), - ) - .await - .context("failed to get build versions")? - .pop() - else { + let Some(version) = (ListBuildVersions { + build: build_id.to_string(), + limit: Some(1), + ..Default::default() + }) + .resolve(&ReadArgs { + user: sync_user().to_owned(), + }) + .await + .map_err(|e| e.error) + .context("failed to get build versions")? + .pop() else { // The build has never been built. // Skip deploy regardless of 'after' (it can't be deployed) // Not sure how this would be reached on Running deployment... @@ -716,10 +729,14 @@ async fn insert_target_using_after_list<'a>( )), ); return Ok(()); - }, + } // The parent will not deploy, do nothing here. - Some(None) => {}, - None => return Err(anyhow!("Did not find parent in cache after build recursion. This should not happen.")) + Some(None) => {} + None => { + return Err(anyhow!( + "Did not find parent in cache after build recursion. This should not happen." + )); + } } } ResourceTarget::Stack(name) => { @@ -759,10 +776,14 @@ async fn insert_target_using_after_list<'a>( )), ); return Ok(()); - }, + } // The parent will not deploy, do nothing here. - Some(None) => {}, - None => return Err(anyhow!("Did not find parent in cache after build recursion. This should not happen.")) + Some(None) => {} + None => { + return Err(anyhow!( + "Did not find parent in cache after build recursion. This should not happen." + )); + } } } _ => unreachable!(), diff --git a/bin/core/src/sync/execute.rs b/bin/core/src/sync/execute.rs index 8ab030357..24927bf90 100644 --- a/bin/core/src/sync/execute.rs +++ b/bin/core/src/sync/execute.rs @@ -1,23 +1,22 @@ use std::collections::HashMap; use anyhow::Context; -use formatting::{bold, colored, muted, Color}; +use formatting::{Color, bold, colored, muted}; use komodo_client::{ api::write::{UpdateDescription, UpdateTagsOnResource}, entities::{ - tag::Tag, toml::ResourceToml, update::Log, user::sync_user, - ResourceTargetVariant, + ResourceTargetVariant, tag::Tag, toml::ResourceToml, update::Log, + user::sync_user, }, }; use mungos::find::find_collect; use partial_derive2::MaybeNone; use resolver_api::Resolve; -use crate::state::State; +use crate::api::write::WriteArgs; use super::{ - AllResourcesById, ResourceSyncTrait, ToCreate, ToDelete, ToUpdate, - ToUpdateItem, UpdatesResult, + AllResourcesById, ResourceSyncTrait, SyncDeltas, ToUpdateItem, }; /// Gets all the resources to update. For use in sync execution. @@ -31,7 +30,7 @@ pub async fn get_updates_for_execution< match_resources: Option<&[String]>, id_to_tags: &HashMap, match_tags: &[String], -) -> anyhow::Result> { +) -> anyhow::Result> { let map = find_collect(Resource::coll(), None, None) .await .context("failed to get resources from db")? @@ -64,14 +63,12 @@ pub async fn get_updates_for_execution< }) .collect::>(); - let mut to_create = ToCreate::::new(); - let mut to_update = ToUpdate::::new(); - let mut to_delete = ToDelete::new(); + let mut deltas = SyncDeltas::::default(); if delete { for resource in map.values() { if !resources.iter().any(|r| r.name == resource.name) { - to_delete.push(resource.name.clone()); + deltas.to_delete.push(resource.name.clone()); } } } @@ -120,20 +117,22 @@ pub async fn get_updates_for_execution< resource, }; - to_update.push(update); + deltas.to_update.push(update); } - None => to_create.push(resource), + None => deltas.to_create.push(resource), } } - Ok((to_create, to_update, to_delete)) + Ok(deltas) } pub trait ExecuteResourceSync: ResourceSyncTrait { async fn execute_sync_updates( - to_create: ToCreate, - to_update: ToUpdate, - to_delete: ToDelete, + SyncDeltas { + to_create, + to_update, + to_delete, + }: SyncDeltas, ) -> Option { if to_create.is_empty() && to_update.is_empty() @@ -256,8 +255,13 @@ pub trait ExecuteResourceSync: ResourceSyncTrait { } for resource in to_delete { - if let Err(e) = - crate::resource::delete::(&resource, sync_user()).await + if let Err(e) = crate::resource::delete::( + &resource, + &WriteArgs { + user: sync_user().to_owned(), + }, + ) + .await { has_error = true; log.push_str(&format!( @@ -294,22 +298,22 @@ pub async fn run_update_tags( has_error: &mut bool, ) { // Update tags - if let Err(e) = State - .resolve( - UpdateTagsOnResource { - target: Resource::resource_target(id), - tags, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateTagsOnResource { + target: Resource::resource_target(id), + tags, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to update tags on {} '{}' | {e:#}", + "\n{}: failed to update tags on {} '{}' | {:#}", colored("ERROR", Color::Red), Resource::resource_type(), bold(name), + e.error )) } else { log.push_str(&format!( @@ -329,22 +333,22 @@ pub async fn run_update_description( log: &mut String, has_error: &mut bool, ) { - if let Err(e) = State - .resolve( - UpdateDescription { - target: Resource::resource_target(id.clone()), - description, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateDescription { + target: Resource::resource_target(id.clone()), + description, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to update description on {} '{}' | {e:#}", + "\n{}: failed to update description on {} '{}' | {:#}", colored("ERROR", Color::Red), Resource::resource_type(), bold(name), + e.error )) } else { log.push_str(&format!( diff --git a/bin/core/src/sync/file.rs b/bin/core/src/sync/file.rs index 4f3691705..227c465b9 100644 --- a/bin/core/src/sync/file.rs +++ b/bin/core/src/sync/file.rs @@ -3,8 +3,8 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::{anyhow, Context}; -use formatting::{bold, colored, format_serror, muted, Color}; +use anyhow::{Context, anyhow}; +use formatting::{Color, bold, colored, format_serror, muted}; use komodo_client::entities::{ sync::SyncFileContents, toml::{ResourceToml, ResourcesToml}, @@ -143,9 +143,7 @@ fn read_resource_file( path: file_path.display().to_string(), contents: contents.clone(), }); - let more = toml::from_str::(&contents) - // the error without this comes through with multiple lines (\n) and looks bad - .map_err(|e| anyhow!("{e:#}")) + let more = super::deserialize_resources_toml(&contents) .context("failed to parse resource file contents")?; log.push('\n'); let path_for_view = @@ -167,6 +165,7 @@ fn read_resource_file( } /// Reads down into directories. +#[allow(clippy::too_many_arguments)] fn read_resources_directory( root_path: &Path, // relative to root path. diff --git a/bin/core/src/sync/mod.rs b/bin/core/src/sync/mod.rs index 0bf855555..a4c8a21a1 100644 --- a/bin/core/src/sync/mod.rs +++ b/bin/core/src/sync/mod.rs @@ -1,11 +1,21 @@ use std::{collections::HashMap, str::FromStr}; +use anyhow::anyhow; use komodo_client::entities::{ - action::Action, alerter::Alerter, build::Build, builder::Builder, - deployment::Deployment, procedure::Procedure, repo::Repo, - server::Server, server_template::ServerTemplate, stack::Stack, - sync::ResourceSync, tag::Tag, toml::ResourceToml, ResourceTarget, - ResourceTargetVariant, + ResourceTarget, ResourceTargetVariant, + action::Action, + alerter::Alerter, + build::Build, + builder::Builder, + deployment::Deployment, + procedure::Procedure, + repo::Repo, + server::Server, + server_template::ServerTemplate, + stack::Stack, + sync::ResourceSync, + tag::Tag, + toml::{ResourceToml, ResourcesToml}, }; use mungos::mongodb::bson::oid::ObjectId; use toml::ToToml; @@ -22,12 +32,20 @@ pub mod user_groups; pub mod variables; pub mod view; -pub type ToUpdate = Vec>; -pub type ToCreate = Vec>; -/// Vec of resource names -pub type ToDelete = Vec; +#[derive(Default)] +pub struct SyncDeltas { + pub to_create: Vec>, + pub to_update: Vec>, + pub to_delete: Vec, +} -type UpdatesResult = (ToCreate, ToUpdate, ToDelete); +impl SyncDeltas { + pub fn no_changes(&self) -> bool { + self.to_create.is_empty() + && self.to_update.is_empty() + && self.to_delete.is_empty() + } +} pub struct ToUpdateItem { pub id: String, @@ -209,3 +227,30 @@ impl AllResourcesById { }) } } + +fn deserialize_resources_toml( + toml_str: &str, +) -> anyhow::Result { + ::toml::from_str::(&escape_between_triple_string( + toml_str, + )) + // the error without this comes through with multiple lines (\n) and looks bad + .map_err(|e| anyhow!("{e:#}")) +} + +fn escape_between_triple_string(toml_str: &str) -> String { + toml_str + .split(r#"""""#) + .enumerate() + .map(|(i, section)| { + // The odd entries are between triple string, + // and the \ need to be escaped. + if i % 2 == 0 { + section.to_string() + } else { + section.replace(r#"\"#, r#"\\"#) + } + }) + .collect::>() + .join(r#"""""#) +} diff --git a/bin/core/src/sync/remote.rs b/bin/core/src/sync/remote.rs index 7eeef06e4..b581ff904 100644 --- a/bin/core/src/sync/remote.rs +++ b/bin/core/src/sync/remote.rs @@ -1,11 +1,11 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use git::GitRes; use komodo_client::entities::{ + CloneArgs, sync::{ResourceSync, SyncFileContents}, to_komodo_name, toml::ResourcesToml, update::Log, - CloneArgs, }; use crate::{config::core_config, helpers::git_token}; @@ -56,7 +56,7 @@ pub async fn get_remote_resources( // ========== let mut resources = ResourcesToml::default(); let resources = if !sync.config.file_contents.is_empty() { - toml::from_str::(&sync.config.file_contents) + super::deserialize_resources_toml(&sync.config.file_contents) .context("failed to parse resource file contents") .map(|more| { extend_resources( @@ -133,9 +133,9 @@ pub async fn get_remote_resources( format!("Failed to update resource repo at {repo_path:?}") })?; - let hash = hash.context("failed to get commit hash")?; - let message = - message.context("failed to get commit hash message")?; + // let hash = hash.context("failed to get commit hash")?; + // let message = + // message.context("failed to get commit hash message")?; let (mut files, mut file_errors) = (Vec::new(), Vec::new()); let resources = super::file::read_resources( @@ -152,7 +152,7 @@ pub async fn get_remote_resources( files, file_errors, logs, - hash: Some(hash), - message: Some(message), + hash, + message, }) } diff --git a/bin/core/src/sync/resources.rs b/bin/core/src/sync/resources.rs index aa69c9731..eeb590c20 100644 --- a/bin/core/src/sync/resources.rs +++ b/bin/core/src/sync/resources.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; -use formatting::{bold, colored, muted, Color}; +use formatting::{Color, bold, colored, muted}; use komodo_client::{ api::execute::Execution, entities::{ + ResourceTarget, ResourceTargetVariant, action::Action, alerter::Alerter, build::Build, @@ -18,24 +19,24 @@ use komodo_client::{ tag::Tag, update::Log, user::sync_user, - ResourceTarget, ResourceTargetVariant, }, }; use partial_derive2::{MaybeNone, PartialDiff}; use crate::{ + api::write::WriteArgs, resource::KomodoResource, sync::{ - execute::{run_update_description, run_update_tags}, ToUpdateItem, + execute::{run_update_description, run_update_tags}, }, }; use super::{ + AllResourcesById, ResourceSyncTrait, SyncDeltas, execute::ExecuteResourceSync, include_resource_by_resource_type_and_name, - include_resource_by_tags, AllResourcesById, ResourceSyncTrait, - ToCreate, ToDelete, ToUpdate, + include_resource_by_tags, }; impl ResourceSyncTrait for Server { @@ -700,6 +701,13 @@ impl ResourceSyncTrait for Procedure { .unwrap_or_default(); } Execution::BatchDestroyStack(_config) => {} + Execution::TestAlerter(config) => { + config.alerter = resources + .alerters + .get(&config.alerter) + .map(|a| a.name.clone()) + .unwrap_or_default(); + } Execution::Sleep(_) => {} } } @@ -710,9 +718,11 @@ impl ResourceSyncTrait for Procedure { impl ExecuteResourceSync for Procedure { async fn execute_sync_updates( - mut to_create: ToCreate, - mut to_update: ToUpdate, - to_delete: ToDelete, + SyncDeltas { + mut to_create, + mut to_update, + to_delete, + }: SyncDeltas, ) -> Option { if to_create.is_empty() && to_update.is_empty() @@ -726,8 +736,13 @@ impl ExecuteResourceSync for Procedure { format!("running updates on {}s", Self::resource_type()); for name in to_delete { - if let Err(e) = - crate::resource::delete::(&name, sync_user()).await + if let Err(e) = crate::resource::delete::( + &name, + &WriteArgs { + user: sync_user().to_owned(), + }, + ) + .await { has_error = true; log.push_str(&format!( diff --git a/bin/core/src/sync/toml.rs b/bin/core/src/sync/toml.rs index 1bad6be95..48dc03f65 100644 --- a/bin/core/src/sync/toml.rs +++ b/bin/core/src/sync/toml.rs @@ -188,6 +188,7 @@ impl ToToml for Stack { config .into_iter() .map(|(key, value)| { + #[allow(clippy::single_match)] match key.as_str() { "server_id" => return Ok((String::from("server"), value)), _ => {} @@ -338,7 +339,7 @@ impl ToToml for Repo { match key.as_str() { "server_id" => return Ok((String::from("server"), value)), "builder_id" => { - return Ok((String::from("builder"), value)) + return Ok((String::from("builder"), value)); } _ => {} } @@ -789,6 +790,13 @@ impl ToToml for Procedure { .unwrap_or(&String::new()), ), Execution::BatchDestroyStack(_exec) => {} + Execution::TestAlerter(exec) => exec.alerter.clone_from( + all + .alerters + .get(&exec.alerter) + .map(|a| &a.name) + .unwrap_or(&String::new()), + ), Execution::Sleep(_) | Execution::None(_) => {} } } diff --git a/bin/core/src/sync/user_groups.rs b/bin/core/src/sync/user_groups.rs index 7a00bd82a..bab309f30 100644 --- a/bin/core/src/sync/user_groups.rs +++ b/bin/core/src/sync/user_groups.rs @@ -1,7 +1,7 @@ use std::{cmp::Ordering, collections::HashMap}; use anyhow::Context; -use formatting::{bold, colored, muted, Color}; +use formatting::{Color, bold, colored, muted}; use komodo_client::{ api::{ read::ListUserTargetPermissions, @@ -11,22 +11,25 @@ use komodo_client::{ }, }, entities::{ + ResourceTarget, ResourceTargetVariant, permission::{PermissionLevel, UserTarget}, sync::DiffData, toml::{PermissionToml, UserGroupToml}, update::Log, - user::{sync_user, User}, + user::{User, sync_user}, user_group::UserGroup, - ResourceTarget, ResourceTargetVariant, }, }; use mungos::find::find_collect; use regex::Regex; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::{ + api::{read::ReadArgs, write::WriteArgs}, + state::db_client, +}; -use super::{toml::TOML_PRETTY_OPTIONS, AllResourcesById}; +use super::{AllResourcesById, toml::TOML_PRETTY_OPTIONS}; pub struct UpdateItem { user_group: UserGroupToml, @@ -211,105 +214,105 @@ pub async fn get_updates_for_execution( }) .collect::>(); - let mut original_permissions = State - .resolve( - ListUserTargetPermissions { - user_target: UserTarget::UserGroup(original.id), - }, - sync_user().to_owned(), - ) - .await - .context("failed to query for existing UserGroup permissions")? - .into_iter() - .filter(|p| p.level > PermissionLevel::None) - .map(|mut p| { - // replace the ids with names - match &mut p.resource_target { - ResourceTarget::System(_) => {} - ResourceTarget::Build(id) => { - *id = all_resources - .builds - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Builder(id) => { - *id = all_resources - .builders - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Deployment(id) => { - *id = all_resources - .deployments - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Server(id) => { - *id = all_resources - .servers - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Repo(id) => { - *id = all_resources - .repos - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Alerter(id) => { - *id = all_resources - .alerters - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Procedure(id) => { - *id = all_resources - .procedures - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Action(id) => { - *id = all_resources - .actions - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ServerTemplate(id) => { - *id = all_resources - .templates - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ResourceSync(id) => { - *id = all_resources - .syncs - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Stack(id) => { - *id = all_resources - .stacks - .get(id) - .map(|b| b.name.clone()) - .unwrap_or_default() - } + let mut original_permissions = (ListUserTargetPermissions { + user_target: UserTarget::UserGroup(original.id), + }) + .resolve(&ReadArgs { + user: sync_user().to_owned(), + }) + .await + .map_err(|e| e.error) + .context("failed to query for existing UserGroup permissions")? + .into_iter() + .filter(|p| p.level > PermissionLevel::None) + .map(|mut p| { + // replace the ids with names + match &mut p.resource_target { + ResourceTarget::System(_) => {} + ResourceTarget::Build(id) => { + *id = all_resources + .builds + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() } - PermissionToml { - target: p.resource_target, - level: p.level, + ResourceTarget::Builder(id) => { + *id = all_resources + .builders + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() } - }) - .collect::>(); + ResourceTarget::Deployment(id) => { + *id = all_resources + .deployments + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Server(id) => { + *id = all_resources + .servers + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Repo(id) => { + *id = all_resources + .repos + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Alerter(id) => { + *id = all_resources + .alerters + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Procedure(id) => { + *id = all_resources + .procedures + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Action(id) => { + *id = all_resources + .actions + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ServerTemplate(id) => { + *id = all_resources + .templates + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ResourceSync(id) => { + *id = all_resources + .syncs + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Stack(id) => { + *id = all_resources + .stacks + .get(id) + .map(|b| b.name.clone()) + .unwrap_or_default() + } + } + PermissionToml { + target: p.resource_target, + level: p.level, + } + }) + .collect::>(); original_users.sort(); user_group.users.sort(); @@ -404,20 +407,20 @@ pub async fn run_updates( // Create the non-existant user groups for user_group in to_create { // Create the user group - if let Err(e) = State - .resolve( - CreateUserGroup { - name: user_group.name.clone(), - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (CreateUserGroup { + name: user_group.name.clone(), + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to create user group '{}' | {e:#}", + "\n{}: failed to create user group '{}' | {:#}", colored("ERROR", Color::Red), - bold(&user_group.name) + bold(&user_group.name), + e.error )); continue; } else { @@ -489,18 +492,18 @@ pub async fn run_updates( } for user_group in to_delete { - if let Err(e) = State - .resolve( - DeleteUserGroup { id: user_group.id }, - sync_user().to_owned(), - ) + if let Err(e) = (DeleteUserGroup { id: user_group.id }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) .await { has_error = true; log.push_str(&format!( - "\n{}: failed to delete user group '{}' | {e:#}", + "\n{}: failed to delete user group '{}' | {:#}", colored("ERROR", Color::Red), - bold(&user_group.name) + bold(&user_group.name), + e.error )) } else { log.push_str(&format!( @@ -526,21 +529,21 @@ async fn set_users( log: &mut String, has_error: &mut bool, ) { - if let Err(e) = State - .resolve( - SetUsersInUserGroup { - user_group: user_group.clone(), - users, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (SetUsersInUserGroup { + user_group: user_group.clone(), + users, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to set users in group {} | {e:#}", + "\n{}: failed to set users in group {} | {:#}", colored("ERROR", Color::Red), - bold(&user_group) + bold(&user_group), + e.error )) } else { log.push_str(&format!( @@ -559,22 +562,22 @@ async fn run_update_all( has_error: &mut bool, ) { for (resource_type, permission) in all_diff { - if let Err(e) = State - .resolve( - UpdatePermissionOnResourceType { - user_target: UserTarget::UserGroup(user_group.clone()), - resource_type, - permission, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdatePermissionOnResourceType { + user_target: UserTarget::UserGroup(user_group.clone()), + resource_type, + permission, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to set base permissions on {resource_type} in group {} | {e:#}", + "\n{}: failed to set base permissions on {resource_type} in group {} | {:#}", colored("ERROR", Color::Red), - bold(&user_group) + bold(&user_group), + e.error )) } else { log.push_str(&format!( @@ -594,22 +597,22 @@ async fn run_update_permissions( has_error: &mut bool, ) { for PermissionToml { target, level } in permissions { - if let Err(e) = State - .resolve( - UpdatePermissionOnTarget { - user_target: UserTarget::UserGroup(user_group.clone()), - resource_target: target.clone(), - permission: level, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdatePermissionOnTarget { + user_target: UserTarget::UserGroup(user_group.clone()), + resource_target: target.clone(), + permission: level, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { *has_error = true; log.push_str(&format!( - "\n{}: failed to set permission in group {} | target: {target:?} | {e:#}", + "\n{}: failed to set permission in group {} | target: {target:?} | {:#}", colored("ERROR", Color::Red), - bold(&user_group) + bold(&user_group), + e.error )) } else { log.push_str(&format!( @@ -830,105 +833,105 @@ pub async fn convert_user_groups( for user_group in user_groups { // this method is admin only, but we already know user can see user group if above does not return Err - let mut permissions = State - .resolve( - ListUserTargetPermissions { - user_target: UserTarget::UserGroup(user_group.id.clone()), - }, - User { - admin: true, - ..Default::default() - }, - ) - .await? - .into_iter() - .map(|mut permission| { - match &mut permission.resource_target { - ResourceTarget::Build(id) => { - *id = all - .builds - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Builder(id) => { - *id = all - .builders - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Deployment(id) => { - *id = all - .deployments - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Server(id) => { - *id = all - .servers - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Repo(id) => { - *id = all - .repos - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Alerter(id) => { - *id = all - .alerters - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Procedure(id) => { - *id = all - .procedures - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Action(id) => { - *id = all - .actions - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ServerTemplate(id) => { - *id = all - .templates - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::ResourceSync(id) => { - *id = all - .syncs - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::Stack(id) => { - *id = all - .stacks - .get(id) - .map(|r| r.name.clone()) - .unwrap_or_default() - } - ResourceTarget::System(_) => {} + let mut permissions = (ListUserTargetPermissions { + user_target: UserTarget::UserGroup(user_group.id.clone()), + }) + .resolve(&ReadArgs { + user: User { + admin: true, + ..Default::default() + }, + }) + .await + .map_err(|e| e.error)? + .into_iter() + .map(|mut permission| { + match &mut permission.resource_target { + ResourceTarget::Build(id) => { + *id = all + .builds + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() } - PermissionToml { - target: permission.resource_target, - level: permission.level, + ResourceTarget::Builder(id) => { + *id = all + .builders + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() } - }) - .collect::>(); + ResourceTarget::Deployment(id) => { + *id = all + .deployments + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Server(id) => { + *id = all + .servers + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Repo(id) => { + *id = all + .repos + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Alerter(id) => { + *id = all + .alerters + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Procedure(id) => { + *id = all + .procedures + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Action(id) => { + *id = all + .actions + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ServerTemplate(id) => { + *id = all + .templates + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::ResourceSync(id) => { + *id = all + .syncs + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::Stack(id) => { + *id = all + .stacks + .get(id) + .map(|r| r.name.clone()) + .unwrap_or_default() + } + ResourceTarget::System(_) => {} + } + PermissionToml { + target: permission.resource_target, + level: permission.level, + } + }) + .collect::>(); let mut users = user_group .users .into_iter() diff --git a/bin/core/src/sync/variables.rs b/bin/core/src/sync/variables.rs index a251808f2..b45922b73 100644 --- a/bin/core/src/sync/variables.rs +++ b/bin/core/src/sync/variables.rs @@ -1,12 +1,9 @@ use std::collections::HashMap; use anyhow::Context; -use formatting::{bold, colored, muted, Color}; +use formatting::{Color, bold, colored, muted}; use komodo_client::{ - api::write::{ - CreateVariable, DeleteVariable, UpdateVariableDescription, - UpdateVariableIsSecret, UpdateVariableValue, - }, + api::write::*, entities::{ sync::DiffData, update::Log, user::sync_user, variable::Variable, }, @@ -14,7 +11,7 @@ use komodo_client::{ use mungos::find::find_collect; use resolver_api::Resolve; -use crate::state::{db_client, State}; +use crate::{api::write::WriteArgs, state::db_client}; use super::toml::TOML_PRETTY_OPTIONS; @@ -154,23 +151,23 @@ pub async fn run_updates( let mut log = String::from("running updates on Variables"); for variable in to_create { - if let Err(e) = State - .resolve( - CreateVariable { - name: variable.name.clone(), - value: variable.value, - description: variable.description, - is_secret: variable.is_secret, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (CreateVariable { + name: variable.name.clone(), + value: variable.value, + description: variable.description, + is_secret: variable.is_secret, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to create variable '{}' | {e:#}", + "\n{}: failed to create variable '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error )); } else { log.push_str(&format!( @@ -190,21 +187,21 @@ pub async fn run_updates( } in to_update { if update_value { - if let Err(e) = State - .resolve( - UpdateVariableValue { - name: variable.name.clone(), - value: variable.value, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateVariableValue { + name: variable.name.clone(), + value: variable.value, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to update variable value for '{}' | {e:#}", + "\n{}: failed to update variable value for '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error )) } else { log.push_str(&format!( @@ -216,21 +213,21 @@ pub async fn run_updates( }; } if update_description { - if let Err(e) = State - .resolve( - UpdateVariableDescription { - name: variable.name.clone(), - description: variable.description, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateVariableDescription { + name: variable.name.clone(), + description: variable.description, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to update variable description for '{}' | {e:#}", + "\n{}: failed to update variable description for '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error )) } else { log.push_str(&format!( @@ -242,21 +239,21 @@ pub async fn run_updates( }; } if update_is_secret { - if let Err(e) = State - .resolve( - UpdateVariableIsSecret { - name: variable.name.clone(), - is_secret: variable.is_secret, - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (UpdateVariableIsSecret { + name: variable.name.clone(), + is_secret: variable.is_secret, + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to update variable is secret for '{}' | {e:#}", + "\n{}: failed to update variable is secret for '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable.name) + bold(&variable.name), + e.error, )) } else { log.push_str(&format!( @@ -270,20 +267,20 @@ pub async fn run_updates( } for variable in to_delete { - if let Err(e) = State - .resolve( - DeleteVariable { - name: variable.clone(), - }, - sync_user().to_owned(), - ) - .await + if let Err(e) = (DeleteVariable { + name: variable.clone(), + }) + .resolve(&WriteArgs { + user: sync_user().to_owned(), + }) + .await { has_error = true; log.push_str(&format!( - "\n{}: failed to delete variable '{}' | {e:#}", + "\n{}: failed to delete variable '{}' | {:#}", colored("ERROR", Color::Red), - bold(&variable) + bold(&variable), + e.error )) } else { log.push_str(&format!( diff --git a/bin/core/src/sync/view.rs b/bin/core/src/sync/view.rs index fe4260f10..a362aa5a2 100644 --- a/bin/core/src/sync/view.rs +++ b/bin/core/src/sync/view.rs @@ -2,16 +2,17 @@ use std::collections::HashMap; use anyhow::Context; use komodo_client::entities::{ + ResourceTargetVariant, sync::{DiffData, ResourceDiff}, tag::Tag, toml::ResourceToml, - ResourceTargetVariant, }; use mungos::find::find_collect; use partial_derive2::MaybeNone; use super::{AllResourcesById, ResourceSyncTrait}; +#[allow(clippy::too_many_arguments)] pub async fn push_updates_for_view( resources: Vec>, delete: bool, diff --git a/bin/core/src/ts_client.rs b/bin/core/src/ts_client.rs index 8eedb9e0c..1caf6322d 100644 --- a/bin/core/src/ts_client.rs +++ b/bin/core/src/ts_client.rs @@ -1,9 +1,9 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use axum::{ + Router, extract::Path, http::{HeaderMap, HeaderValue}, routing::get, - Router, }; use reqwest::StatusCode; use serde::Deserialize; @@ -13,7 +13,7 @@ use tokio::fs; use crate::config::core_config; pub fn router() -> Router { - Router::new().route("/:path", get(serve_client_file)) + Router::new().route("/{path}", get(serve_client_file)) } const ALLOWED_FILES: &[&str] = &[ @@ -30,6 +30,7 @@ struct FilePath { path: String, } +#[axum::debug_handler] async fn serve_client_file( Path(FilePath { path }): Path, ) -> serror::Result<(HeaderMap, String)> { diff --git a/bin/core/src/ws.rs b/bin/core/src/ws.rs index cc3998f3f..65c2a41be 100644 --- a/bin/core/src/ws.rs +++ b/bin/core/src/ws.rs @@ -1,17 +1,17 @@ use anyhow::anyhow; use axum::{ + Router, extract::{ - ws::{Message, WebSocket}, WebSocketUpgrade, + ws::{Message, WebSocket}, }, response::IntoResponse, routing::get, - Router, }; use futures::{SinkExt, StreamExt}; use komodo_client::{ entities::{ - permission::PermissionLevel, user::User, ResourceTarget, + ResourceTarget, permission::PermissionLevel, user::User, }, ws::WsLoginMessage, }; @@ -62,7 +62,7 @@ async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse { let user = match user { Err(e) => { let _ = ws_sender - .send(Message::Text(json!({ "type": "INVALID_USER", "msg": serialize_error(&e) }).to_string())) + .send(Message::text(json!({ "type": "INVALID_USER", "msg": serialize_error(&e) }).to_string())) .await; let _ = ws_sender.close().await; return; @@ -73,7 +73,7 @@ async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse { // Only send if user has permission on the target resource. if user_can_see_update(&user, &update.target).await.is_ok() { let _ = ws_sender - .send(Message::Text(serde_json::to_string(&update).unwrap())) + .send(Message::text(serde_json::to_string(&update).unwrap())) .await; } } @@ -103,7 +103,9 @@ async fn ws_login( mut socket: WebSocket, ) -> Option<(WebSocket, User)> { let login_msg = match socket.recv().await { - Some(Ok(Message::Text(login_msg))) => LoginMessage::Ok(login_msg), + Some(Ok(Message::Text(login_msg))) => { + LoginMessage::Ok(login_msg.to_string()) + } Some(Ok(msg)) => { LoginMessage::Err(format!("invalid login message: {msg:?}")) } @@ -117,7 +119,7 @@ async fn ws_login( let login_msg = match login_msg { LoginMessage::Ok(login_msg) => login_msg, LoginMessage::Err(msg) => { - let _ = socket.send(Message::Text(msg)).await; + let _ = socket.send(Message::text(msg)).await; let _ = socket.close().await; return None; } @@ -127,13 +129,12 @@ async fn ws_login( Ok(WsLoginMessage::Jwt { jwt }) => { match auth_jwt_check_enabled(&jwt).await { Ok(user) => { - let _ = - socket.send(Message::Text("LOGGED_IN".to_string())).await; + let _ = socket.send(Message::text("LOGGED_IN")).await; Some((socket, user)) } Err(e) => { let _ = socket - .send(Message::Text(format!( + .send(Message::text(format!( "failed to authenticate user using jwt | {e:#}" ))) .await; @@ -146,13 +147,12 @@ async fn ws_login( Ok(WsLoginMessage::ApiKeys { key, secret }) => { match auth_api_key_check_enabled(&key, &secret).await { Ok(user) => { - let _ = - socket.send(Message::Text("LOGGED_IN".to_string())).await; + let _ = socket.send(Message::text("LOGGED_IN")).await; Some((socket, user)) } Err(e) => { let _ = socket - .send(Message::Text(format!( + .send(Message::text(format!( "failed to authenticate user using api keys | {e:#}" ))) .await; @@ -163,7 +163,7 @@ async fn ws_login( } Err(e) => { let _ = socket - .send(Message::Text(format!( + .send(Message::text(format!( "failed to parse login message: {e:#}" ))) .await; diff --git a/bin/periphery/Cargo.toml b/bin/periphery/Cargo.toml index d7021f304..64075e9f1 100644 --- a/bin/periphery/Cargo.toml +++ b/bin/periphery/Cargo.toml @@ -19,6 +19,7 @@ komodo_client.workspace = true periphery_client.workspace = true environment_file.workspace = true formatting.workspace = true +response.workspace = true command.workspace = true logger.workspace = true cache.workspace = true @@ -33,8 +34,8 @@ run_command.workspace = true svi.workspace = true # external axum-server.workspace = true -axum-extra.workspace = true serde_json.workspace = true +serde_yaml.workspace = true futures.workspace = true tracing.workspace = true bollard.workspace = true diff --git a/bin/periphery/aio.Dockerfile b/bin/periphery/aio.Dockerfile index 3f13a1fd4..a844c9e85 100644 --- a/bin/periphery/aio.Dockerfile +++ b/bin/periphery/aio.Dockerfile @@ -1,6 +1,6 @@ ## All in one, multi stage compile + runtime Docker build for your architecture. -FROM rust:1.82.0-bullseye AS builder +FROM rust:1.85.1-bullseye AS builder WORKDIR /builder COPY Cargo.toml Cargo.lock ./ @@ -22,7 +22,7 @@ COPY --from=builder /builder/target/release/periphery /usr/local/bin/periphery EXPOSE 8120 -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Periphery" LABEL org.opencontainers.image.licenses=GPL-3.0 diff --git a/bin/periphery/multi-arch.Dockerfile b/bin/periphery/multi-arch.Dockerfile index 481630e36..166563157 100644 --- a/bin/periphery/multi-arch.Dockerfile +++ b/bin/periphery/multi-arch.Dockerfile @@ -2,7 +2,7 @@ ## Sets up the necessary runtime container dependencies for Komodo Periphery. ## Since theres no heavy build here, QEMU multi-arch builds are fine for this image. -ARG BINARIES_IMAGE=ghcr.io/mbecker20/komodo-binaries:latest +ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest ARG X86_64_BINARIES=${BINARIES_IMAGE}-x86_64 ARG AARCH64_BINARIES=${BINARIES_IMAGE}-aarch64 @@ -26,7 +26,7 @@ RUN mv /app/arch/${TARGETPLATFORM} /usr/local/bin/periphery && rm -r /app/arch EXPOSE 8120 -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Periphery" LABEL org.opencontainers.image.licenses=GPL-3.0 diff --git a/bin/periphery/single-arch.Dockerfile b/bin/periphery/single-arch.Dockerfile index bf3198bd8..71e9cd761 100644 --- a/bin/periphery/single-arch.Dockerfile +++ b/bin/periphery/single-arch.Dockerfile @@ -1,7 +1,7 @@ ## Assumes the latest binaries for the required arch are already built (by binaries.Dockerfile). ## Sets up the necessary runtime container dependencies for Komodo Periphery. -ARG BINARIES_IMAGE=ghcr.io/mbecker20/komodo-binaries:latest +ARG BINARIES_IMAGE=ghcr.io/moghtech/komodo-binaries:latest # This is required to work with COPY --from FROM ${BINARIES_IMAGE} AS binaries @@ -16,7 +16,7 @@ COPY --from=binaries /periphery /usr/local/bin/periphery EXPOSE 8120 -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Periphery" LABEL org.opencontainers.image.licenses=GPL-3.0 diff --git a/bin/periphery/src/api/build.rs b/bin/periphery/src/api/build.rs index 57852525c..57f47df77 100644 --- a/bin/periphery/src/api/build.rs +++ b/bin/periphery/src/api/build.rs @@ -1,13 +1,17 @@ -use anyhow::{anyhow, Context}; -use command::run_komodo_command; +use std::{fmt::Write, path::Path}; + +use anyhow::{Context, anyhow}; +use command::{ + run_komodo_command, run_komodo_command_with_interpolation, +}; use formatting::format_serror; use komodo_client::{ entities::{ + EnvironmentVar, Version, build::{Build, BuildConfig}, environment_vars_from_str, get_image_name, optional_string, to_komodo_name, update::Log, - EnvironmentVar, Version, }, parsers::QUOTE_PATTERN, }; @@ -20,21 +24,20 @@ use crate::{ config::periphery_config, docker::docker_login, helpers::{parse_extra_args, parse_labels}, - State, }; -impl Resolve for State { - #[instrument(name = "Build", skip_all)] +impl Resolve for build::Build { + #[instrument(name = "Build", skip_all, fields(build = self.build.name.to_string()))] async fn resolve( - &self, - build::Build { + self, + _: &super::Args, + ) -> serror::Result> { + let build::Build { build, registry_token, additional_tags, replacers: core_replacers, - }: build::Build, - _: (), - ) -> anyhow::Result> { + } = self; let Build { name, config: @@ -68,7 +71,7 @@ impl Resolve for State { Ok(should_push) => should_push, Err(e) => { logs.push(Log::error( - "docker login", + "Docker Login", format_serror( &e.context("failed to login to docker registry").into(), ), @@ -104,8 +107,12 @@ impl Resolve for State { let secret_args = environment_vars_from_str(secret_args) .context("Invalid secret_args")?; - let command_secret_args = - parse_secret_args(&secret_args, *skip_secret_interp)?; + let command_secret_args = parse_secret_args( + &secret_args, + &build_dir, + *skip_secret_interp, + ) + .await?; let labels = parse_labels( &environment_vars_from_str(labels).context("Invalid labels")?, @@ -123,44 +130,25 @@ impl Resolve for State { if *skip_secret_interp { let build_log = run_komodo_command( - "docker build", + "Docker Build", build_dir.as_ref(), command, - false, ) .await; logs.push(build_log); - } else { - // Interpolate any missing secrets - let (command, mut replacers) = svi::interpolate_variables( - &command, - &periphery_config().secrets, - svi::Interpolator::DoubleBrackets, - true, - ) - .context( - "failed to interpolate secrets into docker build command", - )?; - replacers.extend(core_replacers); - - let mut build_log = run_komodo_command( - "docker build", - build_dir.as_ref(), - command, - false, - ) - .await; - build_log.command = - svi::replace_in_string(&build_log.command, &replacers); - build_log.stdout = - svi::replace_in_string(&build_log.stdout, &replacers); - build_log.stderr = - svi::replace_in_string(&build_log.stderr, &replacers); - logs.push(build_log); + } else if let Some(log) = run_komodo_command_with_interpolation( + "Docker Build", + build_dir.as_ref(), + command, + false, + &periphery_config().secrets, + &core_replacers, + ) + .await + { + logs.push(log) } - cleanup_secret_env_vars(&secret_args); - Ok(logs) } } @@ -204,74 +192,77 @@ fn parse_build_args(build_args: &[EnvironmentVar]) -> String { .join("") } -fn parse_secret_args( +/// +async fn parse_secret_args( secret_args: &[EnvironmentVar], + build_dir: &Path, skip_secret_interp: bool, ) -> anyhow::Result { let periphery_config = periphery_config(); - Ok( - secret_args - .iter() - .map(|EnvironmentVar { variable, value }| { - if variable.is_empty() { - return Err(anyhow!("secret variable cannot be empty string")) - } else if variable.contains('=') { - return Err(anyhow!("invalid variable {variable}. variable cannot contain '='")) - } - let value = if skip_secret_interp { - value.to_string() - } else { - svi::interpolate_variables( - value, - &periphery_config.secrets, - svi::Interpolator::DoubleBrackets, - true, - ) - .context( - "failed to interpolate periphery secrets into build secrets", - )?.0 - }; - std::env::set_var(variable, value); - anyhow::Ok(format!(" --secret id={variable}")) - }) - .collect::>>()? - .join(""), - ) -} - -fn cleanup_secret_env_vars(secret_args: &[EnvironmentVar]) { - secret_args.iter().for_each( - |EnvironmentVar { variable, .. }| std::env::remove_var(variable), - ) -} - -// - -impl Resolve for State { - #[instrument(name = "PruneBuilders", skip(self))] - async fn resolve( - &self, - _: PruneBuilders, - _: (), - ) -> anyhow::Result { - let command = String::from("docker builder prune -a -f"); - Ok( - run_komodo_command("prune builders", None, command, false) - .await, + let mut res = String::new(); + for EnvironmentVar { variable, value } in secret_args { + // Check edge cases + if variable.is_empty() { + return Err(anyhow!("secret variable cannot be empty string")); + } else if variable.contains('=') { + return Err(anyhow!( + "invalid variable {variable}. variable cannot contain '='" + )); + } + // Interpolate in value + let value = if skip_secret_interp { + value.to_string() + } else { + svi::interpolate_variables( + value, + &periphery_config.secrets, + svi::Interpolator::DoubleBrackets, + true, + ) + .context( + "Failed to interpolate periphery secrets into build secrets", + )? + .0 + }; + // Write the value to file to mount + let path = build_dir.join(variable); + tokio::fs::write(&path, value).await.with_context(|| { + format!( + "Failed to write build secret {variable} to {}", + path.display() + ) + })?; + // Extend the command + write!( + &mut res, + " --secret id={variable},src={}", + path.display() ) + .with_context(|| { + format!( + "Failed to format build secret arguments for {variable}" + ) + })?; + } + Ok(res) +} + +// + +impl Resolve for PruneBuilders { + #[instrument(name = "PruneBuilders", skip_all)] + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = String::from("docker builder prune -a -f"); + Ok(run_komodo_command("Prune Builders", None, command).await) } } // -impl Resolve for State { - #[instrument(name = "PruneBuildx", skip(self))] - async fn resolve( - &self, - _: PruneBuildx, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneBuildx { + #[instrument(name = "PruneBuildx", skip_all)] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker buildx prune -a -f"); - Ok(run_komodo_command("prune buildx", None, command, false).await) + Ok(run_komodo_command("Prune Buildx", None, command).await) } } diff --git a/bin/periphery/src/api/compose.rs b/bin/periphery/src/api/compose.rs index 6ea86a9ee..8443a88c4 100644 --- a/bin/periphery/src/api/compose.rs +++ b/bin/periphery/src/api/compose.rs @@ -1,11 +1,11 @@ use std::{fmt::Write, path::PathBuf}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use command::run_komodo_command; use formatting::format_serror; -use git::{write_commit_file, GitRes}; +use git::{GitRes, write_commit_file}; use komodo_client::entities::{ - stack::ComposeProject, to_komodo_name, update::Log, FileContents, + FileContents, stack::ComposeProject, to_komodo_name, update::Log, }; use periphery_client::api::{compose::*, git::RepoActionResponse}; use resolver_api::Resolve; @@ -13,33 +13,34 @@ use serde::{Deserialize, Serialize}; use tokio::fs; use crate::{ - compose::{compose_up, docker_compose, write_stack, WriteStackRes}, + compose::{WriteStackRes, compose_up, docker_compose, write_stack}, config::periphery_config, docker::docker_login, helpers::{log_grep, pull_or_clone_stack}, - State, }; -impl Resolve for State { - #[instrument(name = "ComposeInfo", level = "debug", skip(self))] +impl Resolve for ListComposeProjects { + #[instrument(name = "ComposeInfo", level = "debug", skip_all)] async fn resolve( - &self, - ListComposeProjects {}: ListComposeProjects, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let docker_compose = docker_compose(); let res = run_komodo_command( - "list projects", + "List Projects", None, format!("{docker_compose} ls --all --format json"), - false, ) .await; if !res.success { - return Err(anyhow!("{}", res.combined()).context(format!( - "failed to list compose projects using {docker_compose} ls" - ))); + return Err( + anyhow!("{}", res.combined()) + .context(format!( + "failed to list compose projects using {docker_compose} ls" + )) + .into(), + ); } let res = @@ -80,81 +81,62 @@ pub struct DockerComposeLsItem { // -impl Resolve for State { - #[instrument( - name = "GetComposeServiceLog", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - GetComposeServiceLog { +impl Resolve for GetComposeLog { + #[instrument(name = "GetComposeLog", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetComposeLog { project, - service, + services, tail, timestamps, - }: GetComposeServiceLog, - _: (), - ) -> anyhow::Result { + } = self; let docker_compose = docker_compose(); let timestamps = timestamps.then_some(" --timestamps").unwrap_or_default(); let command = format!( - "{docker_compose} -p {project} logs {service} --tail {tail}{timestamps}" + "{docker_compose} -p {project} logs --tail {tail}{timestamps} {}", + services.join(" ") ); - Ok( - run_komodo_command("get stack log", None, command, false).await, - ) + Ok(run_komodo_command("get stack log", None, command).await) } } -impl Resolve for State { - #[instrument( - name = "GetComposeServiceLogSearch", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - GetComposeServiceLogSearch { +impl Resolve for GetComposeLogSearch { + #[instrument(name = "GetComposeLogSearch", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetComposeLogSearch { project, - service, + services, terms, combinator, invert, timestamps, - }: GetComposeServiceLogSearch, - _: (), - ) -> anyhow::Result { + } = self; let docker_compose = docker_compose(); let grep = log_grep(&terms, combinator, invert); let timestamps = timestamps.then_some(" --timestamps").unwrap_or_default(); - let command = format!("{docker_compose} -p {project} logs {service} --tail 5000{timestamps} 2>&1 | {grep}"); - Ok( - run_komodo_command("get stack log grep", None, command, false) - .await, - ) + let command = format!( + "{docker_compose} -p {project} logs --tail 5000{timestamps} {} 2>&1 | {grep}", + services.join(" ") + ); + Ok(run_komodo_command("Get stack log grep", None, command).await) } } // -impl Resolve for State { - #[instrument( - name = "GetComposeContentsOnHost", - level = "debug", - skip(self) - )] +impl Resolve for GetComposeContentsOnHost { + #[instrument(name = "GetComposeContentsOnHost", level = "debug")] async fn resolve( - &self, - GetComposeContentsOnHost { + self, + _: &super::Args, + ) -> serror::Result { + let GetComposeContentsOnHost { name, run_directory, file_paths, - }: GetComposeContentsOnHost, - _: (), - ) -> anyhow::Result { + } = self; let root = periphery_config().stack_dir.join(to_komodo_name(&name)); let run_directory = @@ -196,18 +178,23 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "WriteComposeContentsToHost", skip(self))] - async fn resolve( - &self, - WriteComposeContentsToHost { +impl Resolve for WriteComposeContentsToHost { + #[instrument( + name = "WriteComposeContentsToHost", + skip_all, + fields( + stack = &self.name, + run_directory = &self.run_directory, + file_path = &self.file_path, + ) + )] + async fn resolve(self, _: &super::Args) -> serror::Result { + let WriteComposeContentsToHost { name, run_directory, file_path, contents, - }: WriteComposeContentsToHost, - _: (), - ) -> anyhow::Result { + } = self; let file_path = periphery_config() .stack_dir .join(to_komodo_name(&name)) @@ -217,7 +204,9 @@ impl Resolve for State { .collect::(); // Ensure parent directory exists if let Some(parent) = file_path.parent() { - let _ = fs::create_dir_all(&parent).await; + fs::create_dir_all(&parent) + .await + .with_context(|| format!("Failed to initialize environment file parent directory {parent:?}"))?; } fs::write(&file_path, contents).await.with_context(|| { format!( @@ -233,19 +222,28 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "WriteCommitComposeContents", skip(self))] +impl Resolve for WriteCommitComposeContents { + #[instrument( + name = "WriteCommitComposeContents", + skip_all, + fields( + stack = &self.stack.name, + username = &self.username, + file_path = &self.file_path, + ) + )] async fn resolve( - &self, - WriteCommitComposeContents { + self, + _: &super::Args, + ) -> serror::Result { + let WriteCommitComposeContents { stack, username, file_path, contents, git_token, - }: WriteCommitComposeContents, - _: (), - ) -> anyhow::Result { + } = self; + let root = pull_or_clone_stack(&stack, git_token).await?; let file_path = stack @@ -266,7 +264,14 @@ impl Resolve for State { hash, message, .. - } = write_commit_file(&msg, &root, &file_path, &contents).await?; + } = write_commit_file( + &msg, + &root, + &file_path, + &contents, + &stack.config.branch, + ) + .await?; Ok(RepoActionResponse { logs, @@ -279,30 +284,44 @@ impl Resolve for State { // -impl<'a> WriteStackRes for &'a mut ComposePullResponse { +impl WriteStackRes for &mut ComposePullResponse { fn logs(&mut self) -> &mut Vec { &mut self.logs } } -impl Resolve for State { +impl Resolve for ComposePull { #[instrument( name = "ComposePull", - skip(self, git_token, registry_token) + skip_all, + fields( + stack = &self.stack.name, + services = format!("{:?}", self.services), + ) )] async fn resolve( - &self, - ComposePull { + self, + _: &super::Args, + ) -> serror::Result { + let ComposePull { stack, - service, + services, git_token, registry_token, - }: ComposePull, - _: (), - ) -> anyhow::Result { + } = self; let mut res = ComposePullResponse::default(); - let (run_directory, env_file_path) = - write_stack(&stack, git_token, &mut res).await?; + + let (run_directory, env_file_path, _replacers) = + match write_stack(&stack, git_token, &mut res).await { + Ok(res) => res, + Err(e) => { + res.logs.push(Log::error( + "Write Stack", + format_serror(&e.into()), + )); + return Ok(res); + } + }; // Canonicalize the path to ensure it exists, and is the cleanest path to the run directory. let run_directory = run_directory.canonicalize().context( @@ -323,15 +342,17 @@ impl Resolve for State { for (path, full_path) in &file_paths { if !full_path.exists() { - return Err(anyhow!("Missing compose file at {path}")); + return Err(anyhow!("Missing compose file at {path}").into()); } } let docker_compose = docker_compose(); - let service_arg = service - .as_ref() - .map(|service| format!(" {service}")) - .unwrap_or_default(); + + let service_args = if services.is_empty() { + String::new() + } else { + format!(" {}", services.join(" ")) + }; let file_args = if stack.config.file_paths.is_empty() { String::from("compose.yaml") @@ -375,12 +396,11 @@ impl Resolve for State { let project_name = stack.project_name(false); let log = run_komodo_command( - "compose pull", + "Compose Pull", run_directory.as_ref(), format!( - "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} pull{service_arg}", + "{docker_compose} -p {project_name} -f {file_args}{additional_env_files}{env_file} pull{service_args}", ), - false, ) .await; @@ -392,26 +412,30 @@ impl Resolve for State { // -impl Resolve for State { +impl Resolve for ComposeUp { #[instrument( name = "ComposeUp", - skip(self, git_token, registry_token) + skip_all, + fields( + stack = &self.stack.name, + services = format!("{:?}", self.services), + ) )] async fn resolve( - &self, - ComposeUp { + self, + _: &super::Args, + ) -> serror::Result { + let ComposeUp { stack, - service, + services, git_token, registry_token, replacers, - }: ComposeUp, - _: (), - ) -> anyhow::Result { + } = self; let mut res = ComposeUpResponse::default(); if let Err(e) = compose_up( stack, - service, + services, git_token, registry_token, &mut res, @@ -420,7 +444,7 @@ impl Resolve for State { .await { res.logs.push(Log::error( - "compose up failed", + "Compose Up - Failed", format_serror(&e.into()), )); }; @@ -430,19 +454,15 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "ComposeExecution", skip(self))] - async fn resolve( - &self, - ComposeExecution { project, command }: ComposeExecution, - _: (), - ) -> anyhow::Result { +impl Resolve for ComposeExecution { + #[instrument(name = "ComposeExecution")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let ComposeExecution { project, command } = self; let docker_compose = docker_compose(); let log = run_komodo_command( - "compose command", + "Compose Command", None, format!("{docker_compose} -p {project} {command}"), - false, ) .await; Ok(log) diff --git a/bin/periphery/src/api/container.rs b/bin/periphery/src/api/container.rs index 8ad785e67..c6440ac56 100644 --- a/bin/periphery/src/api/container.rs +++ b/bin/periphery/src/api/container.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use command::run_komodo_command; use futures::future::join_all; use komodo_client::entities::{ @@ -12,7 +12,6 @@ use resolver_api::Resolve; use crate::{ docker::{container_stats, docker_client, stop_container_command}, helpers::log_grep, - State, }; // ====== @@ -21,64 +20,46 @@ use crate::{ // -impl Resolve for State { - #[instrument( - name = "InspectContainer", - level = "debug", - skip(self) - )] +impl Resolve for InspectContainer { + #[instrument(name = "InspectContainer", level = "debug")] async fn resolve( - &self, - InspectContainer { name }: InspectContainer, - _: (), - ) -> anyhow::Result { - docker_client().inspect_container(&name).await + self, + _: &super::Args, + ) -> serror::Result { + Ok(docker_client().inspect_container(&self.name).await?) } } // -impl Resolve for State { - #[instrument(name = "GetContainerLog", level = "debug", skip(self))] - async fn resolve( - &self, - GetContainerLog { +impl Resolve for GetContainerLog { + #[instrument(name = "GetContainerLog", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetContainerLog { name, tail, timestamps, - }: GetContainerLog, - _: (), - ) -> anyhow::Result { + } = self; let timestamps = timestamps.then_some(" --timestamps").unwrap_or_default(); let command = format!("docker logs {name} --tail {tail}{timestamps}"); - Ok( - run_komodo_command("get container log", None, command, false) - .await, - ) + Ok(run_komodo_command("Get container log", None, command).await) } } // -impl Resolve for State { - #[instrument( - name = "GetContainerLogSearch", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - GetContainerLogSearch { +impl Resolve for GetContainerLogSearch { + #[instrument(name = "GetContainerLogSearch", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let GetContainerLogSearch { name, terms, combinator, invert, timestamps, - }: GetContainerLogSearch, - _: (), - ) -> anyhow::Result { + } = self; let grep = log_grep(&terms, combinator, invert); let timestamps = timestamps.then_some(" --timestamps").unwrap_or_default(); @@ -86,241 +67,7 @@ impl Resolve for State { "docker logs {name} --tail 5000{timestamps} 2>&1 | {grep}" ); Ok( - run_komodo_command( - "get container log grep", - None, - command, - false, - ) - .await, - ) - } -} - -// - -impl Resolve for State { - #[instrument( - name = "GetContainerStats", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - req: GetContainerStats, - _: (), - ) -> anyhow::Result { - let error = anyhow!("no stats matching {}", req.name); - let mut stats = container_stats(Some(req.name)).await?; - let stats = stats.pop().ok_or(error)?; - Ok(stats) - } -} - -// - -impl Resolve for State { - #[instrument( - name = "GetContainerStatsList", - level = "debug", - skip(self) - )] - async fn resolve( - &self, - _: GetContainerStatsList, - _: (), - ) -> anyhow::Result> { - container_stats(None).await - } -} - -// ========= -// ACTIONS -// ========= - -impl Resolve for State { - #[instrument(name = "StartContainer", skip(self))] - async fn resolve( - &self, - StartContainer { name }: StartContainer, - _: (), - ) -> anyhow::Result { - Ok( - run_komodo_command( - "docker start", - None, - format!("docker start {name}"), - false, - ) - .await, - ) - } -} - -// - -impl Resolve for State { - #[instrument(name = "RestartContainer", skip(self))] - async fn resolve( - &self, - RestartContainer { name }: RestartContainer, - _: (), - ) -> anyhow::Result { - Ok( - run_komodo_command( - "docker restart", - None, - format!("docker restart {name}"), - false, - ) - .await, - ) - } -} - -// - -impl Resolve for State { - #[instrument(name = "PauseContainer", skip(self))] - async fn resolve( - &self, - PauseContainer { name }: PauseContainer, - _: (), - ) -> anyhow::Result { - Ok( - run_komodo_command( - "docker pause", - None, - format!("docker pause {name}"), - false, - ) - .await, - ) - } -} - -impl Resolve for State { - #[instrument(name = "UnpauseContainer", skip(self))] - async fn resolve( - &self, - UnpauseContainer { name }: UnpauseContainer, - _: (), - ) -> anyhow::Result { - Ok( - run_komodo_command( - "docker unpause", - None, - format!("docker unpause {name}"), - false, - ) - .await, - ) - } -} - -// - -impl Resolve for State { - #[instrument(name = "StopContainer", skip(self))] - async fn resolve( - &self, - StopContainer { name, signal, time }: StopContainer, - _: (), - ) -> anyhow::Result { - let command = stop_container_command(&name, signal, time); - let log = - run_komodo_command("docker stop", None, command, false).await; - if log.stderr.contains("unknown flag: --signal") { - let command = stop_container_command(&name, None, time); - let mut log = - run_komodo_command("docker stop", None, command, false).await; - log.stderr = format!( - "old docker version: unable to use --signal flag{}", - if !log.stderr.is_empty() { - format!("\n\n{}", log.stderr) - } else { - String::new() - } - ); - Ok(log) - } else { - Ok(log) - } - } -} - -// - -impl Resolve for State { - #[instrument(name = "RemoveContainer", skip(self))] - async fn resolve( - &self, - RemoveContainer { name, signal, time }: RemoveContainer, - _: (), - ) -> anyhow::Result { - let stop_command = stop_container_command(&name, signal, time); - let command = - format!("{stop_command} && docker container rm {name}"); - let log = run_komodo_command( - "docker stop and remove", - None, - command, - false, - ) - .await; - if log.stderr.contains("unknown flag: --signal") { - let stop_command = stop_container_command(&name, None, time); - let command = - format!("{stop_command} && docker container rm {name}"); - let mut log = - run_komodo_command("docker stop", None, command, false).await; - log.stderr = format!( - "old docker version: unable to use --signal flag{}", - if !log.stderr.is_empty() { - format!("\n\n{}", log.stderr) - } else { - String::new() - } - ); - Ok(log) - } else { - Ok(log) - } - } -} - -// - -impl Resolve for State { - #[instrument(name = "RenameContainer", skip(self))] - async fn resolve( - &self, - RenameContainer { - curr_name, - new_name, - }: RenameContainer, - _: (), - ) -> anyhow::Result { - let new = to_komodo_name(&new_name); - let command = format!("docker rename {curr_name} {new}"); - Ok( - run_komodo_command("docker rename", None, command, false).await, - ) - } -} - -// - -impl Resolve for State { - #[instrument(name = "PruneContainers", skip(self))] - async fn resolve( - &self, - _: PruneContainers, - _: (), - ) -> anyhow::Result { - let command = String::from("docker container prune -f"); - Ok( - run_komodo_command("prune containers", None, command, false) + run_komodo_command("Get container log grep", None, command) .await, ) } @@ -328,13 +75,189 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "StartAllContainers", skip(self))] +impl Resolve for GetContainerStats { + #[instrument(name = "GetContainerStats", level = "debug")] async fn resolve( - &self, - StartAllContainers {}: StartAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result { + let error = anyhow!("no stats matching {}", self.name); + let mut stats = container_stats(Some(self.name)).await?; + let stats = stats.pop().ok_or(error)?; + Ok(stats) + } +} + +// + +impl Resolve for GetContainerStatsList { + #[instrument(name = "GetContainerStatsList", level = "debug")] + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result> { + Ok(container_stats(None).await?) + } +} + +// ========= +// ACTIONS +// ========= + +impl Resolve for StartContainer { + #[instrument(name = "StartContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok( + run_komodo_command( + "Docker Start", + None, + format!("docker start {}", self.name), + ) + .await, + ) + } +} + +// + +impl Resolve for RestartContainer { + #[instrument(name = "RestartContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok( + run_komodo_command( + "Docker Restart", + None, + format!("docker restart {}", self.name), + ) + .await, + ) + } +} + +// + +impl Resolve for PauseContainer { + #[instrument(name = "PauseContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok( + run_komodo_command( + "Docker Pause", + None, + format!("docker pause {}", self.name), + ) + .await, + ) + } +} + +impl Resolve for UnpauseContainer { + #[instrument(name = "UnpauseContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok( + run_komodo_command( + "Docker Unpause", + None, + format!("docker unpause {}", self.name), + ) + .await, + ) + } +} + +// + +impl Resolve for StopContainer { + #[instrument(name = "StopContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let StopContainer { name, signal, time } = self; + let command = stop_container_command(&name, signal, time); + let log = run_komodo_command("Docker Stop", None, command).await; + if log.stderr.contains("unknown flag: --signal") { + let command = stop_container_command(&name, None, time); + let mut log = + run_komodo_command("Docker Stop", None, command).await; + log.stderr = format!( + "old docker version: unable to use --signal flag{}", + if !log.stderr.is_empty() { + format!("\n\n{}", log.stderr) + } else { + String::new() + } + ); + Ok(log) + } else { + Ok(log) + } + } +} + +// + +impl Resolve for RemoveContainer { + #[instrument(name = "RemoveContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let RemoveContainer { name, signal, time } = self; + let stop_command = stop_container_command(&name, signal, time); + let command = + format!("{stop_command} && docker container rm {name}"); + let log = + run_komodo_command("Docker Stop and Remove", None, command) + .await; + if log.stderr.contains("unknown flag: --signal") { + let stop_command = stop_container_command(&name, None, time); + let command = + format!("{stop_command} && docker container rm {name}"); + let mut log = + run_komodo_command("Docker Stop and Remove", None, command) + .await; + log.stderr = format!( + "Old docker version: unable to use --signal flag{}", + if !log.stderr.is_empty() { + format!("\n\n{}", log.stderr) + } else { + String::new() + } + ); + Ok(log) + } else { + Ok(log) + } + } +} + +// + +impl Resolve for RenameContainer { + #[instrument(name = "RenameContainer")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let RenameContainer { + curr_name, + new_name, + } = self; + let new = to_komodo_name(&new_name); + let command = format!("docker rename {curr_name} {new}"); + Ok(run_komodo_command("Docker Rename", None, command).await) + } +} + +// + +impl Resolve for PruneContainers { + #[instrument(name = "PruneContainers", skip_all)] + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = String::from("docker container prune -f"); + Ok(run_komodo_command("Prune Containers", None, command).await) + } +} + +// + +impl Resolve for StartAllContainers { + #[instrument(name = "StartAllContainers", skip_all)] + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -346,8 +269,7 @@ impl Resolve for State { } let command = format!("docker start {name}"); Some(async move { - run_komodo_command(&command.clone(), None, command, false) - .await + run_komodo_command(&command.clone(), None, command).await }) }, ); @@ -357,13 +279,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "RestartAllContainers", skip(self))] +impl Resolve for RestartAllContainers { + #[instrument(name = "RestartAllContainers", skip_all)] async fn resolve( - &self, - RestartAllContainers {}: RestartAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -375,8 +296,7 @@ impl Resolve for State { } let command = format!("docker restart {name}"); Some(async move { - run_komodo_command(&command.clone(), None, command, false) - .await + run_komodo_command(&command.clone(), None, command).await }) }, ); @@ -386,13 +306,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "PauseAllContainers", skip(self))] +impl Resolve for PauseAllContainers { + #[instrument(name = "PauseAllContainers", skip_all)] async fn resolve( - &self, - PauseAllContainers {}: PauseAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -404,8 +323,7 @@ impl Resolve for State { } let command = format!("docker pause {name}"); Some(async move { - run_komodo_command(&command.clone(), None, command, false) - .await + run_komodo_command(&command.clone(), None, command).await }) }, ); @@ -415,13 +333,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "UnpauseAllContainers", skip(self))] +impl Resolve for UnpauseAllContainers { + #[instrument(name = "UnpauseAllContainers", skip_all)] async fn resolve( - &self, - UnpauseAllContainers {}: UnpauseAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -433,8 +350,7 @@ impl Resolve for State { } let command = format!("docker unpause {name}"); Some(async move { - run_komodo_command(&command.clone(), None, command, false) - .await + run_komodo_command(&command.clone(), None, command).await }) }, ); @@ -444,13 +360,12 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "StopAllContainers", skip(self))] +impl Resolve for StopAllContainers { + #[instrument(name = "StopAllContainers", skip_all)] async fn resolve( - &self, - StopAllContainers {}: StopAllContainers, - _: (), - ) -> anyhow::Result> { + self, + _: &super::Args, + ) -> serror::Result> { let containers = docker_client() .list_containers() .await @@ -465,7 +380,6 @@ impl Resolve for State { &format!("docker stop {name}"), None, stop_container_command(name, None, None), - false, ) .await }) diff --git a/bin/periphery/src/api/deploy.rs b/bin/periphery/src/api/deploy.rs index 8b9d614c8..f123f4c85 100644 --- a/bin/periphery/src/api/deploy.rs +++ b/bin/periphery/src/api/deploy.rs @@ -1,15 +1,17 @@ use anyhow::Context; -use command::run_komodo_command; +use command::{ + run_komodo_command, run_komodo_command_with_interpolation, +}; use formatting::format_serror; use komodo_client::{ entities::{ + EnvironmentVar, deployment::{ - conversions_from_str, extract_registry_domain, Conversion, - Deployment, DeploymentConfig, DeploymentImage, RestartMode, + Conversion, Deployment, DeploymentConfig, DeploymentImage, + RestartMode, conversions_from_str, extract_registry_domain, }, environment_vars_from_str, to_komodo_name, update::Log, - EnvironmentVar, }, parsers::QUOTE_PATTERN, }; @@ -20,25 +22,26 @@ use crate::{ config::periphery_config, docker::{docker_login, pull_image}, helpers::{parse_extra_args, parse_labels}, - State, }; -impl Resolve for State { +impl Resolve for Deploy { #[instrument( name = "Deploy", - skip(self, core_replacers, registry_token) + skip_all, + fields( + stack = &self.deployment.name, + stop_signal = format!("{:?}", self.stop_signal), + stop_time = self.stop_time, + ) )] - async fn resolve( - &self, - Deploy { + async fn resolve(self, _: &super::Args) -> serror::Result { + let Deploy { deployment, stop_signal, stop_time, registry_token, replacers: core_replacers, - }: Deploy, - _: (), - ) -> anyhow::Result { + } = self; let image = if let DeploymentImage::Image { image } = &deployment.config.image { @@ -73,16 +76,13 @@ impl Resolve for State { let _ = pull_image(image).await; debug!("image pulled"); - let _ = State - .resolve( - RemoveContainer { - name: deployment.name.clone(), - signal: stop_signal, - time: stop_time, - }, - (), - ) - .await; + let _ = (RemoveContainer { + name: deployment.name.clone(), + signal: stop_signal, + time: stop_time, + }) + .resolve(&super::Args) + .await; debug!("container stopped and removed"); let command = docker_run_command(&deployment, image) @@ -90,33 +90,22 @@ impl Resolve for State { debug!("docker run command: {command}"); if deployment.config.skip_secret_interp { - Ok(run_komodo_command("docker run", None, command, false).await) + Ok(run_komodo_command("Docker Run", None, command).await) } else { - let command = svi::interpolate_variables( - &command, + match run_komodo_command_with_interpolation( + "Docker Run", + None, + command, + false, &periphery_config().secrets, - svi::Interpolator::DoubleBrackets, - true, + &core_replacers, ) - .context( - "failed to interpolate secrets into docker run command", - ); - - let (command, mut replacers) = match command { - Ok(res) => res, - Err(e) => { - return Ok(Log::error("docker run", format!("{e:?}"))); - } - }; - - replacers.extend(core_replacers); - let mut log = - run_komodo_command("docker run", None, command, false).await; - log.command = svi::replace_in_string(&log.command, &replacers); - log.stdout = svi::replace_in_string(&log.stdout, &replacers); - log.stderr = svi::replace_in_string(&log.stderr, &replacers); - - Ok(log) + .await + { + Some(log) => Ok(log), + // The None case can not be reached, as the command is always non-empty + None => unreachable!(), + } } } } @@ -160,7 +149,9 @@ fn docker_run_command( ); let command = parse_command(command); let extra_args = parse_extra_args(extra_args); - let command = format!("docker run -d --name {name}{ports}{volumes}{network}{restart}{environment}{labels}{extra_args} {image}{command}"); + let command = format!( + "docker run -d --name {name}{ports}{volumes}{network}{restart}{environment}{labels}{extra_args} {image}{command}" + ); Ok(command) } diff --git a/bin/periphery/src/api/git.rs b/bin/periphery/src/api/git.rs index 08b81ec24..0b7ea42d3 100644 --- a/bin/periphery/src/api/git.rs +++ b/bin/periphery/src/api/git.rs @@ -1,48 +1,60 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use git::GitRes; -use komodo_client::entities::{update::Log, CloneArgs, LatestCommit}; +use komodo_client::entities::{CloneArgs, LatestCommit, update::Log}; use periphery_client::api::git::{ CloneRepo, DeleteRepo, GetLatestCommit, PullOrCloneRepo, PullRepo, RenameRepo, RepoActionResponse, }; use resolver_api::Resolve; +use std::path::PathBuf; use tokio::fs; -use crate::{config::periphery_config, State}; +use crate::config::periphery_config; -impl Resolve for State { +impl Resolve for GetLatestCommit { + #[instrument(name = "CloneRepo", level = "debug")] async fn resolve( - &self, - GetLatestCommit { name }: GetLatestCommit, - _: (), - ) -> anyhow::Result { - let repo_path = periphery_config().repo_dir.join(name); + self, + _: &super::Args, + ) -> serror::Result { + let repo_path = match self.path { + Some(p) => PathBuf::from(p), + None => periphery_config().repo_dir.join(self.name), + }; if !repo_path.is_dir() { - return Err(anyhow!( - "Repo path is not directory. is it cloned?" - )); + return Err( + anyhow!( + "Repo path {} is not directory. is it cloned?", + repo_path.display() + ) + .into(), + ); } - git::get_commit_hash_info(&repo_path).await + Ok(git::get_commit_hash_info(&repo_path).await?) } } -impl Resolve for State { +impl Resolve for CloneRepo { #[instrument( name = "CloneRepo", - skip(self, git_token, environment, replacers) + skip_all, + fields( + args = format!("{:?}", self.args), + skip_secret_interp = self.skip_secret_interp, + ) )] async fn resolve( - &self, - CloneRepo { + self, + _: &super::Args, + ) -> serror::Result { + let CloneRepo { args, git_token, environment, env_file_path, skip_secret_interp, replacers, - }: CloneRepo, - _: (), - ) -> anyhow::Result { + } = self; let CloneArgs { provider, account, .. } = &args; @@ -81,28 +93,33 @@ impl Resolve for State { } }, ) + .map_err(Into::into) } } // -impl Resolve for State { +impl Resolve for PullRepo { #[instrument( name = "PullRepo", - skip(self, git_token, environment, replacers) + skip_all, + fields( + args = format!("{:?}", self.args), + skip_secret_interp = self.skip_secret_interp, + ) )] async fn resolve( - &self, - PullRepo { + self, + _: &super::Args, + ) -> serror::Result { + let PullRepo { args, git_token, environment, env_file_path, skip_secret_interp, replacers, - }: PullRepo, - _: (), - ) -> anyhow::Result { + } = self; let CloneArgs { provider, account, .. } = &args; @@ -141,28 +158,33 @@ impl Resolve for State { } }, ) + .map_err(Into::into) } } // -impl Resolve for State { +impl Resolve for PullOrCloneRepo { #[instrument( name = "PullOrCloneRepo", - skip(self, git_token, environment, replacers) + skip_all, + fields( + args = format!("{:?}", self.args), + skip_secret_interp = self.skip_secret_interp, + ) )] async fn resolve( - &self, - PullOrCloneRepo { + self, + _: &super::Args, + ) -> serror::Result { + let PullOrCloneRepo { args, git_token, environment, env_file_path, skip_secret_interp, replacers, - }: PullOrCloneRepo, - _: (), - ) -> anyhow::Result { + } = self; let CloneArgs { provider, account, .. } = &args; @@ -201,21 +223,19 @@ impl Resolve for State { } }, ) + .map_err(Into::into) } } // -impl Resolve for State { - #[instrument(name = "RenameRepo", skip(self))] - async fn resolve( - &self, - RenameRepo { +impl Resolve for RenameRepo { + #[instrument(name = "RenameRepo")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let RenameRepo { curr_name, new_name, - }: RenameRepo, - _: (), - ) -> anyhow::Result { + } = self; let renamed = fs::rename( periphery_config().repo_dir.join(&curr_name), periphery_config().repo_dir.join(&new_name), @@ -231,13 +251,10 @@ impl Resolve for State { // -impl Resolve for State { - #[instrument(name = "DeleteRepo", skip(self))] - async fn resolve( - &self, - DeleteRepo { name }: DeleteRepo, - _: (), - ) -> anyhow::Result { +impl Resolve for DeleteRepo { + #[instrument(name = "DeleteRepo")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let DeleteRepo { name } = self; // If using custom clone path, it will be passed by core instead of name. // So the join will resolve to just the absolute path. let deleted = diff --git a/bin/periphery/src/api/image.rs b/bin/periphery/src/api/image.rs index e226b7672..a780e8d90 100644 --- a/bin/periphery/src/api/image.rs +++ b/bin/periphery/src/api/image.rs @@ -11,34 +11,26 @@ use komodo_client::entities::{ use periphery_client::api::image::*; use resolver_api::Resolve; -use crate::{ - docker::{docker_client, docker_login}, - State, -}; +use crate::docker::{docker_client, docker_login}; // -impl Resolve for State { - #[instrument(name = "InspectImage", level = "debug", skip(self))] - async fn resolve( - &self, - InspectImage { name }: InspectImage, - _: (), - ) -> anyhow::Result { - docker_client().inspect_image(&name).await +impl Resolve for InspectImage { + #[instrument(name = "InspectImage", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok(docker_client().inspect_image(&self.name).await?) } } // -impl Resolve for State { - #[instrument(name = "ImageHistory", level = "debug", skip(self))] +impl Resolve for ImageHistory { + #[instrument(name = "ImageHistory", level = "debug")] async fn resolve( - &self, - ImageHistory { name }: ImageHistory, - _: (), - ) -> anyhow::Result> { - docker_client().image_history(&name).await + self, + _: &super::Args, + ) -> serror::Result> { + Ok(docker_client().image_history(&self.name).await?) } } @@ -53,17 +45,14 @@ fn pull_cache() -> &'static TimeoutCache { PULL_CACHE.get_or_init(Default::default) } -impl Resolve for State { - #[instrument(name = "PullImage", skip(self))] - async fn resolve( - &self, - PullImage { +impl Resolve for PullImage { + #[instrument(name = "PullImage", skip_all, fields(name = &self.name))] + async fn resolve(self, _: &super::Args) -> serror::Result { + let PullImage { name, account, token, - }: PullImage, - _: (), - ) -> anyhow::Result { + } = self; // Acquire the image lock let lock = pull_cache().get_lock(name.clone()).await; @@ -74,7 +63,7 @@ impl Resolve for State { // Early return from cache if lasted pulled with PULL_TIMEOUT if locked.last_ts + PULL_TIMEOUT > komodo_timestamp() { - return locked.clone_res(); + return locked.clone_res().map_err(Into::into); } let res = async { @@ -86,10 +75,9 @@ impl Resolve for State { .await?; anyhow::Ok( run_komodo_command( - "docker pull", + "Docker Pull", None, format!("docker pull {name}"), - false, ) .await, ) @@ -100,34 +88,26 @@ impl Resolve for State { // then immediately also use this same result. locked.set(&res, komodo_timestamp()); - res + res.map_err(Into::into) } } // -impl Resolve for State { - #[instrument(name = "DeleteImage", skip(self))] - async fn resolve( - &self, - DeleteImage { name }: DeleteImage, - _: (), - ) -> anyhow::Result { - let command = format!("docker image rm {name}"); - Ok(run_komodo_command("delete image", None, command, false).await) +impl Resolve for DeleteImage { + #[instrument(name = "DeleteImage")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = format!("docker image rm {}", self.name); + Ok(run_komodo_command("Delete Image", None, command).await) } } // -impl Resolve for State { - #[instrument(name = "PruneImages", skip(self))] - async fn resolve( - &self, - _: PruneImages, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneImages { + #[instrument(name = "PruneImages")] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker image prune -a -f"); - Ok(run_komodo_command("prune images", None, command, false).await) + Ok(run_komodo_command("Prune Images", None, command).await) } } diff --git a/bin/periphery/src/api/mod.rs b/bin/periphery/src/api/mod.rs index e4e9dea0e..b8a090200 100644 --- a/bin/periphery/src/api/mod.rs +++ b/bin/periphery/src/api/mod.rs @@ -2,24 +2,23 @@ use anyhow::Context; use command::run_komodo_command; use derive_variants::EnumVariants; use futures::TryFutureExt; -use komodo_client::entities::{update::Log, SystemCommand}; -use periphery_client::api::{ - build::*, compose::*, container::*, git::*, image::*, network::*, - stats::*, volume::*, GetDockerLists, GetDockerListsResponse, - GetHealth, GetVersion, GetVersionResponse, ListDockerRegistries, - ListGitProviders, ListSecrets, PruneSystem, RunCommand, +use komodo_client::entities::{ + SystemCommand, + config::{DockerRegistry, GitProvider}, + update::Log, }; -use resolver_api::{derive::Resolver, Resolve, ResolveToString}; +use periphery_client::api::{ + GetDockerLists, GetDockerListsResponse, GetHealth, + GetHealthResponse, GetVersion, GetVersionResponse, + ListDockerRegistries, ListGitProviders, ListSecrets, PruneSystem, + RunCommand, build::*, compose::*, container::*, git::*, image::*, + network::*, stats::*, volume::*, +}; +use resolver_api::Resolve; +use response::Response; use serde::{Deserialize, Serialize}; -use crate::{ - config::{ - docker_registries_response, git_providers_response, - secrets_response, - }, - docker::docker_client, - State, -}; +use crate::{config::periphery_config, docker::docker_client}; mod build; mod compose; @@ -31,32 +30,29 @@ mod network; mod stats; mod volume; +pub struct Args; + #[derive( - Serialize, Deserialize, Debug, Clone, Resolver, EnumVariants, + Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants, )] +#[args(Args)] +#[response(Response)] +#[error(serror::Error)] #[variant_derive(Debug)] #[serde(tag = "type", content = "params")] -#[resolver_target(State)] #[allow(clippy::enum_variant_names, clippy::large_enum_variant)] pub enum PeripheryRequest { GetVersion(GetVersion), - #[to_string_resolver] GetHealth(GetHealth), // Config (Read) - #[to_string_resolver] ListGitProviders(ListGitProviders), - #[to_string_resolver] ListDockerRegistries(ListDockerRegistries), - #[to_string_resolver] ListSecrets(ListSecrets), // Stats / Info (Read) - #[to_string_resolver] GetSystemInformation(GetSystemInformation), - #[to_string_resolver] GetSystemStats(GetSystemStats), - #[to_string_resolver] GetSystemProcesses(GetSystemProcesses), GetLatestCommit(GetLatestCommit), @@ -76,8 +72,8 @@ pub enum PeripheryRequest { // Compose (Read) GetComposeContentsOnHost(GetComposeContentsOnHost), - GetComposeServiceLog(GetComposeServiceLog), - GetComposeServiceLogSearch(GetComposeServiceLogSearch), + GetComposeLog(GetComposeLog), + GetComposeLogSearch(GetComposeLogSearch), // Compose (Write) WriteComposeContentsToHost(WriteComposeContentsToHost), @@ -142,26 +138,24 @@ pub enum PeripheryRequest { // -impl ResolveToString for State { - #[instrument(name = "GetHealth", level = "debug", skip(self))] - async fn resolve_to_string( - &self, - _: GetHealth, - _: (), - ) -> anyhow::Result { - Ok(String::from("{}")) +impl Resolve for GetHealth { + #[instrument(name = "GetHealth", level = "debug", skip_all)] + async fn resolve( + self, + _: &Args, + ) -> serror::Result { + Ok(GetHealthResponse {}) } } // -impl Resolve for State { +impl Resolve for GetVersion { #[instrument(name = "GetVersion", level = "debug", skip(self))] async fn resolve( - &self, - _: GetVersion, - _: (), - ) -> anyhow::Result { + self, + _: &Args, + ) -> serror::Result { Ok(GetVersionResponse { version: env!("CARGO_PKG_VERSION").to_string(), }) @@ -170,56 +164,51 @@ impl Resolve for State { // -impl ResolveToString for State { - #[instrument( - name = "ListGitProviders", - level = "debug", - skip(self) - )] - async fn resolve_to_string( - &self, - _: ListGitProviders, - _: (), - ) -> anyhow::Result { - Ok(git_providers_response().clone()) +impl Resolve for ListGitProviders { + #[instrument(name = "ListGitProviders", level = "debug", skip_all)] + async fn resolve( + self, + _: &Args, + ) -> serror::Result> { + Ok(periphery_config().git_providers.clone()) } } -impl ResolveToString for State { +impl Resolve for ListDockerRegistries { #[instrument( name = "ListDockerRegistries", level = "debug", - skip(self) + skip_all )] - async fn resolve_to_string( - &self, - _: ListDockerRegistries, - _: (), - ) -> anyhow::Result { - Ok(docker_registries_response().clone()) + async fn resolve( + self, + _: &Args, + ) -> serror::Result> { + Ok(periphery_config().docker_registries.clone()) } } // -impl ResolveToString for State { - #[instrument(name = "ListSecrets", level = "debug", skip(self))] - async fn resolve_to_string( - &self, - _: ListSecrets, - _: (), - ) -> anyhow::Result { - Ok(secrets_response().clone()) +impl Resolve for ListSecrets { + #[instrument(name = "ListSecrets", level = "debug", skip_all)] + async fn resolve(self, _: &Args) -> serror::Result> { + Ok( + periphery_config() + .secrets + .keys() + .cloned() + .collect::>(), + ) } } -impl Resolve for State { - #[instrument(name = "GetDockerLists", level = "debug", skip(self))] +impl Resolve for GetDockerLists { + #[instrument(name = "GetDockerLists", level = "debug", skip_all)] async fn resolve( - &self, - GetDockerLists {}: GetDockerLists, - _: (), - ) -> anyhow::Result { + self, + _: &Args, + ) -> serror::Result { let docker = docker_client(); let containers = docker.list_containers().await.map_err(Into::into); @@ -232,7 +221,9 @@ impl Resolve for State { docker.list_networks(_containers).map_err(Into::into), docker.list_images(_containers).map_err(Into::into), docker.list_volumes(_containers).map_err(Into::into), - self.resolve(ListComposeProjects {}, ()).map_err(Into::into) + ListComposeProjects {} + .resolve(&Args) + .map_err(|e| e.error.into()) ); Ok(GetDockerListsResponse { containers, @@ -244,36 +235,30 @@ impl Resolve for State { } } -impl Resolve for State { - #[instrument(name = "RunCommand", skip(self))] - async fn resolve( - &self, - RunCommand { +impl Resolve for RunCommand { + #[instrument(name = "RunCommand")] + async fn resolve(self, _: &Args) -> serror::Result { + let RunCommand { command: SystemCommand { path, command }, - }: RunCommand, - _: (), - ) -> anyhow::Result { - tokio::spawn(async move { + } = self; + let res = tokio::spawn(async move { let command = if path.is_empty() { command } else { format!("cd {path} && {command}") }; - run_komodo_command("run command", None, command, false).await + run_komodo_command("run command", None, command).await }) .await - .context("failure in spawned task") + .context("failure in spawned task")?; + Ok(res) } } -impl Resolve for State { - #[instrument(name = "PruneSystem", skip(self))] - async fn resolve( - &self, - PruneSystem {}: PruneSystem, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneSystem { + #[instrument(name = "PruneSystem", skip_all)] + async fn resolve(self, _: &Args) -> serror::Result { let command = String::from("docker system prune -a -f --volumes"); - Ok(run_komodo_command("prune system", None, command, false).await) + Ok(run_komodo_command("Prune System", None, command).await) } } diff --git a/bin/periphery/src/api/network.rs b/bin/periphery/src/api/network.rs index f13edcf43..e546b6eef 100644 --- a/bin/periphery/src/api/network.rs +++ b/bin/periphery/src/api/network.rs @@ -5,72 +5,48 @@ use komodo_client::entities::{ use periphery_client::api::network::*; use resolver_api::Resolve; -use crate::{docker::docker_client, State}; +use crate::docker::docker_client; // -impl Resolve for State { - #[instrument(name = "InspectNetwork", level = "debug", skip(self))] - async fn resolve( - &self, - InspectNetwork { name }: InspectNetwork, - _: (), - ) -> anyhow::Result { - docker_client().inspect_network(&name).await +impl Resolve for InspectNetwork { + #[instrument(name = "InspectNetwork", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok(docker_client().inspect_network(&self.name).await?) } } // -impl Resolve for State { +impl Resolve for CreateNetwork { #[instrument(name = "CreateNetwork", skip(self))] - async fn resolve( - &self, - CreateNetwork { name, driver }: CreateNetwork, - _: (), - ) -> anyhow::Result { + async fn resolve(self, _: &super::Args) -> serror::Result { + let CreateNetwork { name, driver } = self; let driver = match driver { Some(driver) => format!(" -d {driver}"), None => String::new(), }; let command = format!("docker network create{driver} {name}"); - Ok( - run_komodo_command("create network", None, command, false) - .await, - ) + Ok(run_komodo_command("Create Network", None, command).await) } } // -impl Resolve for State { +impl Resolve for DeleteNetwork { #[instrument(name = "DeleteNetwork", skip(self))] - async fn resolve( - &self, - DeleteNetwork { name }: DeleteNetwork, - _: (), - ) -> anyhow::Result { - let command = format!("docker network rm {name}"); - Ok( - run_komodo_command("delete network", None, command, false) - .await, - ) + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = format!("docker network rm {}", self.name); + Ok(run_komodo_command("Delete Network", None, command).await) } } // -impl Resolve for State { +impl Resolve for PruneNetworks { #[instrument(name = "PruneNetworks", skip(self))] - async fn resolve( - &self, - _: PruneNetworks, - _: (), - ) -> anyhow::Result { + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker network prune -f"); - Ok( - run_komodo_command("prune networks", None, command, false) - .await, - ) + Ok(run_komodo_command("Prune Networks", None, command).await) } } diff --git a/bin/periphery/src/api/stats.rs b/bin/periphery/src/api/stats.rs index f8df366d1..63cd2cee2 100644 --- a/bin/periphery/src/api/stats.rs +++ b/bin/periphery/src/api/stats.rs @@ -1,59 +1,47 @@ -use anyhow::Context; +use komodo_client::entities::stats::{ + SystemInformation, SystemProcess, SystemStats, +}; use periphery_client::api::stats::{ GetSystemInformation, GetSystemProcesses, GetSystemStats, }; -use resolver_api::ResolveToString; +use resolver_api::Resolve; -use crate::{stats::stats_client, State}; +use crate::stats::stats_client; -impl ResolveToString for State { +impl Resolve for GetSystemInformation { #[instrument( name = "GetSystemInformation", level = "debug", - skip(self) + skip_all )] - async fn resolve_to_string( - &self, - _: GetSystemInformation, - _: (), - ) -> anyhow::Result { - let info = &stats_client().read().await.info; - serde_json::to_string(info) - .context("failed to serialize response to string") + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result { + Ok(stats_client().read().await.info.clone()) } } // -impl ResolveToString for State { - #[instrument(name = "GetSystemStats", level = "debug", skip(self))] - async fn resolve_to_string( - &self, - _: GetSystemStats, - _: (), - ) -> anyhow::Result { - let stats = &stats_client().read().await.stats; - serde_json::to_string(stats) - .context("failed to serialize response to string") +impl Resolve for GetSystemStats { + #[instrument(name = "GetSystemStats", level = "debug", skip_all)] + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result { + Ok(stats_client().read().await.stats.clone()) } } // -impl ResolveToString for State { - #[instrument( - name = "GetSystemProcesses", - level = "debug", - skip(self) - )] - async fn resolve_to_string( - &self, - _: GetSystemProcesses, - _: (), - ) -> anyhow::Result { - let stats = &stats_client().read().await.get_processes(); - serde_json::to_string(&stats) - .context("failed to serialize response to string") +impl Resolve for GetSystemProcesses { + #[instrument(name = "GetSystemProcesses", level = "debug")] + async fn resolve( + self, + _: &super::Args, + ) -> serror::Result> { + Ok(stats_client().read().await.get_processes()) } } - diff --git a/bin/periphery/src/api/volume.rs b/bin/periphery/src/api/volume.rs index 622fe6f4b..523e2ee78 100644 --- a/bin/periphery/src/api/volume.rs +++ b/bin/periphery/src/api/volume.rs @@ -3,49 +3,33 @@ use komodo_client::entities::{docker::volume::Volume, update::Log}; use periphery_client::api::volume::*; use resolver_api::Resolve; -use crate::{docker::docker_client, State}; +use crate::docker::docker_client; // -impl Resolve for State { - #[instrument(name = "InspectVolume", level = "debug", skip(self))] - async fn resolve( - &self, - InspectVolume { name }: InspectVolume, - _: (), - ) -> anyhow::Result { - docker_client().inspect_volume(&name).await +impl Resolve for InspectVolume { + #[instrument(name = "InspectVolume", level = "debug")] + async fn resolve(self, _: &super::Args) -> serror::Result { + Ok(docker_client().inspect_volume(&self.name).await?) } } // -impl Resolve for State { - #[instrument(name = "DeleteVolume", skip(self))] - async fn resolve( - &self, - DeleteVolume { name }: DeleteVolume, - _: (), - ) -> anyhow::Result { - let command = format!("docker volume rm {name}"); - Ok( - run_komodo_command("delete volume", None, command, false).await, - ) +impl Resolve for DeleteVolume { + #[instrument(name = "DeleteVolume")] + async fn resolve(self, _: &super::Args) -> serror::Result { + let command = format!("docker volume rm {}", self.name); + Ok(run_komodo_command("Delete Volume", None, command).await) } } // -impl Resolve for State { - #[instrument(name = "PruneVolumes", skip(self))] - async fn resolve( - &self, - _: PruneVolumes, - _: (), - ) -> anyhow::Result { +impl Resolve for PruneVolumes { + #[instrument(name = "PruneVolumes")] + async fn resolve(self, _: &super::Args) -> serror::Result { let command = String::from("docker volume prune -a -f"); - Ok( - run_komodo_command("prune volumes", None, command, false).await, - ) + Ok(run_komodo_command("Prune Volumes", None, command).await) } } diff --git a/bin/periphery/src/compose.rs b/bin/periphery/src/compose.rs index 784670262..2ddd24c17 100644 --- a/bin/periphery/src/compose.rs +++ b/bin/periphery/src/compose.rs @@ -1,18 +1,21 @@ use std::{fmt::Write, path::PathBuf}; -use anyhow::{anyhow, Context}; -use command::run_komodo_command; +use anyhow::{Context, anyhow}; +use command::{ + run_komodo_command, run_komodo_command_multiline, + run_komodo_command_with_interpolation, +}; use formatting::format_serror; use git::environment; use komodo_client::entities::{ - all_logs_success, environment_vars_from_str, + CloneArgs, FileContents, all_logs_success, + environment_vars_from_str, stack::{ ComposeFile, ComposeService, ComposeServiceDeploy, Stack, StackServiceNames, }, to_komodo_name, update::Log, - CloneArgs, FileContents, }; use periphery_client::api::{ compose::ComposeUpResponse, @@ -22,10 +25,8 @@ use resolver_api::Resolve; use tokio::fs; use crate::{ - config::periphery_config, - docker::docker_login, - helpers::{interpolate_variables, parse_extra_args}, - State, + config::periphery_config, docker::docker_login, + helpers::parse_extra_args, }; pub fn docker_compose() -> &'static str { @@ -39,7 +40,7 @@ pub fn docker_compose() -> &'static str { /// If this fn returns Err, the caller of `compose_up` has to write result to the log before return. pub async fn compose_up( stack: Stack, - service: Option, + services: Vec, git_token: Option, registry_token: Option, res: &mut ComposeUpResponse, @@ -48,11 +49,21 @@ pub async fn compose_up( // Write the stack to local disk. For repos, will first delete any existing folder to ensure fresh deploy. // Will also set additional fields on the reponse. // Use the env_file_path in the compose command. - let (run_directory, env_file_path) = + let (run_directory, env_file_path, periphery_replacers) = write_stack(&stack, git_token, &mut *res) .await .context("Failed to write / clone compose file")?; + let replacers = + if let Some(periphery_replacers) = periphery_replacers { + core_replacers + .into_iter() + .chain(periphery_replacers) + .collect() + } else { + core_replacers + }; + // Canonicalize the path to ensure it exists, and is the cleanest path to the run directory. let run_directory = run_directory.canonicalize().context( "Failed to validate run directory on host after stack write (canonicalize error)", @@ -76,32 +87,35 @@ pub async fn compose_up( } } if !res.missing_files.is_empty() { - return Err(anyhow!("A compose file doesn't exist after writing stack. Ensure the run_directory and file_paths are correct.")); + return Err(anyhow!( + "A compose file doesn't exist after writing stack. Ensure the run_directory and file_paths are correct." + )); } for (path, full_path) in &file_paths { - let file_contents = - match fs::read_to_string(&full_path).await.with_context(|| { + let file_contents = match fs::read_to_string(&full_path) + .await + .with_context(|| { format!( "failed to read compose file contents at {full_path:?}" ) }) { - Ok(res) => res, - Err(e) => { - let error = format_serror(&e.into()); - res - .logs - .push(Log::error("read compose file", error.clone())); - // This should only happen for repo stacks, ie remote error - res.remote_errors.push(FileContents { - path: path.to_string(), - contents: error, - }); - return Err(anyhow!( + Ok(res) => res, + Err(e) => { + let error = format_serror(&e.into()); + res + .logs + .push(Log::error("read compose file", error.clone())); + // This should only happen for repo stacks, ie remote error + res.remote_errors.push(FileContents { + path: path.to_string(), + contents: error, + }); + return Err(anyhow!( "failed to read compose file at {full_path:?}, stopping run" )); - } - }; + } + }; res.file_contents.push(FileContents { path: path.to_string(), contents: file_contents, @@ -109,10 +123,12 @@ pub async fn compose_up( } let docker_compose = docker_compose(); - let service_arg = service - .as_ref() - .map(|service| format!(" {service}")) - .unwrap_or_default(); + + let service_args = if services.is_empty() { + String::new() + } else { + format!(" {}", services.join(" ")) + }; let file_args = if stack.config.file_paths.is_empty() { String::from("compose.yaml") @@ -160,13 +176,12 @@ pub async fn compose_up( // after performing interpolation { let command = format!( - "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} config --format json", - ); + "{docker_compose} -p {project_name} -f {file_args}{additional_env_files}{env_file} config", + ); let config_log = run_komodo_command( - "compose build", + "Compose Config", run_directory.as_ref(), command, - false, ) .await; if !config_log.success { @@ -175,8 +190,11 @@ pub async fn compose_up( "Failed to validate compose files, stopping the run." )); } + // Record sanitized compose config output + res.compose_config = + Some(svi::replace_in_string(&config_log.stdout, &replacers)); let compose = - serde_json::from_str::(&config_log.stdout) + serde_yaml::from_str::(&config_log.stdout) .context("Failed to parse compose contents")?; for ( service_name, @@ -221,38 +239,26 @@ pub async fn compose_up( let build_extra_args = parse_extra_args(&stack.config.build_extra_args); let command = format!( - "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} build{build_extra_args}{service_arg}", + "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} build{build_extra_args}{service_args}", ); if stack.config.skip_secret_interp { let log = run_komodo_command( - "compose build", + "Compose Build", run_directory.as_ref(), command, - false, ) .await; res.logs.push(log); - } else { - let (command, mut replacers) = svi::interpolate_variables( - &command, - &periphery_config().secrets, - svi::Interpolator::DoubleBrackets, - true, - ).context("failed to interpolate periphery secrets into stack build command")?; - replacers.extend(core_replacers.clone()); - - let mut log = run_komodo_command( - "compose build", - run_directory.as_ref(), - command, - false, - ) - .await; - - log.command = svi::replace_in_string(&log.command, &replacers); - log.stdout = svi::replace_in_string(&log.stdout, &replacers); - log.stderr = svi::replace_in_string(&log.stderr, &replacers); - + } else if let Some(log) = run_komodo_command_with_interpolation( + "Compose Build", + run_directory.as_ref(), + command, + false, + &periphery_config().secrets, + &replacers, + ) + .await + { res.logs.push(log); } @@ -268,12 +274,11 @@ pub async fn compose_up( // Pull images before destroying to minimize downtime. // If this fails, do not continue. let log = run_komodo_command( - "compose pull", + "Compose Pull", run_directory.as_ref(), format!( - "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} pull{service_arg}", + "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} pull{service_args}", ), - false, ) .await; @@ -286,58 +291,33 @@ pub async fn compose_up( } } - if !stack.config.pre_deploy.command.is_empty() { - let pre_deploy_path = - run_directory.join(&stack.config.pre_deploy.path); - if !stack.config.skip_secret_interp { - let (full_command, mut replacers) = - interpolate_variables(&stack.config.pre_deploy.command) - .context( - "failed to interpolate secrets into pre_deploy command", - )?; - replacers.extend(core_replacers.to_owned()); - let mut pre_deploy_log = run_komodo_command( - "pre deploy", - pre_deploy_path.as_ref(), - &full_command, - true, - ) - .await; - - pre_deploy_log.command = - svi::replace_in_string(&pre_deploy_log.command, &replacers); - pre_deploy_log.stdout = - svi::replace_in_string(&pre_deploy_log.stdout, &replacers); - pre_deploy_log.stderr = - svi::replace_in_string(&pre_deploy_log.stderr, &replacers); - - tracing::debug!( - "run Stack pre_deploy command | command: {} | cwd: {:?}", - pre_deploy_log.command, - pre_deploy_path - ); - - res.logs.push(pre_deploy_log); - } else { - let pre_deploy_log = run_komodo_command( - "pre deploy", - pre_deploy_path.as_ref(), - &stack.config.pre_deploy.command, - true, - ) - .await; - tracing::debug!( - "run Stack pre_deploy command | command: {} | cwd: {:?}", - &stack.config.pre_deploy.command, - pre_deploy_path - ); - res.logs.push(pre_deploy_log); - } - if !all_logs_success(&res.logs) { - return Err(anyhow!( - "Failed at running pre_deploy command, stopping the run." - )); - } + // Pre deploy command + let pre_deploy_path = + run_directory.join(&stack.config.pre_deploy.path); + if let Some(log) = if stack.config.skip_secret_interp { + run_komodo_command_multiline( + "Pre Deploy", + pre_deploy_path.as_ref(), + &stack.config.pre_deploy.command, + ) + .await + } else { + run_komodo_command_with_interpolation( + "Pre Deploy", + pre_deploy_path.as_ref(), + &stack.config.pre_deploy.command, + true, + &periphery_config().secrets, + &replacers, + ) + .await + } { + res.logs.push(log); + } + if !all_logs_success(&res.logs) { + return Err(anyhow!( + "Failed at running pre_deploy command, stopping the run." + )); } if stack.config.destroy_before_deploy @@ -346,7 +326,7 @@ pub async fn compose_up( { // Take down the existing containers. // This one tries to use the previously deployed service name, to ensure the right stack is taken down. - compose_down(&last_project_name, service, res) + compose_down(&last_project_name, &services, res) .await .context("failed to destroy existing containers")?; } @@ -354,44 +334,64 @@ pub async fn compose_up( // Run compose up let extra_args = parse_extra_args(&stack.config.extra_args); let command = format!( - "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} up -d{extra_args}{service_arg}", + "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} up -d{extra_args}{service_args}", ); let log = if stack.config.skip_secret_interp { - run_komodo_command( - "compose up", + run_komodo_command("Compose Up", run_directory.as_ref(), command) + .await + } else { + match run_komodo_command_with_interpolation( + "Compose Up", run_directory.as_ref(), command, false, + &periphery_config().secrets, + &replacers, ) .await - } else { - let (command, mut replacers) = svi::interpolate_variables( - &command, - &periphery_config().secrets, - svi::Interpolator::DoubleBrackets, - true, - ).context("failed to interpolate periphery secrets into stack run command")?; - replacers.extend(core_replacers); - - let mut log = run_komodo_command( - "compose up", - run_directory.as_ref(), - command, - false, - ) - .await; - - log.command = svi::replace_in_string(&log.command, &replacers); - log.stdout = svi::replace_in_string(&log.stdout, &replacers); - log.stderr = svi::replace_in_string(&log.stderr, &replacers); - - log + { + Some(log) => log, + // The command is definitely non-empty, the result will never be None. + None => unreachable!(), + } }; res.deployed = log.success; + + // push the compose up command logs to keep the correct order res.logs.push(log); + if res.deployed { + let post_deploy_path = + run_directory.join(&stack.config.post_deploy.path); + if let Some(log) = if stack.config.skip_secret_interp { + run_komodo_command_multiline( + "Post Deploy", + post_deploy_path.as_ref(), + &stack.config.post_deploy.command, + ) + .await + } else { + run_komodo_command_with_interpolation( + "Post Deploy", + post_deploy_path.as_ref(), + &stack.config.post_deploy.command, + true, + &periphery_config().secrets, + &replacers, + ) + .await + } { + res.logs.push(log) + } + if !all_logs_success(&res.logs) { + return Err(anyhow!( + "Failed at running post_deploy command, stopping the run." + )); + } + } + Ok(()) } @@ -402,7 +402,7 @@ pub trait WriteStackRes { fn set_commit_message(&mut self, _message: Option) {} } -impl<'a> WriteStackRes for &'a mut ComposeUpResponse { +impl WriteStackRes for &mut ComposeUpResponse { fn logs(&mut self) -> &mut Vec { &mut self.logs } @@ -418,12 +418,17 @@ impl<'a> WriteStackRes for &'a mut ComposeUpResponse { } /// Either writes the stack file_contents to a file, or clones the repo. -/// Returns (run_directory, env_file_path) +/// Performs variable replacement on env and writes file. +/// Returns (run_directory, env_file_path, periphery_replacers) pub async fn write_stack( stack: &Stack, git_token: Option, mut res: impl WriteStackRes, -) -> anyhow::Result<(PathBuf, Option<&str>)> { +) -> anyhow::Result<( + PathBuf, + Option<&str>, + Option>, +)> { let root = periphery_config() .stack_dir .join(to_komodo_name(&stack.name)); @@ -432,42 +437,63 @@ pub async fn write_stack( // Cannot use 'canonicalize' yet as directory may not exist. let run_directory = run_directory.components().collect::(); - let env_vars = environment_vars_from_str(&stack.config.environment) + let (env_interpolated, env_replacers) = + if stack.config.skip_secret_interp { + (stack.config.environment.clone(), None) + } else { + let (environment, replacers) = svi::interpolate_variables( + &stack.config.environment, + &periphery_config().secrets, + svi::Interpolator::DoubleBrackets, + true, + ) + .context( + "Failed to interpolate Periphery secrets into Environment", + )?; + (environment, Some(replacers)) + }; + match &env_replacers { + Some(replacers) if !replacers.is_empty() => { + res.logs().push(Log::simple( + "Interpolate - Environment (Periphery)", + replacers + .iter() + .map(|(_, variable)| format!("replaced: {variable}")) + .collect::>() + .join("\n"), + )) + } + _ => {} + } + + let env_vars = environment_vars_from_str(&env_interpolated) .context("Invalid environment variables")?; if stack.config.files_on_host { // ============= // FILES ON HOST // ============= - // Only need to write environment file here (which does nothing if not using this feature) - let env_file_path = match environment::write_file( + let env_file_path = environment::write_file_simple( &env_vars, &stack.config.env_file_path, - stack - .config - .skip_secret_interp - .then_some(&periphery_config().secrets), run_directory.as_ref(), res.logs(), ) - .await - { - Ok(path) => path, - Err(_) => { - return Err(anyhow!("failed to write environment file")); - } - }; + .await?; Ok(( run_directory, - // Env file paths are already relative to run directory, + // Env file paths are expected to be already relative to run directory, // so need to pass original env_file_path here. env_file_path .is_some() .then_some(&stack.config.env_file_path), + env_replacers, )) } else if stack.config.repo.is_empty() { if stack.config.file_contents.trim().is_empty() { - return Err(anyhow!("Must either input compose file contents directly, or use file one host / git repo options.")); + return Err(anyhow!( + "Must either input compose file contents directly, or use files on host / git repo options." + )); } // ============== // UI BASED FILES @@ -478,23 +504,13 @@ pub async fn write_stack( "failed to create stack run directory at {run_directory:?}" ) })?; - let env_file_path = match environment::write_file( + let env_file_path = environment::write_file_simple( &env_vars, &stack.config.env_file_path, - stack - .config - .skip_secret_interp - .then_some(&periphery_config().secrets), run_directory.as_ref(), res.logs(), ) - .await - { - Ok(path) => path, - Err(_) => { - return Err(anyhow!("failed to write environment file")); - } - }; + .await?; let file_path = run_directory .join( stack @@ -508,7 +524,10 @@ pub async fn write_stack( .components() .collect::(); - let file_contents = if !stack.config.skip_secret_interp { + let (file_contents, file_replacers) = if !stack + .config + .skip_secret_interp + { let (contents, replacers) = svi::interpolate_variables( &stack.config.file_contents, &periphery_config().secrets, @@ -518,7 +537,7 @@ pub async fn write_stack( .context("failed to interpolate secrets into file contents")?; if !replacers.is_empty() { res.logs().push(Log::simple( - "Interpolate - Compose file", + "Interpolate - Compose file (Periphery)", replacers .iter() .map(|(_, variable)| format!("replaced: {variable}")) @@ -526,13 +545,13 @@ pub async fn write_stack( .join("\n"), )); } - contents + (contents, Some(replacers)) } else { - stack.config.file_contents.clone() + (stack.config.file_contents.clone(), None) }; fs::write(&file_path, &file_contents).await.with_context( - || format!("failed to write compose file to {file_path:?}"), + || format!("Failed to write compose file to {file_path:?}"), )?; Ok(( @@ -540,6 +559,14 @@ pub async fn write_stack( env_file_path .is_some() .then_some(&stack.config.env_file_path), + match (env_replacers, file_replacers) { + (Some(env_replacers), Some(file_replacers)) => Some( + env_replacers.into_iter().chain(file_replacers).collect(), + ), + (Some(env_replacers), None) => Some(env_replacers), + (None, Some(file_replacers)) => Some(file_replacers), + (None, None) => None, + }, )) } else { // ================ @@ -588,37 +615,29 @@ pub async fn write_stack( .to_string(); let clone_or_pull_res = if stack.config.reclone { - State - .resolve( - CloneRepo { - args, - git_token, - environment: env_vars, - env_file_path, - skip_secret_interp: stack.config.skip_secret_interp, - // repo replacer only needed for on_clone / on_pull, - // which aren't available for stacks - replacers: Default::default(), - }, - (), - ) - .await + CloneRepo { + args, + git_token, + environment: env_vars, + env_file_path, + // Env has already been interpolated above + skip_secret_interp: true, + replacers: Default::default(), + } + .resolve(&crate::api::Args) + .await } else { - State - .resolve( - PullOrCloneRepo { - args, - git_token, - environment: env_vars, - env_file_path, - skip_secret_interp: stack.config.skip_secret_interp, - // repo replacer only needed for on_clone / on_pull, - // which aren't available for stacks - replacers: Default::default(), - }, - (), - ) - .await + PullOrCloneRepo { + args, + git_token, + environment: env_vars, + env_file_path, + // Env has already been interpolated above + skip_secret_interp: true, + replacers: Default::default(), + } + .resolve(&crate::api::Args) + .await }; let RepoActionResponse { @@ -630,17 +649,17 @@ pub async fn write_stack( Ok(res) => res, Err(e) => { let error = format_serror( - &e.context("failed to pull stack repo").into(), + &e.error.context("Failed to pull stack repo").into(), ); res .logs() - .push(Log::error("pull stack repo", error.clone())); + .push(Log::error("Pull Stack Repo", error.clone())); res.add_remote_error(FileContents { path: Default::default(), contents: error, }); return Err(anyhow!( - "failed to pull stack repo, stopping run" + "Failed to pull stack repo, stopping run" )); } }; @@ -658,31 +677,34 @@ pub async fn write_stack( env_file_path .is_some() .then_some(&stack.config.env_file_path), + env_replacers, )) } } async fn compose_down( project: &str, - service: Option, + services: &[String], res: &mut ComposeUpResponse, ) -> anyhow::Result<()> { let docker_compose = docker_compose(); - let service_arg = service - .as_ref() - .map(|service| format!(" {service}")) - .unwrap_or_default(); + let service_args = if services.is_empty() { + String::new() + } else { + format!(" {}", services.join(" ")) + }; let log = run_komodo_command( "compose down", None, - format!("{docker_compose} -p {project} down{service_arg}"), - false, + format!("{docker_compose} -p {project} down{service_args}"), ) .await; let success = log.success; res.logs.push(log); if !success { - return Err(anyhow!("Failed to bring down existing container(s) with docker compose down. Stopping run.")); + return Err(anyhow!( + "Failed to bring down existing container(s) with docker compose down. Stopping run." + )); } Ok(()) diff --git a/bin/periphery/src/config.rs b/bin/periphery/src/config.rs index 83c77015f..4fd2980f4 100644 --- a/bin/periphery/src/config.rs +++ b/bin/periphery/src/config.rs @@ -88,30 +88,3 @@ pub fn periphery_config() -> &'static PeripheryConfig { } }) } - -pub fn git_providers_response() -> &'static String { - static GIT_PROVIDERS_RESPONSE: OnceLock = OnceLock::new(); - GIT_PROVIDERS_RESPONSE.get_or_init(|| { - let config = periphery_config(); - serde_json::to_string(&config.git_providers).unwrap() - }) -} - -pub fn docker_registries_response() -> &'static String { - static DOCKER_REGISTRIES_RESPONSE: OnceLock = - OnceLock::new(); - DOCKER_REGISTRIES_RESPONSE.get_or_init(|| { - let config = periphery_config(); - serde_json::to_string(&config.docker_registries).unwrap() - }) -} - -pub fn secrets_response() -> &'static String { - static SECRETS_RESPONSE: OnceLock = OnceLock::new(); - SECRETS_RESPONSE.get_or_init(|| { - serde_json::to_string( - &periphery_config().secrets.keys().collect::>(), - ) - .unwrap() - }) -} diff --git a/bin/periphery/src/docker.rs b/bin/periphery/src/docker.rs index 6ccaa6ddf..0e4fc49d6 100644 --- a/bin/periphery/src/docker.rs +++ b/bin/periphery/src/docker.rs @@ -1,20 +1,20 @@ use std::{collections::HashMap, sync::OnceLock}; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use bollard::{ + Docker, container::{InspectContainerOptions, ListContainersOptions}, network::InspectNetworkOptions, - Docker, }; use command::run_komodo_command; use komodo_client::entities::{ + TerminationSignal, docker::{ - container::*, image::*, network::*, volume::*, ContainerConfig, - GraphDriverData, HealthConfig, PortBinding, + ContainerConfig, GraphDriverData, HealthConfig, PortBinding, + container::*, image::*, network::*, volume::*, }, to_komodo_name, update::Log, - TerminationSignal, }; use run_command::async_run_command; @@ -969,7 +969,7 @@ pub async fn docker_login( #[instrument] pub async fn pull_image(image: &str) -> Log { let command = format!("docker pull {image}"); - run_komodo_command("docker pull", None, command, false).await + run_komodo_command("Docker Pull", None, command).await } pub fn stop_container_command( diff --git a/bin/periphery/src/helpers.rs b/bin/periphery/src/helpers.rs index 790fd6a01..6a24f29c8 100644 --- a/bin/periphery/src/helpers.rs +++ b/bin/periphery/src/helpers.rs @@ -1,17 +1,17 @@ use std::path::PathBuf; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use komodo_client::{ entities::{ - stack::Stack, to_komodo_name, CloneArgs, EnvironmentVar, - SearchCombinator, + CloneArgs, EnvironmentVar, SearchCombinator, stack::Stack, + to_komodo_name, }, parsers::QUOTE_PATTERN, }; use periphery_client::api::git::PullOrCloneRepo; use resolver_api::Resolve; -use crate::{config::periphery_config, State}; +use crate::config::periphery_config; pub fn git_token( domain: &str, @@ -86,17 +86,6 @@ pub fn log_grep( } } -pub fn interpolate_variables( - input: &str, -) -> svi::Result<(String, Vec<(String, String)>)> { - svi::interpolate_variables( - input, - &periphery_config().secrets, - svi::Interpolator::DoubleBrackets, - true, - ) -} - /// Returns path to root directory of the stack repo. pub async fn pull_or_clone_stack( stack: &Stack, @@ -140,21 +129,19 @@ pub async fn pull_or_clone_stack( } }; - State - .resolve( - PullOrCloneRepo { - args, - git_token, - environment: vec![], - env_file_path: stack.config.env_file_path.clone(), - skip_secret_interp: stack.config.skip_secret_interp, - // repo replacer only needed for on_clone / on_pull, - // which aren't available for stacks - replacers: Default::default(), - }, - (), - ) - .await?; + PullOrCloneRepo { + args, + git_token, + environment: vec![], + env_file_path: stack.config.env_file_path.clone(), + skip_secret_interp: stack.config.skip_secret_interp, + // repo replacer only needed for on_clone / on_pull, + // which aren't available for stacks + replacers: Default::default(), + } + .resolve(&crate::api::Args) + .await + .map_err(|e| e.error)?; Ok(root) } diff --git a/bin/periphery/src/main.rs b/bin/periphery/src/main.rs index 1cfdb1c37..e2272f257 100644 --- a/bin/periphery/src/main.rs +++ b/bin/periphery/src/main.rs @@ -16,8 +16,6 @@ mod router; mod ssl; mod stats; -struct State; - async fn app() -> anyhow::Result<()> { dotenvy::dotenv().ok(); let config = config::periphery_config(); @@ -26,7 +24,7 @@ async fn app() -> anyhow::Result<()> { info!("Komodo Periphery version: v{}", env!("CARGO_PKG_VERSION")); info!("{:?}", config.sanitized()); - stats::spawn_system_stats_polling_threads(); + stats::spawn_system_stats_polling_thread(); let socket_addr = SocketAddr::from_str(&format!("0.0.0.0:{}", config.port)) diff --git a/bin/periphery/src/router.rs b/bin/periphery/src/router.rs index daeda62bf..b4901afad 100644 --- a/bin/periphery/src/router.rs +++ b/bin/periphery/src/router.rs @@ -1,22 +1,21 @@ use std::net::SocketAddr; -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use axum::{ + Router, body::Body, extract::ConnectInfo, http::{Request, StatusCode}, middleware::{self, Next}, response::Response, routing::post, - Router, }; -use axum_extra::{headers::ContentType, TypedHeader}; use derive_variants::ExtractVariant; -use resolver_api::Resolver; +use resolver_api::Resolve; use serror::{AddStatusCode, AddStatusCodeError, Json}; use uuid::Uuid; -use crate::{config::periphery_config, State}; +use crate::config::periphery_config; pub fn router() -> Router { Router::new() @@ -27,7 +26,7 @@ pub fn router() -> Router { async fn handler( Json(request): Json, -) -> serror::Result<(TypedHeader, String)> { +) -> serror::Result { let req_id = Uuid::new_v4(); let res = tokio::spawn(task(req_id, request)) @@ -38,28 +37,22 @@ async fn handler( warn!("request {req_id} spawn error: {e:#}"); } - Ok((TypedHeader(ContentType::json()), res??)) + res? } async fn task( req_id: Uuid, request: crate::api::PeripheryRequest, -) -> anyhow::Result { +) -> serror::Result { let variant = request.extract_variant(); - let res = - State - .resolve_request(request, ()) - .await - .map_err(|e| match e { - resolver_api::Error::Serialization(e) => { - anyhow!("{e:?}").context("response serialization error") - } - resolver_api::Error::Inner(e) => e, - }); + let res = request.resolve(&crate::api::Args).await.map(|res| res.0); if let Err(e) = &res { - warn!("request {req_id} | type: {variant:?} | error: {e:#}"); + warn!( + "request {req_id} | type: {variant:?} | error: {:#}", + e.error + ); } res diff --git a/bin/periphery/src/ssl.rs b/bin/periphery/src/ssl.rs index 4e959ef9f..96435490e 100644 --- a/bin/periphery/src/ssl.rs +++ b/bin/periphery/src/ssl.rs @@ -25,7 +25,9 @@ async fn generate_self_signed_ssl_certs() { let key_path = &config.ssl_key_file.display(); let cert_path = &config.ssl_cert_file.display(); - let command = format!("openssl req -x509 -newkey rsa:4096 -keyout {key_path} -out {cert_path} -sha256 -days 3650 -nodes -subj \"/C=XX/CN=periphery\""); + let command = format!( + "openssl req -x509 -newkey rsa:4096 -keyout {key_path} -out {cert_path} -sha256 -days 3650 -nodes -subj \"/C=XX/CN=periphery\"" + ); let log = run_command::async_run_command(&command).await; if log.success() { diff --git a/bin/periphery/src/stats.rs b/bin/periphery/src/stats.rs index 76e520b2b..d473ba8e2 100644 --- a/bin/periphery/src/stats.rs +++ b/bin/periphery/src/stats.rs @@ -2,7 +2,7 @@ use std::{cmp::Ordering, sync::OnceLock}; use async_timing_util::wait_until_timelength; use komodo_client::entities::stats::{ - SingleDiskUsage, SystemInformation, SystemProcess, SystemStats, SingleNetworkInterfaceUsage, + SingleDiskUsage, SystemInformation, SystemProcess, SystemStats, }; use sysinfo::{ProcessesToUpdate, System}; use tokio::sync::RwLock; @@ -16,21 +16,8 @@ pub fn stats_client() -> &'static RwLock { } /// This should be called before starting the server in main.rs. -/// Keeps the caches stats up to date -pub fn spawn_system_stats_polling_threads() { - tokio::spawn(async move { - let client = stats_client(); - loop { - let ts = wait_until_timelength( - async_timing_util::Timelength::FiveMinutes, - 0, - ) - .await; - let mut client = client.write().await; - client.refresh_lists(); - client.stats.refresh_list_ts = ts as i64; - } - }); +/// Keeps the cached stats up to date +pub fn spawn_system_stats_polling_thread() { tokio::spawn(async move { let polling_rate = periphery_config() .stats_polling_rate @@ -48,7 +35,6 @@ pub fn spawn_system_stats_polling_threads() { }); } - pub struct StatsClient { /// Cached system stats pub stats: SystemStats, @@ -89,52 +75,29 @@ impl StatsClient { self.system.refresh_cpu_all(); self.system.refresh_memory(); self.system.refresh_processes(ProcessesToUpdate::All, true); - self.disks.refresh(); - self.networks.refresh(); - } - - fn refresh_lists(&mut self) { - self.disks.refresh_list(); - self.networks.refresh_list(); + self.disks.refresh(true); + self.networks.refresh(true); } pub fn get_system_stats(&self) -> SystemStats { let total_mem = self.system.total_memory(); let available_mem = self.system.available_memory(); - let mut total_ingress: u64 = 0; - let mut total_egress: u64 = 0; + let mut network_ingress_bytes: u64 = 0; + let mut network_egress_bytes: u64 = 0; - // Fetch network data (Ingress and Egress) - let network_usage: Vec = self.networks - .iter() - .map(|(interface_name, network)| { - let ingress = network.received(); - let egress = network.transmitted(); - - // Update total ingress and egress - total_ingress += ingress; - total_egress += egress; - - // Return per-interface network stats - SingleNetworkInterfaceUsage { - name: interface_name.clone(), - ingress_bytes: ingress as f64, - egress_bytes: egress as f64, - } - }) - .collect(); + for (_, network) in self.networks.iter() { + network_ingress_bytes += network.received(); + network_egress_bytes += network.transmitted(); + } SystemStats { cpu_perc: self.system.global_cpu_usage(), mem_free_gb: self.system.free_memory() as f64 / BYTES_PER_GB, mem_used_gb: (total_mem - available_mem) as f64 / BYTES_PER_GB, mem_total_gb: total_mem as f64 / BYTES_PER_GB, - // Added total ingress and egress - network_ingress_bytes: total_ingress as f64, - network_egress_bytes: total_egress as f64, - network_usage_interface: network_usage, - + network_ingress_bytes: network_ingress_bytes as f64, + network_egress_bytes: network_egress_bytes as f64, disks: self.get_disks(), polling_rate: self.stats.polling_rate, refresh_ts: self.stats.refresh_ts, diff --git a/client/core/rs/Cargo.toml b/client/core/rs/Cargo.toml index f7c49f0b2..f6de964e4 100644 --- a/client/core/rs/Cargo.toml +++ b/client/core/rs/Cargo.toml @@ -18,13 +18,13 @@ blocking = ["reqwest/blocking"] [dependencies] # mogh mongo_indexed = { workspace = true, optional = true } +serror = { workspace = true, features = ["axum"]} derive_default_builder.workspace = true derive_empty_traits.workspace = true async_timing_util.workspace = true partial_derive2.workspace = true derive_variants.workspace = true resolver_api.workspace = true -serror.workspace = true # external tokio-tungstenite.workspace = true derive_builder.workspace = true diff --git a/client/core/rs/README.md b/client/core/rs/README.md index 7859e4615..cb4865dff 100644 --- a/client/core/rs/README.md +++ b/client/core/rs/README.md @@ -1,7 +1,7 @@ # Komodo -*A system to build and deploy software across many servers* +*A system to build and deploy software across many servers*. [https://komo.do](https://komo.do) -Full Docs: [https://docs.rs/komodo_client/latest/komodo_client](https://docs.rs/komodo_client/latest/komodo_client). +Docs: [https://docs.rs/komodo_client/latest/komodo_client](https://docs.rs/komodo_client/latest/komodo_client). This is a client library for the Komodo Core API. It contains: diff --git a/client/core/rs/src/api/auth.rs b/client/core/rs/src/api/auth.rs index b15685c7c..c6ca3b20f 100644 --- a/client/core/rs/src/api/auth.rs +++ b/client/core/rs/src/api/auth.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::{derive::Request, HasResponse}; +use resolver_api::{HasResponse, Resolve}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -22,10 +22,11 @@ pub struct JwtResponse { /// Response: [GetLoginOptionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(GetLoginOptionsResponse)] +#[error(serror::Error)] pub struct GetLoginOptions {} /// The response for [GetLoginOptions]. @@ -53,10 +54,11 @@ pub struct GetLoginOptionsResponse { /// Note. This method is only available if the core api has `local_auth` enabled. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(CreateLocalUserResponse)] +#[error(serror::Error)] pub struct CreateLocalUser { /// The username for the new user. pub username: String, @@ -77,10 +79,11 @@ pub type CreateLocalUserResponse = JwtResponse; /// Note. This method is only available if the core api has `local_auth` enabled. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(LoginLocalUserResponse)] +#[error(serror::Error)] pub struct LoginLocalUser { /// The user's username pub username: String, @@ -99,10 +102,11 @@ pub type LoginLocalUserResponse = JwtResponse; /// Response: [ExchangeForJwtResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(ExchangeForJwtResponse)] +#[error(serror::Error)] pub struct ExchangeForJwt { /// The 'exchange token' pub token: String, @@ -118,10 +122,11 @@ pub type ExchangeForJwtResponse = JwtResponse; /// Response: [User]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoAuthRequest)] #[response(GetUserResponse)] +#[error(serror::Error)] pub struct GetUser {} #[typeshare] diff --git a/client/core/rs/src/api/execute/action.rs b/client/core/rs/src/api/execute/action.rs index 93b5978c0..b5f8a2fb6 100644 --- a/client/core/rs/src/api/execute/action.rs +++ b/client/core/rs/src/api/execute/action.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,12 +16,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunAction { /// Id or name pub action: String, @@ -35,12 +36,13 @@ pub struct RunAction { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchRunAction { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/alerter.rs b/client/core/rs/src/api/execute/alerter.rs new file mode 100644 index 000000000..1a160babb --- /dev/null +++ b/client/core/rs/src/api/execute/alerter.rs @@ -0,0 +1,29 @@ +use clap::Parser; +use derive_empty_traits::EmptyTraits; +use resolver_api::Resolve; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::entities::update::Update; + +use super::KomodoExecuteRequest; + +/// Tests an Alerters ability to reach the configured endpoint. Response: [Update] +#[typeshare] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + PartialEq, + Resolve, + EmptyTraits, + Parser, +)] +#[empty_traits(KomodoExecuteRequest)] +#[response(Update)] +#[error(serror::Error)] +pub struct TestAlerter { + /// Name or id + pub alerter: String, +} diff --git a/client/core/rs/src/api/execute/build.rs b/client/core/rs/src/api/execute/build.rs index 0fd0de62c..dadff102f 100644 --- a/client/core/rs/src/api/execute/build.rs +++ b/client/core/rs/src/api/execute/build.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -23,12 +23,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunBuild { /// Can be build id or name pub build: String, @@ -44,12 +45,13 @@ pub struct RunBuild { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchRunBuild { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -76,12 +78,13 @@ pub struct BatchRunBuild { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct CancelBuild { /// Can be id or name pub build: String, diff --git a/client/core/rs/src/api/execute/deployment.rs b/client/core/rs/src/api/execute/deployment.rs index bdf83e533..67ea89215 100644 --- a/client/core/rs/src/api/execute/deployment.rs +++ b/client/core/rs/src/api/execute/deployment.rs @@ -1,10 +1,10 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{update::Update, TerminationSignal}; +use crate::entities::{TerminationSignal, update::Update}; use super::{BatchExecutionResponse, KomodoExecuteRequest}; @@ -22,12 +22,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct Deploy { /// Name or id pub deployment: String, @@ -49,12 +50,13 @@ pub struct Deploy { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDeploy { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -79,12 +81,13 @@ pub struct BatchDeploy { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PullDeployment { /// Name or id pub deployment: String, @@ -102,12 +105,13 @@ pub struct PullDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartDeployment { /// Name or id pub deployment: String, @@ -125,12 +129,13 @@ pub struct StartDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartDeployment { /// Name or id pub deployment: String, @@ -148,12 +153,13 @@ pub struct RestartDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseDeployment { /// Name or id pub deployment: String, @@ -173,12 +179,13 @@ pub struct PauseDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseDeployment { /// Name or id pub deployment: String, @@ -196,12 +203,13 @@ pub struct UnpauseDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopDeployment { /// Name or id pub deployment: String, @@ -224,12 +232,13 @@ pub struct StopDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DestroyDeployment { /// Name or id. pub deployment: String, @@ -249,12 +258,13 @@ pub struct DestroyDeployment { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDestroyDeployment { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/mod.rs b/client/core/rs/src/api/execute/mod.rs index fb98aa12e..33b2abb11 100644 --- a/client/core/rs/src/api/execute/mod.rs +++ b/client/core/rs/src/api/execute/mod.rs @@ -6,6 +6,7 @@ use strum::{Display, EnumString}; use typeshare::typeshare; mod action; +mod alerter; mod build; mod deployment; mod procedure; @@ -16,6 +17,7 @@ mod stack; mod sync; pub use action::*; +pub use alerter::*; pub use build::*; pub use deployment::*; pub use procedure::*; @@ -27,7 +29,7 @@ pub use sync::*; use crate::{ api::write::CommitSync, - entities::{update::Update, NoData, _Serror, I64}, + entities::{_Serror, I64, NoData, update::Update}, }; pub trait KomodoExecuteRequest: HasResponse {} @@ -134,6 +136,9 @@ pub enum Execution { DestroyStack(DestroyStack), BatchDestroyStack(BatchDestroyStack), + // ALERTER + TestAlerter(TestAlerter), + // SLEEP Sleep(Sleep), } diff --git a/client/core/rs/src/api/execute/procedure.rs b/client/core/rs/src/api/execute/procedure.rs index 950282af9..983d41427 100644 --- a/client/core/rs/src/api/execute/procedure.rs +++ b/client/core/rs/src/api/execute/procedure.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,12 +16,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunProcedure { /// Id or name pub procedure: String, @@ -35,12 +36,13 @@ pub struct RunProcedure { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchRunProcedure { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/repo.rs b/client/core/rs/src/api/execute/repo.rs index 9d04ef358..0e0fed797 100644 --- a/client/core/rs/src/api/execute/repo.rs +++ b/client/core/rs/src/api/execute/repo.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -26,12 +26,13 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct CloneRepo { /// Id or name pub repo: String, @@ -47,12 +48,13 @@ pub struct CloneRepo { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchCloneRepo { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -82,12 +84,13 @@ pub struct BatchCloneRepo { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PullRepo { /// Id or name pub repo: String, @@ -103,12 +106,13 @@ pub struct PullRepo { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchPullRepo { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -142,12 +146,13 @@ pub struct BatchPullRepo { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct BuildRepo { /// Id or name pub repo: String, @@ -163,12 +168,13 @@ pub struct BuildRepo { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchBuildRepo { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -195,12 +201,13 @@ pub struct BatchBuildRepo { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct CancelRepoBuild { /// Can be id or name pub repo: String, diff --git a/client/core/rs/src/api/execute/server.rs b/client/core/rs/src/api/execute/server.rs index 87067ff40..4b6c3b122 100644 --- a/client/core/rs/src/api/execute/server.rs +++ b/client/core/rs/src/api/execute/server.rs @@ -1,10 +1,10 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{update::Update, TerminationSignal}; +use crate::entities::{TerminationSignal, update::Update}; use super::KomodoExecuteRequest; @@ -22,12 +22,13 @@ use super::KomodoExecuteRequest; Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartContainer { /// Name or id pub server: String, @@ -47,12 +48,13 @@ pub struct StartContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartContainer { /// Name or id pub server: String, @@ -72,12 +74,13 @@ pub struct RestartContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseContainer { /// Name or id pub server: String, @@ -99,12 +102,13 @@ pub struct PauseContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseContainer { /// Name or id pub server: String, @@ -124,12 +128,13 @@ pub struct UnpauseContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopContainer { /// Name or id pub server: String, @@ -154,12 +159,13 @@ pub struct StopContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DestroyContainer { /// Name or id pub server: String, @@ -181,12 +187,13 @@ pub struct DestroyContainer { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartAllContainers { /// Name or id pub server: String, @@ -202,12 +209,13 @@ pub struct StartAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartAllContainers { /// Name or id pub server: String, @@ -223,12 +231,13 @@ pub struct RestartAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseAllContainers { /// Name or id pub server: String, @@ -244,12 +253,13 @@ pub struct PauseAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseAllContainers { /// Name or id pub server: String, @@ -265,12 +275,13 @@ pub struct UnpauseAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopAllContainers { /// Name or id pub server: String, @@ -288,12 +299,13 @@ pub struct StopAllContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneContainers { /// Id or name pub server: String, @@ -312,12 +324,13 @@ pub struct PruneContainers { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeleteNetwork { /// Id or name. pub server: String, @@ -337,12 +350,13 @@ pub struct DeleteNetwork { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneNetworks { /// Id or name pub server: String, @@ -359,12 +373,13 @@ pub struct PruneNetworks { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeleteImage { /// Id or name. pub server: String, @@ -384,12 +399,13 @@ pub struct DeleteImage { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneImages { /// Id or name pub server: String, @@ -406,12 +422,13 @@ pub struct PruneImages { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeleteVolume { /// Id or name. pub server: String, @@ -431,12 +448,13 @@ pub struct DeleteVolume { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneVolumes { /// Id or name pub server: String, @@ -454,12 +472,13 @@ pub struct PruneVolumes { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneDockerBuilders { /// Id or name pub server: String, @@ -477,12 +496,13 @@ pub struct PruneDockerBuilders { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneBuildx { /// Id or name pub server: String, @@ -500,12 +520,13 @@ pub struct PruneBuildx { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PruneSystem { /// Id or name pub server: String, diff --git a/client/core/rs/src/api/execute/server_template.rs b/client/core/rs/src/api/execute/server_template.rs index 7971edecd..2f5a0afbd 100644 --- a/client/core/rs/src/api/execute/server_template.rs +++ b/client/core/rs/src/api/execute/server_template.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoExecuteRequest; /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct LaunchServer { /// The name of the created server. pub name: String, diff --git a/client/core/rs/src/api/execute/stack.rs b/client/core/rs/src/api/execute/stack.rs index 0d233d607..ecf78d83c 100644 --- a/client/core/rs/src/api/execute/stack.rs +++ b/client/core/rs/src/api/execute/stack.rs @@ -1,6 +1,6 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -16,17 +16,20 @@ use super::{BatchExecutionResponse, KomodoExecuteRequest}; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeployStack { /// Id or name pub stack: String, - /// Optionally specify a specific service to "compose up" - pub service: Option, + /// Filter to only deploy specific services. + /// If empty, will deploy all services. + #[serde(default)] + pub services: Vec, /// Override the default termination max time. /// Only used if the stack needs to be taken down first. pub stop_time: Option, @@ -42,12 +45,13 @@ pub struct DeployStack { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDeployStack { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -74,12 +78,13 @@ pub struct BatchDeployStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DeployStackIfChanged { /// Id or name pub stack: String, @@ -98,12 +103,13 @@ pub struct DeployStackIfChanged { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDeployStackIfChanged { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. @@ -128,17 +134,20 @@ pub struct BatchDeployStackIfChanged { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PullStack { /// Id or name pub stack: String, - /// Optionally specify a specific service to start - pub service: Option, + /// Filter to only pull specific services. + /// If empty, will pull all services. + #[serde(default)] + pub services: Vec, } // @@ -151,17 +160,20 @@ pub struct PullStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StartStack { /// Id or name pub stack: String, - /// Optionally specify a specific service to start - pub service: Option, + /// Filter to only start specific services. + /// If empty, will start all services. + #[serde(default)] + pub services: Vec, } // @@ -174,17 +186,20 @@ pub struct StartStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RestartStack { /// Id or name pub stack: String, - /// Optionally specify a specific service to restart - pub service: Option, + /// Filter to only restart specific services. + /// If empty, will restart all services. + #[serde(default)] + pub services: Vec, } // @@ -197,17 +212,20 @@ pub struct RestartStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct PauseStack { /// Id or name pub stack: String, - /// Optionally specify a specific service to pause - pub service: Option, + /// Filter to only pause specific services. + /// If empty, will pause all services. + #[serde(default)] + pub services: Vec, } // @@ -222,17 +240,20 @@ pub struct PauseStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct UnpauseStack { /// Id or name pub stack: String, - /// Optionally specify a specific service to unpause - pub service: Option, + /// Filter to only unpause specific services. + /// If empty, will unpause all services. + #[serde(default)] + pub services: Vec, } // @@ -245,19 +266,22 @@ pub struct UnpauseStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct StopStack { /// Id or name pub stack: String, /// Override the default termination max time. pub stop_time: Option, - /// Optionally specify a specific service to stop - pub service: Option, + /// Filter to only stop specific services. + /// If empty, will stop all services. + #[serde(default)] + pub services: Vec, } // @@ -270,17 +294,20 @@ pub struct StopStack { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct DestroyStack { /// Id or name pub stack: String, - /// Optionally specify a specific service to destroy - pub service: Option, + /// Filter to only destroy specific services. + /// If empty, will destroy all services. + #[serde(default)] + pub services: Vec, /// Pass `--remove-orphans` #[serde(default)] pub remove_orphans: bool, @@ -298,12 +325,13 @@ pub struct DestroyStack { Debug, Clone, PartialEq, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(BatchExecutionResponse)] +#[error(serror::Error)] pub struct BatchDestroyStack { /// Id or name or wildcard pattern or regex. /// Supports multiline and comma delineated combinations of the above. diff --git a/client/core/rs/src/api/execute/sync.rs b/client/core/rs/src/api/execute/sync.rs index a7e6d7f97..b5fd9bebc 100644 --- a/client/core/rs/src/api/execute/sync.rs +++ b/client/core/rs/src/api/execute/sync.rs @@ -1,10 +1,10 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{update::Update, ResourceTargetVariant}; +use crate::entities::{ResourceTargetVariant, update::Update}; use super::KomodoExecuteRequest; @@ -16,12 +16,13 @@ use super::KomodoExecuteRequest; PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoExecuteRequest)] #[response(Update)] +#[error(serror::Error)] pub struct RunSync { /// Id or name pub sync: String, diff --git a/client/core/rs/src/api/mod.rs b/client/core/rs/src/api/mod.rs index fbe4bd390..6dfeea1a3 100644 --- a/client/core/rs/src/api/mod.rs +++ b/client/core/rs/src/api/mod.rs @@ -1,6 +1,6 @@ -//! # Komodo core API +//! # Komodo Core API //! -//! Komodo core exposes an HTTP api using standard JSON serialization. +//! Komodo Core exposes an HTTP api using standard JSON serialization. //! //! All calls share some common HTTP params: //! - Method: `POST` diff --git a/client/core/rs/src/api/read/action.rs b/client/core/rs/src/api/read/action.rs index 0f8aa8c46..476628c7f 100644 --- a/client/core/rs/src/api/read/action.rs +++ b/client/core/rs/src/api/read/action.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific action. Response: [Action]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetActionResponse)] +#[error(serror::Error)] pub struct GetAction { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetActionResponse = Action; /// List actions matching optional query. Response: [ListActionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListActionsResponse)] +#[error(serror::Error)] pub struct ListActions { /// optional structured query to filter actions. #[serde(default)] @@ -50,10 +52,11 @@ pub type ListActionsResponse = Vec; /// List actions matching optional query. Response: [ListFullActionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullActionsResponse)] +#[error(serror::Error)] pub struct ListFullActions { /// optional structured query to filter actions. #[serde(default)] @@ -68,10 +71,11 @@ pub type ListFullActionsResponse = Vec; /// Get current action state for the action. Response: [ActionActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetActionActionStateResponse)] +#[error(serror::Error)] pub struct GetActionActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -87,10 +91,11 @@ pub type GetActionActionStateResponse = ActionActionState; /// Response: [GetActionsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetActionsSummaryResponse)] +#[error(serror::Error)] pub struct GetActionsSummary {} /// Response for [GetActionsSummary]. diff --git a/client/core/rs/src/api/read/alert.rs b/client/core/rs/src/api/read/alert.rs index 4f27ecad3..3a679b966 100644 --- a/client/core/rs/src/api/read/alert.rs +++ b/client/core/rs/src/api/read/alert.rs @@ -1,9 +1,9 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{alert::Alert, MongoDocument, I64, U64}; +use crate::entities::{I64, MongoDocument, U64, alert::Alert}; use super::KomodoReadRequest; @@ -11,10 +11,11 @@ use super::KomodoReadRequest; /// Response: [ListAlertsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListAlertsResponse)] +#[error(serror::Error)] pub struct ListAlerts { /// Pass a custom mongo query to filter the alerts. /// @@ -62,10 +63,11 @@ pub struct ListAlertsResponse { /// Get an alert: Response: [Alert]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetAlertResponse)] +#[error(serror::Error)] pub struct GetAlert { pub id: String, } diff --git a/client/core/rs/src/api/read/alerter.rs b/client/core/rs/src/api/read/alerter.rs index ad45f803b..15ce143e7 100644 --- a/client/core/rs/src/api/read/alerter.rs +++ b/client/core/rs/src/api/read/alerter.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific alerter. Response: [Alerter]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetAlerterResponse)] +#[error(serror::Error)] pub struct GetAlerter { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetAlerterResponse = Alerter; /// List alerters matching optional query. Response: [ListAlertersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListAlertersResponse)] +#[error(serror::Error)] pub struct ListAlerters { /// Structured query to filter alerters. #[serde(default)] @@ -48,10 +50,11 @@ pub type ListAlertersResponse = Vec; /// List full alerters matching optional query. Response: [ListFullAlertersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullAlertersResponse)] +#[error(serror::Error)] pub struct ListFullAlerters { /// Structured query to filter alerters. #[serde(default)] @@ -67,10 +70,11 @@ pub type ListFullAlertersResponse = Vec; /// Response: [GetAlertersSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetAlertersSummaryResponse)] +#[error(serror::Error)] pub struct GetAlertersSummary {} /// Response for [GetAlertersSummary]. diff --git a/client/core/rs/src/api/read/build.rs b/client/core/rs/src/api/read/build.rs index cf2c5169c..ac886f751 100644 --- a/client/core/rs/src/api/read/build.rs +++ b/client/core/rs/src/api/read/build.rs @@ -1,13 +1,13 @@ use std::cmp::Ordering; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ + I64, Version, build::{Build, BuildActionState, BuildListItem, BuildQuery}, - Version, I64, }; use super::KomodoReadRequest; @@ -17,10 +17,11 @@ use super::KomodoReadRequest; /// Get a specific build. Response: [Build]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildResponse)] +#[error(serror::Error)] pub struct GetBuild { /// Id or name #[serde(alias = "id", alias = "name")] @@ -35,10 +36,11 @@ pub type GetBuildResponse = Build; /// List builds matching optional query. Response: [ListBuildsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListBuildsResponse)] +#[error(serror::Error)] pub struct ListBuilds { /// optional structured query to filter builds. #[serde(default)] @@ -53,10 +55,11 @@ pub type ListBuildsResponse = Vec; /// List builds matching optional query. Response: [ListFullBuildsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullBuildsResponse)] +#[error(serror::Error)] pub struct ListFullBuilds { /// optional structured query to filter builds. #[serde(default)] @@ -71,10 +74,11 @@ pub type ListFullBuildsResponse = Vec; /// Get current action state for the build. Response: [BuildActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildActionStateResponse)] +#[error(serror::Error)] pub struct GetBuildActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -90,10 +94,11 @@ pub type GetBuildActionStateResponse = BuildActionState; /// Response: [GetBuildsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildsSummaryResponse)] +#[error(serror::Error)] pub struct GetBuildsSummary {} /// Response for [GetBuildsSummary]. @@ -121,10 +126,11 @@ pub struct GetBuildsSummaryResponse { /// Query for older pages by incrementing the page, starting at 0. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildMonthlyStatsResponse)] +#[error(serror::Error)] pub struct GetBuildMonthlyStats { /// Query for older data by incrementing the page. /// `page: 0` is the default, and will return the most recent data. @@ -182,10 +188,11 @@ impl GetBuildMonthlyStatsResponse { /// Response: [ListBuildVersionsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListBuildVersionsResponse)] +#[error(serror::Error)] pub struct ListBuildVersions { /// Id or name #[serde(alias = "id", alias = "name")] @@ -216,10 +223,11 @@ pub struct BuildVersionResponseItem { /// Useful to offer suggestions. Response: [ListCommonBuildExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonBuildExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonBuildExtraArgs { /// optional structured query to filter builds. #[serde(default)] @@ -234,10 +242,11 @@ pub type ListCommonBuildExtraArgsResponse = Vec; /// Get whether a Build's target repo has a webhook for the build configured. Response: [GetBuildWebhookEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildWebhookEnabledResponse)] +#[error(serror::Error)] pub struct GetBuildWebhookEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/builder.rs b/client/core/rs/src/api/read/builder.rs index 0dd7e09e7..4516fdec5 100644 --- a/client/core/rs/src/api/read/builder.rs +++ b/client/core/rs/src/api/read/builder.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific builder by id or name. Response: [Builder]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuilderResponse)] +#[error(serror::Error)] pub struct GetBuilder { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetBuilderResponse = Builder; /// List builders matching structured query. Response: [ListBuildersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListBuildersResponse)] +#[error(serror::Error)] pub struct ListBuilders { #[serde(default)] pub query: BuilderQuery, @@ -49,10 +51,11 @@ pub type ListBuildersResponse = Vec; /// List builders matching structured query. Response: [ListFullBuildersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullBuildersResponse)] +#[error(serror::Error)] pub struct ListFullBuilders { #[serde(default)] pub query: BuilderQuery, @@ -67,10 +70,11 @@ pub type ListFullBuildersResponse = Vec; /// Response: [GetBuildersSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetBuildersSummaryResponse)] +#[error(serror::Error)] pub struct GetBuildersSummary {} /// Response for [GetBuildersSummary]. diff --git a/client/core/rs/src/api/read/deployment.rs b/client/core/rs/src/api/read/deployment.rs index 291a02dda..aabaf58b5 100644 --- a/client/core/rs/src/api/read/deployment.rs +++ b/client/core/rs/src/api/read/deployment.rs @@ -1,16 +1,16 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ + I64, SearchCombinator, U64, deployment::{ Deployment, DeploymentActionState, DeploymentListItem, DeploymentQuery, DeploymentState, }, docker::container::{ContainerListItem, ContainerStats}, update::Log, - SearchCombinator, I64, U64, }; use super::KomodoReadRequest; @@ -20,10 +20,11 @@ use super::KomodoReadRequest; /// Get a specific deployment by name or id. Response: [Deployment]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentResponse)] +#[error(serror::Error)] pub struct GetDeployment { /// Id or name #[serde(alias = "id", alias = "name")] @@ -39,10 +40,11 @@ pub type GetDeploymentResponse = Deployment; /// Response: [ListDeploymentsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDeploymentsResponse)] +#[error(serror::Error)] pub struct ListDeployments { /// optional structured query to filter deployments. #[serde(default)] @@ -58,10 +60,11 @@ pub type ListDeploymentsResponse = Vec; /// Response: [ListFullDeploymentsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullDeploymentsResponse)] +#[error(serror::Error)] pub struct ListFullDeployments { /// optional structured query to filter deployments. #[serde(default)] @@ -81,10 +84,11 @@ pub type ListFullDeploymentsResponse = Vec; /// to keep it up to date. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentContainerResponse)] +#[error(serror::Error)] pub struct GetDeploymentContainer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -107,10 +111,11 @@ pub struct GetDeploymentContainerResponse { /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentLogResponse)] +#[error(serror::Error)] pub struct GetDeploymentLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -140,10 +145,11 @@ pub type GetDeploymentLogResponse = Log; /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(SearchDeploymentLogResponse)] +#[error(serror::Error)] pub struct SearchDeploymentLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -175,10 +181,11 @@ pub type SearchDeploymentLogResponse = Log; /// Note. This call will hit the underlying server directly for most up to date stats. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentStatsResponse)] +#[error(serror::Error)] pub struct GetDeploymentStats { /// Id or name #[serde(alias = "id", alias = "name")] @@ -194,10 +201,11 @@ pub type GetDeploymentStatsResponse = ContainerStats; /// Response: [DeploymentActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(DeploymentActionState)] +#[error(serror::Error)] pub struct GetDeploymentActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -213,10 +221,11 @@ pub type GetDeploymentActionStateResponse = DeploymentActionState; /// Response: [GetDeploymentsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDeploymentsSummaryResponse)] +#[error(serror::Error)] pub struct GetDeploymentsSummary {} /// Response for [GetDeploymentsSummary]. @@ -243,10 +252,11 @@ pub struct GetDeploymentsSummaryResponse { /// Useful to offer suggestions. Response: [ListCommonDeploymentExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonDeploymentExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonDeploymentExtraArgs { /// optional structured query to filter deployments. #[serde(default)] diff --git a/client/core/rs/src/api/read/mod.rs b/client/core/rs/src/api/read/mod.rs index 409dc43d2..dec204a0e 100644 --- a/client/core/rs/src/api/read/mod.rs +++ b/client/core/rs/src/api/read/mod.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::{derive::Request, HasResponse}; +use resolver_api::{HasResponse, Resolve}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -13,7 +13,6 @@ mod permission; mod procedure; mod provider; mod repo; -mod search; mod server; mod server_template; mod stack; @@ -35,7 +34,6 @@ pub use permission::*; pub use procedure::*; pub use provider::*; pub use repo::*; -pub use search::*; pub use server::*; pub use server_template::*; pub use stack::*; @@ -48,8 +46,8 @@ pub use user_group::*; pub use variable::*; use crate::entities::{ - config::{DockerRegistry, GitProvider}, ResourceTarget, Timelength, + config::{DockerRegistry, GitProvider}, }; pub trait KomodoReadRequest: HasResponse {} @@ -60,10 +58,11 @@ pub trait KomodoReadRequest: HasResponse {} /// Response: [GetVersionResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetVersionResponse)] +#[error(serror::Error)] pub struct GetVersion {} /// Response for [GetVersion]. @@ -80,10 +79,11 @@ pub struct GetVersionResponse { /// Response: [GetCoreInfoResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetCoreInfoResponse)] +#[error(serror::Error)] pub struct GetCoreInfo {} /// Response for [GetCoreInfo]. @@ -119,10 +119,11 @@ pub struct GetCoreInfoResponse { /// - providers on the optional Server or Builder #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListGitProvidersFromConfigResponse)] +#[error(serror::Error)] pub struct ListGitProvidersFromConfig { /// Accepts an optional Server or Builder target to expand the core list with /// providers available on that specific resource. @@ -143,10 +144,11 @@ pub type ListGitProvidersFromConfigResponse = Vec; /// - registries on the optional Server or Builder #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerRegistriesFromConfigResponse)] +#[error(serror::Error)] pub struct ListDockerRegistriesFromConfig { /// Accepts an optional Server or Builder target to expand the core list with /// providers available on that specific resource. @@ -162,10 +164,11 @@ pub type ListDockerRegistriesFromConfigResponse = Vec; /// Response: [ListSecretsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListSecretsResponse)] +#[error(serror::Error)] pub struct ListSecrets { /// Accepts an optional Server or Builder target to expand the core list with /// providers available on that specific resource. diff --git a/client/core/rs/src/api/read/permission.rs b/client/core/rs/src/api/read/permission.rs index 98cfa31cc..6adeed507 100644 --- a/client/core/rs/src/api/read/permission.rs +++ b/client/core/rs/src/api/read/permission.rs @@ -1,11 +1,11 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - permission::{Permission, PermissionLevel, UserTarget}, ResourceTarget, + permission::{Permission, PermissionLevel, UserTarget}, }; use super::KomodoReadRequest; @@ -15,10 +15,11 @@ use super::KomodoReadRequest; /// Response: [ListPermissionsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListPermissionsResponse)] +#[error(serror::Error)] pub struct ListPermissions {} #[typeshare] @@ -31,10 +32,11 @@ pub type ListPermissionsResponse = Vec; /// Response: [PermissionLevel] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetPermissionLevelResponse)] +#[error(serror::Error)] pub struct GetPermissionLevel { /// The target to get user permission on. pub target: ResourceTarget, @@ -49,10 +51,11 @@ pub type GetPermissionLevelResponse = PermissionLevel; /// Response: [ListUserTargetPermissionsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUserTargetPermissionsResponse)] +#[error(serror::Error)] pub struct ListUserTargetPermissions { /// Specify either a user or a user group. pub user_target: UserTarget, diff --git a/client/core/rs/src/api/read/procedure.rs b/client/core/rs/src/api/read/procedure.rs index 9663cd6d7..fe13edcc7 100644 --- a/client/core/rs/src/api/read/procedure.rs +++ b/client/core/rs/src/api/read/procedure.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific procedure. Response: [Procedure]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetProcedureResponse)] +#[error(serror::Error)] pub struct GetProcedure { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetProcedureResponse = Procedure; /// List procedures matching optional query. Response: [ListProceduresResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListProceduresResponse)] +#[error(serror::Error)] pub struct ListProcedures { /// optional structured query to filter procedures. #[serde(default)] @@ -50,10 +52,11 @@ pub type ListProceduresResponse = Vec; /// List procedures matching optional query. Response: [ListFullProceduresResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullProceduresResponse)] +#[error(serror::Error)] pub struct ListFullProcedures { /// optional structured query to filter procedures. #[serde(default)] @@ -68,10 +71,11 @@ pub type ListFullProceduresResponse = Vec; /// Get current action state for the procedure. Response: [ProcedureActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetProcedureActionStateResponse)] +#[error(serror::Error)] pub struct GetProcedureActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -87,10 +91,11 @@ pub type GetProcedureActionStateResponse = ProcedureActionState; /// Response: [GetProceduresSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetProceduresSummaryResponse)] +#[error(serror::Error)] pub struct GetProceduresSummary {} /// Response for [GetProceduresSummary]. diff --git a/client/core/rs/src/api/read/provider.rs b/client/core/rs/src/api/read/provider.rs index fd7fb4984..c08b34dae 100644 --- a/client/core/rs/src/api/read/provider.rs +++ b/client/core/rs/src/api/read/provider.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -13,10 +13,11 @@ use super::KomodoReadRequest; /// Response: [GetGitProviderAccountResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetGitProviderAccountResponse)] +#[error(serror::Error)] pub struct GetGitProviderAccount { pub id: String, } @@ -30,10 +31,11 @@ pub type GetGitProviderAccountResponse = GitProviderAccount; /// Response: [ListGitProviderAccountsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListGitProviderAccountsResponse)] +#[error(serror::Error)] pub struct ListGitProviderAccounts { /// Optionally filter by accounts with a specific domain. pub domain: Option, @@ -50,10 +52,11 @@ pub type ListGitProviderAccountsResponse = Vec; /// Response: [GetDockerRegistryAccountResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct GetDockerRegistryAccount { pub id: String, } @@ -67,10 +70,11 @@ pub type GetDockerRegistryAccountResponse = DockerRegistryAccount; /// Response: [ListDockerRegistryAccountsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerRegistryAccountsResponse)] +#[error(serror::Error)] pub struct ListDockerRegistryAccounts { /// Optionally filter by accounts with a specific domain. pub domain: Option, diff --git a/client/core/rs/src/api/read/repo.rs b/client/core/rs/src/api/read/repo.rs index 4f8e7b5f3..16f2521ef 100644 --- a/client/core/rs/src/api/read/repo.rs +++ b/client/core/rs/src/api/read/repo.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific repo. Response: [Repo]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct GetRepo { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetRepoResponse = Repo; /// List repos matching optional query. Response: [ListReposResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListReposResponse)] +#[error(serror::Error)] pub struct ListRepos { /// optional structured query to filter repos. #[serde(default)] @@ -50,10 +52,11 @@ pub type ListReposResponse = Vec; /// List repos matching optional query. Response: [ListFullReposResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullReposResponse)] +#[error(serror::Error)] pub struct ListFullRepos { /// optional structured query to filter repos. #[serde(default)] @@ -68,10 +71,11 @@ pub type ListFullReposResponse = Vec; /// Get current action state for the repo. Response: [RepoActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetRepoActionStateResponse)] +#[error(serror::Error)] pub struct GetRepoActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -87,10 +91,11 @@ pub type GetRepoActionStateResponse = RepoActionState; /// Response: [GetReposSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetReposSummaryResponse)] +#[error(serror::Error)] pub struct GetReposSummary {} /// Response for [GetReposSummary] @@ -118,10 +123,11 @@ pub struct GetReposSummaryResponse { /// Get a target Repo's configured webhooks. Response: [GetRepoWebhooksEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetRepoWebhooksEnabledResponse)] +#[error(serror::Error)] pub struct GetRepoWebhooksEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/search.rs b/client/core/rs/src/api/read/search.rs deleted file mode 100644 index 67f1e2f77..000000000 --- a/client/core/rs/src/api/read/search.rs +++ /dev/null @@ -1,46 +0,0 @@ -use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; -use serde::{Deserialize, Serialize}; -use typeshare::typeshare; - -use crate::entities::{ - build::BuildListItem, deployment::DeploymentListItem, - procedure::ProcedureListItem, repo::RepoListItem, - server::ServerListItem, MongoDocument, ResourceTargetVariant, -}; - -use super::KomodoReadRequest; - -// - -/// Find resources matching a common query. Response: [FindResourcesResponse]. -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, -)] -#[empty_traits(KomodoReadRequest)] -#[response(FindResourcesResponse)] -pub struct FindResources { - /// The mongo query as JSON - #[serde(default)] - pub query: MongoDocument, - /// The resource variants to include in the response. - #[serde(default)] - pub resources: Vec, -} - -/// Response for [FindResources]. -#[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct FindResourcesResponse { - /// The matching servers. - pub servers: Vec, - /// The matching deployments. - pub deployments: Vec, - /// The matching builds. - pub builds: Vec, - /// The matching repos. - pub repos: Vec, - /// The matching procedures. - pub procedures: Vec, -} diff --git a/client/core/rs/src/api/read/server.rs b/client/core/rs/src/api/read/server.rs index a9af84e17..7d7ce9b17 100644 --- a/client/core/rs/src/api/read/server.rs +++ b/client/core/rs/src/api/read/server.rs @@ -1,9 +1,10 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ + I64, ResourceTarget, SearchCombinator, Timelength, U64, docker::{ container::{Container, ContainerListItem}, image::{Image, ImageHistoryResponseItem, ImageListItem}, @@ -19,7 +20,6 @@ use crate::entities::{ SystemInformation, SystemProcess, SystemStats, SystemStatsRecord, }, update::Log, - ResourceTarget, SearchCombinator, Timelength, I64, U64, }; use super::KomodoReadRequest; @@ -29,10 +29,11 @@ use super::KomodoReadRequest; /// Get a specific server. Response: [Server]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(Server)] +#[error(serror::Error)] pub struct GetServer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -47,10 +48,11 @@ pub type GetServerResponse = Server; /// List servers matching optional query. Response: [ListServersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListServersResponse)] +#[error(serror::Error)] pub struct ListServers { /// optional structured query to filter servers. #[serde(default)] @@ -65,10 +67,11 @@ pub type ListServersResponse = Vec; /// List servers matching optional query. Response: [ListFullServersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullServersResponse)] +#[error(serror::Error)] pub struct ListFullServers { /// optional structured query to filter servers. #[serde(default)] @@ -83,10 +86,11 @@ pub type ListFullServersResponse = Vec; /// Get the state of the target server. Response: [GetServerStateResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServerStateResponse)] +#[error(serror::Error)] pub struct GetServerState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -106,10 +110,11 @@ pub struct GetServerStateResponse { /// Get current action state for the servers. Response: [ServerActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ServerActionState)] +#[error(serror::Error)] pub struct GetServerActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -125,10 +130,11 @@ pub type GetServerActionStateResponse = ServerActionState; /// Response: [GetPeripheryVersionResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetPeripheryVersionResponse)] +#[error(serror::Error)] pub struct GetPeripheryVersion { /// Id or name #[serde(alias = "id", alias = "name")] @@ -148,10 +154,11 @@ pub struct GetPeripheryVersionResponse { /// List the docker networks on the server. Response: [ListDockerNetworksResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerNetworksResponse)] +#[error(serror::Error)] pub struct ListDockerNetworks { /// Id or name #[serde(alias = "id", alias = "name")] @@ -166,10 +173,11 @@ pub type ListDockerNetworksResponse = Vec; /// Inspect a docker network on the server. Response: [InspectDockerNetworkResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerNetworkResponse)] +#[error(serror::Error)] pub struct InspectDockerNetwork { /// Id or name #[serde(alias = "id", alias = "name")] @@ -187,10 +195,11 @@ pub type InspectDockerNetworkResponse = Network; /// Response: [ListDockerImagesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerImagesResponse)] +#[error(serror::Error)] pub struct ListDockerImages { /// Id or name #[serde(alias = "id", alias = "name")] @@ -205,10 +214,11 @@ pub type ListDockerImagesResponse = Vec; /// Inspect a docker image on the server. Response: [Image]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerImageResponse)] +#[error(serror::Error)] pub struct InspectDockerImage { /// Id or name #[serde(alias = "id", alias = "name")] @@ -225,10 +235,11 @@ pub type InspectDockerImageResponse = Image; /// Get image history from the server. Response: [ListDockerImageHistoryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerImageHistoryResponse)] +#[error(serror::Error)] pub struct ListDockerImageHistory { /// Id or name #[serde(alias = "id", alias = "name")] @@ -247,10 +258,11 @@ pub type ListDockerImageHistoryResponse = /// Response: [ListDockerContainersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerContainersResponse)] +#[error(serror::Error)] pub struct ListDockerContainers { /// Id or name #[serde(alias = "id", alias = "name")] @@ -266,10 +278,11 @@ pub type ListDockerContainersResponse = Vec; /// Response: [ListDockerContainersResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListAllDockerContainersResponse)] +#[error(serror::Error)] pub struct ListAllDockerContainers { /// Filter by server id or name. #[serde(default)] @@ -284,10 +297,11 @@ pub type ListAllDockerContainersResponse = Vec; /// Inspect a docker container on the server. Response: [Container]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerContainerResponse)] +#[error(serror::Error)] pub struct InspectDockerContainer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -307,10 +321,11 @@ pub type InspectDockerContainerResponse = Container; /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetContainerLogResponse)] +#[error(serror::Error)] pub struct GetContainerLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -342,10 +357,11 @@ pub type GetContainerLogResponse = Log; /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(SearchContainerLogResponse)] +#[error(serror::Error)] pub struct SearchContainerLog { /// Id or name #[serde(alias = "id", alias = "name")] @@ -376,10 +392,11 @@ pub type SearchContainerLogResponse = Log; /// Find the attached resource for a container. Either Deployment or Stack. Response: [GetResourceMatchingContainerResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetResourceMatchingContainerResponse)] +#[error(serror::Error)] pub struct GetResourceMatchingContainer { /// Id or name #[serde(alias = "id", alias = "name")] @@ -401,10 +418,11 @@ pub struct GetResourceMatchingContainerResponse { /// Response: [ListDockerVolumesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListDockerVolumesResponse)] +#[error(serror::Error)] pub struct ListDockerVolumes { /// Id or name #[serde(alias = "id", alias = "name")] @@ -419,10 +437,11 @@ pub type ListDockerVolumesResponse = Vec; /// Inspect a docker volume on the server. Response: [Volume]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(InspectDockerVolumeResponse)] +#[error(serror::Error)] pub struct InspectDockerVolume { /// Id or name #[serde(alias = "id", alias = "name")] @@ -440,10 +459,11 @@ pub type InspectDockerVolumeResponse = Volume; /// Response: [ListComposeProjectsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListComposeProjectsResponse)] +#[error(serror::Error)] pub struct ListComposeProjects { /// Id or name #[serde(alias = "id", alias = "name")] @@ -459,10 +479,11 @@ pub type ListComposeProjectsResponse = Vec; /// Response: [SystemInformation]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetSystemInformationResponse)] +#[error(serror::Error)] pub struct GetSystemInformation { /// Id or name #[serde(alias = "id", alias = "name")] @@ -481,10 +502,11 @@ pub type GetSystemInformationResponse = SystemInformation; /// to keep it up to date. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetSystemStatsResponse)] +#[error(serror::Error)] pub struct GetSystemStats { /// Id or name #[serde(alias = "id", alias = "name")] @@ -504,10 +526,11 @@ pub type GetSystemStatsResponse = SystemStats; /// to keep it up to date. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListSystemProcessesResponse)] +#[error(serror::Error)] pub struct ListSystemProcesses { /// Id or name #[serde(alias = "id", alias = "name")] @@ -523,10 +546,11 @@ pub type ListSystemProcessesResponse = Vec; /// Response: [GetHistoricalServerStatsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetHistoricalServerStatsResponse)] +#[error(serror::Error)] pub struct GetHistoricalServerStats { /// Id or name #[serde(alias = "id", alias = "name")] @@ -555,10 +579,11 @@ pub struct GetHistoricalServerStatsResponse { /// Response: [GetServersSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServersSummaryResponse)] +#[error(serror::Error)] pub struct GetServersSummary {} /// Response for [GetServersSummary]. diff --git a/client/core/rs/src/api/read/server_template.rs b/client/core/rs/src/api/read/server_template.rs index 2a52d1ff5..cd0be5b32 100644 --- a/client/core/rs/src/api/read/server_template.rs +++ b/client/core/rs/src/api/read/server_template.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Get a specific server template by id or name. Response: [ServerTemplate]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServerTemplateResponse)] +#[error(serror::Error)] pub struct GetServerTemplate { /// Id or name #[serde(alias = "id", alias = "name")] @@ -32,10 +33,11 @@ pub type GetServerTemplateResponse = ServerTemplate; /// List server templates matching structured query. Response: [ListServerTemplatesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListServerTemplatesResponse)] +#[error(serror::Error)] pub struct ListServerTemplates { #[serde(default)] pub query: ServerTemplateQuery, @@ -49,10 +51,11 @@ pub type ListServerTemplatesResponse = Vec; /// List server templates matching structured query. Response: [ListFullServerTemplatesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullServerTemplatesResponse)] +#[error(serror::Error)] pub struct ListFullServerTemplates { #[serde(default)] pub query: ServerTemplateQuery, @@ -67,10 +70,11 @@ pub type ListFullServerTemplatesResponse = Vec; /// Response: [GetServerTemplatesSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetServerTemplatesSummaryResponse)] +#[error(serror::Error)] pub struct GetServerTemplatesSummary {} /// Response for [GetServerTemplatesSummary]. diff --git a/client/core/rs/src/api/read/stack.rs b/client/core/rs/src/api/read/stack.rs index 3c3be1c6d..39557d408 100644 --- a/client/core/rs/src/api/read/stack.rs +++ b/client/core/rs/src/api/read/stack.rs @@ -1,14 +1,14 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ + SearchCombinator, U64, stack::{ Stack, StackActionState, StackListItem, StackQuery, StackService, }, update::Log, - SearchCombinator, U64, }; use super::KomodoReadRequest; @@ -18,10 +18,11 @@ use super::KomodoReadRequest; /// Get a specific stack. Response: [Stack]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStackResponse)] +#[error(serror::Error)] pub struct GetStack { /// Id or name #[serde(alias = "id", alias = "name")] @@ -36,10 +37,11 @@ pub type GetStackResponse = Stack; /// Lists a specific stacks services (the containers). Response: [ListStackServicesResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListStackServicesResponse)] +#[error(serror::Error)] pub struct ListStackServices { /// Id or name #[serde(alias = "id", alias = "name")] @@ -51,19 +53,23 @@ pub type ListStackServicesResponse = Vec; // -/// Get a stack service's log. Response: [GetStackServiceLogResponse]. +/// Get a stack's logs. Filter down included services. Response: [GetStackLogResponse]. +/// +/// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] -#[response(GetStackServiceLogResponse)] -pub struct GetStackServiceLog { +#[response(GetStackLogResponse)] +#[error(serror::Error)] +pub struct GetStackLog { /// Id or name #[serde(alias = "id", alias = "name")] pub stack: String, - /// The service to get the log for. - pub service: String, + /// Filter the logs to only ones from specific services. + /// If empty, will include logs from all services. + pub services: Vec, /// The number of lines of the log tail to include. /// Default: 100. /// Max: 5000. @@ -79,26 +85,28 @@ fn default_tail() -> u64 { } #[typeshare] -pub type GetStackServiceLogResponse = Log; +pub type GetStackLogResponse = Log; // -/// Search the deployment log's tail using `grep`. All lines go to stdout. -/// Response: [Log]. +/// Search the stack log's tail using `grep`. All lines go to stdout. +/// Response: [SearchStackLogResponse]. /// /// Note. This call will hit the underlying server directly for most up to date log. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] -#[response(SearchStackServiceLogResponse)] -pub struct SearchStackServiceLog { +#[response(SearchStackLogResponse)] +#[error(serror::Error)] +pub struct SearchStackLog { /// Id or name #[serde(alias = "id", alias = "name")] pub stack: String, - /// The service to get the log for. - pub service: String, + /// Filter the logs to only ones from specific services. + /// If empty, will include logs from all services. + pub services: Vec, /// The terms to search for. pub terms: Vec, /// When searching for multiple terms, can use `AND` or `OR` combinator. @@ -116,7 +124,7 @@ pub struct SearchStackServiceLog { } #[typeshare] -pub type SearchStackServiceLogResponse = Log; +pub type SearchStackLogResponse = Log; // @@ -124,10 +132,11 @@ pub type SearchStackServiceLogResponse = Log; /// Useful to offer suggestions. Response: [ListCommonStackExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonStackExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonStackExtraArgs { /// optional structured query to filter stacks. #[serde(default)] @@ -143,10 +152,11 @@ pub type ListCommonStackExtraArgsResponse = Vec; /// Useful to offer suggestions. Response: [ListCommonStackBuildExtraArgsResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListCommonStackBuildExtraArgsResponse)] +#[error(serror::Error)] pub struct ListCommonStackBuildExtraArgs { /// optional structured query to filter stacks. #[serde(default)] @@ -161,12 +171,13 @@ pub type ListCommonStackBuildExtraArgsResponse = Vec; /// List stacks matching optional query. Response: [ListStacksResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListStacksResponse)] +#[error(serror::Error)] pub struct ListStacks { - /// optional structured query to filter syncs. + /// optional structured query to filter stacks. #[serde(default)] pub query: StackQuery, } @@ -179,10 +190,11 @@ pub type ListStacksResponse = Vec; /// List stacks matching optional query. Response: [ListFullStacksResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullStacksResponse)] +#[error(serror::Error)] pub struct ListFullStacks { /// optional structured query to filter stacks. #[serde(default)] @@ -197,10 +209,11 @@ pub type ListFullStacksResponse = Vec; /// Get current action state for the stack. Response: [StackActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStackActionStateResponse)] +#[error(serror::Error)] pub struct GetStackActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -216,10 +229,11 @@ pub type GetStackActionStateResponse = StackActionState; /// Response: [GetStacksSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStacksSummaryResponse)] +#[error(serror::Error)] pub struct GetStacksSummary {} /// Response for [GetStacksSummary] @@ -245,10 +259,11 @@ pub struct GetStacksSummaryResponse { /// Get a target stack's configured webhooks. Response: [GetStackWebhooksEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetStackWebhooksEnabledResponse)] +#[error(serror::Error)] pub struct GetStackWebhooksEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/sync.rs b/client/core/rs/src/api/read/sync.rs index a49f3bd17..54623c887 100644 --- a/client/core/rs/src/api/read/sync.rs +++ b/client/core/rs/src/api/read/sync.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoReadRequest; /// Get a specific sync. Response: [ResourceSync]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct GetResourceSync { /// Id or name #[serde(alias = "id", alias = "name")] @@ -33,10 +34,11 @@ pub type GetResourceSyncResponse = ResourceSync; /// List syncs matching optional query. Response: [ListResourceSyncsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListResourceSyncsResponse)] +#[error(serror::Error)] pub struct ListResourceSyncs { /// optional structured query to filter syncs. #[serde(default)] @@ -51,10 +53,11 @@ pub type ListResourceSyncsResponse = Vec; /// List syncs matching optional query. Response: [ListFullResourceSyncsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListFullResourceSyncsResponse)] +#[error(serror::Error)] pub struct ListFullResourceSyncs { /// optional structured query to filter syncs. #[serde(default)] @@ -69,10 +72,11 @@ pub type ListFullResourceSyncsResponse = Vec; /// Get current action state for the sync. Response: [ResourceSyncActionState]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetResourceSyncActionStateResponse)] +#[error(serror::Error)] pub struct GetResourceSyncActionState { /// Id or name #[serde(alias = "id", alias = "name")] @@ -88,10 +92,11 @@ pub type GetResourceSyncActionStateResponse = ResourceSyncActionState; /// Response: [GetResourceSyncsSummaryResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetResourceSyncsSummaryResponse)] +#[error(serror::Error)] pub struct GetResourceSyncsSummary {} /// Response for [GetResourceSyncsSummary] @@ -117,10 +122,11 @@ pub struct GetResourceSyncsSummaryResponse { /// Get a target Sync's configured webhooks. Response: [GetSyncWebhooksEnabledResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetSyncWebhooksEnabledResponse)] +#[error(serror::Error)] pub struct GetSyncWebhooksEnabled { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/read/tag.rs b/client/core/rs/src/api/read/tag.rs index 169b703c2..9eff08446 100644 --- a/client/core/rs/src/api/read/tag.rs +++ b/client/core/rs/src/api/read/tag.rs @@ -1,9 +1,9 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{tag::Tag, MongoDocument}; +use crate::entities::{MongoDocument, tag::Tag}; use super::KomodoReadRequest; @@ -12,10 +12,11 @@ use super::KomodoReadRequest; /// Get data for a specific tag. Response [Tag]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetTagResponse)] +#[error(serror::Error)] pub struct GetTag { /// Id or name #[serde(alias = "id", alias = "name")] @@ -31,10 +32,11 @@ pub type GetTagResponse = Tag; /// Response: [ListTagsResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListTagsResponse)] +#[error(serror::Error)] pub struct ListTags { pub query: Option, } diff --git a/client/core/rs/src/api/read/toml.rs b/client/core/rs/src/api/read/toml.rs index afcef36b3..3e46802ba 100644 --- a/client/core/rs/src/api/read/toml.rs +++ b/client/core/rs/src/api/read/toml.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -21,14 +21,33 @@ pub struct TomlResponse { /// Response: [TomlResponse]. #[typeshare] #[derive( - Debug, Clone, Default, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ExportAllResourcesToTomlResponse)] +#[error(serror::Error)] pub struct ExportAllResourcesToToml { - /// Tag name or id. Empty array will not filter by tag. + /// Whether to include any resources (servers, stacks, etc.) + /// in the exported contents. + /// Default: `true` + #[serde(default = "default_include_resources")] + pub include_resources: bool, + /// Filter resources by tag. + /// Accepts tag name or id. Empty array will not filter by tag. #[serde(default)] pub tags: Vec, + /// Whether to include variables in the exported contents. + /// Default: false + #[serde(default)] + pub include_variables: bool, + /// Whether to include user groups in the exported contents. + /// Default: false + #[serde(default)] + pub include_user_groups: bool, +} + +fn default_include_resources() -> bool { + true } #[typeshare] @@ -40,10 +59,11 @@ pub type ExportAllResourcesToTomlResponse = TomlResponse; /// Response: [TomlResponse]. #[typeshare] #[derive( - Debug, Clone, Default, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ExportResourcesToTomlResponse)] +#[error(serror::Error)] pub struct ExportResourcesToToml { /// The targets to include in the export. #[serde(default)] diff --git a/client/core/rs/src/api/read/update.rs b/client/core/rs/src/api/read/update.rs index f153a871f..200cc1b6d 100644 --- a/client/core/rs/src/api/read/update.rs +++ b/client/core/rs/src/api/read/update.rs @@ -1,11 +1,11 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - update::{Update, UpdateListItem}, MongoDocument, + update::{Update, UpdateListItem}, }; use super::KomodoReadRequest; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetUpdateResponse)] +#[error(serror::Error)] pub struct GetUpdate { /// The update id. pub id: String, @@ -32,10 +33,11 @@ pub type GetUpdateResponse = Update; /// More recent updates will be returned first. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUpdatesResponse)] +#[error(serror::Error)] pub struct ListUpdates { /// An optional mongo query to filter the updates. pub query: Option, diff --git a/client/core/rs/src/api/read/user.rs b/client/core/rs/src/api/read/user.rs index dbf51b957..bd9f73154 100644 --- a/client/core/rs/src/api/read/user.rs +++ b/client/core/rs/src/api/read/user.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoReadRequest; /// Response: [ListApiKeysResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListApiKeysResponse)] +#[error(serror::Error)] pub struct ListApiKeys {} #[typeshare] @@ -28,10 +29,11 @@ pub type ListApiKeysResponse = Vec; /// Response: [ListApiKeysForServiceUserResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListApiKeysForServiceUserResponse)] +#[error(serror::Error)] pub struct ListApiKeysForServiceUser { /// Id or username #[serde(alias = "id", alias = "username")] @@ -48,10 +50,11 @@ pub type ListApiKeysForServiceUserResponse = Vec; /// Response: [FindUserResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(FindUserResponse)] +#[error(serror::Error)] pub struct FindUser { /// Id or username #[serde(alias = "id", alias = "username")] @@ -68,10 +71,11 @@ pub type FindUserResponse = User; /// Response: [ListUsersResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUsersResponse)] +#[error(serror::Error)] pub struct ListUsers {} #[typeshare] @@ -83,10 +87,11 @@ pub type ListUsersResponse = Vec; /// Response: [GetUsernameResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetUsernameResponse)] +#[error(serror::Error)] pub struct GetUsername { /// The id of the user. pub user_id: String, diff --git a/client/core/rs/src/api/read/user_group.rs b/client/core/rs/src/api/read/user_group.rs index 76103fa3b..9188fba32 100644 --- a/client/core/rs/src/api/read/user_group.rs +++ b/client/core/rs/src/api/read/user_group.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoReadRequest; /// Response: [UserGroup]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetUserGroupResponse)] +#[error(serror::Error)] pub struct GetUserGroup { /// Name or Id pub user_group: String, @@ -31,10 +32,11 @@ pub type GetUserGroupResponse = UserGroup; /// and users can see user groups to which they belong. #[typeshare] #[derive( - Debug, Clone, Default, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Default, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListUserGroupsResponse)] +#[error(serror::Error)] pub struct ListUserGroups {} #[typeshare] diff --git a/client/core/rs/src/api/read/variable.rs b/client/core/rs/src/api/read/variable.rs index d034f073c..5ec082758 100644 --- a/client/core/rs/src/api/read/variable.rs +++ b/client/core/rs/src/api/read/variable.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -14,10 +14,11 @@ use super::KomodoReadRequest; /// secret variables will have their values obscured. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(GetVariableResponse)] +#[error(serror::Error)] pub struct GetVariable { /// The name of the variable to get. pub name: String, @@ -35,10 +36,11 @@ pub type GetVariableResponse = Variable; /// secret variables will have their values obscured. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Default, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Default, Resolve, EmptyTraits, )] #[empty_traits(KomodoReadRequest)] #[response(ListVariablesResponse)] +#[error(serror::Error)] pub struct ListVariables {} #[typeshare] diff --git a/client/core/rs/src/api/user.rs b/client/core/rs/src/api/user.rs index 3c18e5de5..d0bbc0868 100644 --- a/client/core/rs/src/api/user.rs +++ b/client/core/rs/src/api/user.rs @@ -1,9 +1,9 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::{derive::Request, HasResponse}; +use resolver_api::{HasResponse, Resolve}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{NoData, ResourceTarget, I64}; +use crate::entities::{I64, NoData, ResourceTarget}; pub trait KomodoUserRequest: HasResponse {} @@ -13,10 +13,11 @@ pub trait KomodoUserRequest: HasResponse {} /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(PushRecentlyViewedResponse)] +#[error(serror::Error)] pub struct PushRecentlyViewed { /// The target to push. pub resource: ResourceTarget, @@ -32,10 +33,11 @@ pub type PushRecentlyViewedResponse = NoData; /// Response: [NoData] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(SetLastSeenUpdateResponse)] +#[error(serror::Error)] pub struct SetLastSeenUpdate {} #[typeshare] @@ -50,10 +52,11 @@ pub type SetLastSeenUpdateResponse = NoData; /// to get the secret later. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(CreateApiKeyResponse)] +#[error(serror::Error)] pub struct CreateApiKey { /// The name for the api key. pub name: String, @@ -84,10 +87,11 @@ pub struct CreateApiKeyResponse { /// Response: [NoData] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoUserRequest)] #[response(DeleteApiKeyResponse)] +#[error(serror::Error)] pub struct DeleteApiKey { /// The key which the user intends to delete. pub key: String, diff --git a/client/core/rs/src/api/write/action.rs b/client/core/rs/src/api/write/action.rs index 47768ad43..72b24d3ee 100644 --- a/client/core/rs/src/api/write/action.rs +++ b/client/core/rs/src/api/write/action.rs @@ -1,12 +1,12 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - action::{Action, _PartialActionConfig}, - update::Update, NoData, + action::{_PartialActionConfig, Action}, + update::Update, }; use super::KomodoWriteRequest; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a action. Response: [Action]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct CreateAction { /// The name given to newly created action. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateAction { /// of the action at the given `id`. Response: [Action]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct CopyAction { /// The name of the new action. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyAction { /// Response: [Action] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct DeleteAction { /// The id or name of the action to delete. pub id: String, @@ -72,10 +75,11 @@ pub struct DeleteAction { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Action)] +#[error(serror::Error)] pub struct UpdateAction { /// The id of the action to update. pub id: String, @@ -89,10 +93,12 @@ pub struct UpdateAction { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameAction { /// The id or name of the Action to rename. pub id: String, @@ -104,10 +110,11 @@ pub struct RenameAction { /// passed in request. Response: [CreateActionWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateActionWebhookResponse)] +#[error(serror::Error)] pub struct CreateActionWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -123,10 +130,11 @@ pub type CreateActionWebhookResponse = NoData; /// passed in request. Response: [DeleteActionWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteActionWebhookResponse)] +#[error(serror::Error)] pub struct DeleteActionWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/alerter.rs b/client/core/rs/src/api/write/alerter.rs index 7072f813b..dad9aa4cf 100644 --- a/client/core/rs/src/api/write/alerter.rs +++ b/client/core/rs/src/api/write/alerter.rs @@ -1,10 +1,10 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - alerter::{Alerter, _PartialAlerterConfig}, + alerter::{_PartialAlerterConfig, Alerter}, update::Update, }; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create an alerter. Response: [Alerter]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct CreateAlerter { /// The name given to newly created alerter. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateAlerter { /// of the alerter at the given `id`. Response: [Alerter]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct CopyAlerter { /// The name of the new alerter. pub name: String, @@ -50,10 +52,11 @@ pub struct CopyAlerter { /// Response: [Alerter] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct DeleteAlerter { /// The id or name of the alerter to delete. pub id: String, @@ -69,10 +72,11 @@ pub struct DeleteAlerter { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Alerter)] +#[error(serror::Error)] pub struct UpdateAlerter { /// The id of the alerter to update. pub id: String, @@ -86,10 +90,12 @@ pub struct UpdateAlerter { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameAlerter { /// The id or name of the Alerter to rename. pub id: String, diff --git a/client/core/rs/src/api/write/api_key.rs b/client/core/rs/src/api/write/api_key.rs index 95a016d20..a1dcc8b38 100644 --- a/client/core/rs/src/api/write/api_key.rs +++ b/client/core/rs/src/api/write/api_key.rs @@ -1,11 +1,11 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::{ api::user::CreateApiKeyResponse, - entities::{NoData, I64}, + entities::{I64, NoData}, }; use super::KomodoWriteRequest; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Response: [CreateApiKeyResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateApiKeyForServiceUserResponse)] +#[error(serror::Error)] pub struct CreateApiKeyForServiceUser { /// Must be service user pub user_id: String, @@ -40,10 +41,11 @@ pub type CreateApiKeyForServiceUserResponse = CreateApiKeyResponse; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteApiKeyForServiceUserResponse)] +#[error(serror::Error)] pub struct DeleteApiKeyForServiceUser { pub key: String, } diff --git a/client/core/rs/src/api/write/build.rs b/client/core/rs/src/api/write/build.rs index c293787b5..6d4dbc84f 100644 --- a/client/core/rs/src/api/write/build.rs +++ b/client/core/rs/src/api/write/build.rs @@ -1,12 +1,12 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - build::{Build, _PartialBuildConfig}, - update::Update, NoData, + build::{_PartialBuildConfig, Build}, + update::Update, }; use super::KomodoWriteRequest; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a build. Response: [Build]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct CreateBuild { /// The name given to newly created build. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateBuild { /// of the build at the given `id`. Response: [Build]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct CopyBuild { /// The name of the new build. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyBuild { /// Response: [Build] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct DeleteBuild { /// The id or name of the build to delete. pub id: String, @@ -72,10 +75,11 @@ pub struct DeleteBuild { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Build)] +#[error(serror::Error)] pub struct UpdateBuild { /// The id of the build to update. pub id: String, @@ -89,10 +93,12 @@ pub struct UpdateBuild { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameBuild { /// The id or name of the Build to rename. pub id: String, @@ -105,10 +111,11 @@ pub struct RenameBuild { /// Trigger a refresh of the cached latest hash and message. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(NoData)] +#[error(serror::Error)] pub struct RefreshBuildCache { /// Id or name #[serde(alias = "id", alias = "name")] @@ -121,10 +128,11 @@ pub struct RefreshBuildCache { /// passed in request. Response: [CreateBuildWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateBuildWebhookResponse)] +#[error(serror::Error)] pub struct CreateBuildWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -140,10 +148,11 @@ pub type CreateBuildWebhookResponse = NoData; /// passed in request. Response: [CreateBuildWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteBuildWebhookResponse)] +#[error(serror::Error)] pub struct DeleteBuildWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/builder.rs b/client/core/rs/src/api/write/builder.rs index f929946a9..5b5e3a2b7 100644 --- a/client/core/rs/src/api/write/builder.rs +++ b/client/core/rs/src/api/write/builder.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a builder. Response: [Builder]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct CreateBuilder { /// The name given to newly created builder. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateBuilder { /// of the builder at the given `id`. Response: [Builder] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct CopyBuilder { /// The name of the new builder. pub name: String, @@ -50,10 +52,11 @@ pub struct CopyBuilder { /// Response: [Builder] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct DeleteBuilder { /// The id or name of the builder to delete. pub id: String, @@ -71,10 +74,11 @@ pub struct DeleteBuilder { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Builder)] +#[error(serror::Error)] pub struct UpdateBuilder { /// The id of the builder to update. pub id: String, @@ -88,10 +92,12 @@ pub struct UpdateBuilder { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameBuilder { /// The id or name of the Builder to rename. pub id: String, diff --git a/client/core/rs/src/api/write/deployment.rs b/client/core/rs/src/api/write/deployment.rs index b7c6e6505..e9adb8eef 100644 --- a/client/core/rs/src/api/write/deployment.rs +++ b/client/core/rs/src/api/write/deployment.rs @@ -1,10 +1,10 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - deployment::{Deployment, _PartialDeploymentConfig}, + deployment::{_PartialDeploymentConfig, Deployment}, update::Update, }; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a deployment. Response: [Deployment]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct CreateDeployment { /// The name given to newly created deployment. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateDeployment { /// of the deployment at the given `id`. Response: [Deployment] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct CopyDeployment { /// The name of the new deployment. pub name: String, @@ -49,10 +51,11 @@ pub struct CopyDeployment { /// Create a Deployment from an existing container. Response: [Deployment]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct CreateDeploymentFromContainer { /// The name or id of the existing container. pub name: String, @@ -69,10 +72,11 @@ pub struct CreateDeploymentFromContainer { /// the deployment clean up. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct DeleteDeployment { /// The id or name of the deployment to delete. pub id: String, @@ -93,10 +97,11 @@ pub struct DeleteDeployment { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Deployment)] +#[error(serror::Error)] pub struct UpdateDeployment { /// The deployment id to update. pub id: String, @@ -112,10 +117,12 @@ pub struct UpdateDeployment { /// `docker rename ...`. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameDeployment { /// The id of the deployment to rename. pub id: String, diff --git a/client/core/rs/src/api/write/description.rs b/client/core/rs/src/api/write/description.rs index eb99542bd..7d88c27f5 100644 --- a/client/core/rs/src/api/write/description.rs +++ b/client/core/rs/src/api/write/description.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoWriteRequest; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateDescriptionResponse)] +#[error(serror::Error)] pub struct UpdateDescription { /// The target resource to set description for. pub target: ResourceTarget, diff --git a/client/core/rs/src/api/write/permissions.rs b/client/core/rs/src/api/write/permissions.rs index 19dc28e00..2b1738d4b 100644 --- a/client/core/rs/src/api/write/permissions.rs +++ b/client/core/rs/src/api/write/permissions.rs @@ -1,11 +1,11 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - permission::{PermissionLevel, UserTarget}, NoData, ResourceTarget, ResourceTargetVariant, + permission::{PermissionLevel, UserTarget}, }; use super::KomodoWriteRequest; @@ -14,10 +14,11 @@ use super::KomodoWriteRequest; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdatePermissionOnTargetResponse)] +#[error(serror::Error)] pub struct UpdatePermissionOnTarget { /// Specify the user or user group. pub user_target: UserTarget, @@ -36,10 +37,11 @@ pub type UpdatePermissionOnTargetResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdatePermissionOnResourceTypeResponse)] +#[error(serror::Error)] pub struct UpdatePermissionOnResourceType { /// Specify the user or user group. pub user_target: UserTarget, @@ -58,10 +60,11 @@ pub type UpdatePermissionOnResourceTypeResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserBasePermissionsResponse)] +#[error(serror::Error)] pub struct UpdateUserBasePermissions { /// The target user. pub user_id: String, @@ -80,10 +83,11 @@ pub type UpdateUserBasePermissionsResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserAdminResponse)] +#[error(serror::Error)] pub struct UpdateUserAdmin { /// The target user. pub user_id: String, diff --git a/client/core/rs/src/api/write/procedure.rs b/client/core/rs/src/api/write/procedure.rs index e818c844a..653fac095 100644 --- a/client/core/rs/src/api/write/procedure.rs +++ b/client/core/rs/src/api/write/procedure.rs @@ -1,10 +1,10 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - procedure::{Procedure, _PartialProcedureConfig}, + procedure::{_PartialProcedureConfig, Procedure}, update::Update, }; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a procedure. Response: [Procedure]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateProcedureResponse)] +#[error(serror::Error)] pub struct CreateProcedure { /// The name given to newly created build. pub name: String, @@ -36,10 +37,11 @@ pub type CreateProcedureResponse = Procedure; /// of the procedure at the given `id`. Response: [Procedure]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CopyProcedureResponse)] +#[error(serror::Error)] pub struct CopyProcedure { /// The name of the new procedure. pub name: String, @@ -56,10 +58,11 @@ pub type CopyProcedureResponse = Procedure; /// Response: [Procedure] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteProcedureResponse)] +#[error(serror::Error)] pub struct DeleteProcedure { /// The id or name of the procedure to delete. pub id: String, @@ -80,10 +83,11 @@ pub type DeleteProcedureResponse = Procedure; /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateProcedureResponse)] +#[error(serror::Error)] pub struct UpdateProcedure { /// The id of the procedure to update. pub id: String, @@ -100,10 +104,12 @@ pub type UpdateProcedureResponse = Procedure; /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameProcedure { /// The id or name of the Procedure to rename. pub id: String, diff --git a/client/core/rs/src/api/write/provider.rs b/client/core/rs/src/api/write/provider.rs index f3e99a6ad..415618195 100644 --- a/client/core/rs/src/api/write/provider.rs +++ b/client/core/rs/src/api/write/provider.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -11,10 +11,11 @@ use super::KomodoWriteRequest; /// Response: [GitProviderAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateGitProviderAccountResponse)] +#[error(serror::Error)] pub struct CreateGitProviderAccount { /// The initial account config. Anything in the _id field will be ignored, /// as this is generated on creation. @@ -30,10 +31,11 @@ pub type CreateGitProviderAccountResponse = GitProviderAccount; /// Response: [GitProviderAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateGitProviderAccountResponse)] +#[error(serror::Error)] pub struct UpdateGitProviderAccount { /// The id of the git provider account to update. pub id: String, @@ -50,10 +52,11 @@ pub type UpdateGitProviderAccountResponse = GitProviderAccount; /// Response: [DeleteGitProviderAccountResponse]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteGitProviderAccountResponse)] +#[error(serror::Error)] pub struct DeleteGitProviderAccount { /// The id of the git provider to delete pub id: String, @@ -68,10 +71,11 @@ pub type DeleteGitProviderAccountResponse = GitProviderAccount; /// Response: [DockerRegistryAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct CreateDockerRegistryAccount { pub account: _PartialDockerRegistryAccount, } @@ -85,10 +89,11 @@ pub type CreateDockerRegistryAccountResponse = DockerRegistryAccount; /// Response: [DockerRegistryAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct UpdateDockerRegistryAccount { /// The id of the docker registry to update pub id: String, @@ -105,10 +110,11 @@ pub type UpdateDockerRegistryAccountResponse = DockerRegistryAccount; /// Response: [DockerRegistryAccount]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteDockerRegistryAccountResponse)] +#[error(serror::Error)] pub struct DeleteDockerRegistryAccount { /// The id of the docker registry account to delete pub id: String, diff --git a/client/core/rs/src/api/write/repo.rs b/client/core/rs/src/api/write/repo.rs index 3008fc9f6..7b4467cbb 100644 --- a/client/core/rs/src/api/write/repo.rs +++ b/client/core/rs/src/api/write/repo.rs @@ -1,12 +1,12 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - repo::{Repo, _PartialRepoConfig}, - update::Update, NoData, + repo::{_PartialRepoConfig, Repo}, + update::Update, }; use super::KomodoWriteRequest; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a repo. Response: [Repo]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct CreateRepo { /// The name given to newly created repo. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateRepo { /// of the repo at the given `id`. Response: [Repo]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct CopyRepo { /// The name of the new repo. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyRepo { /// Response: [Repo] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct DeleteRepo { /// The id or name of the repo to delete. pub id: String, @@ -75,10 +78,11 @@ pub struct DeleteRepo { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Repo)] +#[error(serror::Error)] pub struct UpdateRepo { /// The id of the repo to update. pub id: String, @@ -92,10 +96,12 @@ pub struct UpdateRepo { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameRepo { /// The id or name of the Repo to rename. pub id: String, @@ -108,10 +114,11 @@ pub struct RenameRepo { /// Trigger a refresh of the cached latest hash and message. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(NoData)] +#[error(serror::Error)] pub struct RefreshRepoCache { /// Id or name #[serde(alias = "id", alias = "name")] @@ -132,10 +139,11 @@ pub enum RepoWebhookAction { /// passed in request. Response: [CreateRepoWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateRepoWebhookResponse)] +#[error(serror::Error)] pub struct CreateRepoWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -153,10 +161,11 @@ pub type CreateRepoWebhookResponse = NoData; /// passed in request. Response: [DeleteRepoWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteRepoWebhookResponse)] +#[error(serror::Error)] pub struct DeleteRepoWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/server.rs b/client/core/rs/src/api/write/server.rs index f4dce6ece..148349483 100644 --- a/client/core/rs/src/api/write/server.rs +++ b/client/core/rs/src/api/write/server.rs @@ -1,10 +1,10 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - server::{Server, _PartialServerConfig}, + server::{_PartialServerConfig, Server}, update::Update, }; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a server. Response: [Server]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Server)] +#[error(serror::Error)] pub struct CreateServer { /// The name given to newly created server. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateServer { /// Response: [Server] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Server)] +#[error(serror::Error)] pub struct DeleteServer { /// The id or name of the server to delete. pub id: String, @@ -54,10 +56,11 @@ pub struct DeleteServer { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Server)] +#[error(serror::Error)] pub struct UpdateServer { /// The id or name of the server to update. pub id: String, @@ -71,10 +74,12 @@ pub struct UpdateServer { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameServer { /// The id or name of the Server to rename. pub id: String, @@ -90,10 +95,12 @@ pub struct RenameServer { /// `docker network create {name}` #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct CreateNetwork { /// Server Id or name pub server: String, diff --git a/client/core/rs/src/api/write/server_template.rs b/client/core/rs/src/api/write/server_template.rs index 5ddc4b21f..59e8e2346 100644 --- a/client/core/rs/src/api/write/server_template.rs +++ b/client/core/rs/src/api/write/server_template.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -15,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a server template. Response: [ServerTemplate]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct CreateServerTemplate { /// The name given to newly created server template. pub name: String, @@ -33,10 +34,11 @@ pub struct CreateServerTemplate { /// of the server template at the given `id`. Response: [ServerTemplate] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct CopyServerTemplate { /// The name of the new server template. pub name: String, @@ -50,10 +52,11 @@ pub struct CopyServerTemplate { /// Response: [ServerTemplate] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct DeleteServerTemplate { /// The id or name of the server template to delete. pub id: String, @@ -71,10 +74,11 @@ pub struct DeleteServerTemplate { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ServerTemplate)] +#[error(serror::Error)] pub struct UpdateServerTemplate { /// The id of the server template to update. pub id: String, @@ -88,10 +92,12 @@ pub struct UpdateServerTemplate { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameServerTemplate { /// The id or name of the ServerTemplate to rename. pub id: String, diff --git a/client/core/rs/src/api/write/stack.rs b/client/core/rs/src/api/write/stack.rs index 8cbd0e1ca..26de59515 100644 --- a/client/core/rs/src/api/write/stack.rs +++ b/client/core/rs/src/api/write/stack.rs @@ -1,12 +1,12 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - stack::{Stack, _PartialStackConfig}, - update::Update, NoData, + stack::{_PartialStackConfig, Stack}, + update::Update, }; use super::KomodoWriteRequest; @@ -16,10 +16,11 @@ use super::KomodoWriteRequest; /// Create a stack. Response: [Stack]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct CreateStack { /// The name given to newly created stack. pub name: String, @@ -34,10 +35,11 @@ pub struct CreateStack { /// of the stack at the given `id`. Response: [Stack]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct CopyStack { /// The name of the new stack. pub name: String, @@ -51,10 +53,11 @@ pub struct CopyStack { /// Response: [Stack] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct DeleteStack { /// The id or name of the stack to delete. pub id: String, @@ -75,10 +78,11 @@ pub struct DeleteStack { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Stack)] +#[error(serror::Error)] pub struct UpdateStack { /// The id of the Stack to update. pub id: String, @@ -91,10 +95,12 @@ pub struct UpdateStack { /// Rename the stack at id to the given name. Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameStack { /// The id of the stack to rename. pub id: String, @@ -107,10 +113,12 @@ pub struct RenameStack { /// Update file contents in Files on Server or Git Repo mode. Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct WriteStackFileContents { /// The name or id of the target Stack. #[serde(alias = "id", alias = "name")] @@ -130,10 +138,11 @@ pub struct WriteStackFileContents { /// - The latest json, and for repos, the remote contents, hash, and message. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(NoData)] +#[error(serror::Error)] pub struct RefreshStackCache { /// Id or name #[serde(alias = "id", alias = "name")] @@ -153,10 +162,11 @@ pub enum StackWebhookAction { /// passed in request. Response: [CreateStackWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateStackWebhookResponse)] +#[error(serror::Error)] pub struct CreateStackWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -174,10 +184,11 @@ pub type CreateStackWebhookResponse = NoData; /// passed in request. Response: [DeleteStackWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteStackWebhookResponse)] +#[error(serror::Error)] pub struct DeleteStackWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/sync.rs b/client/core/rs/src/api/write/sync.rs index 04816e7bc..3b5d76e66 100644 --- a/client/core/rs/src/api/write/sync.rs +++ b/client/core/rs/src/api/write/sync.rs @@ -1,13 +1,13 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; use crate::entities::{ - sync::{ResourceSync, _PartialResourceSyncConfig}, - update::Update, NoData, + sync::{_PartialResourceSyncConfig, ResourceSync}, + update::Update, }; use super::KomodoWriteRequest; @@ -17,10 +17,11 @@ use super::KomodoWriteRequest; /// Create a sync. Response: [ResourceSync]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct CreateResourceSync { /// The name given to newly created sync. pub name: String, @@ -35,10 +36,11 @@ pub struct CreateResourceSync { /// of the sync at the given `id`. Response: [ResourceSync]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct CopyResourceSync { /// The name of the new sync. pub name: String, @@ -52,10 +54,11 @@ pub struct CopyResourceSync { /// Response: [ResourceSync] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct DeleteResourceSync { /// The id or name of the sync to delete. pub id: String, @@ -73,10 +76,11 @@ pub struct DeleteResourceSync { /// field changes occur from out of date local state. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct UpdateResourceSync { /// The id of the sync to update. pub id: String, @@ -90,10 +94,12 @@ pub struct UpdateResourceSync { /// Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct RenameResourceSync { /// The id or name of the ResourceSync to rename. pub id: String, @@ -106,10 +112,11 @@ pub struct RenameResourceSync { /// Trigger a refresh of the computed diff logs for view. Response: [ResourceSync] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(ResourceSync)] +#[error(serror::Error)] pub struct RefreshResourceSyncPending { /// Id or name #[serde(alias = "id", alias = "name")] @@ -121,10 +128,12 @@ pub struct RefreshResourceSyncPending { /// Rename the stack at id to the given name. Response: [Update]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct WriteSyncFileContents { /// The name or id of the target Sync. #[serde(alias = "id", alias = "name")] @@ -150,12 +159,14 @@ pub struct WriteSyncFileContents { PartialEq, Serialize, Deserialize, - Request, + Resolve, EmptyTraits, Parser, )] #[empty_traits(KomodoWriteRequest)] #[response(Update)] +#[error(serror::Error)] +#[error(serror::Error)] pub struct CommitSync { /// Id or name #[serde(alias = "id", alias = "name")] @@ -175,10 +186,11 @@ pub enum SyncWebhookAction { /// passed in request. Response: [CreateSyncWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateSyncWebhookResponse)] +#[error(serror::Error)] pub struct CreateSyncWebhook { /// Id or name #[serde(alias = "id", alias = "name")] @@ -196,10 +208,11 @@ pub type CreateSyncWebhookResponse = NoData; /// passed in request. Response: [DeleteSyncWebhookResponse] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteSyncWebhookResponse)] +#[error(serror::Error)] pub struct DeleteSyncWebhook { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/api/write/tags.rs b/client/core/rs/src/api/write/tags.rs index 2e5baa4d5..3634b4b49 100644 --- a/client/core/rs/src/api/write/tags.rs +++ b/client/core/rs/src/api/write/tags.rs @@ -1,9 +1,12 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{tag::Tag, NoData, ResourceTarget}; +use crate::entities::{ + NoData, ResourceTarget, + tag::{Tag, TagColor}, +}; use super::KomodoWriteRequest; @@ -12,10 +15,11 @@ use super::KomodoWriteRequest; /// Create a tag. Response: [Tag]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Tag)] +#[error(serror::Error)] pub struct CreateTag { /// The name of the tag. pub name: String, @@ -28,10 +32,11 @@ pub struct CreateTag { /// Note. Will also remove this tag from all attached resources. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Tag)] +#[error(serror::Error)] pub struct DeleteTag { /// The id of the tag to delete. pub id: String, @@ -42,10 +47,11 @@ pub struct DeleteTag { /// Rename a tag at id. Response: [Tag]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(Tag)] +#[error(serror::Error)] pub struct RenameTag { /// The id of the tag to rename. pub id: String, @@ -53,16 +59,32 @@ pub struct RenameTag { pub name: String, } +/// Update color for tag. Response: [Tag]. +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoWriteRequest)] +#[response(Tag)] +#[error(serror::Error)] +pub struct UpdateTagColor { + /// The name or id of the tag to update. + pub tag: String, + /// The new color for the tag. + pub color: TagColor, +} + // /// Update the tags on a resource. /// Response: [NoData] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateTagsOnResourceResponse)] +#[error(serror::Error)] pub struct UpdateTagsOnResource { pub target: ResourceTarget, /// Tag Ids diff --git a/client/core/rs/src/api/write/user.rs b/client/core/rs/src/api/write/user.rs index 00e647e41..991db9aad 100644 --- a/client/core/rs/src/api/write/user.rs +++ b/client/core/rs/src/api/write/user.rs @@ -1,9 +1,9 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{user::User, NoData}; +use crate::entities::{NoData, user::User}; use super::KomodoWriteRequest; @@ -13,10 +13,11 @@ use super::KomodoWriteRequest; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserUsernameResponse)] +#[error(serror::Error)] pub struct UpdateUserUsername { pub username: String, } @@ -30,10 +31,11 @@ pub type UpdateUserUsernameResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateUserPasswordResponse)] +#[error(serror::Error)] pub struct UpdateUserPassword { pub password: String, } @@ -51,10 +53,11 @@ pub type UpdateUserPasswordResponse = NoData; /// Response: [NoData]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteUserResponse)] +#[error(serror::Error)] pub struct DeleteUser { /// User id or username #[serde(alias = "username", alias = "id")] @@ -70,10 +73,11 @@ pub type DeleteUserResponse = User; /// Response: [User]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateServiceUserResponse)] +#[error(serror::Error)] pub struct CreateServiceUser { /// The username for the service user. pub username: String, @@ -90,10 +94,11 @@ pub type CreateServiceUserResponse = User; /// Response: [User]. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateServiceUserDescriptionResponse)] +#[error(serror::Error)] pub struct UpdateServiceUserDescription { /// The service user's username pub username: String, diff --git a/client/core/rs/src/api/write/user_group.rs b/client/core/rs/src/api/write/user_group.rs index dbb5f722a..b7e8b37ca 100644 --- a/client/core/rs/src/api/write/user_group.rs +++ b/client/core/rs/src/api/write/user_group.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -10,10 +10,11 @@ use super::KomodoWriteRequest; /// **Admin only.** Create a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct CreateUserGroup { /// The name to assign to the new UserGroup pub name: String, @@ -24,10 +25,11 @@ pub struct CreateUserGroup { /// **Admin only.** Rename a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct RenameUserGroup { /// The id of the UserGroup pub id: String, @@ -40,10 +42,11 @@ pub struct RenameUserGroup { /// **Admin only.** Delete a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct DeleteUserGroup { /// The id of the UserGroup pub id: String, @@ -54,10 +57,11 @@ pub struct DeleteUserGroup { /// **Admin only.** Add a user to a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct AddUserToUserGroup { /// The name or id of UserGroup that user should be added to. pub user_group: String, @@ -70,10 +74,11 @@ pub struct AddUserToUserGroup { /// **Admin only.** Remove a user from a user group. Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct RemoveUserFromUserGroup { /// The name or id of UserGroup that user should be removed from. pub user_group: String, @@ -87,10 +92,11 @@ pub struct RemoveUserFromUserGroup { /// Response: [UserGroup] #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UserGroup)] +#[error(serror::Error)] pub struct SetUsersInUserGroup { /// Id or name. pub user_group: String, diff --git a/client/core/rs/src/api/write/variable.rs b/client/core/rs/src/api/write/variable.rs index 2f55f637e..ebd45e4ec 100644 --- a/client/core/rs/src/api/write/variable.rs +++ b/client/core/rs/src/api/write/variable.rs @@ -1,5 +1,5 @@ use derive_empty_traits::EmptyTraits; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -10,10 +10,11 @@ use super::KomodoWriteRequest; /// **Admin only.** Create variable. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(CreateVariableResponse)] +#[error(serror::Error)] pub struct CreateVariable { /// The name of the variable to create. pub name: String, @@ -36,10 +37,11 @@ pub type CreateVariableResponse = Variable; /// **Admin only.** Update variable value. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateVariableValueResponse)] +#[error(serror::Error)] pub struct UpdateVariableValue { /// The name of the variable to update. pub name: String, @@ -55,10 +57,11 @@ pub type UpdateVariableValueResponse = Variable; /// **Admin only.** Update variable description. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateVariableDescriptionResponse)] +#[error(serror::Error)] pub struct UpdateVariableDescription { /// The name of the variable to update. pub name: String, @@ -74,10 +77,11 @@ pub type UpdateVariableDescriptionResponse = Variable; /// **Admin only.** Update whether variable is secret. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(UpdateVariableIsSecretResponse)] +#[error(serror::Error)] pub struct UpdateVariableIsSecret { /// The name of the variable to update. pub name: String, @@ -93,10 +97,11 @@ pub type UpdateVariableIsSecretResponse = Variable; /// **Admin only.** Delete a variable. Response: [Variable]. #[typeshare] #[derive( - Debug, Clone, Serialize, Deserialize, Request, EmptyTraits, + Debug, Clone, Serialize, Deserialize, Resolve, EmptyTraits, )] #[empty_traits(KomodoWriteRequest)] #[response(DeleteVariableResponse)] +#[error(serror::Error)] pub struct DeleteVariable { pub name: String, } diff --git a/client/core/rs/src/deserializers/conversion.rs b/client/core/rs/src/deserializers/conversion.rs index b5629a809..9e7665cbc 100644 --- a/client/core/rs/src/deserializers/conversion.rs +++ b/client/core/rs/src/deserializers/conversion.rs @@ -1,6 +1,6 @@ use serde::{ - de::{value::SeqAccessDeserializer, Visitor}, Deserialize, Deserializer, + de::{Visitor, value::SeqAccessDeserializer}, }; use crate::entities::deployment::Conversion; diff --git a/client/core/rs/src/deserializers/environment.rs b/client/core/rs/src/deserializers/environment.rs index 606c7009d..6c279874c 100644 --- a/client/core/rs/src/deserializers/environment.rs +++ b/client/core/rs/src/deserializers/environment.rs @@ -1,6 +1,6 @@ use serde::{ - de::{value::SeqAccessDeserializer, Visitor}, Deserialize, Deserializer, + de::{Visitor, value::SeqAccessDeserializer}, }; use crate::entities::EnvironmentVar; diff --git a/client/core/rs/src/deserializers/file_contents.rs b/client/core/rs/src/deserializers/file_contents.rs index 61995566a..e36844f65 100644 --- a/client/core/rs/src/deserializers/file_contents.rs +++ b/client/core/rs/src/deserializers/file_contents.rs @@ -1,4 +1,4 @@ -use serde::{de::Visitor, Deserializer}; +use serde::{Deserializer, de::Visitor}; /// Using this ensures the file contents end with trailing '\n' pub fn file_contents_deserializer<'de, D>( @@ -22,7 +22,7 @@ where struct FileContentsVisitor; -impl<'de> Visitor<'de> for FileContentsVisitor { +impl Visitor<'_> for FileContentsVisitor { type Value = String; fn expecting( @@ -47,7 +47,7 @@ impl<'de> Visitor<'de> for FileContentsVisitor { struct OptionFileContentsVisitor; -impl<'de> Visitor<'de> for OptionFileContentsVisitor { +impl Visitor<'_> for OptionFileContentsVisitor { type Value = Option; fn expecting( diff --git a/client/core/rs/src/deserializers/labels.rs b/client/core/rs/src/deserializers/labels.rs index f8620d12b..f161c86d3 100644 --- a/client/core/rs/src/deserializers/labels.rs +++ b/client/core/rs/src/deserializers/labels.rs @@ -1,6 +1,6 @@ use serde::{ - de::{value::SeqAccessDeserializer, Visitor}, Deserialize, Deserializer, + de::{Visitor, value::SeqAccessDeserializer}, }; use crate::entities::EnvironmentVar; diff --git a/client/core/rs/src/deserializers/maybe_string_i64.rs b/client/core/rs/src/deserializers/maybe_string_i64.rs index 8737dd011..af35c4682 100644 --- a/client/core/rs/src/deserializers/maybe_string_i64.rs +++ b/client/core/rs/src/deserializers/maybe_string_i64.rs @@ -1,4 +1,4 @@ -use serde::{de::Visitor, Deserializer}; +use serde::{Deserializer, de::Visitor}; pub fn maybe_string_i64_deserializer<'de, D>( deserializer: D, @@ -20,7 +20,7 @@ where struct MaybeStringI64Visitor; -impl<'de> Visitor<'de> for MaybeStringI64Visitor { +impl Visitor<'_> for MaybeStringI64Visitor { type Value = i64; fn expecting( @@ -110,7 +110,7 @@ impl<'de> Visitor<'de> for MaybeStringI64Visitor { struct OptionMaybeStringI64Visitor; -impl<'de> Visitor<'de> for OptionMaybeStringI64Visitor { +impl Visitor<'_> for OptionMaybeStringI64Visitor { type Value = Option; fn expecting( diff --git a/client/core/rs/src/deserializers/string_list.rs b/client/core/rs/src/deserializers/string_list.rs index 9611fa393..44dcfec5d 100644 --- a/client/core/rs/src/deserializers/string_list.rs +++ b/client/core/rs/src/deserializers/string_list.rs @@ -1,6 +1,6 @@ use serde::{ - de::{value::SeqAccessDeserializer, SeqAccess, Visitor}, Deserialize, Deserializer, + de::{SeqAccess, Visitor, value::SeqAccessDeserializer}, }; use crate::parsers::parse_string_list; diff --git a/client/core/rs/src/deserializers/term_signal_labels.rs b/client/core/rs/src/deserializers/term_signal_labels.rs index 8c5d8936b..cc884d04b 100644 --- a/client/core/rs/src/deserializers/term_signal_labels.rs +++ b/client/core/rs/src/deserializers/term_signal_labels.rs @@ -1,6 +1,6 @@ use serde::{ - de::{value::SeqAccessDeserializer, Visitor}, Deserialize, Deserializer, + de::{Visitor, value::SeqAccessDeserializer}, }; use crate::entities::deployment::TerminationSignalLabel; diff --git a/client/core/rs/src/entities/action.rs b/client/core/rs/src/entities/action.rs index aad0c1897..265552af8 100644 --- a/client/core/rs/src/entities/action.rs +++ b/client/core/rs/src/entities/action.rs @@ -1,4 +1,4 @@ -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use partial_derive2::Partial; diff --git a/client/core/rs/src/entities/alert.rs b/client/core/rs/src/entities/alert.rs index 6311d0ebf..c13b636a1 100644 --- a/client/core/rs/src/entities/alert.rs +++ b/client/core/rs/src/entities/alert.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use strum::{Display, EnumString}; use typeshare::typeshare; -use crate::entities::{MongoId, I64}; +use crate::entities::{I64, MongoId}; use super::{ - _Serror, deployment::DeploymentState, stack::StackState, - ResourceTarget, Version, + _Serror, ResourceTarget, Version, deployment::DeploymentState, + stack::StackState, }; /// Representation of an alert in the system. @@ -74,6 +74,15 @@ pub enum AlertData { /// A null alert None {}, + /// The user triggered a test of the + /// Alerter configuration. + Test { + /// The id of the alerter + id: String, + /// The name of the alerter + name: String, + }, + /// A server could not be reached. ServerUnreachable { /// The id of the server diff --git a/client/core/rs/src/entities/alerter.rs b/client/core/rs/src/entities/alerter.rs index 6075813b4..838496abb 100644 --- a/client/core/rs/src/entities/alerter.rs +++ b/client/core/rs/src/entities/alerter.rs @@ -1,4 +1,4 @@ -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use derive_variants::EnumVariants; @@ -8,9 +8,9 @@ use strum::{AsRefStr, Display, EnumString}; use typeshare::typeshare; use super::{ + ResourceTarget, alert::AlertDataVariant, resource::{Resource, ResourceListItem, ResourceQuery}, - ResourceTarget, }; #[typeshare] @@ -72,6 +72,7 @@ impl AlerterConfig { } } +#[allow(clippy::derivable_impls)] impl Default for AlerterConfig { fn default() -> Self { Self { diff --git a/client/core/rs/src/entities/build.rs b/client/core/rs/src/entities/build.rs index 2674c90bb..0195243a7 100644 --- a/client/core/rs/src/entities/build.rs +++ b/client/core/rs/src/entities/build.rs @@ -1,10 +1,10 @@ -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use partial_derive2::Partial; use serde::{ - de::{value::MapAccessDeserializer, Visitor}, Deserialize, Deserializer, Serialize, + de::{Visitor, value::MapAccessDeserializer}, }; use strum::Display; use typeshare::typeshare; @@ -19,8 +19,8 @@ use crate::{ }; use super::{ - resource::{Resource, ResourceListItem, ResourceQuery}, NoData, SystemCommand, Version, + resource::{Resource, ResourceListItem, ResourceQuery}, }; #[typeshare] @@ -121,8 +121,8 @@ pub struct BuildConfig { #[builder(default)] pub image_name: String, - /// An extra tag put before the build version, for the image pushed to the repository. - /// Eg. in image tag of `aarch64` would push to mbecker20/komodo:1.13.2-aarch64. + /// An extra tag put after the build version, for the image pushed to the repository. + /// Eg. in image tag of `aarch64` would push to moghtech/komodo-core:1.13.2-aarch64. /// If this is empty, the image tag will just be the build version. /// /// Can be used in conjunction with `image_name` to direct multiple builds @@ -390,7 +390,10 @@ impl<'de> Visitor<'de> for ImageRegistryVisitor { &self, formatter: &mut std::fmt::Formatter, ) -> std::fmt::Result { - write!(formatter, "{{ \"domain\": string, \"account\": string, \"organization\": string }}") + write!( + formatter, + "{{ \"domain\": string, \"account\": string, \"organization\": string }}" + ) } fn visit_map(self, map: A) -> Result @@ -420,7 +423,10 @@ impl<'de> Visitor<'de> for OptionImageRegistryVisitor { &self, formatter: &mut std::fmt::Formatter, ) -> std::fmt::Result { - write!(formatter, "null or {{ \"domain\": string, \"account\": string, \"organization\": string }}") + write!( + formatter, + "null or {{ \"domain\": string, \"account\": string, \"organization\": string }}" + ) } fn visit_map(self, map: A) -> Result diff --git a/client/core/rs/src/entities/builder.rs b/client/core/rs/src/entities/builder.rs index e66961c73..3597416b2 100644 --- a/client/core/rs/src/entities/builder.rs +++ b/client/core/rs/src/entities/builder.rs @@ -10,9 +10,9 @@ use crate::deserializers::{ }; use super::{ + MergePartial, config::{DockerRegistry, GitProvider}, resource::{AddFilters, Resource, ResourceListItem, ResourceQuery}, - MergePartial, }; #[typeshare] diff --git a/client/core/rs/src/entities/config/core.rs b/client/core/rs/src/entities/config/core.rs index 93f0e1577..0880c039b 100644 --- a/client/core/rs/src/entities/config/core.rs +++ b/client/core/rs/src/entities/config/core.rs @@ -14,11 +14,11 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; use serde::{Deserialize, Serialize}; use crate::entities::{ - logger::{LogConfig, LogLevel, StdioLogMode}, Timelength, + logger::{LogConfig, LogLevel, StdioLogMode}, }; -use super::{empty_or_redacted, DockerRegistry, GitProvider}; +use super::{DockerRegistry, GitProvider, empty_or_redacted}; /// # Komodo Core Environment Variables /// @@ -27,7 +27,7 @@ use super::{empty_or_redacted, DockerRegistry, GitProvider}; /// although the lower case format can still be parsed. /// /// *Note.* The Komodo Core docker image includes the default core configuration found at -/// [https://github.com/mbecker20/komodo/blob/main/config/core.config.toml](https://github.com/mbecker20/komodo/blob/main/config/core.config.toml). +/// [https://github.com/moghtech/komodo/blob/main/config/core.config.toml](https://github.com/moghtech/komodo/blob/main/config/core.config.toml). /// To configure the core api, you can either mount your own custom configuration file to /// `/config/config.toml` inside the container, /// or simply override whichever fields you need using the environment. @@ -96,6 +96,8 @@ pub struct Env { pub komodo_enable_new_users: Option, /// Override `disable_user_registration` pub komodo_disable_user_registration: Option, + /// Override `lock_login_credentials_for` + pub komodo_lock_login_credentials_for: Option>, /// Override `disable_confirm_dialog` pub komodo_disable_confirm_dialog: Option, /// Override `disable_non_admin_create` @@ -227,12 +229,12 @@ fn default_config_path() -> String { /// and then applying any config field overrides specified in the environment. /// /// *Note.* The Komodo Core docker image includes the default core configuration found at -/// [https://github.com/mbecker20/komodo/blob/main/config/core.config.toml](https://github.com/mbecker20/komodo/blob/main/config/core.config.toml). +/// [https://github.com/moghtech/komodo/blob/main/config/core.config.toml](https://github.com/moghtech/komodo/blob/main/config/core.config.toml). /// To configure the core api, you can either mount your own custom configuration file to /// `/config/config.toml` inside the container, /// or simply override whichever fields you need using the environment. /// -/// Refer to the [example file](https://github.com/mbecker20/komodo/blob/main/config/core.config.toml) for a full example. +/// Refer to the [example file](https://github.com/moghtech/komodo/blob/main/config/core.config.toml) for a full example. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CoreConfig { // =========== @@ -300,6 +302,13 @@ pub struct CoreConfig { #[serde(default)] pub disable_user_registration: bool, + /// List of usernames for which the update username / password + /// APIs are disabled. Used by demo to lock the 'demo' : 'demo' login. + /// + /// To lock the api for all users, use `lock_login_credentials_for = ["__ALL__"]` + #[serde(default)] + pub lock_login_credentials_for: Vec, + /// Normally all users can create resources. /// If `disable_non_admin_create = true`, only admins will be able to create resources. #[serde(default)] @@ -581,6 +590,7 @@ impl CoreConfig { enable_new_users: config.enable_new_users, disable_user_registration: config.disable_user_registration, disable_non_admin_create: config.disable_non_admin_create, + lock_login_credentials_for: config.lock_login_credentials_for, local_auth: config.local_auth, oidc_enabled: config.oidc_enabled, oidc_provider: config.oidc_provider, diff --git a/client/core/rs/src/entities/config/mod.rs b/client/core/rs/src/entities/config/mod.rs index 735871ae1..517b8b310 100644 --- a/client/core/rs/src/entities/config/mod.rs +++ b/client/core/rs/src/entities/config/mod.rs @@ -23,7 +23,7 @@ pub struct GitProvider { /// Whether to use https. Default: true. #[serde(default = "default_git_https")] pub https: bool, - /// The account username. Required. + /// The accounts on the git provider. Required. #[serde(alias = "account")] pub accounts: Vec, } @@ -52,8 +52,8 @@ pub struct DockerRegistry { /// The docker provider domain. Default: `docker.io`. #[serde(default = "default_docker_provider")] pub domain: String, - /// The account username. Required. - #[serde(default, alias = "account")] + /// The accounts on the registry. Required. + #[serde(alias = "account")] pub accounts: Vec, /// Available organizations on the registry provider. /// Used to push an image under an organization's repo rather than an account's repo. diff --git a/client/core/rs/src/entities/config/periphery.rs b/client/core/rs/src/entities/config/periphery.rs index 9e46bdf9b..ed01d0d4a 100644 --- a/client/core/rs/src/entities/config/periphery.rs +++ b/client/core/rs/src/entities/config/periphery.rs @@ -18,12 +18,12 @@ use clap::Parser; use serde::Deserialize; use crate::entities::{ - logger::{LogConfig, LogLevel, StdioLogMode}, Timelength, + logger::{LogConfig, LogLevel, StdioLogMode}, }; use super::{ - empty_or_redacted, DockerRegistry, GitProvider, ProviderAccount, + DockerRegistry, GitProvider, ProviderAccount, empty_or_redacted, }; /// # Periphery Command Line Arguments. @@ -154,7 +154,7 @@ pub struct Env { /// # Periphery Configuration File /// -/// Refer to the [example file](https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml) for a full example. +/// Refer to the [example file](https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml) for a full example. #[derive(Debug, Clone, Deserialize)] pub struct PeripheryConfig { /// The port periphery will run on. diff --git a/client/core/rs/src/entities/deployment.rs b/client/core/rs/src/entities/deployment.rs index f702dc03f..8c3fb5bc1 100644 --- a/client/core/rs/src/entities/deployment.rs +++ b/client/core/rs/src/entities/deployment.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use derive_variants::EnumVariants; @@ -20,9 +20,9 @@ use crate::{ }; use super::{ + TerminationSignal, Version, docker::container::ContainerStateStatusEnum, resource::{Resource, ResourceListItem, ResourceQuery}, - TerminationSignal, Version, }; #[typeshare] @@ -453,11 +453,21 @@ pub type DeploymentQuery = ResourceQuery; Debug, Clone, Default, Serialize, Deserialize, DefaultBuilder, )] pub struct DeploymentQuerySpecifics { + /// Query only for Deployments on these Servers. + /// If empty, does not filter by Server. + /// Only accepts Server id (not name). #[serde(default)] pub server_ids: Vec, + /// Query only for Deployments with these Builds attached. + /// If empty, does not filter by Build. + /// Only accepts Build id (not name). #[serde(default)] pub build_ids: Vec, + + /// Query only for Deployments with available image updates. + #[serde(default)] + pub update_available: bool, } impl super::resource::AddFilters for DeploymentQuerySpecifics { diff --git a/client/core/rs/src/entities/docker/container.rs b/client/core/rs/src/entities/docker/container.rs index 0e7e09a0d..c948ba16e 100644 --- a/client/core/rs/src/entities/docker/container.rs +++ b/client/core/rs/src/entities/docker/container.rs @@ -4,7 +4,7 @@ use anyhow::anyhow; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{Usize, I64}; +use crate::entities::{I64, Usize}; use super::{ContainerConfig, GraphDriverData, PortBinding}; diff --git a/client/core/rs/src/entities/mod.rs b/client/core/rs/src/entities/mod.rs index 97f0e63ac..852c4533c 100644 --- a/client/core/rs/src/entities/mod.rs +++ b/client/core/rs/src/entities/mod.rs @@ -10,8 +10,8 @@ use clap::Parser; use derive_empty_traits::EmptyTraits; use derive_variants::{EnumVariants, ExtractVariant}; use serde::{ - de::{value::MapAccessDeserializer, Visitor}, Deserialize, Serialize, + de::{Visitor, value::MapAccessDeserializer}, }; use serror::Serror; use strum::{AsRefStr, Display, EnumString}; @@ -213,11 +213,7 @@ impl SystemCommand { } pub fn into_option(self) -> Option { - if self.is_none() { - None - } else { - Some(self) - } + if self.is_none() { None } else { Some(self) } } pub fn is_none(&self) -> bool { @@ -431,7 +427,7 @@ impl CloneArgs { .as_ref() .context("resource has no repo attached")?; Ok(format!( - "{protocol}://{access_token_at}{}/{repo}.git", + "{protocol}://{access_token_at}{}/{repo}", self.provider )) } @@ -753,6 +749,7 @@ pub enum Operation { UpdateAlerter, RenameAlerter, DeleteAlerter, + TestAlerter, // server template CreateServerTemplate, diff --git a/client/core/rs/src/entities/procedure.rs b/client/core/rs/src/entities/procedure.rs index fe8eaccc8..316451623 100644 --- a/client/core/rs/src/entities/procedure.rs +++ b/client/core/rs/src/entities/procedure.rs @@ -9,8 +9,8 @@ use typeshare::typeshare; use crate::api::execute::Execution; use super::{ - resource::{Resource, ResourceListItem, ResourceQuery}, I64, + resource::{Resource, ResourceListItem, ResourceQuery}, }; #[typeshare] diff --git a/client/core/rs/src/entities/repo.rs b/client/core/rs/src/entities/repo.rs index 872e220f8..d00067282 100644 --- a/client/core/rs/src/entities/repo.rs +++ b/client/core/rs/src/entities/repo.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use partial_derive2::Partial; @@ -16,9 +16,8 @@ use crate::{ }; use super::{ - environment_vars_from_str, + EnvironmentVar, SystemCommand, environment_vars_from_str, resource::{Resource, ResourceListItem, ResourceQuery}, - EnvironmentVar, SystemCommand, }; #[typeshare] diff --git a/client/core/rs/src/entities/resource.rs b/client/core/rs/src/entities/resource.rs index 5c1c4bc92..21c4b564b 100644 --- a/client/core/rs/src/entities/resource.rs +++ b/client/core/rs/src/entities/resource.rs @@ -1,4 +1,4 @@ -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use serde::{Deserialize, Serialize}; @@ -6,10 +6,10 @@ use typeshare::typeshare; use crate::{ deserializers::string_list_deserializer, - entities::{MongoId, I64}, + entities::{I64, MongoId}, }; -use super::{permission::PermissionLevel, ResourceTargetVariant}; +use super::{ResourceTargetVariant, permission::PermissionLevel}; #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize, Builder)] diff --git a/client/core/rs/src/entities/server.rs b/client/core/rs/src/entities/server.rs index aa448a78b..a4c4c36df 100644 --- a/client/core/rs/src/entities/server.rs +++ b/client/core/rs/src/entities/server.rs @@ -10,9 +10,9 @@ use crate::deserializers::{ }; use super::{ + I64, alert::SeverityLevel, resource::{AddFilters, Resource, ResourceListItem, ResourceQuery}, - I64, }; #[typeshare] diff --git a/client/core/rs/src/entities/server_template/aws.rs b/client/core/rs/src/entities/server_template/aws.rs index a7de38a95..cb293018a 100644 --- a/client/core/rs/src/entities/server_template/aws.rs +++ b/client/core/rs/src/entities/server_template/aws.rs @@ -127,7 +127,7 @@ apt upgrade -y curl -fsSL https://get.docker.com | sh systemctl enable docker.service systemctl enable containerd.service -curl -sSL https://raw.githubusercontent.com/mbecker20/komodo/main/scripts/setup-periphery.py | HOME=/root python3 +curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py | HOME=/root python3 systemctl enable periphery.service") } diff --git a/client/core/rs/src/entities/server_template/hetzner.rs b/client/core/rs/src/entities/server_template/hetzner.rs index eb0028fd7..49b12a04c 100644 --- a/client/core/rs/src/entities/server_template/hetzner.rs +++ b/client/core/rs/src/entities/server_template/hetzner.rs @@ -121,7 +121,7 @@ runcmd: - curl -fsSL https://get.docker.com | sh - systemctl enable docker.service - systemctl enable containerd.service - - curl -sSL 'https://raw.githubusercontent.com/mbecker20/komodo/main/scripts/setup-periphery.py' | HOME=/root python3 + - curl -sSL 'https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py' | HOME=/root python3 - systemctl enable periphery.service") } diff --git a/client/core/rs/src/entities/server_template/mod.rs b/client/core/rs/src/entities/server_template/mod.rs index a26c11d8d..0744811c8 100644 --- a/client/core/rs/src/entities/server_template/mod.rs +++ b/client/core/rs/src/entities/server_template/mod.rs @@ -1,4 +1,4 @@ -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_default_builder::DefaultBuilder; use derive_variants::EnumVariants; use partial_derive2::{Diff, MaybeNone, PartialDiff}; @@ -11,8 +11,8 @@ use self::{ }; use super::{ - resource::{AddFilters, Resource, ResourceListItem, ResourceQuery}, MergePartial, + resource::{AddFilters, Resource, ResourceListItem, ResourceQuery}, }; pub mod aws; diff --git a/client/core/rs/src/entities/stack.rs b/client/core/rs/src/entities/stack.rs index 46ffbafdf..23350ae54 100644 --- a/client/core/rs/src/entities/stack.rs +++ b/client/core/rs/src/entities/stack.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::OnceLock}; -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use partial_derive2::Partial; @@ -16,9 +16,10 @@ use crate::deserializers::{ }; use super::{ + FileContents, SystemCommand, docker::container::ContainerListItem, resource::{Resource, ResourceListItem, ResourceQuery}, - to_komodo_name, FileContents, SystemCommand, + to_komodo_name, }; #[typeshare] @@ -163,12 +164,15 @@ pub struct StackInfo { pub deployed_hash: Option, /// Deployed commit message, or null. Only for repo based stacks pub deployed_message: Option, - /// The deployed compose file contents. This is updated whenever Komodo successfully deploys the stack. + /// The deployed compose file contents. + /// This is updated whenever Komodo successfully deploys the stack. pub deployed_contents: Option>, /// The deployed service names. /// This is updated whenever it is empty, or deployed contents is updated. pub deployed_services: Option>, - + /// The output of `docker compose config`. + /// This is updated whenever Komodo successfully deploys the stack. + pub deployed_config: Option, /// The latest service names. /// This is updated whenever the stack cache refreshes, using the latest file contents (either db defined or remote). #[serde(default)] @@ -247,6 +251,14 @@ pub struct StackConfig { #[builder(default)] pub auto_update: bool, + /// If auto update is enabled, Komodo will + /// by default only update the specific services + /// with image updates. If this parameter is set to true, + /// Komodo will redeploy the whole Stack (all services). + #[serde(default)] + #[builder(default)] + pub auto_update_all_services: bool, + /// Whether to run `docker compose down` before `compose up`. #[serde(default)] #[builder(default)] @@ -382,6 +394,11 @@ pub struct StackConfig { #[builder(default)] pub pre_deploy: SystemCommand, + /// The optional command to run after the Stack is deployed. + #[serde(default)] + #[builder(default)] + pub post_deploy: SystemCommand, + /// The extra arguments to pass after `docker compose up -d`. /// If empty, no extra arguments will be passed. #[serde(default, deserialize_with = "string_list_deserializer")] @@ -487,8 +504,10 @@ impl Default for StackConfig { auto_pull: default_auto_pull(), poll_for_updates: Default::default(), auto_update: Default::default(), + auto_update_all_services: Default::default(), ignore_services: Default::default(), pre_deploy: Default::default(), + post_deploy: Default::default(), extra_args: Default::default(), environment: Default::default(), env_file_path: default_env_file_path(), @@ -585,12 +604,25 @@ pub type StackQuery = ResourceQuery; Serialize, Deserialize, Debug, Clone, Default, DefaultBuilder, )] pub struct StackQuerySpecifics { + /// Query only for Stacks on these Servers. + /// If empty, does not filter by Server. + /// Only accepts Server id (not name). + #[serde(default)] + pub server_ids: Vec, /// Filter syncs by their repo. + #[serde(default)] pub repos: Vec, + /// Query only for Stack with available image updates. + #[serde(default)] + pub update_available: bool, } impl super::resource::AddFilters for StackQuerySpecifics { fn add_filters(&self, filters: &mut Document) { + if !self.server_ids.is_empty() { + filters + .insert("config.server_id", doc! { "$in": &self.server_ids }); + } if !self.repos.is_empty() { filters.insert("config.repo", doc! { "$in": &self.repos }); } diff --git a/client/core/rs/src/entities/stats.rs b/client/core/rs/src/entities/stats.rs index 69bdc4fed..3170ceb9d 100644 --- a/client/core/rs/src/entities/stats.rs +++ b/client/core/rs/src/entities/stats.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{Timelength, I64}; +use crate::entities::{I64, Timelength}; /// System information of a server #[typeshare] @@ -49,17 +49,17 @@ pub struct SystemStatsRecord { pub disk_used_gb: f64, /// Total disk size in GB pub disk_total_gb: f64, - /// Breakdown of individual disks, ie their usages, sizes, and mount points + /// Breakdown of individual disks, including their usage, total size, and mount point pub disks: Vec, - /// Network ingress usage in bytes - #[serde(default)] - pub network_ingress_bytes: f64, - /// Network egress usage in bytes - #[serde(default)] - pub network_egress_bytes: f64, - /// Network usage by interface name (ingress, egress in bytes) - #[serde(default)] - pub network_usage_interface: Vec, // interface -> (ingress, egress) + /// Total network ingress in bytes + #[serde(default)] + pub network_ingress_bytes: f64, + /// Total network egress in bytes + #[serde(default)] + pub network_egress_bytes: f64, + // /// Network usage by interface name (ingress, egress in bytes) + // #[serde(default)] + // pub network_usage_interface: Vec, // interface -> (ingress, egress) } /// Realtime system stats data. @@ -86,9 +86,9 @@ pub struct SystemStats { /// Network egress usage in MB #[serde(default)] pub network_egress_bytes: f64, - /// Network usage by interface name (ingress, egress in bytes) - #[serde(default)] - pub network_usage_interface: Vec, // interface -> (ingress, egress) + // /// Network usage by interface name (ingress, egress in bytes) + // #[serde(default)] + // pub network_usage_interface: Vec, // interface -> (ingress, egress) // metadata /// The rate the system stats are being polled from the system pub polling_rate: Timelength, diff --git a/client/core/rs/src/entities/sync.rs b/client/core/rs/src/entities/sync.rs index 4475e0826..1fbc7333c 100644 --- a/client/core/rs/src/entities/sync.rs +++ b/client/core/rs/src/entities/sync.rs @@ -1,4 +1,4 @@ -use bson::{doc, Document}; +use bson::{Document, doc}; use derive_builder::Builder; use derive_default_builder::DefaultBuilder; use partial_derive2::Partial; @@ -12,8 +12,8 @@ use crate::deserializers::{ }; use super::{ + I64, ResourceTarget, resource::{Resource, ResourceListItem, ResourceQuery}, - ResourceTarget, I64, }; #[typeshare] @@ -244,6 +244,13 @@ pub struct ResourceSyncConfig { #[builder(default)] pub delete: bool, + /// Whether sync should include resources. + /// Default: true + #[serde(default = "default_include_resources")] + #[builder(default = "default_include_resources()")] + #[partial_default(default_include_resources())] + pub include_resources: bool, + /// When using `managed` resource sync, will only export resources /// matching all of the given tags. If none, will match all resources. #[serde(default, deserialize_with = "string_list_deserializer")] @@ -254,6 +261,16 @@ pub struct ResourceSyncConfig { #[builder(default)] pub match_tags: Vec, + /// Whether sync should include variables. + #[serde(default)] + #[builder(default)] + pub include_variables: bool, + + /// Whether sync should include user groups. + #[serde(default)] + #[builder(default)] + pub include_user_groups: bool, + /// Manage the file contents in the UI. #[serde(default, deserialize_with = "file_contents_deserializer")] #[partial_attr(serde( @@ -297,6 +314,10 @@ fn default_webhook_enabled() -> bool { true } +fn default_include_resources() -> bool { + true +} + impl Default for ResourceSyncConfig { fn default() -> Self { Self { @@ -310,7 +331,10 @@ impl Default for ResourceSyncConfig { files_on_host: Default::default(), file_contents: Default::default(), managed: Default::default(), + include_resources: default_include_resources(), match_tags: Default::default(), + include_variables: Default::default(), + include_user_groups: Default::default(), delete: Default::default(), webhook_enabled: default_webhook_enabled(), webhook_secret: Default::default(), diff --git a/client/core/rs/src/entities/tag.rs b/client/core/rs/src/entities/tag.rs index 1e9997c6f..67c674e3a 100644 --- a/client/core/rs/src/entities/tag.rs +++ b/client/core/rs/src/entities/tag.rs @@ -1,6 +1,7 @@ use derive_builder::Builder; use partial_derive2::Partial; use serde::{Deserialize, Serialize}; +use strum::AsRefStr; use typeshare::typeshare; use crate::entities::MongoId; @@ -31,8 +32,89 @@ pub struct Tag { #[cfg_attr(feature = "mongo", unique_index)] pub name: String, + /// Hex color code with alpha for UI display + #[serde(default)] + pub color: TagColor, + #[serde(default)] #[builder(default)] #[cfg_attr(feature = "mongo", index)] pub owner: String, } + +#[typeshare] +#[derive(Serialize, Deserialize, Default, Debug, Clone, AsRefStr)] +pub enum TagColor { + LightSlate, + #[default] + Slate, + DarkSlate, + + LightRed, + Red, + DarkRed, + + LightOrange, + Orange, + DarkOrange, + + LightAmber, + Amber, + DarkAmber, + + LightYellow, + Yellow, + DarkYellow, + + LightLime, + Lime, + DarkLime, + + LightGreen, + Green, + DarkGreen, + + LightEmerald, + Emerald, + DarkEmerald, + + LightTeal, + Teal, + DarkTeal, + + LightCyan, + Cyan, + DarkCyan, + + LightSky, + Sky, + DarkSky, + + LightBlue, + Blue, + DarkBlue, + + LightIndigo, + Indigo, + DarkIndigo, + + LightViolet, + Violet, + DarkViolet, + + LightPurple, + Purple, + DarkPurple, + + LightFuchsia, + Fuchsia, + DarkFuchsia, + + LightPink, + Pink, + DarkPink, + + LightRose, + Rose, + DarkRose, +} diff --git a/client/core/rs/src/entities/toml.rs b/client/core/rs/src/entities/toml.rs index 744a21c88..cebb3922d 100644 --- a/client/core/rs/src/entities/toml.rs +++ b/client/core/rs/src/entities/toml.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use typeshare::typeshare; use super::{ + ResourceTarget, ResourceTargetVariant, action::_PartialActionConfig, alerter::_PartialAlerterConfig, build::_PartialBuildConfig, builder::_PartialBuilderConfig, deployment::_PartialDeploymentConfig, permission::PermissionLevel, @@ -11,7 +12,7 @@ use super::{ server::_PartialServerConfig, server_template::PartialServerTemplateConfig, stack::_PartialStackConfig, sync::_PartialResourceSyncConfig, - variable::Variable, ResourceTarget, ResourceTargetVariant, + variable::Variable, }; /// Specifies resources to sync on Komodo diff --git a/client/core/rs/src/entities/update.rs b/client/core/rs/src/entities/update.rs index 896c51da4..2ba030dde 100644 --- a/client/core/rs/src/entities/update.rs +++ b/client/core/rs/src/entities/update.rs @@ -4,7 +4,7 @@ use strum::{Display, EnumString}; use typeshare::typeshare; use crate::entities::{ - all_logs_success, komodo_timestamp, MongoId, Operation, I64, + I64, MongoId, Operation, all_logs_success, komodo_timestamp, }; use super::{ResourceTarget, Version}; diff --git a/client/core/rs/src/entities/user.rs b/client/core/rs/src/entities/user.rs index 2cf539496..ed207500c 100644 --- a/client/core/rs/src/entities/user.rs +++ b/client/core/rs/src/entities/user.rs @@ -3,9 +3,9 @@ use std::{collections::HashMap, sync::OnceLock}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::entities::{MongoId, I64}; +use crate::entities::{I64, MongoId}; -use super::{permission::PermissionLevel, ResourceTargetVariant}; +use super::{ResourceTargetVariant, permission::PermissionLevel}; #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Default)] diff --git a/client/core/rs/src/entities/user_group.rs b/client/core/rs/src/entities/user_group.rs index 3c01a80d5..3668643d2 100644 --- a/client/core/rs/src/entities/user_group.rs +++ b/client/core/rs/src/entities/user_group.rs @@ -6,7 +6,7 @@ use typeshare::typeshare; use crate::deserializers::string_list_deserializer; use super::{ - permission::PermissionLevel, MongoId, ResourceTargetVariant, I64, + I64, MongoId, ResourceTargetVariant, permission::PermissionLevel, }; /// Permission users at the group level. diff --git a/client/core/rs/src/lib.rs b/client/core/rs/src/lib.rs index 0c46adcf9..08ac6c555 100644 --- a/client/core/rs/src/lib.rs +++ b/client/core/rs/src/lib.rs @@ -1,5 +1,5 @@ //! # Komodo -//! *A system to build and deploy software across many servers* +//! *A system to build and deploy software across many servers*. [**https://komo.do**](https://komo.do) //! //! This is a client library for the Komodo Core API. //! It contains: diff --git a/client/core/rs/src/request.rs b/client/core/rs/src/request.rs index ed7d497d1..973e9ba3e 100644 --- a/client/core/rs/src/request.rs +++ b/client/core/rs/src/request.rs @@ -1,24 +1,28 @@ -use anyhow::{anyhow, Context}; +use anyhow::{Context, anyhow}; use reqwest::StatusCode; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; use serde_json::json; use serror::deserialize_error; use crate::{ + KomodoClient, api::{ auth::KomodoAuthRequest, execute::KomodoExecuteRequest, read::KomodoReadRequest, user::KomodoUserRequest, write::KomodoWriteRequest, }, - KomodoClient, }; impl KomodoClient { #[cfg(not(feature = "blocking"))] - pub async fn auth( + pub async fn auth( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoAuthRequest, + T::Response: DeserializeOwned, + { self .post( "/auth", @@ -31,10 +35,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn auth( - &self, - request: T, - ) -> anyhow::Result { + pub fn auth(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoAuthRequest, + T::Response: DeserializeOwned, + { self.post( "/auth", json!({ @@ -45,10 +50,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn user( + pub async fn user( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoUserRequest, + T::Response: DeserializeOwned, + { self .post( "/auth", @@ -61,10 +70,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn user( - &self, - request: T, - ) -> anyhow::Result { + pub fn user(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoUserRequest, + T::Response: DeserializeOwned, + { self.post( "/auth", json!({ @@ -75,10 +85,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn read( + pub async fn read( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoReadRequest, + T::Response: DeserializeOwned, + { self .post( "/read", @@ -91,10 +105,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn read( - &self, - request: T, - ) -> anyhow::Result { + pub fn read(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoReadRequest, + T::Response: DeserializeOwned, + { self.post( "/read", json!({ @@ -105,10 +120,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn write( + pub async fn write( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoWriteRequest, + T::Response: DeserializeOwned, + { self .post( "/write", @@ -121,10 +140,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn write( - &self, - request: T, - ) -> anyhow::Result { + pub fn write(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoWriteRequest, + T::Response: DeserializeOwned, + { self.post( "/write", json!({ @@ -135,10 +155,14 @@ impl KomodoClient { } #[cfg(not(feature = "blocking"))] - pub async fn execute( + pub async fn execute( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: Serialize + KomodoExecuteRequest, + T::Response: DeserializeOwned, + { self .post( "/execute", @@ -151,10 +175,11 @@ impl KomodoClient { } #[cfg(feature = "blocking")] - pub fn execute( - &self, - request: T, - ) -> anyhow::Result { + pub fn execute(&self, request: T) -> anyhow::Result + where + T: Serialize + KomodoExecuteRequest, + T::Response: DeserializeOwned, + { self.post( "/execute", json!({ diff --git a/client/core/rs/src/ws.rs b/client/core/rs/src/ws.rs index b374611d5..78b6527c8 100644 --- a/client/core/rs/src/ws.rs +++ b/client/core/rs/src/ws.rs @@ -8,11 +8,11 @@ use thiserror::Error; use tokio::sync::broadcast; use tokio_tungstenite::{connect_async, tungstenite::Message}; use tokio_util::sync::CancellationToken; -use tracing::{debug, info, info_span, warn, Instrument}; +use tracing::{Instrument, debug, info, info_span, warn}; use typeshare::typeshare; use uuid::Uuid; -use crate::{entities::update::UpdateListItem, KomodoClient}; +use crate::{KomodoClient, entities::update::UpdateListItem}; #[typeshare] #[derive(Debug, Clone, Serialize, Deserialize)] @@ -57,15 +57,32 @@ pub enum UpdateWsError { const MAX_SHORT_RETRY_COUNT: usize = 5; impl KomodoClient { + /// Subscribes to the Komodo Core update websocket, + /// and forwards the updates over a channel. + /// Handles reconnection internally. + /// + /// ``` + /// let (mut rx, _) = komodo.subscribe_to_updates()?; + /// loop { + /// let update = match rx.recv().await { + /// Ok(msg) => msg, + /// Err(e) => { + /// error!("🚨 recv error | {e:?}"); + /// break; + /// } + /// }; + /// // Handle the update + /// info!("Got update: {update:?}"); + /// } + /// ``` pub fn subscribe_to_updates( &self, - capacity: usize, - retry_cooldown_secs: u64, + // retry_cooldown_secs: u64, ) -> anyhow::Result<( broadcast::Receiver, CancellationToken, )> { - let (tx, rx) = broadcast::channel(capacity); + let (tx, rx) = broadcast::channel(128); let cancel = CancellationToken::new(); let cancel_clone = cancel.clone(); let address = @@ -137,7 +154,7 @@ impl KomodoClient { // SEND LOGIN MSG // ================== let login_send_res = ws - .send(Message::Text(login_msg.clone())) + .send(Message::text(&login_msg)) .await .context("failed to send login message"); @@ -157,7 +174,7 @@ impl KomodoClient { Ok(Some(Message::Text(msg))) => { if msg != "LOGGED_IN" { let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::LoginError(msg.clone()), + UpdateWsError::LoginError(msg.to_string()), )); let _ = ws.close(None).await; warn!("breaking inner loop | got msg {msg} instead of 'LOGGED_IN' | inner uuid {inner_uuid} | outer uuid {outer_uuid} | master uuid {master_uuid}"); @@ -227,7 +244,7 @@ impl KomodoClient { "got unrecognized message: {msg:?} | inner uuid {inner_uuid} | outer uuid {outer_uuid} | master uuid {master_uuid}" ); let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::MessageUnrecognized(msg), + UpdateWsError::MessageUnrecognized(msg.to_string()), )); } } @@ -262,8 +279,7 @@ impl KomodoClient { } }.instrument(span).await; - tokio::time::sleep(Duration::from_secs(retry_cooldown_secs)) - .await; + tokio::time::sleep(Duration::from_secs(3)).await; } }); diff --git a/client/core/ts/README.md b/client/core/ts/README.md index 6db23826e..dcec97b55 100644 --- a/client/core/ts/README.md +++ b/client/core/ts/README.md @@ -1,6 +1,6 @@ # Komodo -_A system to build and deploy software across many servers_ +_A system to build and deploy software across many servers_. [https://komo.do](https://komo.do) ```sh npm install komodo_client diff --git a/client/core/ts/package.json b/client/core/ts/package.json index 4626d1f35..ed83c59a6 100644 --- a/client/core/ts/package.json +++ b/client/core/ts/package.json @@ -1,6 +1,6 @@ { "name": "komodo_client", - "version": "1.16.12", + "version": "1.16.13", "description": "Komodo client package", "homepage": "https://komo.do", "main": "dist/lib.js", diff --git a/client/core/ts/src/lib.ts b/client/core/ts/src/lib.ts index 10cfc32ee..ec621d824 100644 --- a/client/core/ts/src/lib.ts +++ b/client/core/ts/src/lib.ts @@ -9,8 +9,10 @@ import { AuthRequest, ExecuteRequest, ReadRequest, + UpdateListItem, UserRequest, WriteRequest, + WsLoginMessage, } from "./types.js"; export * as Types from "./types.js"; @@ -19,6 +21,16 @@ type InitOptions = | { type: "jwt"; params: { jwt: string } } | { type: "api-key"; params: { key: string; secret: string } }; +export class CancelToken { + cancelled: boolean; + constructor() { + this.cancelled = false; + } + cancel() { + this.cancelled = true; + } +} + /** Initialize a new client for Komodo */ export function KomodoClient(url: string, options: InitOptions) { const state = { @@ -107,7 +119,6 @@ export function KomodoClient(url: string, options: InitOptions) { UserResponses[Req["type"]] >("/user", { type, params }); - const read = async < T extends ReadRequest["type"], Req extends Extract @@ -120,7 +131,6 @@ export function KomodoClient(url: string, options: InitOptions) { ReadResponses[Req["type"]] >("/read", { type, params }); - const write = async < T extends WriteRequest["type"], Req extends Extract @@ -133,7 +143,6 @@ export function KomodoClient(url: string, options: InitOptions) { WriteResponses[Req["type"]] >("/write", { type, params }); - const execute = async < T extends ExecuteRequest["type"], Req extends Extract @@ -148,6 +157,77 @@ export function KomodoClient(url: string, options: InitOptions) { const core_version = () => read("GetVersion", {}).then((res) => res.version); + const subscribe_to_update_websocket = async ({ + on_update, + on_login, + on_close, + retry_timeout_ms = 5_000, + cancel = new CancelToken(), + on_cancel, + }: { + on_update: (update: UpdateListItem) => void; + on_login?: () => void; + on_open?: () => void; + on_close?: () => void; + retry_timeout_ms?: number; + cancel?: CancelToken; + on_cancel?: () => void; + }) => { + while (true) { + if (cancel.cancelled) { + on_cancel?.(); + return; + } + + try { + const ws = new WebSocket(url.replace("http", "ws") + "/ws/update"); + + // Handle login on websocket open + ws.addEventListener("open", () => { + const login_msg: WsLoginMessage = + options.type === "jwt" + ? { + type: "Jwt", + params: { + jwt: options.params.jwt, + }, + } + : { + type: "ApiKeys", + params: { + key: options.params.key, + secret: options.params.secret, + }, + }; + ws.send(JSON.stringify(login_msg)); + }); + + ws.addEventListener("message", ({ data }: MessageEvent) => { + if (data == "LOGGED_IN") return on_login?.(); + on_update(JSON.parse(data)); + }); + + if (on_close) { + ws.addEventListener("close", on_close); + } + + // This while loop will end when the socket is closed + while ( + ws.readyState !== WebSocket.CLOSING && + ws.readyState !== WebSocket.CLOSED + ) { + if (cancel.cancelled) ws.close(); + // Sleep for a bit before checking for websocket closed + await new Promise((resolve) => setTimeout(resolve, 500)); + } + } catch (error) { + console.error(error); + // Sleep for a bit before retrying, maybe Komodo Core is down temporarily. + await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms)); + } + } + }; + return { /** * Call the `/auth` api. @@ -212,5 +292,11 @@ export function KomodoClient(url: string, options: InitOptions) { execute, /** Returns the version of Komodo Core the client is calling to. */ core_version, + /** + * Subscribes to the update websocket with automatic reconnect loop. + * + * Note. Awaiting this method will never finish. + */ + subscribe_to_update_websocket, }; } diff --git a/client/core/ts/src/responses.ts b/client/core/ts/src/responses.ts index d1a1092e5..293bc8afb 100644 --- a/client/core/ts/src/responses.ts +++ b/client/core/ts/src/responses.ts @@ -36,9 +36,6 @@ export type ReadResponses = { GetUserGroup: Types.GetUserGroupResponse; ListUserGroups: Types.ListUserGroupsResponse; - // ==== SEARCH ==== - FindResources: Types.FindResourcesResponse; - // ==== PROCEDURE ==== GetProceduresSummary: Types.GetProceduresSummaryResponse; GetProcedure: Types.GetProcedureResponse; @@ -127,8 +124,8 @@ export type ReadResponses = { GetStack: Types.GetStackResponse; GetStackActionState: Types.GetStackActionStateResponse; GetStackWebhooksEnabled: Types.GetStackWebhooksEnabledResponse; - GetStackServiceLog: Types.GetStackServiceLogResponse; - SearchStackServiceLog: Types.SearchStackServiceLogResponse; + GetStackLog: Types.GetStackLogResponse; + SearchStackLog: Types.SearchStackLogResponse; ListStacks: Types.ListStacksResponse; ListFullStacks: Types.ListFullStacksResponse; ListStackServices: Types.ListStackServicesResponse; @@ -306,6 +303,7 @@ export type WriteResponses = { CreateTag: Types.Tag; DeleteTag: Types.Tag; RenameTag: Types.Tag; + UpdateTagColor: Types.Tag; UpdateTagsOnResource: Types.UpdateTagsOnResourceResponse; // ==== VARIABLE ==== @@ -410,4 +408,7 @@ export type ExecuteResponses = { PauseStackService: Types.Update; UnpauseStackService: Types.Update; DestroyStackService: Types.Update; + + // ==== ALERTER ==== + TestAlerter: Types.Update; }; diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 724bc5fe8..718707f25 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -252,8 +252,8 @@ export interface BuildConfig { */ image_name?: string; /** - * An extra tag put before the build version, for the image pushed to the repository. - * Eg. in image tag of `aarch64` would push to mbecker20/komodo:1.13.2-aarch64. + * An extra tag put after the build version, for the image pushed to the repository. + * Eg. in image tag of `aarch64` would push to moghtech/komodo-core:1.13.2-aarch64. * If this is empty, the image tag will just be the build version. * * Can be used in conjunction with `image_name` to direct multiple builds @@ -484,6 +484,7 @@ export type Execution = | { type: "StopStack", params: StopStack } | { type: "DestroyStack", params: DestroyStack } | { type: "BatchDestroyStack", params: BatchDestroyStack } + | { type: "TestAlerter", params: TestAlerter } | { type: "Sleep", params: Sleep }; /** Allows to enable / disabled procedures in the sequence / parallel vec on the fly */ @@ -881,8 +882,20 @@ export interface DeploymentListItemInfo { export type DeploymentListItem = ResourceListItem; export interface DeploymentQuerySpecifics { + /** + * Query only for Deployments on these Servers. + * If empty, does not filter by Server. + * Only accepts Server id (not name). + */ server_ids?: string[]; + /** + * Query only for Deployments with these Builds attached. + * If empty, does not filter by Build. + * Only accepts Build id (not name). + */ build_ids?: string[]; + /** Query only for Deployments with available image updates. */ + update_available?: boolean; } export type DeploymentQuery = ResourceQuery; @@ -924,6 +937,16 @@ export enum SeverityLevel { export type AlertData = /** A null alert */ | { type: "None", data: { +}} + /** + * The user triggered a test of the + * Alerter configuration. + */ + | { type: "Test", data: { + /** The id of the alerter */ + id: string; + /** The name of the alerter */ + name: string; }} /** A server could not be reached. */ | { type: "ServerUnreachable", data: { @@ -1352,11 +1375,20 @@ export interface ResourceSyncConfig { * not declared in the resource files */ delete?: boolean; + /** + * Whether sync should include resources. + * Default: true + */ + include_resources: boolean; /** * When using `managed` resource sync, will only export resources * matching all of the given tags. If none, will match all resources. */ match_tags?: string[]; + /** Whether sync should include variables. */ + include_variables?: boolean; + /** Whether sync should include user groups. */ + include_user_groups?: boolean; /** Manage the file contents in the UI. */ file_contents?: string; } @@ -1554,6 +1586,8 @@ export interface StackActionState { export type GetStackActionStateResponse = StackActionState; +export type GetStackLogResponse = Log; + /** The compose file configuration. */ export interface StackConfig { /** The server to deploy the stack on. */ @@ -1588,6 +1622,13 @@ export interface StackConfig { * enable both. */ auto_update?: boolean; + /** + * If auto update is enabled, Komodo will + * by default only update the specific services + * with image updates. If this parameter is set to true, + * Komodo will redeploy the whole Stack (all services). + */ + auto_update_all_services?: boolean; /** Whether to run `docker compose down` before `compose up`. */ destroy_before_deploy?: boolean; /** Whether to skip secret interpolation into the stack environment variables. */ @@ -1664,6 +1705,8 @@ export interface StackConfig { registry_account?: string; /** The optional command to run before the Stack is deployed. */ pre_deploy?: SystemCommand; + /** The optional command to run after the Stack is deployed. */ + post_deploy?: SystemCommand; /** * The extra arguments to pass after `docker compose up -d`. * If empty, no extra arguments will be passed. @@ -1747,13 +1790,21 @@ export interface StackInfo { deployed_hash?: string; /** Deployed commit message, or null. Only for repo based stacks */ deployed_message?: string; - /** The deployed compose file contents. This is updated whenever Komodo successfully deploys the stack. */ + /** + * The deployed compose file contents. + * This is updated whenever Komodo successfully deploys the stack. + */ deployed_contents?: FileContents[]; /** * The deployed service names. * This is updated whenever it is empty, or deployed contents is updated. */ deployed_services?: StackServiceNames[]; + /** + * The output of `docker compose config`. + * This is updated whenever Komodo successfully deploys the stack. + */ + deployed_config?: string; /** * The latest service names. * This is updated whenever the stack cache refreshes, using the latest file contents (either db defined or remote). @@ -1777,8 +1828,6 @@ export type Stack = Resource; export type GetStackResponse = Stack; -export type GetStackServiceLogResponse = Log; - /** System information of a server */ export interface SystemInformation { /** The system name */ @@ -1809,16 +1858,6 @@ export interface SingleDiskUsage { total_gb: number; } -/** Info for network interface usage. */ -export interface SingleNetworkInterfaceUsage { - /** The network interface name */ - name: string; - /** The ingress in bytes */ - ingress_bytes: number; - /** The egress in bytes */ - egress_bytes: number; -} - export enum Timelength { OneSecond = "1-sec", FiveSeconds = "5-sec", @@ -1864,8 +1903,6 @@ export interface SystemStats { network_ingress_bytes?: number; /** Network egress usage in MB */ network_egress_bytes?: number; - /** Network usage by interface name (ingress, egress in bytes) */ - network_usage_interface?: SingleNetworkInterfaceUsage[]; /** The rate the system stats are being polled from the system */ polling_rate: Timelength; /** Unix timestamp in milliseconds when stats were last polled */ @@ -1876,6 +1913,63 @@ export interface SystemStats { export type GetSystemStatsResponse = SystemStats; +export enum TagColor { + LightSlate = "LightSlate", + Slate = "Slate", + DarkSlate = "DarkSlate", + LightRed = "LightRed", + Red = "Red", + DarkRed = "DarkRed", + LightOrange = "LightOrange", + Orange = "Orange", + DarkOrange = "DarkOrange", + LightAmber = "LightAmber", + Amber = "Amber", + DarkAmber = "DarkAmber", + LightYellow = "LightYellow", + Yellow = "Yellow", + DarkYellow = "DarkYellow", + LightLime = "LightLime", + Lime = "Lime", + DarkLime = "DarkLime", + LightGreen = "LightGreen", + Green = "Green", + DarkGreen = "DarkGreen", + LightEmerald = "LightEmerald", + Emerald = "Emerald", + DarkEmerald = "DarkEmerald", + LightTeal = "LightTeal", + Teal = "Teal", + DarkTeal = "DarkTeal", + LightCyan = "LightCyan", + Cyan = "Cyan", + DarkCyan = "DarkCyan", + LightSky = "LightSky", + Sky = "Sky", + DarkSky = "DarkSky", + LightBlue = "LightBlue", + Blue = "Blue", + DarkBlue = "DarkBlue", + LightIndigo = "LightIndigo", + Indigo = "Indigo", + DarkIndigo = "DarkIndigo", + LightViolet = "LightViolet", + Violet = "Violet", + DarkViolet = "DarkViolet", + LightPurple = "LightPurple", + Purple = "Purple", + DarkPurple = "DarkPurple", + LightFuchsia = "LightFuchsia", + Fuchsia = "Fuchsia", + DarkFuchsia = "DarkFuchsia", + LightPink = "LightPink", + Pink = "Pink", + DarkPink = "DarkPink", + LightRose = "LightRose", + Rose = "Rose", + DarkRose = "DarkRose", +} + export interface Tag { /** * The Mongo ID of the tag. @@ -1884,6 +1978,8 @@ export interface Tag { */ _id?: MongoId; name: string; + /** Hex color code with alpha for UI display */ + color?: TagColor; owner?: string; } @@ -1983,6 +2079,7 @@ export enum Operation { UpdateAlerter = "UpdateAlerter", RenameAlerter = "RenameAlerter", DeleteAlerter = "DeleteAlerter", + TestAlerter = "TestAlerter", CreateServerTemplate = "CreateServerTemplate", UpdateServerTemplate = "UpdateServerTemplate", RenameServerTemplate = "RenameServerTemplate", @@ -3047,8 +3144,8 @@ export interface ProviderAccount { export interface DockerRegistry { /** The docker provider domain. Default: `docker.io`. */ domain: string; - /** The account username. Required. */ - accounts?: ProviderAccount[]; + /** The accounts on the registry. Required. */ + accounts: ProviderAccount[]; /** * Available organizations on the registry provider. * Used to push an image under an organization's repo rather than an account's repo. @@ -3104,7 +3201,7 @@ export interface GitProvider { domain: string; /** Whether to use https. Default: true. */ https: boolean; - /** The account username. Required. */ + /** The accounts on the git provider. Required. */ accounts: ProviderAccount[]; } @@ -3439,7 +3536,7 @@ export type SearchContainerLogResponse = Log; export type SearchDeploymentLogResponse = Log; -export type SearchStackServiceLogResponse = Log; +export type SearchStackLogResponse = Log; export interface ServerQuerySpecifics { } @@ -3456,8 +3553,16 @@ export type ServerTemplateQuery = ResourceQuery; export type SetLastSeenUpdateResponse = NoData; export interface StackQuerySpecifics { + /** + * Query only for Stacks on these Servers. + * If empty, does not filter by Server. + * Only accepts Server id (not name). + */ + server_ids?: string[]; /** Filter syncs by their repo. */ - repos: string[]; + repos?: string[]; + /** Query only for Stack with available image updates. */ + update_available?: boolean; } export type StackQuery = ResourceQuery; @@ -4635,8 +4740,11 @@ export interface Deploy { export interface DeployStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to "compose up" */ - service?: string; + /** + * Filter to only deploy specific services. + * If empty, will deploy all services. + */ + services?: string[]; /** * Override the default termination max time. * Only used if the stack needs to be taken down first. @@ -4695,8 +4803,11 @@ export interface DestroyDeployment { export interface DestroyStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to destroy */ - service?: string; + /** + * Filter to only destroy specific services. + * If empty, will destroy all services. + */ + services?: string[]; /** Pass `--remove-orphans` */ remove_orphans?: boolean; /** Override the default termination max time. */ @@ -4730,8 +4841,27 @@ export interface ExchangeForJwt { * Response: [TomlResponse]. */ export interface ExportAllResourcesToToml { - /** Tag name or id. Empty array will not filter by tag. */ + /** + * Whether to include any resources (servers, stacks, etc.) + * in the exported contents. + * Default: `true` + */ + include_resources: boolean; + /** + * Filter resources by tag. + * Accepts tag name or id. Empty array will not filter by tag. + */ tags?: string[]; + /** + * Whether to include variables in the exported contents. + * Default: false + */ + include_variables?: boolean; + /** + * Whether to include user groups in the exported contents. + * Default: false + */ + include_user_groups?: boolean; } /** @@ -4747,28 +4877,6 @@ export interface ExportResourcesToToml { include_variables?: boolean; } -/** Find resources matching a common query. Response: [FindResourcesResponse]. */ -export interface FindResources { - /** The mongo query as JSON */ - query?: MongoDocument; - /** The resource variants to include in the response. */ - resources?: ResourceTarget["type"][]; -} - -/** Response for [FindResources]. */ -export interface FindResourcesResponse { - /** The matching servers. */ - servers: ServerListItem[]; - /** The matching deployments. */ - deployments: DeploymentListItem[]; - /** The matching builds. */ - builds: BuildListItem[]; - /** The matching repos. */ - repos: RepoListItem[]; - /** The matching procedures. */ - procedures: ProcedureListItem[]; -} - /** * **Admin only.** * Find a user. @@ -5109,14 +5217,12 @@ export interface SystemStatsRecord { disk_used_gb: number; /** Total disk size in GB */ disk_total_gb: number; - /** Breakdown of individual disks, ie their usages, sizes, and mount points */ + /** Breakdown of individual disks, including their usage, total size, and mount point */ disks: SingleDiskUsage[]; - /** Network ingress usage in bytes */ + /** Total network ingress in bytes */ network_ingress_bytes?: number; - /** Network egress usage in bytes */ + /** Total network egress in bytes */ network_egress_bytes?: number; - /** Network usage by interface name (ingress, egress in bytes) */ - network_usage_interface?: SingleNetworkInterfaceUsage[]; } /** Response to [GetHistoricalServerStats]. */ @@ -5387,12 +5493,19 @@ export interface GetStackActionState { stack: string; } -/** Get a stack service's log. Response: [GetStackServiceLogResponse]. */ -export interface GetStackServiceLog { +/** + * Get a stack's logs. Filter down included services. Response: [GetStackLogResponse]. + * + * Note. This call will hit the underlying server directly for most up to date log. + */ +export interface GetStackLog { /** Id or name */ stack: string; - /** The service to get the log for. */ - service: string; + /** + * Filter the logs to only ones from specific services. + * If empty, will include logs from all services. + */ + services: string[]; /** * The number of lines of the log tail to include. * Default: 100. @@ -6104,7 +6217,7 @@ export interface ListStackServices { /** List stacks matching optional query. Response: [ListStacksResponse]. */ export interface ListStacks { - /** optional structured query to filter syncs. */ + /** optional structured query to filter stacks. */ query?: StackQuery; } @@ -6273,8 +6386,11 @@ export interface PauseDeployment { export interface PauseStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to pause */ - service?: string; + /** + * Filter to only pause specific services. + * If empty, will pause all services. + */ + services?: string[]; } export interface PermissionToml { @@ -6405,8 +6521,11 @@ export interface PullRepo { export interface PullStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to start */ - service?: string; + /** + * Filter to only pull specific services. + * If empty, will pull all services. + */ + services?: string[]; } /** @@ -6677,8 +6796,11 @@ export interface RestartDeployment { export interface RestartStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to restart */ - service?: string; + /** + * Filter to only restart specific services. + * If empty, will restart all services. + */ + services?: string[]; } /** Runs the target Action. Response: [Update] */ @@ -6779,16 +6901,19 @@ export interface SearchDeploymentLog { } /** - * Search the deployment log's tail using `grep`. All lines go to stdout. - * Response: [Log]. + * Search the stack log's tail using `grep`. All lines go to stdout. + * Response: [SearchStackLogResponse]. * * Note. This call will hit the underlying server directly for most up to date log. */ -export interface SearchStackServiceLog { +export interface SearchStackLog { /** Id or name */ stack: string; - /** The service to get the log for. */ - service: string; + /** + * Filter the logs to only ones from specific services. + * If empty, will include logs from all services. + */ + services: string[]; /** The terms to search for. */ terms: string[]; /** @@ -6843,6 +6968,16 @@ export interface SetUsersInUserGroup { users: string[]; } +/** Info for network interface usage. */ +export interface SingleNetworkInterfaceUsage { + /** The network interface name */ + name: string; + /** The ingress in bytes */ + ingress_bytes: number; + /** The egress in bytes */ + egress_bytes: number; +} + /** Configuration for a Slack alerter. */ export interface SlackAlerterEndpoint { /** The Slack app webhook url */ @@ -6885,8 +7020,11 @@ export interface StartDeployment { export interface StartStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to start */ - service?: string; + /** + * Filter to only start specific services. + * If empty, will start all services. + */ + services?: string[]; } /** Stops all containers on the target server. Response: [Update] */ @@ -6931,8 +7069,11 @@ export interface StopStack { stack: string; /** Override the default termination max time. */ stop_time?: number; - /** Optionally specify a specific service to stop */ - service?: string; + /** + * Filter to only stop specific services. + * If empty, will stop all services. + */ + services?: string[]; } export interface TerminationSignalLabel { @@ -6940,6 +7081,12 @@ export interface TerminationSignalLabel { label: string; } +/** Tests an Alerters ability to reach the configured endpoint. Response: [Update] */ +export interface TestAlerter { + /** Name or id */ + alerter: string; +} + /** Info for the all system disks combined. */ export interface TotalDiskUsage { /** Used portion in GB */ @@ -6988,8 +7135,11 @@ export interface UnpauseDeployment { export interface UnpauseStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to unpause */ - service?: string; + /** + * Filter to only unpause specific services. + * If empty, will unpause all services. + */ + services?: string[]; } /** @@ -7256,6 +7406,14 @@ export interface UpdateStack { config: _PartialStackConfig; } +/** Update color for tag. Response: [Tag]. */ +export interface UpdateTagColor { + /** The name or id of the tag to update. */ + tag: string; + /** The new color for the tag. */ + color: TagColor; +} + /** * Update the tags on a resource. * Response: [NoData] @@ -7434,6 +7592,7 @@ export type ExecuteRequest = | { type: "RunAction", params: RunAction } | { type: "BatchRunAction", params: BatchRunAction } | { type: "LaunchServer", params: LaunchServer } + | { type: "TestAlerter", params: TestAlerter } | { type: "RunSync", params: RunSync }; /** Configuration for the registry to push the built image to. */ @@ -7459,7 +7618,6 @@ export type ReadRequest = | { type: "ListUserTargetPermissions", params: ListUserTargetPermissions } | { type: "GetUserGroup", params: GetUserGroup } | { type: "ListUserGroups", params: ListUserGroups } - | { type: "FindResources", params: FindResources } | { type: "GetProceduresSummary", params: GetProceduresSummary } | { type: "GetProcedure", params: GetProcedure } | { type: "GetProcedureActionState", params: GetProcedureActionState } @@ -7531,8 +7689,8 @@ export type ReadRequest = | { type: "GetStack", params: GetStack } | { type: "GetStackActionState", params: GetStackActionState } | { type: "GetStackWebhooksEnabled", params: GetStackWebhooksEnabled } - | { type: "GetStackServiceLog", params: GetStackServiceLog } - | { type: "SearchStackServiceLog", params: SearchStackServiceLog } + | { type: "GetStackLog", params: GetStackLog } + | { type: "SearchStackLog", params: SearchStackLog } | { type: "ListStacks", params: ListStacks } | { type: "ListFullStacks", params: ListFullStacks } | { type: "ListStackServices", params: ListStackServices } @@ -7663,6 +7821,7 @@ export type WriteRequest = | { type: "CreateTag", params: CreateTag } | { type: "DeleteTag", params: DeleteTag } | { type: "RenameTag", params: RenameTag } + | { type: "UpdateTagColor", params: UpdateTagColor } | { type: "UpdateTagsOnResource", params: UpdateTagsOnResource } | { type: "CreateVariable", params: CreateVariable } | { type: "UpdateVariableValue", params: UpdateVariableValue } diff --git a/client/periphery/rs/src/api/build.rs b/client/periphery/rs/src/api/build.rs index 219b4370d..2e45f2df3 100644 --- a/client/periphery/rs/src/api/build.rs +++ b/client/periphery/rs/src/api/build.rs @@ -1,9 +1,10 @@ use komodo_client::entities::update::Log; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(BuildResponse)] +#[error(serror::Error)] pub struct Build { pub build: komodo_client::entities::build::Build, /// Override registry token with one sent from core. @@ -20,12 +21,14 @@ pub type BuildResponse = Vec; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneBuilders {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneBuildx {} diff --git a/client/periphery/rs/src/api/compose.rs b/client/periphery/rs/src/api/compose.rs index 770e18043..2e561be67 100644 --- a/client/periphery/rs/src/api/compose.rs +++ b/client/periphery/rs/src/api/compose.rs @@ -1,9 +1,9 @@ use komodo_client::entities::{ + FileContents, SearchCombinator, stack::{ComposeProject, Stack, StackServiceNames}, update::Log, - FileContents, SearchCombinator, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use super::git::RepoActionResponse; @@ -13,16 +13,18 @@ use super::git::RepoActionResponse; /// /// Incoming from docker like: /// [{"Name":"project_name","Status":"running(1)","ConfigFiles":"/root/compose/compose.yaml,/root/compose/compose2.yaml"}] -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct ListComposeProjects {} // /// Get the compose contents on the host, for stacks using /// `files_on_host`. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(GetComposeContentsOnHostResponse)] +#[error(serror::Error)] pub struct GetComposeContentsOnHost { /// The name of the stack pub name: String, @@ -39,13 +41,16 @@ pub struct GetComposeContentsOnHostResponse { // /// The stack folder must already exist for this to work -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] -pub struct GetComposeServiceLog { +#[error(serror::Error)] +pub struct GetComposeLog { /// The name of the project pub project: String, - /// The service name - pub service: String, + /// Filter the logs to only ones from specific services. + /// If empty, will include logs from all services. + #[serde(default)] + pub services: Vec, /// Pass `--tail` for only recent log contents. Max of 5000 #[serde(default = "default_tail")] pub tail: u64, @@ -61,13 +66,16 @@ fn default_tail() -> u64 { // /// The stack folder must already exist for this to work -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] -pub struct GetComposeServiceLogSearch { +#[error(serror::Error)] +pub struct GetComposeLogSearch { /// The name of the project pub project: String, - /// The service name - pub service: String, + /// Filter the logs to only ones from specific services. + /// If empty, will include logs from all services. + #[serde(default)] + pub services: Vec, /// The search terms. pub terms: Vec, /// And: Only lines matching all terms @@ -86,8 +94,9 @@ pub struct GetComposeServiceLogSearch { /// Write the compose contents to the file on the host, for stacks using /// `files_on_host`. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct WriteComposeContentsToHost { /// The name of the stack pub name: String, @@ -104,8 +113,9 @@ pub struct WriteComposeContentsToHost { /// Write and commit compose contents. /// Only works with git repo based stacks. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct WriteCommitComposeContents { /// The stack to write to. pub stack: Stack, @@ -123,13 +133,16 @@ pub struct WriteCommitComposeContents { /// Rewrites the compose directory, pulls any images, takes down existing containers, /// and runs docker compose up. Response: [ComposePullResponse] -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(ComposePullResponse)] +#[error(serror::Error)] pub struct ComposePull { /// The stack to deploy pub stack: Stack, - /// Only deploy one service - pub service: Option, + /// Filter to only pull specific services. + /// If empty, will pull all services. + #[serde(default)] + pub services: Vec, /// If provided, use it to login in. Otherwise check periphery local registries. pub git_token: Option, /// If provided, use it to login in. Otherwise check periphery local git providers. @@ -145,13 +158,16 @@ pub struct ComposePullResponse { // /// docker compose up. -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(ComposeUpResponse)] +#[error(serror::Error)] pub struct ComposeUp { /// The stack to deploy pub stack: Stack, - /// Only deploy one service - pub service: Option, + /// Filter to only deploy specific services. + /// If empty, will deploy all services. + #[serde(default)] + pub services: Vec, /// If provided, use it to login in. Otherwise check periphery local registries. pub git_token: Option, /// If provided, use it to login in. Otherwise check periphery local git providers. @@ -178,6 +194,8 @@ pub struct ComposeUpResponse { pub file_contents: Vec, /// The error in getting remote file contents at the path, or null pub remote_errors: Vec, + /// The output of `docker compose config` at deploy time + pub compose_config: Option, /// If its a repo based stack, will include the latest commit hash pub commit_hash: Option, /// If its a repo based stack, will include the latest commit message @@ -187,8 +205,9 @@ pub struct ComposeUpResponse { // /// General compose command runner -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct ComposeExecution { /// The compose project name to run the execution on. /// Usually its he name of the stack / folder under the `stack_dir`. diff --git a/client/periphery/rs/src/api/container.rs b/client/periphery/rs/src/api/container.rs index cead5e7f8..3be1910c8 100644 --- a/client/periphery/rs/src/api/container.rs +++ b/client/periphery/rs/src/api/container.rs @@ -1,24 +1,26 @@ use komodo_client::entities::{ + SearchCombinator, TerminationSignal, deployment::Deployment, docker::container::{Container, ContainerStats}, update::Log, - SearchCombinator, TerminationSignal, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Container)] +#[error(serror::Error)] pub struct InspectContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct GetContainerLog { pub name: String, #[serde(default = "default_tail")] @@ -34,8 +36,9 @@ fn default_tail() -> u64 { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct GetContainerLogSearch { pub name: String, pub terms: Vec, @@ -50,16 +53,18 @@ pub struct GetContainerLogSearch { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(ContainerStats)] +#[error(serror::Error)] pub struct GetContainerStats { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct GetContainerStatsList {} // @@ -68,8 +73,9 @@ pub struct GetContainerStatsList {} // ACTIONS // ======= -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct Deploy { pub deployment: Deployment, pub stop_signal: Option, @@ -83,40 +89,45 @@ pub struct Deploy { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct StartContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RestartContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PauseContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct UnpauseContainer { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct StopContainer { pub name: String, pub signal: Option, @@ -125,8 +136,9 @@ pub struct StopContainer { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RemoveContainer { pub name: String, pub signal: Option, @@ -135,8 +147,9 @@ pub struct RemoveContainer { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RenameContainer { pub curr_name: String, pub new_name: String, @@ -144,36 +157,42 @@ pub struct RenameContainer { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct StartAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct RestartAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct PauseAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct UnpauseAllContainers {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct StopAllContainers {} diff --git a/client/periphery/rs/src/api/git.rs b/client/periphery/rs/src/api/git.rs index 3302da1c1..9c53dabeb 100644 --- a/client/periphery/rs/src/api/git.rs +++ b/client/periphery/rs/src/api/git.rs @@ -1,19 +1,22 @@ use std::path::PathBuf; use komodo_client::entities::{ - update::Log, CloneArgs, EnvironmentVar, LatestCommit, + CloneArgs, EnvironmentVar, LatestCommit, update::Log, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(LatestCommit)] +#[error(serror::Error)] pub struct GetLatestCommit { pub name: String, + pub path: Option, } -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct CloneRepo { pub args: CloneArgs, #[serde(default)] @@ -35,8 +38,9 @@ fn default_env_file_path() -> String { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct PullRepo { pub args: CloneArgs, #[serde(default)] @@ -55,8 +59,9 @@ pub struct PullRepo { // /// Either pull or clone depending on whether it exists. -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(RepoActionResponse)] +#[error(serror::Error)] pub struct PullOrCloneRepo { pub args: CloneArgs, #[serde(default)] @@ -86,8 +91,9 @@ pub struct RepoActionResponse { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RenameRepo { pub curr_name: String, pub new_name: String, @@ -95,8 +101,9 @@ pub struct RenameRepo { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteRepo { pub name: String, } diff --git a/client/periphery/rs/src/api/image.rs b/client/periphery/rs/src/api/image.rs index 08c1b6da7..f5a1c2123 100644 --- a/client/periphery/rs/src/api/image.rs +++ b/client/periphery/rs/src/api/image.rs @@ -2,29 +2,32 @@ use komodo_client::entities::{ docker::image::{Image, ImageHistoryResponseItem}, update::Log, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Image)] +#[error(serror::Error)] pub struct InspectImage { pub name: String, } // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct ImageHistory { pub name: String, } // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PullImage { /// The name of the image. pub name: String, @@ -36,8 +39,9 @@ pub struct PullImage { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteImage { /// Id or name pub name: String, @@ -45,6 +49,7 @@ pub struct DeleteImage { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneImages {} diff --git a/client/periphery/rs/src/api/mod.rs b/client/periphery/rs/src/api/mod.rs index dade628f6..bad9699ca 100644 --- a/client/periphery/rs/src/api/mod.rs +++ b/client/periphery/rs/src/api/mod.rs @@ -1,4 +1,5 @@ use komodo_client::entities::{ + SystemCommand, config::{DockerRegistry, GitProvider}, docker::{ container::ContainerListItem, image::ImageListItem, @@ -6,9 +7,8 @@ use komodo_client::entities::{ }, stack::ComposeProject, update::Log, - SystemCommand, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; use serror::Serror; @@ -23,8 +23,9 @@ pub mod volume; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(GetHealthResponse)] +#[error(serror::Error)] pub struct GetHealth {} #[derive(Serialize, Deserialize, Debug, Clone)] @@ -32,8 +33,9 @@ pub struct GetHealthResponse {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(GetVersionResponse)] +#[error(serror::Error)] pub struct GetVersion {} #[derive(Serialize, Deserialize, Debug, Clone)] @@ -42,8 +44,9 @@ pub struct GetVersionResponse { } /// Returns all containers, networks, images, compose projects -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(GetDockerListsResponse)] +#[error(serror::Error)] pub struct GetDockerLists {} #[derive(Serialize, Deserialize, Debug, Clone)] @@ -57,36 +60,41 @@ pub struct GetDockerListsResponse { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(ListGitProvidersResponse)] +#[error(serror::Error)] pub struct ListGitProviders {} pub type ListGitProvidersResponse = Vec; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(ListDockerRegistriesResponse)] +#[error(serror::Error)] pub struct ListDockerRegistries {} pub type ListDockerRegistriesResponse = Vec; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] +#[error(serror::Error)] pub struct ListSecrets {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneSystem {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct RunCommand { pub command: SystemCommand, } diff --git a/client/periphery/rs/src/api/network.rs b/client/periphery/rs/src/api/network.rs index 67b76547e..21e2de3b4 100644 --- a/client/periphery/rs/src/api/network.rs +++ b/client/periphery/rs/src/api/network.rs @@ -1,21 +1,23 @@ use komodo_client::entities::{ docker::network::Network, update::Log, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Network)] +#[error(serror::Error)] pub struct InspectNetwork { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct CreateNetwork { pub name: String, pub driver: Option, @@ -23,8 +25,9 @@ pub struct CreateNetwork { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteNetwork { /// Id or name pub name: String, @@ -32,6 +35,7 @@ pub struct DeleteNetwork { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneNetworks {} diff --git a/client/periphery/rs/src/api/stats.rs b/client/periphery/rs/src/api/stats.rs index 06fa5a4fe..fcaf072f9 100644 --- a/client/periphery/rs/src/api/stats.rs +++ b/client/periphery/rs/src/api/stats.rs @@ -1,23 +1,28 @@ use komodo_client::entities::stats::{ SystemInformation, SystemProcess, SystemStats, }; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(SystemInformation)] +#[error(serror::Error)] pub struct GetSystemInformation {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(SystemStats)] +#[error(serror::Error)] pub struct GetSystemStats {} // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Vec)] -pub struct GetSystemProcesses {} \ No newline at end of file +#[error(serror::Error)] +pub struct GetSystemProcesses {} + +// diff --git a/client/periphery/rs/src/api/volume.rs b/client/periphery/rs/src/api/volume.rs index 39ac64084..462b1806d 100644 --- a/client/periphery/rs/src/api/volume.rs +++ b/client/periphery/rs/src/api/volume.rs @@ -1,19 +1,21 @@ use komodo_client::entities::{docker::volume::Volume, update::Log}; -use resolver_api::derive::Request; +use resolver_api::Resolve; use serde::{Deserialize, Serialize}; // -#[derive(Debug, Clone, Serialize, Deserialize, Request)] +#[derive(Debug, Clone, Serialize, Deserialize, Resolve)] #[response(Volume)] +#[error(serror::Error)] pub struct InspectVolume { pub name: String, } // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct DeleteVolume { /// Id or name pub name: String, @@ -21,6 +23,7 @@ pub struct DeleteVolume { // -#[derive(Serialize, Deserialize, Debug, Clone, Request)] +#[derive(Serialize, Deserialize, Debug, Clone, Resolve)] #[response(Log)] +#[error(serror::Error)] pub struct PruneVolumes {} diff --git a/client/periphery/rs/src/lib.rs b/client/periphery/rs/src/lib.rs index 8480491f3..685529182 100644 --- a/client/periphery/rs/src/lib.rs +++ b/client/periphery/rs/src/lib.rs @@ -3,8 +3,8 @@ use std::{sync::OnceLock, time::Duration}; use anyhow::Context; use reqwest::StatusCode; use resolver_api::HasResponse; +use serde::{Serialize, de::DeserializeOwned}; use serde_json::json; -use serror::deserialize_error; pub mod api; @@ -45,10 +45,14 @@ impl PeripheryClient { level = "debug", skip(self) )] - pub async fn request( + pub async fn request( &self, request: T, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: std::fmt::Debug + Serialize + HasResponse, + T::Response: DeserializeOwned, + { tracing::debug!("running health check"); self.health_check().await?; tracing::debug!("health check passed. running inner request"); @@ -64,11 +68,15 @@ impl PeripheryClient { } #[tracing::instrument(level = "debug", skip(self))] - async fn request_inner( + async fn request_inner( &self, request: T, timeout: Option, - ) -> anyhow::Result { + ) -> anyhow::Result + where + T: std::fmt::Debug + Serialize + HasResponse, + T::Response: DeserializeOwned, + { let req_type = T::req_type(); tracing::trace!( "sending request | type: {req_type} | body: {request:?}" @@ -104,8 +112,7 @@ impl PeripheryClient { tracing::debug!("got response text, deserializing error"); - let error = deserialize_error(text) - .context(format!("request to periphery failed | {status}")); + let error = serror::deserialize_error(text).context(status); Err(error) } diff --git a/compose/compose.env b/compose/compose.env index d514f8ea5..7807966ad 100644 --- a/compose/compose.env +++ b/compose/compose.env @@ -26,7 +26,7 @@ KOMODO_PASSKEY=a_random_passkey #=-------------------------=# ## Full variable list + descriptions are available here: -## 🦎 https://github.com/mbecker20/komodo/blob/main/config/core.config.toml 🦎 +## 🦎 https://github.com/moghtech/komodo/blob/main/config/core.config.toml 🦎 ## Note. Secret variables also support `${VARIABLE}_FILE` syntax to pass docker compose secrets. ## Docs: https://docs.docker.com/compose/how-tos/use-secrets/#examples @@ -79,10 +79,13 @@ KOMODO_OIDC_ENABLED=false ## Change the host to one reachable be reachable by users (optional if it is the same as above). ## DO NOT include the `path` part of the URL. # KOMODO_OIDC_REDIRECT_HOST=https://oidc.provider.external -## Your client credentials +## Your OIDC client id # KOMODO_OIDC_CLIENT_ID= # Alt: KOMODO_OIDC_CLIENT_ID_FILE +## Your OIDC client secret. +## If your provider supports PKCE flow, this can be ommitted. # KOMODO_OIDC_CLIENT_SECRET= # Alt: KOMODO_OIDC_CLIENT_SECRET_FILE ## Make usernames the full email. +## Note. This does not work for all OIDC providers. # KOMODO_OIDC_USE_FULL_EMAIL=true ## Add additional trusted audiences for token claims verification. ## Supports comma separated list, and passing with _FILE (for compose secrets). @@ -111,7 +114,7 @@ KOMODO_HETZNER_TOKEN= # Alt: KOMODO_HETZNER_TOKEN_FILE #=------------------------------=# ## Full variable list + descriptions are available here: -## 🦎 https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml 🦎 +## 🦎 https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml 🦎 ## Periphery passkeys must include KOMODO_PASSKEY to authenticate. PERIPHERY_PASSKEYS=${KOMODO_PASSKEY} diff --git a/compose/mongo.compose.yaml b/compose/mongo.compose.yaml index 9e404ade3..36ea06ce2 100644 --- a/compose/mongo.compose.yaml +++ b/compose/mongo.compose.yaml @@ -16,8 +16,6 @@ services: restart: unless-stopped logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default # ports: # - 27017:27017 volumes: @@ -28,7 +26,7 @@ services: MONGO_INITDB_ROOT_PASSWORD: ${KOMODO_DB_PASSWORD} core: - image: ghcr.io/mbecker20/komodo:${COMPOSE_KOMODO_IMAGE_TAG:-latest} + image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest} labels: komodo.skip: # Prevent Komodo from stopping with StopAllContainers restart: unless-stopped @@ -36,8 +34,6 @@ services: - mongo logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default ports: - 9120:9120 env_file: ./compose.env @@ -59,16 +55,14 @@ services: ## Deploy Periphery container using this block, ## or deploy the Periphery binary with systemd using - ## https://github.com/mbecker20/komodo/tree/main/scripts + ## https://github.com/moghtech/komodo/tree/main/scripts periphery: - image: ghcr.io/mbecker20/periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest} + image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest} labels: komodo.skip: # Prevent Komodo from stopping with StopAllContainers restart: unless-stopped logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default env_file: ./compose.env environment: PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos @@ -82,7 +76,7 @@ services: - /proc:/proc ## Specify the Periphery agent root directory. ## Must be the same inside and outside the container, - ## or docker will get confused. See https://github.com/mbecker20/komodo/discussions/180. + ## or docker will get confused. See https://github.com/moghtech/komodo/discussions/180. ## Default: /etc/komodo. - ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo} @@ -91,7 +85,4 @@ volumes: mongo-data: mongo-config: # Core - repo-cache: - -networks: - default: {} \ No newline at end of file + repo-cache: \ No newline at end of file diff --git a/compose/periphery.compose.yaml b/compose/periphery.compose.yaml new file mode 100644 index 000000000..36355ddc1 --- /dev/null +++ b/compose/periphery.compose.yaml @@ -0,0 +1,39 @@ +#################################### +# 🦎 KOMODO COMPOSE - PERIPHERY 🦎 # +#################################### + +## This compose file will deploy: +## 1. Komodo Periphery + +services: + periphery: + image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest} + labels: + komodo.skip: # Prevent Komodo from stopping with StopAllContainers + restart: unless-stopped + logging: + driver: ${COMPOSE_LOGGING_DRIVER:-local} + ## https://komo.do/docs/connect-servers#configuration + environment: + PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos + PERIPHERY_STACK_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/stacks + PERIPHERY_SSL_KEY_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/key.pem + PERIPHERY_SSL_CERT_FILE: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/ssl/cert.pem + ## Pass the same passkey as used by the Komodo Core connecting to this Periphery agent. + PERIPHERY_PASSKEYS: abc123 + ## If the disk size is overreporting, can use one of these to + ## whitelist / blacklist the disks to filter them, whichever is easier. + ## Accepts comma separated list of paths. + ## Usually whitelisting just /etc/hostname gives correct size for single root disk. + PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname + # PERIPHERY_EXCLUDE_DISK_MOUNTS: /snap,/etc/repos + volumes: + ## Mount external docker socket + - /var/run/docker.sock:/var/run/docker.sock + ## Allow Periphery to see processes outside of container + - /proc:/proc + ## Specify the Periphery agent root directory. + ## Must be the same inside and outside the container, + ## or docker will get confused. See https://github.com/moghtech/komodo/discussions/180. + ## Default: /etc/komodo. + - ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo} \ No newline at end of file diff --git a/compose/postgres.compose.yaml b/compose/postgres.compose.yaml index 4a9ee10c8..d05d00f37 100644 --- a/compose/postgres.compose.yaml +++ b/compose/postgres.compose.yaml @@ -15,8 +15,6 @@ services: restart: unless-stopped logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default # ports: # - 5432:5432 volumes: @@ -35,15 +33,13 @@ services: - postgres logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default # ports: # - 27017:27017 environment: - FERRETDB_POSTGRESQL_URL=postgres://postgres:5432/${KOMODO_DATABASE_DB_NAME:-komodo} core: - image: ghcr.io/mbecker20/komodo:${COMPOSE_KOMODO_IMAGE_TAG:-latest} + image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest} labels: komodo.skip: # Prevent Komodo from stopping with StopAllContainers restart: unless-stopped @@ -51,8 +47,6 @@ services: - ferretdb logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default ports: - 9120:9120 env_file: ./compose.env @@ -72,16 +66,14 @@ services: ## Deploy Periphery container using this block, ## or deploy the Periphery binary with systemd using - ## https://github.com/mbecker20/komodo/tree/main/scripts + ## https://github.com/moghtech/komodo/tree/main/scripts periphery: - image: ghcr.io/mbecker20/periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest} + image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest} labels: komodo.skip: # Prevent Komodo from stopping with StopAllContainers restart: unless-stopped logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default env_file: ./compose.env environment: PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos @@ -95,7 +87,7 @@ services: - /proc:/proc ## Specify the Periphery agent root directory. ## Must be the same inside and outside the container, - ## or docker will get confused. See https://github.com/mbecker20/komodo/discussions/180. + ## or docker will get confused. See https://github.com/moghtech/komodo/discussions/180. ## Default: /etc/komodo. - ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo} @@ -103,7 +95,4 @@ volumes: # Postgres pg-data: # Core - repo-cache: - -networks: - default: {} \ No newline at end of file + repo-cache: \ No newline at end of file diff --git a/compose/sqlite.compose.yaml b/compose/sqlite.compose.yaml index 825d4d8cc..187791ce7 100644 --- a/compose/sqlite.compose.yaml +++ b/compose/sqlite.compose.yaml @@ -15,8 +15,6 @@ services: restart: unless-stopped logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default # ports: # - 27017:27017 volumes: @@ -25,7 +23,7 @@ services: - FERRETDB_HANDLER=sqlite core: - image: ghcr.io/mbecker20/komodo:${COMPOSE_KOMODO_IMAGE_TAG:-latest} + image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest} labels: komodo.skip: # Prevent Komodo from stopping with StopAllContainers restart: unless-stopped @@ -33,8 +31,6 @@ services: - ferretdb logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default ports: - 9120:9120 env_file: ./compose.env @@ -54,16 +50,14 @@ services: ## Deploy Periphery container using this block, ## or deploy the Periphery binary with systemd using - ## https://github.com/mbecker20/komodo/tree/main/scripts + ## https://github.com/moghtech/komodo/tree/main/scripts periphery: - image: ghcr.io/mbecker20/periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest} + image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest} labels: komodo.skip: # Prevent Komodo from stopping with StopAllContainers restart: unless-stopped logging: driver: ${COMPOSE_LOGGING_DRIVER:-local} - networks: - - default env_file: ./compose.env environment: PERIPHERY_REPO_DIR: ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}/repos @@ -77,7 +71,7 @@ services: - /proc:/proc ## Specify the Periphery agent root directory. ## Must be the same inside and outside the container, - ## or docker will get confused. See https://github.com/mbecker20/komodo/discussions/180. + ## or docker will get confused. See https://github.com/moghtech/komodo/discussions/180. ## Default: /etc/komodo. - ${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/etc/komodo} @@ -85,7 +79,4 @@ volumes: # Sqlite sqlite-data: # Core - repo-cache: - -networks: - default: {} \ No newline at end of file + repo-cache: \ No newline at end of file diff --git a/config/core.config.toml b/config/core.config.toml index 12789b7ee..ad60f561e 100644 --- a/config/core.config.toml +++ b/config/core.config.toml @@ -4,12 +4,12 @@ ## This is the offical "Default" config file for Komodo Core. ## It serves as documentation for the meaning of the fields. -## It is located at `https://github.com/mbecker20/komodo/blob/main/config/core.config.toml`. +## It is located at `https://github.com/moghtech/komodo/blob/main/config/core.config.toml`. ## All fields with a "Default" provided are optional. If they are ## left out of the file, the "Default" value will be used. -## This file is bundled into the official image, `ghcr.io/mbecker20/komodo`, +## This file is bundled into the official image, `ghcr.io/moghtech/komodo`, ## as the default config at `/config/config.toml`. ## Komodo can start with no external config file mounted. @@ -118,6 +118,16 @@ transparent_mode = false ## Default: false disable_non_admin_create = false +## Normally users can update their username / password using the API. +## This will disable this ability for specific users or all users. +## Example: +## - `lock_login_credentials_for = []` will allow all users to update username / password. +## - `lock_login_credentials_for = ["demo"]` will block the demo user from doing so. +## - `lock_login_credentials_for = ["__ALL__"]` will block all users. +## Env: KOMODO_LOCK_LOGIN_CREDENTIALS_FOR +## Default: empty list +lock_login_credentials_for = [] + ## Optionally provide a specific jwt secret. ## Passing nothing or an empty string will cause one to be generated on every startup. ## This means users will have to log in again if Komodo restarts. @@ -165,17 +175,20 @@ oidc_provider = "https://oidc.provider.internal/application/o/komodo" ## Optional, no default. oidc_redirect_host = "" -## Give the OIDC Client ID. +## Set the OIDC Client ID. ## Env: KOMODO_OIDC_CLIENT_ID or KOMODO_OIDC_CLIENT_ID_FILE oidc_client_id = "" -## Give the OIDC Client Secret. +## Set the OIDC Client Secret. +## If the OIDC provider supports PKCE-only flow, +## the client secret is not necessary and can be ommitted or left empty. ## Env: KOMODO_OIDC_CLIENT_SECRET or KOMODO_OIDC_CLIENT_SECRET_FILE oidc_client_secret = "" ## If true, use the full email for usernames. ## Otherwise, the @address will be stripped, ## making usernames more concise. +## Note. This does not work for all OIDC providers. ## Env: KOMODO_OIDC_USE_FULL_EMAIL ## Default: false. oidc_use_full_email = false diff --git a/config/periphery.config.toml b/config/periphery.config.toml index e03a341eb..bf785243d 100644 --- a/config/periphery.config.toml +++ b/config/periphery.config.toml @@ -4,7 +4,7 @@ ## This is the offical "Default" config file for Komodo Periphery. ## It serves as documentation for the meaning of the fields. -## It is located at `https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml`. +## It is located at `https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml`. ## All fields with a "Default" provided are optional. If they are ## left out of the file, the "Default" value will be used. diff --git a/test.compose.yaml b/dev.compose.yaml similarity index 96% rename from test.compose.yaml rename to dev.compose.yaml index e5c3a19f0..728348a85 100644 --- a/test.compose.yaml +++ b/dev.compose.yaml @@ -35,7 +35,7 @@ services: PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname ferretdb: - image: ghcr.io/ferretdb/ferretdb + image: ghcr.io/ferretdb/ferretdb:1 restart: unless-stopped logging: driver: local diff --git a/docsite/docs/build-images/builders.md b/docsite/docs/build-images/builders.md index c9efd6500..a116a9e4f 100644 --- a/docsite/docs/build-images/builders.md +++ b/docsite/docs/build-images/builders.md @@ -22,7 +22,7 @@ apt upgrade -y curl -fsSL https://get.docker.com | sh systemctl enable docker.service systemctl enable containerd.service -curl -sSL https://raw.githubusercontent.com/mbecker20/komodo/main/scripts/setup-periphery.py | HOME=/root python3 +curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py | HOME=/root python3 systemctl enable periphery.service ``` diff --git a/docsite/docs/build-images/configuration.md b/docsite/docs/build-images/configuration.md index 9956bd14b..512aad7d3 100644 --- a/docsite/docs/build-images/configuration.md +++ b/docsite/docs/build-images/configuration.md @@ -9,7 +9,7 @@ Accounts / access tokens can be configured in either the [core config](../setup/ or in the [periphery config](../connect-servers.mdx#manual-install-steps---binaries). ### Repo configuration -To specify the git repo to build, just give it the name of the repo and the branch under *repo config*. The name is given like `mbecker20/komodo`, it includes the username / organization that owns the repo. +To specify the git repo to build, just give it the name of the repo and the branch under *repo config*. The name is given like `moghtech/komodo`, it includes the username / organization that owns the repo. Many repos are private, in this case an access token is needed by the building server. It can either come from a provider defined in the core configuration, diff --git a/docsite/docs/connect-servers.mdx b/docsite/docs/connect-servers.mdx index 8012dd5c8..b3850fa1c 100644 --- a/docsite/docs/connect-servers.mdx +++ b/docsite/docs/connect-servers.mdx @@ -1,5 +1,9 @@ # Connect More Servers +```mdx-code-block +import RemoteCodeFile from "@site/src/components/RemoteCodeFile"; +``` + Connecting a server to Komodo has 2 steps: 1. Install the Periphery agent on the server (either binary or container). @@ -7,29 +11,29 @@ Connecting a server to Komodo has 2 steps: ## Install Periphery -You can install Periphery as a systemd managed process, run it as a [docker container](https://github.com/mbecker20/komodo/pkgs/container/periphery), or do whatever you want with the binary. +You can install Periphery as a systemd managed process, run it as a [docker container](https://github.com/moghtech/komodo/pkgs/container/periphery), or do whatever you want with the binary. :::warning Allowing unintended access to the Periphery agent API is a security risk. Ensure to take appropriate measures to block access to the Periphery API, such as firewall rules on port `8120`. -Additionally, you can whitelist your Komodo Core IP address in the [Periphery config](https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml#L46), -and configure it to [only accept requests matching including your Core passkey](https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml#L51). +Additionally, you can whitelist your Komodo Core IP address in the [Periphery config](https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml#L46), +and configure it to [only accept requests including your Core passkey](https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml#L51). ::: ### Install the Periphery agent - systemd As root user: ```bash -curl -sSL https://raw.githubusercontent.com/mbecker20/komodo/main/scripts/setup-periphery.py | python3 +curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py | python3 ``` Periphery can also be installed to run as the calling user, just note this comes with some additional configuration. ```bash -curl -sSL https://raw.githubusercontent.com/mbecker20/komodo/main/scripts/setup-periphery.py | python3 - --user +curl -sSL https://raw.githubusercontent.com/moghtech/komodo/main/scripts/setup-periphery.py | python3 - --user ``` -You can find more information (and view the script) in the [readme](https://github.com/mbecker20/komodo/tree/main/scripts). +You can find more information (and view the script) in the [readme](https://github.com/moghtech/komodo/tree/main/scripts). :::info This script can be run multiple times without issue, and it won't change existing config after the first run. Just run it again after a Komodo version release, and it will update the periphery version. @@ -37,60 +41,21 @@ This script can be run multiple times without issue, and it won't change existin ### Install the Periphery agent - container -You can use a docker compose file like this: -```yaml -services: - periphery: - image: ghcr.io/mbecker20/periphery:latest - # image: ghcr.io/mbecker20/periphery:latest-aarch64 # use for arm support - labels: - komodo.skip: # Prevent Komodo from stopping with StopAllContainers - logging: - driver: local - ports: - - 8120:8120 - volumes: - ## Mount external docker socket - - /var/run/docker.sock:/var/run/docker.sock - ## Allow Periphery to see processes outside of container - - /proc:/proc - ## use self signed certs in docker volume, - ## or mount your own signed certs. - - ssl-certs:/etc/komodo/ssl - ## manage repos in a docker volume, - ## or change it to an accessible host directory. - - repos:/etc/komodo/repos - ## manage stack files in a docker volume, - ## or change it to an accessible host directory. - - stacks:/etc/komodo/stacks - ## Optionally mount a path to store compose files - # - /path/to/compose:/host/compose - environment: - ## Full list: `https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml` - ## Configure the same passkey given to Komodo Core (KOMODO_PASSKEY) - PERIPHERY_PASSKEYS: your_core_passkey # Alt: PERIPHERY_PASSKEYS_FILE - ## Adding IP here will ensure calling IP is in the list. (optional) - PERIPHERY_ALLOWED_IPS: - ## Enable HTTPS server - PERIPHERY_SSL_ENABLED: true - ## If the disk size is overreporting, can use one of these to - ## whitelist / blacklist the disks to filter them, whichever is easier. - ## Accepts comma separated list of paths. - ## Usually whitelisting /etc/hostname gives correct size. - PERIPHERY_INCLUDE_DISK_MOUNTS: /etc/hostname - # PERIPHERY_EXCLUDE_DISK_MOUNTS: /snap,/etc/repos +You can use a docker compose file: -volumes: - ssl-certs: - repos: - stacks: +```mdx-code-block + ``` ### Manual install steps - binaries -1. Download the periphery binary from the latest [release](https://github.com/mbecker20/komodo/releases). +1. Download the periphery binary from the latest [release](https://github.com/moghtech/komodo/releases). -2. Create and edit your config files, following the [config example](https://github.com/mbecker20/komodo/blob/main/config/periphery.config.toml). +2. Create and edit your config files, following the [config example](https://github.com/moghtech/komodo/blob/main/config/periphery.config.toml). :::note See the [periphery config docs](https://docs.rs/komodo_client/latest/komodo_client/entities/config/periphery/index.html) @@ -142,15 +107,13 @@ Similarly, you can specify a base docker / github account pair, and extend them Quick download to `./komodo/periphery.config.toml`: ```bash -wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/config/periphery.config.toml +wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/config/periphery.config.toml ``` ```mdx-code-block -import RemoteCodeFile from "@site/src/components/RemoteCodeFile"; - ``` diff --git a/docsite/docs/development.md b/docsite/docs/development.md index b3be3613a..ec67e3b3e 100644 --- a/docsite/docs/development.md +++ b/docsite/docs/development.md @@ -4,7 +4,7 @@ If you are looking to contribute to Komodo, this page is a launching point for s ## Dependencies -Running Komodo from [source](https://github.com/mbecker20/komodo) requires either [Docker](https://www.docker.com/) (and can use the included [devcontainer](https://code.visualstudio.com/docs/devcontainers/containers)), or can have the development dependencies installed locally: +Running Komodo from [source](https://github.com/moghtech/komodo) requires either [Docker](https://www.docker.com/) (and can use the included [devcontainer](https://code.visualstudio.com/docs/devcontainers/containers)), or can have the development dependencies installed locally: * Backend (Core / Periphery APIs) * [Rust](https://www.rust-lang.org/) stable via [rustup installer](https://rustup.rs/) @@ -22,7 +22,7 @@ Running Komodo from [source](https://github.com/mbecker20/komodo) requires eithe ## Docker -After making changes to the project, run `run -r test-compose-build` to rebuild Komodo and then `run -r test-compose-exposed` to start a Komodo container with the UI accessible at `localhost:9120`. Any changes made to source files will require re-running the `test-compose-build` and `test-compose-exposed` commands. +After making changes to the project, run `run -r dev-compose-build` to rebuild Komodo and then `run -r dev-compose-exposed` to start a Komodo container with the UI accessible at `localhost:9120`. Any changes made to source files will require re-running the `dev-compose-build` and `dev-compose-exposed` commands. ## Devcontainer @@ -39,17 +39,17 @@ To run a full Komodo instance from a non-container environment run commands in t * Ensure dependencies are up to date * `rustup update` -- ensure rust toolchain is up to date * Build and Run backend - * `run -r test-core` -- Build and run Core API - * `run -r test-periphery` -- Build and run Periphery API + * `run -r dev-core` -- Build and run Core API + * `run -r dev-periphery` -- Build and run Periphery API * Build Frontend * Install **typeshare-cli**: `cargo install typeshare-cli` * **Run this once** -- `run -r link-client` -- generates TS client and links to the frontend * After running the above once: * `run -r gen-client` -- Rebuild client - * `run -r start-frontend` -- Start in dev (watch) mode + * `run -r dev-frontend` -- Start in dev (watch) mode * `run -r build-frontend` -- Typecheck and build ## Docsite Development -Use `run -r docsite-start` to start the [Docusaurus](https://docusaurus.io/) Komodo docs site in development mode. Changes made to files in `./docsite` will be automatically reloaded by the server. \ No newline at end of file +Use `run -r dev-docsite` to start the [Docusaurus](https://docusaurus.io/) Komodo docs site in development mode. Changes made to files in `./docsite` will be automatically reloaded by the server. \ No newline at end of file diff --git a/docsite/docs/intro.md b/docsite/docs/intro.md index 15a73972d..e2353b84c 100644 --- a/docsite/docs/intro.md +++ b/docsite/docs/intro.md @@ -39,7 +39,7 @@ Komodo Periphery is a small stateless web server that runs on all connected serv ## Core API -Komodo exposes powerful functionality over the Core's REST and Websocket API, enabling infrastructure engineers to manage their infrastructure programmatically. There is a [rust crate](https://crates.io/crates/komodo_client) to simplify programmatic interaction with the API, but in general this can be accomplished using any programming language that can make REST requests. +Komodo exposes powerful functionality over the Core's REST and Websocket API, enabling infrastructure engineers to manage their infrastructure programmatically. There is a [rust crate](https://crates.io/crates/komodo_client) and [npm package](https://www.npmjs.com/package/komodo_client) to simplify programmatic interaction with the API, but in general this can be accomplished using any programming language that can make REST requests. ## Permissioning diff --git a/docsite/docs/setup/advanced.mdx b/docsite/docs/setup/advanced.mdx index 5731e98ea..1b11d811d 100644 --- a/docsite/docs/setup/advanced.mdx +++ b/docsite/docs/setup/advanced.mdx @@ -25,15 +25,15 @@ Configuration can still be passed in environment variables, and will take preced Quick download to `./komodo/core.config.toml`: ```bash -wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/config/core.config.toml +wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/config/core.config.toml ``` ```mdx-code-block import RemoteCodeFile from "@site/src/components/RemoteCodeFile"; ``` \ No newline at end of file diff --git a/docsite/docs/setup/index.mdx b/docsite/docs/setup/index.mdx index 60665536f..a64289d26 100644 --- a/docsite/docs/setup/index.mdx +++ b/docsite/docs/setup/index.mdx @@ -14,9 +14,9 @@ Komodo is able to support Postgres and Sqlite by utilizing the [FerretDB Mongo A ### First login -Core should now be accessible on the specified port, so navigating to `http://
:` will display the login page. -On first login, you need to click `sign-up`, _not_ login, to create an initial admin user for Komodo. -Any additional users to create accounts will be disabled by default, and must be enabled by an admin. +Core should now be accessible on the specified port, so navigating to `http://
:` will display the login page. +Enter your preferred admin username and password, and click **"Sign Up"**, _not_ "Log In", to create your admin user for Komodo. +Any additional users to create accounts will be disabled by default, and must be enabled by an admin. ### Https diff --git a/docsite/docs/setup/mongo.mdx b/docsite/docs/setup/mongo.mdx index 61bf8b93d..b67c05e3a 100644 --- a/docsite/docs/setup/mongo.mdx +++ b/docsite/docs/setup/mongo.mdx @@ -2,8 +2,8 @@ 1. Copy `komodo/mongo.compose.yaml` and `komodo/compose.env` to your host: ```bash -wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/compose/mongo.compose.yaml && \ - wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/compose/compose.env +wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/mongo.compose.yaml && \ + wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env ``` 2. Edit the variables in `komodo/compose.env`. 3. Deploy: diff --git a/docsite/docs/setup/postgres.mdx b/docsite/docs/setup/postgres.mdx index 988d47091..60fbb46cd 100644 --- a/docsite/docs/setup/postgres.mdx +++ b/docsite/docs/setup/postgres.mdx @@ -2,8 +2,8 @@ 1. Copy `komodo/postgres.compose.yaml` and `komodo/compose.env` to your host: ```bash -wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/compose/postgres.compose.yaml && \ - wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/compose/compose.env +wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/postgres.compose.yaml && \ + wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env ``` 2. Edit the variables in `komodo/compose.env`. 3. Deploy: diff --git a/docsite/docs/setup/sqlite.mdx b/docsite/docs/setup/sqlite.mdx index 8a75c2514..33f98d99b 100644 --- a/docsite/docs/setup/sqlite.mdx +++ b/docsite/docs/setup/sqlite.mdx @@ -8,8 +8,8 @@ are ignored when using Sqlite, and the database port is not exposed publicly by 1. Copy `komodo/sqlite.compose.yaml` and `komodo/compose.env` to your host: ```bash -wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/compose/sqlite.compose.yaml && \ - wget -P komodo https://raw.githubusercontent.com/mbecker20/komodo/main/compose/compose.env +wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/sqlite.compose.yaml && \ + wget -P komodo https://raw.githubusercontent.com/moghtech/komodo/main/compose/compose.env ``` 2. Edit the variables in `komodo/compose.env`. 3. Deploy: diff --git a/docsite/docs/sync-resources.md b/docsite/docs/sync-resources.md index 1042034da..fd67de8cd 100644 --- a/docsite/docs/sync-resources.md +++ b/docsite/docs/sync-resources.md @@ -1,17 +1,23 @@ # Sync Resources Komodo is able to create, update, delete, and deploy resources declared in TOML files by diffing them against the existing resources, -and apply updates based on the diffs. Push the files to a remote git repo and create a `ResourceSync` pointing to the repo, -and the core backend will poll for any updates (you can also manually trigger an update poll / execution in the UI). +and apply updates based on the diffs. Similar to Stacks, the files can be configured in UI, in a local file, or in files pushed to a remote git repo. +The Komodo Core backend will poll the files for for any updates, and alert about pending changes when diffs are detected. -File detection is additive and recursive, so you can spread out your resource declarations across any number of files -and use any nesting of folders to organize resources inside a root folder. Additionally, you can create multiple `ResourceSyncs` +You can spread out your resource declarations across any number of files +and use any nesting of folders to organize resources inside a root folder. +Additionally, you can create multiple `ResourceSyncs` and configure `Match Tags` to filter down which resources are synced, and each sync will be handled independently. This allows different syncs to manage resources on a "per-project" basis. The UI will display the computed sync actions and only execute them upon manual confirmation. Or the sync execution git webhook may be configured on the git repo to automatically execute syncs upon pushes to the configured branch. +## Commit to Syncs + +If the Sync is pointing to just a single file, you can enable "Managed Mode" to allow Core to write the updates you made in UI _back to the file_. +This works no matter where the files are located, and will create a commit to your git repository for repo based files. + ## Example Declarations ### Server @@ -209,7 +215,7 @@ tags = ["komodo"] server_id = "server-01" git_provider = "git.mogh.tech" # use an alternate git provider (default is github.com) git_account = "mbecker20" -repo = "mbecker20/komodo" +repo = "moghtech/komodo" # Run an action after the repo is pulled on_pull.path = "." on_pull.command = """ @@ -230,7 +236,7 @@ name = "resource-sync" [resource_sync.config] git_provider = "git.mogh.tech" # use an alternate git provider (default is github.com) git_account = "mbecker20" -repo = "mbecker20/komodo" +repo = "moghtech/komodo" resource_path = ["stacks.toml", "repos.toml"] ``` diff --git a/docsite/docs/version-upgrades.md b/docsite/docs/version-upgrades.md index f92ed43bb..26e138051 100644 --- a/docsite/docs/version-upgrades.md +++ b/docsite/docs/version-upgrades.md @@ -1,7 +1,7 @@ # Version Upgrades -Most version upgrades only require a redeployment of the Core container after pulling the latest version, and are fully backward compatible with the periphery clients, which may be updated later on as convenient. This is the default, and will be the case unless specifically mentioned in the [version release notes](https://github.com/mbecker20/komodo/releases). +Most version upgrades only require a redeployment of the Core container after pulling the latest version, and are fully backward compatible with the periphery clients, which may be updated later on as convenient. This is the default, and will be the case unless specifically mentioned in the [version release notes](https://github.com/moghtech/komodo/releases). Some Core API upgrades may change behavior such as building / cloning, and require updating the Periphery binaries to match the Core version before this functionality can be restored. This will be specifically mentioned in the release notes. -Additionally, some Core API upgrades may include database schema changes, and require a database migration. This can be accomplished by using the [komodo migrator](https://github.com/mbecker20/komodo/blob/main/bin/migrator/README.md) for the particular version upgrade before upgrading the Core API container. \ No newline at end of file +Additionally, some Core API upgrades may include database schema changes, and require a database migration. This can be accomplished by using the [komodo migrator](https://github.com/moghtech/komodo/blob/main/bin/migrator/README.md) for the particular version upgrade before upgrading the Core API container. \ No newline at end of file diff --git a/docsite/docusaurus.config.ts b/docsite/docusaurus.config.ts index 345bd2784..957d72bc6 100644 --- a/docsite/docusaurus.config.ts +++ b/docsite/docusaurus.config.ts @@ -19,7 +19,7 @@ const config: Config = { // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. - organizationName: "mbecker20", // Usually your GitHub org/user name. + organizationName: "moghtech", // Usually your GitHub org/user name. projectName: "komodo", // Usually your repo name. trailingSlash: false, deploymentBranch: "gh-pages-docs", @@ -41,13 +41,11 @@ const config: Config = { { docs: { sidebarPath: "./sidebars.ts", - editUrl: - "https://github.com/mbecker20/komodo/tree/main/docsite", + editUrl: "https://github.com/moghtech/komodo/tree/main/docsite", }, blog: { showReadingTime: true, - editUrl: - "https://github.com/mbecker20/komodo/tree/main/docsite", + editUrl: "https://github.com/moghtech/komodo/tree/main/docsite", }, theme: { customCss: "./src/css/custom.css", @@ -67,7 +65,7 @@ const config: Config = { title: "Komodo", logo: { alt: "monitor lizard", - src: "img/logo512.png", + src: "img/komodo-512x512.png", width: "34px", }, items: [ @@ -78,12 +76,17 @@ const config: Config = { label: "docs", }, { - href: "https://docs.rs/komodo_client/latest/komodo_client/", + href: "https://opencollective.com/komodo", + label: "Donate", + position: "right", + }, + { + href: "https://docs.rs/komodo_client/latest/komodo_client", label: "Docs.rs", position: "right", }, { - href: "https://github.com/mbecker20/komodo", + href: "https://github.com/moghtech/komodo", label: "Github", position: "right", }, diff --git a/docsite/src/components/ComposeAndEnv.tsx b/docsite/src/components/ComposeAndEnv.tsx index 36f1b35e7..c6e5f4db6 100644 --- a/docsite/src/components/ComposeAndEnv.tsx +++ b/docsite/src/components/ComposeAndEnv.tsx @@ -12,15 +12,15 @@ export default function ComposeAndEnv({ diff --git a/docsite/src/pages/index.tsx b/docsite/src/pages/index.tsx index b350b3be3..6c420c6b9 100644 --- a/docsite/src/pages/index.tsx +++ b/docsite/src/pages/index.tsx @@ -40,13 +40,13 @@ function HomepageHeader() { Github @urSGBe5p-aZzQo1x+0Vi3>@PRj^^3tgo*T-c>Hc!W zW{?{Wv76hu(cnytXSFGX@wkqR$Im~1emQw9pDoXqXUpua+n7Qg`&C+=b&>=B2*-n5-AnlGaXDYZ?pcYB>M==G48T&=cl4GKVu)@-D0%;DNNPV*%*i9 zzvM_`mt^SDQ*oo0+|G@$@7`3;|Hv{L#x@k= zStEODPwpch#F8`fY}vTzsa9j7_21*XAV7&d@1vP>U^gr0ujEueTE%u10J-pNF6R;M z4ZF|0n0wl`n9`qe+8GxPKWF_S|0zC366ejbjkl#lohqhQAxv`0a=7CRnFblHLC8gG z)#jdSAL!`5;F^A+&&YAE*Ouo@>ht+?kG1b%aIDR#oOmtyRuA{~h*x3gn&x?hssYPB zvh$ZV%tus%2NZ!{73@_AupKC5O2S{31uT zB@b>x{tg)YvI$(z%ZOi?o2(zgp1(MA4C=8hX9c9#JwmD->pIqUC;uLf`D@NG4N@`k z89TX-4S&&l1ZU`nT4Kx-{GGG1Gmd`7w$A{}Ys`V+Mq6)>^vaq(HvdIG`bOJ*wKC_w zvqrY>D$HITI)B}dx?Zt8wHA5*_Umk&?e^e5^^GZGsE>M&*n-Nvvd=+c&phKhx2zxP zbLzcTC%3*1o`3JR(sS!^wDaE2XI&a0Dx zZ27YvrgO9CSw5W?`DmMddRNVt`?0ajPNY$Gajx%~_-Io3MGe$=r6+jAf(z!Pva{OA zwbaBC$E9tY3foZOUbpIFGWX7`gu*te` z>VET>HR@cV4rYDK;E-RnHgW&P`Z}AY71{-77b{wgZ-G3cR!Ipf* z+}7s0FXwXDan1L@%{o-^cT5_u(_M>aw7;`O1J1bf+#?zdfBI9L27e#OXu@`PjYmLu z?~S}i+mXu50G@SNznZ6VKUx!W#;lp@d+)75V<_A!{(Y#{tm|+bTIaR!HKELL^lil%rW~L8IP=F?Gj$S?W4jwj9L&mS;2-PX zW6*n0bskDx^=eD%5WJ(kRp6z zt=3`>?$Gb%UDrDE`v7j)cB&CD=k`W?&Z*XewbbDrV5dCWxN7dNuVz#FJ}$BFydpo{ zE7&jcec7ISrhYd%rt6qxW;`-xHKha1Je3;-`jM@lk1*q~0dKlFkUX9nQ{9Xc*YP#09xcX+e3H(lO=cXmdn)wb&j~9t`-G(!=2}Tu~km!)BGz z_GB3D7b)GlTnvltNlJICVexPbISh*da`O}95#;T|$?!6z+c(?e_mFQOzq^6F-5y_G zr*yr!KfW>f@*VnjUmvOb^6*IdDN()w&yjo&T#i%TtK2{y1OIM&nHF!!Ym-)x_lt+L z_$TFXc>;+z?oZYd*Y;#3ac+MHiJ&nBdAYh1xjDM@Jgqp6ybY8?LWU?e$P6VvMe^^o zPH`=9?fg3}=*T{pT2{{I&jD2TP|Pa1kr8$^XmK!U#=m*c??-ah^IXA2%xDtZ`%MtR z)1VO+4)AGbD}2x2L-^g<_$Z>HPVy;AbpSSA?gQ{E_uGQr2?hQ4Mia>N)+M*Kl z?7XhyWggo2U?V*Je2>oy7^8;(JL+-XKh)!;zBM+)B=?DoQiF+U9MYWW`&h}x7r4Jf z1$UNZJ!$tSjT^ty%W69sj^eU&Zal!}jNzbw(S08kK4wuR|vX@UQD` zu@gIKj|Pfr*2u7tcD-x=sV zz`u*=x<(z#jh>kn6KzYJ&z2br^FFR)iX?n>&Gh^U4xq2GJMXXt#L(Y8$@x|7d*x*{ zW_U7Ywc(sxIxlkZG0KsDJWmID<{$ZSyXcFYLk;9yVVu~GLmBROSCVts2`8^*kKfK{ z&6(uWYNPNLJkPcBq7TP3|Kz}r`Nw@s`4&CNE9%uHs``EfrU6mV(hHAz3h znOL-SeanLS6hDZkVeGTZ}CMfjGpr7muc@PdRm5=NP=qQfOok=|jo?&XPn|mSrRB=iU$dDgB zA7tLR>pE#3s#w9x`pA8$JCBEGtc%{7aIRn(Zw}&P)U)K6)r{u=GXMTJ_@-rTP5-p- zeWr8JXgef^J)gw2b@3c%?t`i5N?shmC6v%)uX;ai>rkG99W2;!$hyeEQaJctZ2sn% z^Nuhla~_CO)xw;W&UqeWEn^M1sc}^if4V}!DfJ+Gd8so7VCvGdA+1r{pQuGs8H*aC znJ<$$4`pvlZFz0GrmU;(yFvEp&b;mQ4NiE4`odXKVyo6U^RqL;*USkDQq{4Tdt*&H ztjj$c_E@g*utta->jd8gwY2)Ej~ES5zK>*mn0JrY;6%0jc!U$E20z8HJ6NW3>+yQ> z+5j&lhAX^i!Rv#E!>iTy;HVVc=zwmKK=dopFaK5 z4^N+d^NLqWyuN*j3$H!?hU>@c%Yz@^Z4ZCC9S+{#4V(YnukP?t;}+Q8!3SUH;8hTP z;=}h8PjH>$I*^}e^gbRMB@ZO;qk(nTXH1z>V|@-S1A2yKqvU_9DRpf3HmxO|Q!h07SLcvDf&$HGFI@s;wKFtig^}dUJsL$p3K8C!>y@yoA4)c6&V4pdq zBtLDd2g!lNhj?MnIr5+`o}J(N8gNfD7R{}F&mcGG=kGJJet4#n_&$D38{*K@1$=m} z#Rj#)apuALI5&81+1ce-#wC8g1CD&x(inB$gB#aFFXn|gx9_cmsA?!Z`b!bTx$?S~cKr5@uwD-N0)ypMhLO-ldx7XK&p jtCary2mGJZ-=y^SU*r01NN};pZ}$;h9XvarArX@J!}*+B!O-{z4}6^uSD} z1Pmx(=yr+*zbv%n2M0Io@Ld1LPTqUP&3oJ3oVU%(`!7+y(eu1E4XB|b6xGz!)O3*J zxVHee$94SQf`5hU22*&>cJuy~JkL6Le-F=1K@c3Exj~2j1=n@FC&1b0<^0nXT|=Bw zapRAl_rHuRoBX`DOwmvsnG7@+!goW^9QYboQ~j^jPk+0ike7?fsrb`CvB@#IgTP#o(y z!7t0@)?MfQt+;KRp5p%cXf3iRxB3(0^IG$+2iEA?+N#UTlo8y zLd(rkk8Hjfo9;sQNz&#x;RTGrABsWY+sLw&dJnegp}rm&e^@TG-beX;B^>fNj{ge! zzvd&q@JFiSd?)Df-C(20t#G4K=7aDsCLFsjLd<(eMg`hA~U>(Of5FYb_e9(!UjGsSVtTy=Q!3X;u8s)v|$U5J5 z{H?sLbX@O+zzNS5Tv%tY=}A80Cv{|s{YUhW*`Q_wFF_di&^V7d@jCprmCNNLq|bH3 zaS>FeskvGF_i^RFdHh$13L)!P42p~C-yR0Rp<;XJsySdlnrTDc1;8+WJm!yoMd$_> z;6LBu{Rw2muHJVQe2TWTsV4ZkZlQuLZi3Fs;A|6_k^i^8>rcC8?lt`szJjhpM0f7j zUx_mg&lsMps!?h1tH;%D!@h?|%wWv^1^J&w{tb-%N7&{=>g$;|Pg33_v0(7v!3Q^B zmFlv&q`HvY^SuDyyH)HZwx*09Y#G&)?XD{_`8VN9Ih$f*PBx^Thv@c0_u`OeSGLJTcEN9b-*47<`mdQIDJsfO!GAaA?GQ(D{;4+ZzU$d6^L!F?-ZXS1 zP9Ps{ndWGy5%WcF))5n*ps}d^$o9`0h`6jFVK-FkjQ?TSgIKNY0PS`7Q^F2L95v7L z{z`$3{OUURRf)ySb*tE~kH3Dz%LT>`*yK0e-Q9ndM#snh*@C%KbBQ?Etn7M9fJB%5 zlFjtPPk%1-MK|$D8x=bhu+1*TAK&hM{@O$}h0qHpO_?&KzfG^E4jN-W0NvL{W3k`$W$T zo?vZzr=?I{K6dQbQJQ0w*q9T?e6ARlB>&sNv)6UqiiS!4CEpl~za6aojd)6~sNf}5 zxxpFmzb)dwTgFJRDUYIUv$Qk!Uw2v4lT-yJ7!%>Cn1@e&-G({!lv59&Z8r2}PCe~; z;om43Pyt{3mEh7ppT1`m9daxipT572;=ps~oO4dITDxNa^l-eZQ~Yn&-BlZ5!$$K9D+c{>=1CDeE0`Wv|Sa)|R#h@By-U{bTND@#WVP4f>;U zBb;MDAx105%h12JQrif0S|a@Q{Int#WWd{u4r+`e8n8wJCeyZ_XHU=Go+{C(M*DLS|2p>9vcmTlLRTKiFUg<8 z-;KS=V^iyX)Cb;EqfZ!nvFVpN5Sb+|ln<))sp@|snh{h=d{f0_{H2Fn?QY>M`LZe{ z|F&gG)xlYc|Gh6Tk_%TUiwNX{G6%>Xku{osel-7#1R3%1zZ3jIP?Tp+nxBkqy3M?c z%;i$6?swhM!qxs^OY(ScKK#EDk7pErG+dTB9(EXdR=I<j=#nV(EE3x1;17FKSa6g*kg`e%6>}O zR`FphnByjA*#pe4(SHdr%Ug=ASJv=$Tk`C^w#b?)wlT-U#B?lp7ccp!X?PT)=l<_2k#pU_9JMq((*{}U8bRR^P|3wxVqsN49grSO z@=E0RI&0M;{DHlVjC+(rkxR(_*#cLwk2UZryG`oV{LA=_-+TU*Mz%z1ZE3wU8Vi-{ zlndptHaN0f)ZIn?Rp%Ep1wQsOek5^kQTw1uZT?vY*EG$UGsiLfWnN+Dp{6gN&$)%B z!qMj4E=2`-T({Vc4DZA=B>&FW$-fPPMMRoHuXuyO#Rpc=6!~B_GiJ;fy@m;3gI|~n zJ!m{{=%~`#f27eXgW5kYxdcA{p8{**QVa>c&71WGY)@0t6##Svg|t#%jfehbNNKpo~szY#rQFE%zv9YIgtMzKDLhW ze3tSaiS4fIj^fFaOCE|;nqY#gY5X)YrwCf4BwoP3f_#_ZcMl*h`?r+8+wXO9B6L}W zPWQ4lT+Eqn87&fPqQx#!&~EwT>)5Obn=aHc`{@{Ba+CcjC8t@#xXiDf!6fCYYstEe zNj&QiEhiVv^+SB45I`H8=rZDT*V_18{ya#y#ds= ztK3M=S99Km$X;fy$vE`Im$IC*jmG8@E2{BQe&Pj%7ZE4fl-`-mIxFr$^{&H4h`7!%P=6VcgD!}VIJd+EnSn~<6 zS9i&ow)|3}rd|!DnzB$R3@a8p<(#J1{GbLYYG`U|=8i7-Q5=`_dbc6HPD?(6&ztj3 zJ!k6Njr7K+QG4iyvzYUz#4xeqx)Q&}0LcpZA!h(y@_ftn<^dI>%324^TZ#F?nRXQ- zNi*>lQ%SVOFUvPJRy4X*Wz5vhK9O|SYru*+;V)22;3P|ga^ zJ@?$dl=zuAH?#p$bUAIL$0+y_`AHmCKE~cN>C8&5MEgTV zKgu2Gdzv7!*E>-5dwfH1dZ}3Y+fuQ-xVxkKn^UJw9XfO7%;+0~m_?#e%oknG&3-9o zyyQ+F!0*X&Q~8JlP#<4JM`UUv{=_jAdUwk=2WlT{#VO{>iej@q7wzcm7=Zs?M^5%K z?UG0A=39Yn#bV2S!-o(5LId{wxIdo_?Dlx>v7QhQW;DQ2T|rR#l2p>;rS3%ZeVLB! ztV!sUV@;i4*}jLIYJtEiVkl0$J%N7tE+$$F)w$S}@tKDLjQ&K<0R9o!XQ@qB{1*8i zsDf8LXUdeb`ge781ulCc^nJ{oikHe-dSyM(D!Zm zzDU#8e+5@;Cblqe+%5j1rBr^kxBEYAe~*DRyriW4BRA)sXVaIu<8%z@>>PFi_l4?g zt-YWFKo8ogXa0l>jlO|27 ziHT*|nAW{;=ew!! zy7-LN2oRNGQe?SJ%i*tf@~-VeZ|gg?6cPi6ZxEZkbsA6W!1r3?h#CJy8!{n(b&Y7K zgGGb8Iq%KmjvIA(=oi#Ip!DG%w+|l7-Fh!S^!~A;A@jG__-Q#0@|Q}bFCeS-JH^BA zLDvgJ=6Zf&$+%Bl@VT|D)pDkyV=mu>5VNE6;#wt){Md65xN7~1WZ1zyZ({wjw8%nV zz6DeAw6+ZCX>VA0rs{El*{$?;uiTu-R z<%{hvx=8$xTvKwr%@St~ysA6i``CB`{NJN}3y;K(8XJUR@sURPA%6Ud__E}0xm;f5 z&#@2Zs`;~#+my}ZrQlWXz+^0P-frYwiA)a<>pEuP(9WTEA?xeN^&C&Ucg4Ry?5K1< z)>-Mg2U{-({&&z?4$LwhfmeDk2HRUpZI{-~4-tfMITTnqW4yaS{!G(8_b*0%DP7l* z^=WgY?*`7luY=a3v~4K(#SO)<_$0DEEB+AMB=R}F>)C~T%Ey5hU)s*|4sA>L=JxyG z{EY9BzdCW^#NOvF=Lz7%`xlX^*Zre(nqSrZve=-BeZe^KQR30mJ!?TLf-rFo%?8$yO{ea$@7D$wewX3PpTs^6`Iex zsr|b^%3Qz>GVhFy)cHw#cA6w5DEb$-#}(Bsc{_5Qk9;yuPeY_0@l)^K*MHNSyD}`MGVkxK1UmN5Wly>GcP7Rk{|&F*eHm;%_!1 zI4O|3gfE|FHT7QAj2H+1V)6gPIOzQOGr7MwdF07Q4k@=u+_mj5XHTIBXY`fW=pujb z0@v@6`GO7?L7#Cjd5Pks?vLQZ{ZZe4MjnHKFaMMHyDdNQ^S6j-TVEu>+M8Gxuf31d8_O-1$JxN zPio|MyhrMN%ULVjr*Dz-3BHl!-$1Fe)K98=#{qc!;1&BV%H?=$}>sf=`eFXe6_{(c7k z`ZnW2e&#)gZP(#9lJmTae(QK&L43KBGmCTNJ2rDh9LpJ3U~bp@F3X=F|73yvw!r@Z Dtny7a diff --git a/docsite/static/img/komodo-512x512.png b/docsite/static/img/komodo-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..9f31ddfdacc420b5afb4ebec76f8ef97759e5411 GIT binary patch literal 158194 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 z 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%0D(zlH<3 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 literal 0 HcmV?d00001 diff --git a/docsite/static/img/logo512.png b/docsite/static/img/logo512.png deleted file mode 100644 index 4b070d7b7391fb87c4913db4405eb5471a544379..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33400 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 z zm>cK_B1CgLmG?2uqzW{7HI?Xwewkka?{d=+>ifc2T6A?mtdS7D;JQ`#iq&@3j*F7m zEQTB0`DLNRJcf<>EJ;*2U-E-pMMbTl)lw&TLhCnj--7tIz;B>4dgBW(kod@jO_~>F zLiYkvj^yg3+#(5j;FNMf2!QC!th3A- zEB(Huk-g0~F^{klqhN=!Iw>ExWwG@^u95h9SJw6W4rT-9<0Z{qr|dI zs>1*@7*vujCn(H(l_peJqh~3G6A2B+SfXA_$cZKdCQ1fQ%6V2Kd;QCz|58dK3P`{k zY=XQ6l3y23nt5rfBN}l>L!WUG5V9zOXJ0G!Gd$yCzcoNCA4h_IKxUK0RB!G*`*5Ev zgp?-tcfh(t45$G|IZu(`42SZ)HESk4;k|1&r zuyo;QrN9X#+K>$#&=-sivU>uFNB;2UVY2+M@yn4ehhMfzBK@(>Co&N;4gt`Bg3s3= zR#ikyZUX>NsH}vTx^FPmUCDJQ1%w%iEyqz^$SD-hGVzSjU8Z0`eZfbY$3%c#*& zoLRyI+}OeNj-Y_ispmGEnQx7#0RWavG`QeCvti2#K@+MU-_h@x?$dKh$1JoTMqc|M zm|m`GX!$IqF}%jXnvVf5Zz1^s2@DjS+ziZ&3SBZ>xB)L`F!s&`pW|ltpVSuZi-(y% zm>0xD5GbyD*vuJv5J0#q%pCj`gB6BQ3u{`k&!*snMQIA1Wy}X)QIJ4LIJ(c|s<_ca z!#-l`vO-C>&Ih>%{YsK-iqPe8ekI;ecJ7nmL3vB4T@ z4O}Ike~Qav5yXl1I^R8t$i;fRu=Bde>kFAFA#;>%M;_`Yd@Y zwlDieb}hNmOW$E-agTNR1=>Im+EM}MEDVnZS;u3Z2RUr8-^$i@@w_xJQT3u^Gxy+C zRXfj*sLF=ILd#mip=@_oDBu1r7pF1cT7p-D7Y18)SmjP*GrfFm5StY1$5p1``VI3S zhgd)?%L=^5FiodTTc!Z#MjwgsO}SVh_NBVM&9{NqE(SM)S@shc;N|V1&zMKW=!H%z S9=8DaCo8EWQ6X;p`Tqf0M}Fu4 diff --git a/docsite/yarn.lock b/docsite/yarn.lock new file mode 100644 index 000000000..3e742b2a0 --- /dev/null +++ b/docsite/yarn.lock @@ -0,0 +1,9001 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@algolia/autocomplete-core@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz#83374c47dc72482aa45d6b953e89377047f0dcdc" + integrity sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.17.9" + "@algolia/autocomplete-shared" "1.17.9" + +"@algolia/autocomplete-plugin-algolia-insights@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz#74c86024d09d09e8bfa3dd90b844b77d9f9947b6" + integrity sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ== + dependencies: + "@algolia/autocomplete-shared" "1.17.9" + +"@algolia/autocomplete-preset-algolia@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz#911f3250544eb8ea4096fcfb268f156b085321b5" + integrity sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ== + dependencies: + "@algolia/autocomplete-shared" "1.17.9" + +"@algolia/autocomplete-shared@1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz#5f38868f7cb1d54b014b17a10fc4f7e79d427fa8" + integrity sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ== + +"@algolia/client-abtesting@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.21.0.tgz#565c79275769c614ecf75bd8679dbd510a0c88c1" + integrity sha512-I239aSmXa3pXDhp3AWGaIfesqJBNFA7drUM8SIfNxMIzvQXUnHRf4rW1o77QXLI/nIClNsb8KOLaB62gO9LnlQ== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/client-analytics@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.21.0.tgz#4c4863b3cb7380de5bd1ba82691516e0a60ad167" + integrity sha512-OxoUfeG9G4VE4gS7B4q65KkHzdGsQsDwxQfR5J9uKB8poSGuNlHJWsF3ABqCkc5VliAR0m8KMjsQ9o/kOpEGnQ== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/client-common@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.21.0.tgz#f32c28d25ccaf2954aca5ae5954a810fdef5b85e" + integrity sha512-iHLgDQFyZNe9M16vipbx6FGOA8NoMswHrfom/QlCGoyh7ntjGvfMb+J2Ss8rRsAlOWluv8h923Ku3QVaB0oWDQ== + +"@algolia/client-insights@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.21.0.tgz#971c76f795923c1210f89c830d43bc14fa76de61" + integrity sha512-y7XBO9Iwb75FLDl95AYcWSLIViJTpR5SUUCyKsYhpP9DgyUqWbISqDLXc96TS9shj+H+7VsTKA9cJK8NUfVN6g== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/client-personalization@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.21.0.tgz#0ab7c370a115d0b83edd8db55a4ea2f2b9212190" + integrity sha512-6KU658lD9Tss4oCX6c/O15tNZxw7vR+WAUG95YtZzYG/KGJHTpy2uckqbMmC2cEK4a86FAq4pH5azSJ7cGMjuw== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/client-query-suggestions@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.21.0.tgz#14291a63db8ccd53e415d46578390fa5e1d1d35f" + integrity sha512-pG6MyVh1v0X+uwrKHn3U+suHdgJ2C+gug+UGkNHfMELHMsEoWIAQhxMBOFg7hCnWBFjQnuq6qhM3X9X5QO3d9Q== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/client-search@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.21.0.tgz#37807d286a18e59b32af06dc62d4bd853d50121c" + integrity sha512-nZfgJH4njBK98tFCmCW1VX/ExH4bNOl9DSboxeXGgvhoL0fG1+4DDr/mrLe21OggVCQqHwXBMh6fFInvBeyhiQ== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/events@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" + integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== + +"@algolia/ingestion@1.21.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.21.0.tgz#7524dcc848abc44656508ea0951cceaf18e3f51b" + integrity sha512-k6MZxLbZphGN5uRri9J/krQQBjUrqNcScPh985XXEFXbSCRvOPKVtjjLdVjGVHXXPOQgKrIZHxIdRNbHS+wVuA== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/monitoring@1.21.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.21.0.tgz#9daab7fe728b44ae998c2425d12e4bd77efe07f5" + integrity sha512-FiW5nnmyHvaGdorqLClw3PM6keXexAMiwbwJ9xzQr4LcNefLG3ln82NafRPgJO/z0dETAOKjds5aSmEFMiITHQ== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/recommend@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.21.0.tgz#4c9a2e90bab87c9d63f8eebaf56c12e4f9e517c0" + integrity sha512-+JXavbbliaLmah5QNgc/TDW/+r0ALa+rGhg5Y7+pF6GpNnzO0L+nlUaDNE8QbiJfz54F9BkwFUnJJeRJAuzTFw== + dependencies: + "@algolia/client-common" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +"@algolia/requester-browser-xhr@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.21.0.tgz#7840e52a45fd8a7b58340470c4700492d32fdf7d" + integrity sha512-Iw+Yj5hOmo/iixHS94vEAQ3zi5GPpJywhfxn1el/zWo4AvPIte/+1h9Ywgw/+3M7YBj4jgAkScxjxQCxzLBsjA== + dependencies: + "@algolia/client-common" "5.21.0" + +"@algolia/requester-fetch@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.21.0.tgz#8c4caf767995aaf24c8fc5f873e9075df98fbf44" + integrity sha512-Z00SRLlIFj3SjYVfsd9Yd3kB3dUwQFAkQG18NunWP7cix2ezXpJqA+xAoEf9vc4QZHdxU3Gm8gHAtRiM2iVaTQ== + dependencies: + "@algolia/client-common" "5.21.0" + +"@algolia/requester-node-http@5.21.0": + version "5.21.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.21.0.tgz#c1a8cd0f33e375c147bc5efda73f9677a47416c9" + integrity sha512-WqU0VumUILrIeVYCTGZlyyZoC/tbvhiyPxfGRRO1cSjxN558bnJLlR2BvS0SJ5b75dRNK7HDvtXo2QoP9eLfiA== + dependencies: + "@algolia/client-common" "5.21.0" + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.8.3": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5", "@babel/compat-data@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" + integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== + +"@babel/core@^7.21.3", "@babel/core@^7.25.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" + integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/traverse" "^7.26.10" + "@babel/types" "^7.26.10" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.25.9", "@babel/generator@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7" + integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang== + dependencies: + "@babel/parser" "^7.26.10" + "@babel/types" "^7.26.10" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== + dependencies: + "@babel/types" "^7.25.9" + +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.25.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" + integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/helper-replace-supers" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/traverse" "^7.26.9" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0" + integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + regexpu-core "^6.2.0" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.3", "@babel/helper-define-polyfill-provider@^0.6.4": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz#15e8746368bfa671785f5926ff74b3064c291fab" + integrity sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-member-expression-to-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" + integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-optimise-call-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" + integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== + dependencies: + "@babel/types" "^7.25.9" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + +"@babel/helper-remap-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" + integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-wrap-function" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-replace-supers@^7.25.9", "@babel/helper-replace-supers@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" + integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/traverse" "^7.26.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" + integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helper-wrap-function@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" + integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g== + dependencies: + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helpers@^7.26.10": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384" + integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g== + dependencies: + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.10" + +"@babel/parser@^7.26.10", "@babel/parser@^7.26.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749" + integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA== + dependencies: + "@babel/types" "^7.26.10" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" + integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30" + integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137" + integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1" + integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e" + integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-import-assertions@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f" + integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-import-attributes@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" + integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-async-generator-functions@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz#5e3991135e3b9c6eaaf5eff56d1ae5a11df45ff8" + integrity sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-remap-async-to-generator" "^7.25.9" + "@babel/traverse" "^7.26.8" + +"@babel/plugin-transform-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71" + integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-remap-async-to-generator" "^7.25.9" + +"@babel/plugin-transform-block-scoped-functions@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz#3dc4405d31ad1cbe45293aa57205a6e3b009d53e" + integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-block-scoping@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1" + integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-class-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f" + integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-class-static-block@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0" + integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-classes@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52" + integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/traverse" "^7.25.9" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b" + integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/template" "^7.25.9" + +"@babel/plugin-transform-destructuring@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1" + integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-dotall-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a" + integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-duplicate-keys@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d" + integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31" + integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-dynamic-import@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8" + integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-exponentiation-operator@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc" + integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-export-namespace-from@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2" + integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-for-of@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz#27231f79d5170ef33b5111f07fe5cafeb2c96a56" + integrity sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + +"@babel/plugin-transform-function-name@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97" + integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-transform-json-strings@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660" + integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de" + integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-logical-assignment-operators@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7" + integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-member-expression-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de" + integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-modules-amd@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5" + integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-modules-commonjs@^7.25.9", "@babel/plugin-transform-modules-commonjs@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" + integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== + dependencies: + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-modules-systemjs@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8" + integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-transform-modules-umd@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9" + integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a" + integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-new-target@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd" + integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.26.6": + version "7.26.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz#fbf6b3c92cb509e7b319ee46e3da89c5bedd31fe" + integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-numeric-separator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1" + integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-object-rest-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18" + integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + +"@babel/plugin-transform-object-super@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03" + integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + +"@babel/plugin-transform-optional-catch-binding@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3" + integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" + integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + +"@babel/plugin-transform-parameters@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257" + integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-private-methods@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" + integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-private-property-in-object@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33" + integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-property-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f" + integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-constant-elements@^7.21.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz#08a1de35a301929b60fdf2788a54b46cd8ecd0ef" + integrity sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-display-name@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz#4b79746b59efa1f38c8695065a92a9f5afb24f7d" + integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-jsx-development@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz#8fd220a77dd139c07e25225a903b8be8c829e0d7" + integrity sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.25.9" + +"@babel/plugin-transform-react-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166" + integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/plugin-transform-react-pure-annotations@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz#ea1c11b2f9dbb8e2d97025f43a3b5bc47e18ae62" + integrity sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-regenerator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" + integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-regexp-modifiers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850" + integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-reserved-words@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce" + integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-runtime@^7.25.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz#6b4504233de8238e7d666c15cde681dc62adff87" + integrity sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.26.5" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.11.0" + babel-plugin-polyfill-regenerator "^0.6.1" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" + integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9" + integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + +"@babel/plugin-transform-sticky-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32" + integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-template-literals@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz#966b15d153a991172a540a69ad5e1845ced990b5" + integrity sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-typeof-symbol@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz#d0e33acd9223744c1e857dbd6fa17bd0a3786937" + integrity sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + +"@babel/plugin-transform-typescript@^7.25.9": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz#2e9caa870aa102f50d7125240d9dbf91334b0950" + integrity sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-syntax-typescript" "^7.25.9" + +"@babel/plugin-transform-unicode-escapes@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82" + integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-unicode-property-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3" + integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-unicode-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1" + integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-unicode-sets-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe" + integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/preset-env@^7.20.2", "@babel/preset-env@^7.25.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.9.tgz#2ec64e903d0efe743699f77a10bdf7955c2123c3" + integrity sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ== + dependencies: + "@babel/compat-data" "^7.26.8" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions" "^7.26.0" + "@babel/plugin-syntax-import-attributes" "^7.26.0" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.25.9" + "@babel/plugin-transform-async-generator-functions" "^7.26.8" + "@babel/plugin-transform-async-to-generator" "^7.25.9" + "@babel/plugin-transform-block-scoped-functions" "^7.26.5" + "@babel/plugin-transform-block-scoping" "^7.25.9" + "@babel/plugin-transform-class-properties" "^7.25.9" + "@babel/plugin-transform-class-static-block" "^7.26.0" + "@babel/plugin-transform-classes" "^7.25.9" + "@babel/plugin-transform-computed-properties" "^7.25.9" + "@babel/plugin-transform-destructuring" "^7.25.9" + "@babel/plugin-transform-dotall-regex" "^7.25.9" + "@babel/plugin-transform-duplicate-keys" "^7.25.9" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-dynamic-import" "^7.25.9" + "@babel/plugin-transform-exponentiation-operator" "^7.26.3" + "@babel/plugin-transform-export-namespace-from" "^7.25.9" + "@babel/plugin-transform-for-of" "^7.26.9" + "@babel/plugin-transform-function-name" "^7.25.9" + "@babel/plugin-transform-json-strings" "^7.25.9" + "@babel/plugin-transform-literals" "^7.25.9" + "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" + "@babel/plugin-transform-member-expression-literals" "^7.25.9" + "@babel/plugin-transform-modules-amd" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.26.3" + "@babel/plugin-transform-modules-systemjs" "^7.25.9" + "@babel/plugin-transform-modules-umd" "^7.25.9" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-new-target" "^7.25.9" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.26.6" + "@babel/plugin-transform-numeric-separator" "^7.25.9" + "@babel/plugin-transform-object-rest-spread" "^7.25.9" + "@babel/plugin-transform-object-super" "^7.25.9" + "@babel/plugin-transform-optional-catch-binding" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + "@babel/plugin-transform-private-methods" "^7.25.9" + "@babel/plugin-transform-private-property-in-object" "^7.25.9" + "@babel/plugin-transform-property-literals" "^7.25.9" + "@babel/plugin-transform-regenerator" "^7.25.9" + "@babel/plugin-transform-regexp-modifiers" "^7.26.0" + "@babel/plugin-transform-reserved-words" "^7.25.9" + "@babel/plugin-transform-shorthand-properties" "^7.25.9" + "@babel/plugin-transform-spread" "^7.25.9" + "@babel/plugin-transform-sticky-regex" "^7.25.9" + "@babel/plugin-transform-template-literals" "^7.26.8" + "@babel/plugin-transform-typeof-symbol" "^7.26.7" + "@babel/plugin-transform-unicode-escapes" "^7.25.9" + "@babel/plugin-transform-unicode-property-regex" "^7.25.9" + "@babel/plugin-transform-unicode-regex" "^7.25.9" + "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.11.0" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.40.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.18.6", "@babel/preset-react@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa" + integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-transform-react-display-name" "^7.25.9" + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/plugin-transform-react-jsx-development" "^7.25.9" + "@babel/plugin-transform-react-pure-annotations" "^7.25.9" + +"@babel/preset-typescript@^7.21.0", "@babel/preset-typescript@^7.25.9": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" + integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.25.9" + "@babel/plugin-transform-typescript" "^7.25.9" + +"@babel/runtime-corejs3@^7.25.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.26.10.tgz#5a3185ca2813f8de8ae68622572086edf5cf51f2" + integrity sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg== + dependencies: + core-js-pure "^3.30.2" + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.25.9", "@babel/runtime@^7.8.4": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.10.tgz#a07b4d8fa27af131a633d7b3524db803eb4764c2" + integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.25.9", "@babel/template@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" + integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/parser" "^7.26.9" + "@babel/types" "^7.26.9" + +"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380" + integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.10" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.21.3", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9", "@babel/types@^7.4.4": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259" + integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@csstools/cascade-layer-name-parser@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz#64d128529397aa1e1c986f685713363b262b81b1" + integrity sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA== + +"@csstools/color-helpers@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.0.2.tgz#82592c9a7c2b83c293d9161894e2a6471feb97b8" + integrity sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA== + +"@csstools/css-calc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.2.tgz#bffd55f002dab119b76d4023f95cd943e6c8c11e" + integrity sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw== + +"@csstools/css-color-parser@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.0.8.tgz#5fe9322920851450bf5e065c2b0e731b9e165394" + integrity sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ== + dependencies: + "@csstools/color-helpers" "^5.0.2" + "@csstools/css-calc" "^2.1.2" + +"@csstools/css-parser-algorithms@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz#74426e93bd1c4dcab3e441f5cc7ba4fb35d94356" + integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A== + +"@csstools/css-tokenizer@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz#a5502c8539265fecbd873c1e395a890339f119c2" + integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw== + +"@csstools/media-query-list-parser@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz#e80e17eba1693fceafb8d6f2cfc68c0e7a9ab78a" + integrity sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A== + +"@csstools/postcss-cascade-layers@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz#9640313e64b5e39133de7e38a5aa7f40dc259597" + integrity sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + +"@csstools/postcss-color-function@^4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-4.0.8.tgz#4c16ea78abfdfd62c947616c6e68836e50f2441c" + integrity sha512-9dUvP2qpZI6PlGQ/sob+95B3u5u7nkYt9yhZFCC7G9HBRHBxj+QxS/wUlwaMGYW0waf+NIierI8aoDTssEdRYw== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-color-mix-function@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.8.tgz#45a006dfcc65f2a61ae60f2df7ebc108fdb9eaf1" + integrity sha512-yuZpgWUzqZWQhEqfvtJufhl28DgO9sBwSbXbf/59gejNuvZcoUTRGQZhzhwF4ccqb53YAGB+u92z9+eSKoB4YA== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-content-alt-text@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz#76f4687fb15ed45bc1139bb71e5775779762897a" + integrity sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-exponential-functions@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.7.tgz#c369f241c6645a5e8a184bfd02cdcc65bd22fcbd" + integrity sha512-XTb6Mw0v2qXtQYRW9d9duAjDnoTbBpsngD7sRNLmYDjvwU2ebpIHplyxgOeo6jp/Kr52gkLi5VaK5RDCqzMzZQ== + dependencies: + "@csstools/css-calc" "^2.1.2" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + +"@csstools/postcss-font-format-keywords@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz#6730836eb0153ff4f3840416cc2322f129c086e6" + integrity sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-gamut-mapping@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.8.tgz#e9441e7b5a7b0d3cc1a92486378824abb76ef849" + integrity sha512-/K8u9ZyGMGPjmwCSIjgaOLKfic2RIGdFHHes84XW5LnmrvdhOTVxo255NppHi3ROEvoHPW7MplMJgjZK5Q+TxA== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + +"@csstools/postcss-gradients-interpolation-method@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.8.tgz#f7f0324fd564c092ac13ce35b5a09ffda0165a90" + integrity sha512-CoHQ/0UXrvxLovu0ZeW6c3/20hjJ/QRg6lyXm3dZLY/JgvRU6bdbQZF/Du30A4TvowfcgvIHQmP1bNXUxgDrAw== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-hwb-function@^4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.8.tgz#13a85203601b3db97a6672e16f6699fe464827b0" + integrity sha512-LpFKjX6hblpeqyych1cKmk+3FJZ19QmaJtqincySoMkbkG/w2tfbnO5oE6mlnCTXcGUJ0rCEuRHvTqKK0nHYUQ== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-ic-unit@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz#b60ec06500717c337447c39ae7fe7952eeb9d48f" + integrity sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-initial@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz#c385bd9d8ad31ad159edd7992069e97ceea4d09a" + integrity sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg== + +"@csstools/postcss-is-pseudo-class@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz#12041448fedf01090dd4626022c28b7f7623f58e" + integrity sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + +"@csstools/postcss-light-dark-function@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz#807c170cd28eebb0c00e64dfc6ab0bf418f19209" + integrity sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-logical-float-and-clear@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz#62617564182cf86ab5d4e7485433ad91e4c58571" + integrity sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ== + +"@csstools/postcss-logical-overflow@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz#c6de7c5f04e3d4233731a847f6c62819bcbcfa1d" + integrity sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA== + +"@csstools/postcss-logical-overscroll-behavior@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz#43c03eaecdf34055ef53bfab691db6dc97a53d37" + integrity sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w== + +"@csstools/postcss-logical-resize@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz#4df0eeb1a61d7bd85395e56a5cce350b5dbfdca6" + integrity sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-logical-viewport-units@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz#f6cc63520ca2a6eb76b9cd946070c38dda66d733" + integrity sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw== + dependencies: + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-media-minmax@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.7.tgz#42816871decf0a092af3f6c8500e04d9918cc342" + integrity sha512-LB6tIP7iBZb5CYv8iRenfBZmbaG3DWNEziOnPjGoQX5P94FBPvvTBy68b/d9NnS5PELKwFmmOYsAEIgEhDPCHA== + dependencies: + "@csstools/css-calc" "^2.1.2" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/media-query-list-parser" "^4.0.2" + +"@csstools/postcss-media-queries-aspect-ratio-number-values@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz#d71102172c74baf3f892fac88cf1ea46a961600d" + integrity sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ== + dependencies: + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/media-query-list-parser" "^4.0.2" + +"@csstools/postcss-nested-calc@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz#754e10edc6958d664c11cde917f44ba144141c62" + integrity sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-normalize-display-values@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz#ecdde2daf4e192e5da0c6fd933b6d8aff32f2a36" + integrity sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-oklab-function@^4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.8.tgz#9d723e0db69703f3df549ebedfd605f849217fff" + integrity sha512-+5aPsNWgxohXoYNS1f+Ys0x3Qnfehgygv3qrPyv+Y25G0yX54/WlVB+IXprqBLOXHM1gsVF+QQSjlArhygna0Q== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-progressive-custom-properties@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz#ecdb85bcdb1852d73970a214a376684a91f82bdc" + integrity sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-random-function@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-random-function/-/postcss-random-function-1.0.3.tgz#f737f5bab3826fc71fd663b21e70ee392b144f20" + integrity sha512-dbNeEEPHxAwfQJ3duRL5IPpuD77QAHtRl4bAHRs0vOVhVbHrsL7mHnwe0irYjbs9kYwhAHZBQTLBgmvufPuRkA== + dependencies: + "@csstools/css-calc" "^2.1.2" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + +"@csstools/postcss-relative-color-syntax@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.8.tgz#833cdea06e5cbec2702f939d1aadfd280e4f4c07" + integrity sha512-eGE31oLnJDoUysDdjS9MLxNZdtqqSxjDXMdISpLh80QMaYrKs7VINpid34tWQ+iU23Wg5x76qAzf1Q/SLLbZVg== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +"@csstools/postcss-scope-pseudo-class@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz#9fe60e9d6d91d58fb5fc6c768a40f6e47e89a235" + integrity sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q== + dependencies: + postcss-selector-parser "^7.0.0" + +"@csstools/postcss-sign-functions@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.2.tgz#9664762870de4f8d189829a86798e532bbaad053" + integrity sha512-4EcAvXTUPh7n6UoZZkCzgtCf/wPzMlTNuddcKg7HG8ozfQkUcHsJ2faQKeLmjyKdYPyOUn4YA7yDPf8K/jfIxw== + dependencies: + "@csstools/css-calc" "^2.1.2" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + +"@csstools/postcss-stepped-value-functions@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.7.tgz#c681fbcdb8a2fcfeaea2bb0ea9d497832bab9ef7" + integrity sha512-rdrRCKRnWtj5FyRin0u/gLla7CIvZRw/zMGI1fVJP0Sg/m1WGicjPVHRANL++3HQtsiXKAbPrcPr+VkyGck0IA== + dependencies: + "@csstools/css-calc" "^2.1.2" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + +"@csstools/postcss-text-decoration-shorthand@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.2.tgz#a3bcf80492e6dda36477538ab8e8943908c9f80a" + integrity sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA== + dependencies: + "@csstools/color-helpers" "^5.0.2" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-trigonometric-functions@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.7.tgz#8941a4c99dc1fec31daf052ac0fb6e7bf7c92403" + integrity sha512-qTrZgLju3AV7Djhzuh2Bq/wjFqbcypnk0FhHjxW8DWJQcZLS1HecIus4X2/RLch1ukX7b+YYCdqbEnpIQO5ccg== + dependencies: + "@csstools/css-calc" "^2.1.2" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + +"@csstools/postcss-unset-value@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz#7caa981a34196d06a737754864baf77d64de4bba" + integrity sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA== + +"@csstools/selector-resolve-nested@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz#704a9b637975680e025e069a4c58b3beb3e2752a" + integrity sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ== + +"@csstools/selector-specificity@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz#037817b574262134cabd68fc4ec1a454f168407b" + integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw== + +"@csstools/utilities@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@csstools/utilities/-/utilities-2.0.0.tgz#f7ff0fee38c9ffb5646d47b6906e0bc8868bde60" + integrity sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ== + +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@docsearch/css@3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.9.0.tgz#3bc29c96bf024350d73b0cfb7c2a7b71bf251cd5" + integrity sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA== + +"@docsearch/react@^3.8.1": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.9.0.tgz#d0842b700c3ee26696786f3c8ae9f10c1a3f0db3" + integrity sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ== + dependencies: + "@algolia/autocomplete-core" "1.17.9" + "@algolia/autocomplete-preset-algolia" "1.17.9" + "@docsearch/css" "3.9.0" + algoliasearch "^5.14.2" + +"@docusaurus/babel@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.7.0.tgz#770dd5da525a9d6a2fee7d3212ec62040327f776" + integrity sha512-0H5uoJLm14S/oKV3Keihxvh8RV+vrid+6Gv+2qhuzbqHanawga8tYnsdpjEyt36ucJjqlby2/Md2ObWjA02UXQ== + dependencies: + "@babel/core" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-runtime" "^7.25.9" + "@babel/preset-env" "^7.25.9" + "@babel/preset-react" "^7.25.9" + "@babel/preset-typescript" "^7.25.9" + "@babel/runtime" "^7.25.9" + "@babel/runtime-corejs3" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@docusaurus/logger" "3.7.0" + "@docusaurus/utils" "3.7.0" + babel-plugin-dynamic-import-node "^2.3.3" + fs-extra "^11.1.1" + tslib "^2.6.0" + +"@docusaurus/bundler@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.7.0.tgz#d8e7867b3b2c43a1e320ed429f8dfe873c38506d" + integrity sha512-CUUT9VlSGukrCU5ctZucykvgCISivct+cby28wJwCC/fkQFgAHRp/GKv2tx38ZmXb7nacrKzFTcp++f9txUYGg== + dependencies: + "@babel/core" "^7.25.9" + "@docusaurus/babel" "3.7.0" + "@docusaurus/cssnano-preset" "3.7.0" + "@docusaurus/logger" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + babel-loader "^9.2.1" + clean-css "^5.3.2" + copy-webpack-plugin "^11.0.0" + css-loader "^6.8.1" + css-minimizer-webpack-plugin "^5.0.1" + cssnano "^6.1.2" + file-loader "^6.2.0" + html-minifier-terser "^7.2.0" + mini-css-extract-plugin "^2.9.1" + null-loader "^4.0.1" + postcss "^8.4.26" + postcss-loader "^7.3.3" + postcss-preset-env "^10.1.0" + react-dev-utils "^12.0.1" + terser-webpack-plugin "^5.3.9" + tslib "^2.6.0" + url-loader "^4.1.1" + webpack "^5.95.0" + webpackbar "^6.0.1" + +"@docusaurus/core@3.7.0", "@docusaurus/core@^3.5.2": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.7.0.tgz#e871586d099093723dfe6de81c1ce610aeb20292" + integrity sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ== + dependencies: + "@docusaurus/babel" "3.7.0" + "@docusaurus/bundler" "3.7.0" + "@docusaurus/logger" "3.7.0" + "@docusaurus/mdx-loader" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + boxen "^6.2.1" + chalk "^4.1.2" + chokidar "^3.5.3" + cli-table3 "^0.6.3" + combine-promises "^1.1.0" + commander "^5.1.0" + core-js "^3.31.1" + del "^6.1.1" + detect-port "^1.5.1" + escape-html "^1.0.3" + eta "^2.2.0" + eval "^0.1.8" + fs-extra "^11.1.1" + html-tags "^3.3.1" + html-webpack-plugin "^5.6.0" + leven "^3.1.0" + lodash "^4.17.21" + p-map "^4.0.0" + prompts "^2.4.2" + react-dev-utils "^12.0.1" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" + react-loadable "npm:@docusaurus/react-loadable@6.0.0" + react-loadable-ssr-addon-v5-slorber "^1.0.1" + react-router "^5.3.4" + react-router-config "^5.1.1" + react-router-dom "^5.3.4" + semver "^7.5.4" + serve-handler "^6.1.6" + shelljs "^0.8.5" + tslib "^2.6.0" + update-notifier "^6.0.2" + webpack "^5.95.0" + webpack-bundle-analyzer "^4.10.2" + webpack-dev-server "^4.15.2" + webpack-merge "^6.0.1" + +"@docusaurus/cssnano-preset@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.7.0.tgz#8fe8f2c3acbd32384b69e14983b9a63c98cae34e" + integrity sha512-X9GYgruZBSOozg4w4dzv9uOz8oK/EpPVQXkp0MM6Tsgp/nRIU9hJzJ0Pxg1aRa3xCeEQTOimZHcocQFlLwYajQ== + dependencies: + cssnano-preset-advanced "^6.1.2" + postcss "^8.4.38" + postcss-sort-media-queries "^5.2.0" + tslib "^2.6.0" + +"@docusaurus/logger@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.7.0.tgz#07ecc2f460c4d2382df4991f9ce4e348e90af04c" + integrity sha512-z7g62X7bYxCYmeNNuO9jmzxLQG95q9QxINCwpboVcNff3SJiHJbGrarxxOVMVmAh1MsrSfxWkVGv4P41ktnFsA== + dependencies: + chalk "^4.1.2" + tslib "^2.6.0" + +"@docusaurus/mdx-loader@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.7.0.tgz#5890c6e7a5b68cb1d066264ac5290cdcd59d4ecc" + integrity sha512-OFBG6oMjZzc78/U3WNPSHs2W9ZJ723ewAcvVJaqS0VgyeUfmzUV8f1sv+iUHA0DtwiR5T5FjOxj6nzEE8LY6VA== + dependencies: + "@docusaurus/logger" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + "@mdx-js/mdx" "^3.0.0" + "@slorber/remark-comment" "^1.0.0" + escape-html "^1.0.3" + estree-util-value-to-estree "^3.0.1" + file-loader "^6.2.0" + fs-extra "^11.1.1" + image-size "^1.0.2" + mdast-util-mdx "^3.0.0" + mdast-util-to-string "^4.0.0" + rehype-raw "^7.0.0" + remark-directive "^3.0.0" + remark-emoji "^4.0.0" + remark-frontmatter "^5.0.0" + remark-gfm "^4.0.0" + stringify-object "^3.3.0" + tslib "^2.6.0" + unified "^11.0.3" + unist-util-visit "^5.0.0" + url-loader "^4.1.1" + vfile "^6.0.1" + webpack "^5.88.1" + +"@docusaurus/module-type-aliases@3.7.0", "@docusaurus/module-type-aliases@^3.5.2": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz#15c0745b829c6966c5b3b2c2527c72b54830b0e5" + integrity sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg== + dependencies: + "@docusaurus/types" "3.7.0" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + "@types/react-router-dom" "*" + react-helmet-async "npm:@slorber/react-helmet-async@*" + react-loadable "npm:@docusaurus/react-loadable@6.0.0" + +"@docusaurus/plugin-content-blog@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.7.0.tgz#7bd69de87a1f3adb652e1473ef5b7ccc9468f47e" + integrity sha512-EFLgEz6tGHYWdPU0rK8tSscZwx+AsyuBW/r+tNig2kbccHYGUJmZtYN38GjAa3Fda4NU+6wqUO5kTXQSRBQD3g== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/logger" "3.7.0" + "@docusaurus/mdx-loader" "3.7.0" + "@docusaurus/theme-common" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + cheerio "1.0.0-rc.12" + feed "^4.2.2" + fs-extra "^11.1.1" + lodash "^4.17.21" + reading-time "^1.5.0" + srcset "^4.0.0" + tslib "^2.6.0" + unist-util-visit "^5.0.0" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@docusaurus/plugin-content-docs@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.7.0.tgz#297a549e926ee2b1147b5242af6f21532c7b107c" + integrity sha512-GXg5V7kC9FZE4FkUZA8oo/NrlRb06UwuICzI6tcbzj0+TVgjq/mpUXXzSgKzMS82YByi4dY2Q808njcBCyy6tQ== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/logger" "3.7.0" + "@docusaurus/mdx-loader" "3.7.0" + "@docusaurus/module-type-aliases" "3.7.0" + "@docusaurus/theme-common" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + "@types/react-router-config" "^5.0.7" + combine-promises "^1.1.0" + fs-extra "^11.1.1" + js-yaml "^4.1.0" + lodash "^4.17.21" + tslib "^2.6.0" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@docusaurus/plugin-content-pages@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.7.0.tgz#c4a8f7237872236aacb77665822c474c0a00e91a" + integrity sha512-YJSU3tjIJf032/Aeao8SZjFOrXJbz/FACMveSMjLyMH4itQyZ2XgUIzt4y+1ISvvk5zrW4DABVT2awTCqBkx0Q== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/mdx-loader" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + fs-extra "^11.1.1" + tslib "^2.6.0" + webpack "^5.88.1" + +"@docusaurus/plugin-debug@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.7.0.tgz#a4fd45132e40cffe96bb51f48e89982a1cb8e194" + integrity sha512-Qgg+IjG/z4svtbCNyTocjIwvNTNEwgRjSXXSJkKVG0oWoH0eX/HAPiu+TS1HBwRPQV+tTYPWLrUypYFepfujZA== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + fs-extra "^11.1.1" + react-json-view-lite "^1.2.0" + tslib "^2.6.0" + +"@docusaurus/plugin-google-analytics@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.7.0.tgz#d20f665e810fb2295d1c1bbfe13398c5ff42eb24" + integrity sha512-otIqiRV/jka6Snjf+AqB360XCeSv7lQC+DKYW+EUZf6XbuE8utz5PeUQ8VuOcD8Bk5zvT1MC4JKcd5zPfDuMWA== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + tslib "^2.6.0" + +"@docusaurus/plugin-google-gtag@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.7.0.tgz#a48638dfd132858060458b875a440b6cbda6bf8f" + integrity sha512-M3vrMct1tY65ModbyeDaMoA+fNJTSPe5qmchhAbtqhDD/iALri0g9LrEpIOwNaoLmm6lO88sfBUADQrSRSGSWA== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + "@types/gtag.js" "^0.0.12" + tslib "^2.6.0" + +"@docusaurus/plugin-google-tag-manager@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.7.0.tgz#0a4390f4b0e760d073bdb1905436bfa7bd71356b" + integrity sha512-X8U78nb8eiMiPNg3jb9zDIVuuo/rE1LjGDGu+5m5CX4UBZzjMy+klOY2fNya6x8ACyE/L3K2erO1ErheP55W/w== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + tslib "^2.6.0" + +"@docusaurus/plugin-sitemap@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.7.0.tgz#2c1bf9de26aeda455df6f77748e5887ace39b2d7" + integrity sha512-bTRT9YLZ/8I/wYWKMQke18+PF9MV8Qub34Sku6aw/vlZ/U+kuEuRpQ8bTcNOjaTSfYsWkK4tTwDMHK2p5S86cA== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/logger" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + fs-extra "^11.1.1" + sitemap "^7.1.1" + tslib "^2.6.0" + +"@docusaurus/plugin-svgr@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.7.0.tgz#018e89efd615d5fde77b891a8c2aadf203013f5d" + integrity sha512-HByXIZTbc4GV5VAUkZ2DXtXv1Qdlnpk3IpuImwSnEzCDBkUMYcec5282hPjn6skZqB25M1TYCmWS91UbhBGxQg== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + "@svgr/core" "8.1.0" + "@svgr/webpack" "^8.1.0" + tslib "^2.6.0" + webpack "^5.88.1" + +"@docusaurus/preset-classic@^3.5.2": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.7.0.tgz#f6656a04ae6a4877523dbd04f7c491632e4003b9" + integrity sha512-nPHj8AxDLAaQXs+O6+BwILFuhiWbjfQWrdw2tifOClQoNfuXDjfjogee6zfx6NGHWqshR23LrcN115DmkHC91Q== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/plugin-content-blog" "3.7.0" + "@docusaurus/plugin-content-docs" "3.7.0" + "@docusaurus/plugin-content-pages" "3.7.0" + "@docusaurus/plugin-debug" "3.7.0" + "@docusaurus/plugin-google-analytics" "3.7.0" + "@docusaurus/plugin-google-gtag" "3.7.0" + "@docusaurus/plugin-google-tag-manager" "3.7.0" + "@docusaurus/plugin-sitemap" "3.7.0" + "@docusaurus/plugin-svgr" "3.7.0" + "@docusaurus/theme-classic" "3.7.0" + "@docusaurus/theme-common" "3.7.0" + "@docusaurus/theme-search-algolia" "3.7.0" + "@docusaurus/types" "3.7.0" + +"@docusaurus/theme-classic@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.7.0.tgz#b483bd8e2923b6994b5f47238884b9f8984222c5" + integrity sha512-MnLxG39WcvLCl4eUzHr0gNcpHQfWoGqzADCly54aqCofQX6UozOS9Th4RK3ARbM9m7zIRv3qbhggI53dQtx/hQ== + dependencies: + "@docusaurus/core" "3.7.0" + "@docusaurus/logger" "3.7.0" + "@docusaurus/mdx-loader" "3.7.0" + "@docusaurus/module-type-aliases" "3.7.0" + "@docusaurus/plugin-content-blog" "3.7.0" + "@docusaurus/plugin-content-docs" "3.7.0" + "@docusaurus/plugin-content-pages" "3.7.0" + "@docusaurus/theme-common" "3.7.0" + "@docusaurus/theme-translations" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + "@mdx-js/react" "^3.0.0" + clsx "^2.0.0" + copy-text-to-clipboard "^3.2.0" + infima "0.2.0-alpha.45" + lodash "^4.17.21" + nprogress "^0.2.0" + postcss "^8.4.26" + prism-react-renderer "^2.3.0" + prismjs "^1.29.0" + react-router-dom "^5.3.4" + rtlcss "^4.1.0" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-common@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.7.0.tgz#18bf5c6b149a701f4bd865715ee8b595aa40b354" + integrity sha512-8eJ5X0y+gWDsURZnBfH0WabdNm8XMCXHv8ENy/3Z/oQKwaB/EHt5lP9VsTDTf36lKEp0V6DjzjFyFIB+CetL0A== + dependencies: + "@docusaurus/mdx-loader" "3.7.0" + "@docusaurus/module-type-aliases" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + clsx "^2.0.0" + parse-numeric-range "^1.3.0" + prism-react-renderer "^2.3.0" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-search-algolia@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.7.0.tgz#2108ddf0b300b82de7c2b9ff9fcf62121b66ea37" + integrity sha512-Al/j5OdzwRU1m3falm+sYy9AaB93S1XF1Lgk9Yc6amp80dNxJVplQdQTR4cYdzkGtuQqbzUA8+kaoYYO0RbK6g== + dependencies: + "@docsearch/react" "^3.8.1" + "@docusaurus/core" "3.7.0" + "@docusaurus/logger" "3.7.0" + "@docusaurus/plugin-content-docs" "3.7.0" + "@docusaurus/theme-common" "3.7.0" + "@docusaurus/theme-translations" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-validation" "3.7.0" + algoliasearch "^5.17.1" + algoliasearch-helper "^3.22.6" + clsx "^2.0.0" + eta "^2.2.0" + fs-extra "^11.1.1" + lodash "^4.17.21" + tslib "^2.6.0" + utility-types "^3.10.0" + +"@docusaurus/theme-translations@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.7.0.tgz#0891aedc7c7040afcb3a1b34051d3a69096d0d25" + integrity sha512-Ewq3bEraWDmienM6eaNK7fx+/lHMtGDHQyd1O+4+3EsDxxUmrzPkV7Ct3nBWTuE0MsoZr3yNwQVKjllzCMuU3g== + dependencies: + fs-extra "^11.1.1" + tslib "^2.6.0" + +"@docusaurus/tsconfig@^3.5.2": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.7.0.tgz#654dcc524e25b8809af0f1b0b42485c18c047ab5" + integrity sha512-vRsyj3yUZCjscgfgcFYjIsTcAru/4h4YH2/XAE8Rs7wWdnng98PgWKvP5ovVc4rmRpRg2WChVW0uOy2xHDvDBQ== + +"@docusaurus/types@3.7.0", "@docusaurus/types@^3.5.2": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.7.0.tgz#3f5a68a60f80ecdcb085666da1d68f019afda943" + integrity sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ== + dependencies: + "@mdx-js/mdx" "^3.0.0" + "@types/history" "^4.7.11" + "@types/react" "*" + commander "^5.1.0" + joi "^17.9.2" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" + utility-types "^3.10.0" + webpack "^5.95.0" + webpack-merge "^5.9.0" + +"@docusaurus/utils-common@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.7.0.tgz#1bef52837d321db5dd2361fc07f3416193b5d029" + integrity sha512-IZeyIfCfXy0Mevj6bWNg7DG7B8G+S6o6JVpddikZtWyxJguiQ7JYr0SIZ0qWd8pGNuMyVwriWmbWqMnK7Y5PwA== + dependencies: + "@docusaurus/types" "3.7.0" + tslib "^2.6.0" + +"@docusaurus/utils-validation@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.7.0.tgz#dc0786fb633ae5cef8e93337bf21c2a826c7ecbd" + integrity sha512-w8eiKk8mRdN+bNfeZqC4nyFoxNyI1/VExMKAzD9tqpJfLLbsa46Wfn5wcKH761g9WkKh36RtFV49iL9lh1DYBA== + dependencies: + "@docusaurus/logger" "3.7.0" + "@docusaurus/utils" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + fs-extra "^11.2.0" + joi "^17.9.2" + js-yaml "^4.1.0" + lodash "^4.17.21" + tslib "^2.6.0" + +"@docusaurus/utils@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.7.0.tgz#dfdebd63524c52b498f36b2907a3b2261930b9bb" + integrity sha512-e7zcB6TPnVzyUaHMJyLSArKa2AG3h9+4CfvKXKKWNx6hRs+p0a+u7HHTJBgo6KW2m+vqDnuIHK4X+bhmoghAFA== + dependencies: + "@docusaurus/logger" "3.7.0" + "@docusaurus/types" "3.7.0" + "@docusaurus/utils-common" "3.7.0" + escape-string-regexp "^4.0.0" + file-loader "^6.2.0" + fs-extra "^11.1.1" + github-slugger "^1.5.0" + globby "^11.1.0" + gray-matter "^4.0.3" + jiti "^1.20.0" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" + prompts "^2.4.2" + resolve-pathname "^3.0.0" + shelljs "^0.8.5" + tslib "^2.6.0" + url-loader "^4.1.1" + utility-types "^3.10.0" + webpack "^5.88.1" + +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== + +"@mdx-js/mdx@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.1.0.tgz#10235cab8ad7d356c262e8c21c68df5850a97dc3" + integrity sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdx" "^2.0.0" + collapse-white-space "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-util-scope "^1.0.0" + estree-walker "^3.0.0" + hast-util-to-jsx-runtime "^2.0.0" + markdown-extensions "^2.0.0" + recma-build-jsx "^1.0.0" + recma-jsx "^1.0.0" + recma-stringify "^1.0.0" + rehype-recma "^1.0.0" + remark-mdx "^3.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + source-map "^0.7.0" + unified "^11.0.0" + unist-util-position-from-estree "^2.0.0" + unist-util-stringify-position "^4.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +"@mdx-js/react@^3.0.0", "@mdx-js/react@^3.0.1": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.0.tgz#c4522e335b3897b9a845db1dbdd2f966ae8fb0ed" + integrity sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ== + dependencies: + "@types/mdx" "^2.0.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pnpm/config.env-replace@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" + integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== + +"@pnpm/network.ca-file@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983" + integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== + dependencies: + graceful-fs "4.2.10" + +"@pnpm/npm-conf@^2.1.0": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz#bb375a571a0bd63ab0a23bece33033c683e9b6b0" + integrity sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw== + dependencies: + "@pnpm/config.env-replace" "^1.1.0" + "@pnpm/network.ca-file" "^1.0.1" + config-chain "^1.1.11" + +"@polka/url@^1.0.0-next.24": + version "1.0.0-next.28" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" + integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== + +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + +"@sindresorhus/is@^5.2.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.6.0.tgz#41dd6093d34652cddb5d5bdeee04eafc33826668" + integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g== + +"@slorber/remark-comment@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@slorber/remark-comment/-/remark-comment-1.0.0.tgz#2a020b3f4579c89dec0361673206c28d67e08f5a" + integrity sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA== + dependencies: + micromark-factory-space "^1.0.0" + micromark-util-character "^1.1.0" + micromark-util-symbol "^1.0.1" + +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== + dependencies: + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" + +"@svgr/webpack@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-8.1.0.tgz#16f1b5346f102f89fda6ec7338b96a701d8be0c2" + integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA== + dependencies: + "@babel/core" "^7.21.3" + "@babel/plugin-transform-react-constant-elements" "^7.21.3" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" + +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" + integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + dependencies: + defer-to-connect "^2.0.1" + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/acorn@^4.0.0": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.6.tgz#d61ca5480300ac41a7d973dd5b84d0a591154a22" + integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ== + dependencies: + "@types/estree" "*" + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.13" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz#41fec4ea20e9c7b22f024ab88a95c6bb288f51b8" + integrity sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.19.6" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" + integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.1.tgz#138d741c6e5db8cc273bec5285cd6e9d0779fc9f" + integrity sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "*" + +"@types/express@^4.17.13": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/gtag.js@^0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.12.tgz#095122edca896689bdfcdd73b057e23064d23572" + integrity sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg== + +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-cache-semantics@^4.0.2": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/http-proxy@^1.17.8": + version "1.17.16" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.16.tgz#dee360707b35b3cc85afcde89ffeebff7d7f9240" + integrity sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/mdast@^4.0.0", "@types/mdast@^4.0.2": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/mdx@^2.0.0": + version "2.0.13" + resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd" + integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "22.13.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.11.tgz#f0ed6b302dcf0f4229d44ea707e77484ad46d234" + integrity sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g== + dependencies: + undici-types "~6.20.0" + +"@types/node@^17.0.5": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/prismjs@^1.26.0": + version "1.26.5" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6" + integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ== + +"@types/qs@*": + version "6.9.18" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2" + integrity sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/react-router-config@*", "@types/react-router-config@^5.0.7": + version "5.0.11" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.11.tgz#2761a23acc7905a66a94419ee40294a65aaa483a" + integrity sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "^5.1.0" + +"@types/react-router-dom@*": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*", "@types/react-router@^5.1.0": + version "5.1.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react@*": + version "19.0.12" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.12.tgz#338b3f7854adbb784be454b3a83053127af96bd3" + integrity sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA== + dependencies: + csstype "^3.0.2" + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/sax@^1.2.1": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.7.tgz#ba5fe7df9aa9c89b6dff7688a19023dd2963091d" + integrity sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A== + dependencies: + "@types/node" "*" + +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.1": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/sockjs@^0.3.33": + version "0.3.36" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== + dependencies: + "@types/node" "*" + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + +"@types/ws@^8.5.5": + version "8.18.0" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.0.tgz#8a2ec491d6f0685ceaab9a9b7ff44146236993b5" + integrity sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@ungap/structured-clone@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" + integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== + dependencies: + "@webassemblyjs/helper-numbers" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + +"@webassemblyjs/floating-point-hex-parser@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" + integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== + +"@webassemblyjs/helper-api-error@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" + integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== + +"@webassemblyjs/helper-buffer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" + integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== + +"@webassemblyjs/helper-numbers@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" + integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.13.2" + "@webassemblyjs/helper-api-error" "1.13.2" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" + integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== + +"@webassemblyjs/helper-wasm-section@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" + integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/wasm-gen" "1.14.1" + +"@webassemblyjs/ieee754@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" + integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" + integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" + integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== + +"@webassemblyjs/wasm-edit@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" + integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/helper-wasm-section" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-opt" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + "@webassemblyjs/wast-printer" "1.14.1" + +"@webassemblyjs/wasm-gen@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" + integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wasm-opt@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" + integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-buffer" "1.14.1" + "@webassemblyjs/wasm-gen" "1.14.1" + "@webassemblyjs/wasm-parser" "1.14.1" + +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" + integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@webassemblyjs/helper-api-error" "1.13.2" + "@webassemblyjs/helper-wasm-bytecode" "1.13.2" + "@webassemblyjs/ieee754" "1.13.2" + "@webassemblyjs/leb128" "1.13.2" + "@webassemblyjs/utf8" "1.13.2" + +"@webassemblyjs/wast-printer@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" + integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== + dependencies: + "@webassemblyjs/ast" "1.14.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.0.0: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.0.0, acorn@^8.0.4, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.2: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +address@^1.0.1, address@^1.1.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" + integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.2, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +algoliasearch-helper@^3.22.6: + version "3.24.2" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.24.2.tgz#332f9813b63442b13b8eaae19f313fe3db1047af" + integrity sha512-vBw/INZDfyh/THbVeDy8On8lZqd2qiUAHde5N4N1ygL4SoeLqLGJ4GHneHrDAYsjikRwTTtodEP0fiXl5MxHFQ== + dependencies: + "@algolia/events" "^4.0.1" + +algoliasearch@^5.14.2, algoliasearch@^5.17.1: + version "5.21.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.21.0.tgz#0517971eba0c03efda8586213294a554db2d3ac9" + integrity sha512-hexLq2lSO1K5SW9j21Ubc+q9Ptx7dyRTY7se19U8lhIlVMLCNXWCyQ6C22p9ez8ccX0v7QVmwkl2l1CnuGoO2Q== + dependencies: + "@algolia/client-abtesting" "5.21.0" + "@algolia/client-analytics" "5.21.0" + "@algolia/client-common" "5.21.0" + "@algolia/client-insights" "5.21.0" + "@algolia/client-personalization" "5.21.0" + "@algolia/client-query-suggestions" "5.21.0" + "@algolia/client-search" "5.21.0" + "@algolia/ingestion" "1.21.0" + "@algolia/monitoring" "1.21.0" + "@algolia/recommend" "5.21.0" + "@algolia/requester-browser-xhr" "5.21.0" + "@algolia/requester-fetch" "5.21.0" + "@algolia/requester-node-http" "5.21.0" + +ansi-align@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + +ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +astring@^1.8.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef" + integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +autoprefixer@^10.4.19: + version "10.4.21" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.21.tgz#77189468e7a8ad1d9a37fbc08efc9f480cf0a95d" + integrity sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ== + dependencies: + browserslist "^4.24.4" + caniuse-lite "^1.0.30001702" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.1.1" + postcss-value-parser "^4.2.0" + +babel-loader@^9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.2.1.tgz#04c7835db16c246dd19ba0914418f3937797587b" + integrity sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA== + dependencies: + find-cache-dir "^4.0.0" + schema-utils "^4.0.0" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.13" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz#7d445f0e0607ebc8fb6b01d7e8fb02069b91dd8b" + integrity sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.4" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz#4e4e182f1bb37c7ba62e2af81d8dd09df31344f6" + integrity sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.3" + core-js-compat "^3.40.0" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz#428c615d3c177292a22b4f93ed99e358d7906a9b" + integrity sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.4" + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.3.0" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722" + integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA== + dependencies: + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + +boxen@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.1.1.tgz#f9ba525413c2fec9cdb88987d835c4f7cad9c8f4" + integrity sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog== + dependencies: + ansi-align "^3.0.1" + camelcase "^7.0.1" + chalk "^5.2.0" + cli-boxes "^3.0.0" + string-width "^5.1.2" + type-fest "^2.13.0" + widest-line "^4.0.1" + wrap-ansi "^8.1.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.23.0, browserslist@^4.24.0, browserslist@^4.24.4: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacheable-lookup@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27" + integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w== + +cacheable-request@^10.2.8: + version "10.2.14" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-10.2.14.tgz#eb915b665fda41b79652782df3f553449c406b9d" + integrity sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ== + dependencies: + "@types/http-cache-semantics" "^4.0.2" + get-stream "^6.0.1" + http-cache-semantics "^4.1.1" + keyv "^4.5.3" + mimic-response "^4.0.0" + normalize-url "^8.0.0" + responselike "^3.0.0" + +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +camelcase@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" + integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001688, caniuse-lite@^1.0.30001702: + version "1.0.30001707" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz#c5e104d199e6f4355a898fcd995a066c7eb9bf41" + integrity sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^5.0.1, chalk@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@1.0.0-rc.12: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +clean-css@^5.2.2, clean-css@^5.3.2, clean-css@~5.3.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" + integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-table3@^0.6.3: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clsx@^2.0.0, clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +collapse-white-space@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca" + integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +colorette@^2.0.10: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combine-promises@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.2.0.tgz#5f2e68451862acf85761ded4d9e2af7769c2ca6a" + integrity sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ== + +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + +compressible@~2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.8.0" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.0.tgz#09420efc96e11a0f44f3a558de59e321364180f7" + integrity sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA== + dependencies: + bytes "3.1.2" + compressible "~2.0.18" + debug "2.6.9" + negotiator "~0.6.4" + on-headers "~1.0.2" + safe-buffer "5.2.1" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +config-chain@^1.1.11: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +configstore@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-6.0.0.tgz#49eca2ebc80983f77e09394a1a56e0aca8235566" + integrity sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA== + dependencies: + dot-prop "^6.0.1" + graceful-fs "^4.2.6" + unique-string "^3.0.0" + write-file-atomic "^3.0.3" + xdg-basedir "^5.0.1" + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +consola@^3.2.3: + version "3.4.2" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.2.tgz#5af110145397bb67afdab77013fdc34cae590ea7" + integrity sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + +copy-text-to-clipboard@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz#0202b2d9bdae30a49a53f898626dcc3b49ad960b" + integrity sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q== + +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + +core-js-compat@^3.40.0: + version "3.41.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.41.0.tgz#4cdfce95f39a8f27759b667cf693d96e5dda3d17" + integrity sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A== + dependencies: + browserslist "^4.24.4" + +core-js-pure@^3.30.2: + version "3.41.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.41.0.tgz#349fecad168d60807a31e83c99d73d786fe80811" + integrity sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q== + +core-js@^3.31.1: + version "3.41.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.41.0.tgz#57714dafb8c751a6095d028a7428f1fb5834a776" + integrity sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-4.0.0.tgz#5a3cc53d7dd86183df5da0312816ceeeb5bb1fc2" + integrity sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA== + dependencies: + type-fest "^1.0.1" + +css-blank-pseudo@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz#32020bff20a209a53ad71b8675852b49e8d57e46" + integrity sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag== + dependencies: + postcss-selector-parser "^7.0.0" + +css-declaration-sorter@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz#6dec1c9523bc4a643e088aab8f09e67a54961024" + integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow== + +css-has-pseudo@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz#fb42e8de7371f2896961e1f6308f13c2c7019b72" + integrity sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ== + dependencies: + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.2.0" + +css-loader@^6.8.1: + version "6.11.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" + integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.33" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.5.4" + +css-minimizer-webpack-plugin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz#33effe662edb1a0bf08ad633c32fa75d0f7ec565" + integrity sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + cssnano "^6.0.1" + jest-worker "^29.4.3" + postcss "^8.4.24" + schema-utils "^4.0.1" + serialize-javascript "^6.0.1" + +css-prefers-color-scheme@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz#ba001b99b8105b8896ca26fc38309ddb2278bd3c" + integrity sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ== + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^6.0.1, css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssdb@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-8.2.4.tgz#f806c6cdca76dbaaf76e0c3bd3c4b50528186633" + integrity sha512-3KSCVkjZJe/QxicVXnbyYSY26WsFc1YoMY7jep1ZKWMEVc7jEm6V2Xq2r+MX8WKQIuB7ofGbnr5iVI+aZpoSzg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-advanced@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz#82b090872b8f98c471f681d541c735acf8b94d3f" + integrity sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ== + dependencies: + autoprefixer "^10.4.19" + browserslist "^4.23.0" + cssnano-preset-default "^6.1.2" + postcss-discard-unused "^6.0.5" + postcss-merge-idents "^6.0.3" + postcss-reduce-idents "^6.0.3" + postcss-zindex "^6.0.2" + +cssnano-preset-default@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz#adf4b89b975aa775f2750c89dbaf199bbd9da35e" + integrity sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg== + dependencies: + browserslist "^4.23.0" + css-declaration-sorter "^7.2.0" + cssnano-utils "^4.0.2" + postcss-calc "^9.0.1" + postcss-colormin "^6.1.0" + postcss-convert-values "^6.1.0" + postcss-discard-comments "^6.0.2" + postcss-discard-duplicates "^6.0.3" + postcss-discard-empty "^6.0.3" + postcss-discard-overridden "^6.0.2" + postcss-merge-longhand "^6.0.5" + postcss-merge-rules "^6.1.1" + postcss-minify-font-values "^6.1.0" + postcss-minify-gradients "^6.0.3" + postcss-minify-params "^6.1.0" + postcss-minify-selectors "^6.0.4" + postcss-normalize-charset "^6.0.2" + postcss-normalize-display-values "^6.0.2" + postcss-normalize-positions "^6.0.2" + postcss-normalize-repeat-style "^6.0.2" + postcss-normalize-string "^6.0.2" + postcss-normalize-timing-functions "^6.0.2" + postcss-normalize-unicode "^6.1.0" + postcss-normalize-url "^6.0.2" + postcss-normalize-whitespace "^6.0.2" + postcss-ordered-values "^6.0.2" + postcss-reduce-initial "^6.1.0" + postcss-reduce-transforms "^6.0.2" + postcss-svgo "^6.0.3" + postcss-unique-selectors "^6.0.4" + +cssnano-utils@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz#56f61c126cd0f11f2eef1596239d730d9fceff3c" + integrity sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ== + +cssnano@^6.0.1, cssnano@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.1.2.tgz#4bd19e505bd37ee7cf0dc902d3d869f6d79c66b8" + integrity sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA== + dependencies: + cssnano-preset-default "^6.1.2" + lilconfig "^3.1.1" + +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + +debug@2.6.9, debug@^2.6.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +decode-named-character-reference@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz#5d6ce68792808901210dac42a8e9853511e2b8bf" + integrity sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w== + dependencies: + character-entities "^2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deepmerge@^4.2.2, deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +defer-to-connect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +del@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detect-port-alt@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +detect-port@^1.5.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.6.1.tgz#45e4073997c5f292b957cb678fb0bb8ed4250a67" + integrity sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q== + dependencies: + address "^1.0.1" + debug "4" + +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-packet@^5.2.2: + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +domutils@^3.0.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + +dotenv@^16.4.5: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.5.73: + version "1.5.123" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz#fae5bdba0ba27045895176327aa79831aba0790c" + integrity sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +emojilib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" + integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +emoticon@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-4.1.0.tgz#d5a156868ee173095627a33de3f1e914c3dde79e" + integrity sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +enhanced-resolve@^5.17.1: + version "5.18.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz#728ab082f8b7b6836de51f1637aab5d3b9568faf" + integrity sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +esast-util-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz#8d1cfb51ad534d2f159dc250e604f3478a79f1ad" + integrity sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + unist-util-position-from-estree "^2.0.0" + +esast-util-from-js@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz#5147bec34cc9da44accf52f87f239a40ac3e8225" + integrity sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw== + dependencies: + "@types/estree-jsx" "^1.0.0" + acorn "^8.0.0" + esast-util-from-estree "^2.0.0" + vfile-message "^4.0.0" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-goat@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-4.0.0.tgz#9424820331b510b0666b98f7873fe11ac4aa8081" + integrity sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg== + +escape-html@^1.0.3, escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-util-attach-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz#344bde6a64c8a31d15231e5ee9e297566a691c2d" + integrity sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw== + dependencies: + "@types/estree" "^1.0.0" + +estree-util-build-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz#b6d0bced1dcc4f06f25cf0ceda2b2dcaf98168f1" + integrity sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-walker "^3.0.0" + +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + +estree-util-scope@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/estree-util-scope/-/estree-util-scope-1.0.0.tgz#9cbdfc77f5cb51e3d9ed4ad9c4adbff22d43e585" + integrity sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + +estree-util-to-js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz#10a6fb924814e6abb62becf0d2bc4dea51d04f17" + integrity sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg== + dependencies: + "@types/estree-jsx" "^1.0.0" + astring "^1.8.0" + source-map "^0.7.0" + +estree-util-value-to-estree@^3.0.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/estree-util-value-to-estree/-/estree-util-value-to-estree-3.3.2.tgz#75bb2263850b6f5ac35edd343929c36b51a69806" + integrity sha512-hYH1aSvQI63Cvq3T3loaem6LW4u72F187zW4FHpTrReJSm6W66vYTFNO1vH/chmcOulp1HlAj1pxn8Ag0oXI5Q== + dependencies: + "@types/estree" "^1.0.0" + +estree-util-visit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz#13a9a9f40ff50ed0c022f831ddf4b58d05446feb" + integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/unist" "^3.0.0" + +estree-walker@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eta@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eta/-/eta-2.2.0.tgz#eb8b5f8c4e8b6306561a455e62cd7492fe3a9b8a" + integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eval@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" + integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== + dependencies: + "@types/node" "*" + require-like ">= 0.1.1" + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +express@^4.17.3: + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.7.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.12" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fault@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c" + integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ== + dependencies: + format "^0.2.0" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +feed@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" + integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ== + dependencies: + xml-js "^1.6.11" + +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz#a30ee0448f81a3990708f6453633c733e2f6eec2" + integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg== + dependencies: + common-path-prefix "^3.0.0" + pkg-dir "^7.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-up@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790" + integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw== + dependencies: + locate-path "^7.1.0" + path-exists "^5.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +follow-redirects@^1.0.0: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +fork-ts-checker-webpack-plugin@^6.5.0: + version "6.5.3" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3" + integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" + chokidar "^3.4.2" + cosmiconfig "^6.0.0" + deepmerge "^4.2.2" + fs-extra "^9.0.0" + glob "^7.1.6" + memfs "^3.1.2" + minimatch "^3.0.4" + schema-utils "2.7.0" + semver "^7.3.2" + tapable "^1.0.0" + +form-data-encoder@^2.1.2: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" + integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== + +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^11.1.1, fs-extra@^11.2.0: + version "11.3.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" + integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^6.0.0, get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +github-slugger@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d" + integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.0.0, glob@^7.1.3, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + +global-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globby@^13.1.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +got@^12.1.0: + version "12.6.1" + resolved "https://registry.yarnpkg.com/got/-/got-12.6.1.tgz#8869560d1383353204b5a9435f782df9c091f549" + integrity sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ== + dependencies: + "@sindresorhus/is" "^5.2.0" + "@szmarczak/http-timer" "^5.0.1" + cacheable-lookup "^7.0.0" + cacheable-request "^10.2.8" + decompress-response "^6.0.0" + form-data-encoder "^2.1.2" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" + responselike "^3.0.0" + +graceful-fs@4.2.10: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +gray-matter@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-yarn@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-3.0.0.tgz#c3c21e559730d1d3b57e28af1f30d06fac38147d" + integrity sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hast-util-from-parse5@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^9.0.0" + property-information "^7.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-raw@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e" + integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-from-parse5 "^8.0.0" + hast-util-to-parse5 "^8.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + parse5 "^7.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-to-estree@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz#e654c1c9374645135695cc0ab9f70b8fcaf733d7" + integrity sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-attach-comments "^3.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + zwitch "^2.0.0" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-to-parse5@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed" + integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hoist-non-react-statics@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^2.3.2: + version "2.5.3" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.3.tgz#d8a0680bd24ee35af8c74d6d4b695627dde61c00" + integrity sha512-D3AfvN7SjhTgBSA8L1BN4FpPzuEd06uy4lHwSoRWr0lndi9BKaNzPLKGOWZ2ocSGguozr08TTb2jhCLHaemruw== + +html-escaper@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-minifier-terser@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942" + integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA== + dependencies: + camel-case "^4.1.2" + clean-css "~5.3.2" + commander "^10.0.0" + entities "^4.4.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.15.1" + +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + +html-webpack-plugin@^5.6.0: + version "5.6.3" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz#a31145f0fee4184d53a794f9513147df1e653685" + integrity sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +htmlparser2@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.9" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.9.tgz#b817b3ca0edea6236225000d795378707c169cec" + integrity sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw== + +http-proxy-middleware@^2.0.3: + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http2-wrapper@^2.1.10: + version "2.2.1" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" + integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +image-size@^1.0.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.2.0.tgz#312af27a2ff4ff58595ad00b9344dd684c910df6" + integrity sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w== + dependencies: + queue "6.0.2" + +immer@^9.0.7: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + +import-fresh@^3.1.0, import-fresh@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infima@0.2.0-alpha.45: + version "0.2.0-alpha.45" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.45.tgz#542aab5a249274d81679631b492973dd2c1e7466" + integrity sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +inline-style-parser@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22" + integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-npm@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.0.0.tgz#b59e75e8915543ca5d881ecff864077cba095261" + integrity sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + +is-root@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.4.1.tgz#b312d902b313f81e4eaf98b6361ba2b45cd694bb" + integrity sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^29.4.3: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jiti@^1.20.0: + version "1.21.7" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== + +joi@^17.9.2: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.1.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +latest-version@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-7.0.0.tgz#843201591ea81a4d404932eeb61240fe04e9e5da" + integrity sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg== + dependencies: + package-json "^8.1.0" + +launch-editor@^2.6.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.10.0.tgz#5ca3edfcb9667df1e8721310f3a40f1127d4bc42" + integrity sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA== + dependencies: + picocolors "^1.0.0" + shell-quote "^1.8.1" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lilconfig@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +loader-utils@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.3.1.tgz#735b9a19fd63648ca7adbd31c2327dfe281304e5" + integrity sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg== + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +locate-path@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a" + integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== + dependencies: + p-locate "^6.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +markdown-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4" + integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== + +markdown-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-2.0.0.tgz#194a90ced26d31fe753d8b9434430214c011865b" + integrity sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A== + dependencies: + repeat-string "^1.0.0" + +markdown-table@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a" + integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mdast-util-directive@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz#f3656f4aab6ae3767d3c72cfab5e8055572ccba1" + integrity sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-find-and-replace@^3.0.0, mdast-util-find-and-replace@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== + dependencies: + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-frontmatter@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz#f5f929eb1eb36c8a7737475c7eb438261f964ee8" + integrity sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + escape-string-regexp "^5.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-extension-frontmatter "^2.0.0" + +mdast-util-gfm-autolink-literal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5" + integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== + dependencies: + "@types/mdast" "^4.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-find-and-replace "^3.0.0" + micromark-util-character "^2.0.0" + +mdast-util-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz#7778e9d9ca3df7238cc2bd3fa2b1bf6a65b19403" + integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + +mdast-util-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16" + integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38" + integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-task-list-item@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936" + integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz#2cdf63b92c2a331406b0fb0db4c077c1b0331751" + integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-gfm-autolink-literal "^2.0.0" + mdast-util-gfm-footnote "^2.0.0" + mdast-util-gfm-strikethrough "^2.0.0" + mdast-util-gfm-table "^2.0.0" + mdast-util-gfm-task-list-item "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdx@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz#792f9cf0361b46bee1fdf1ef36beac424a099c41" + integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.1.2, memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-directive@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz#2eb61985d1995a7c1ff7621676a4f32af29409e8" + integrity sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + parse-entities "^4.0.0" + +micromark-extension-frontmatter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz#651c52ffa5d7a8eeed687c513cd869885882d67a" + integrity sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg== + dependencies: + fault "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-autolink-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-strikethrough@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923" + integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-table@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b" + integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-tagfilter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57" + integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-gfm-task-list-item@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c" + integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b" + integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== + dependencies: + micromark-extension-gfm-autolink-literal "^2.0.0" + micromark-extension-gfm-footnote "^2.0.0" + micromark-extension-gfm-strikethrough "^2.0.0" + micromark-extension-gfm-table "^2.0.0" + micromark-extension-gfm-tagfilter "^2.0.0" + micromark-extension-gfm-task-list-item "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-expression@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz#1407b9ce69916cf5e03a196ad9586889df25302a" + integrity sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz#5abb83da5ddc8e473a374453e6ea56fbd66b59ad" + integrity sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdx-md@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz#1d252881ea35d74698423ab44917e1f5b197b92d" + integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-mdxjs-esm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz#de21b2b045fd2059bd00d36746081de38390d54a" + integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdxjs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz#b5a2e0ed449288f3f6f6c544358159557549de18" + integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ== + dependencies: + acorn "^8.0.0" + acorn-jsx "^5.0.0" + micromark-extension-mdx-expression "^3.0.0" + micromark-extension-mdx-jsx "^3.0.0" + micromark-extension-mdx-md "^2.0.0" + micromark-extension-mdxjs-esm "^3.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-mdx-expression@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz#2afaa8ba6d5f63e0cead3e4dee643cad184ca260" + integrity sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-factory-space@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz#c8f40b0640a0150751d3345ed885a080b0d15faf" + integrity sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ== + dependencies: + micromark-util-character "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^1.0.0, micromark-util-character@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz#4fedaa3646db249bc58caeb000eb3549a8ca5dcc" + integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg== + dependencies: + micromark-util-symbol "^1.0.0" + micromark-util-types "^1.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-events-to-acorn@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz#4275834f5453c088bd29cd72dfbf80e3327cec07" + integrity sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^1.0.0, micromark-util-symbol@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142" + integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag== + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz#e6676a8cae0bb86a2171c498167971886cb7e283" + integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg== + +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromatch@^4.0.2, micromatch@^4.0.5, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +"mime-db@>= 1.43.0 < 2": + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +mimic-response@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" + integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== + +mini-css-extract-plugin@^2.9.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz#966031b468917a5446f4c24a80854b2947503c5b" + integrity sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w== + dependencies: + schema-utils "^4.0.0" + tapable "^2.2.1" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mrmime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.1.tgz#bc3e87f7987853a54c9850eeb1f1078cd44adddc" + integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +nanoid@^3.3.8: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-emoji@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-2.2.0.tgz#1d000e3c76e462577895be1b436f4aa2d6760eb0" + integrity sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw== + dependencies: + "@sindresorhus/is" "^4.6.0" + char-regex "^1.0.2" + emojilib "^2.4.0" + skin-tone "^2.0.0" + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize-url@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-8.0.1.tgz#9b7d96af9836577c58f5883e939365fa15623a4a" + integrity sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nprogress@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" + integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +null-loader@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-4.0.1.tgz#8e63bd3a2dd3c64236a4679428632edd0a6dbc6a" + integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.9, open@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +p-cancelable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" + integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + +p-limit@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-limit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" + integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== + dependencies: + yocto-queue "^1.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-locate@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f" + integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== + dependencies: + p-limit "^4.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-8.1.1.tgz#3e9948e43df40d1e8e78a85485f1070bf8f03dc8" + integrity sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA== + dependencies: + got "^12.1.0" + registry-auth-token "^5.0.1" + registry-url "^6.0.0" + semver "^7.3.7" + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== + dependencies: + domhandler "^5.0.3" + parse5 "^7.0.0" + +parse5@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-exists@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" + integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-is-inside@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== + +path-to-regexp@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" + integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== + +path-to-regexp@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.9.0.tgz#5dc0753acbf8521ca2e0f137b4578b917b10cf24" + integrity sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g== + dependencies: + isarray "0.0.1" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-7.0.0.tgz#8f0c08d6df4476756c5ff29b3282d0bab7517d11" + integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA== + dependencies: + find-up "^6.3.0" + +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +postcss-attribute-case-insensitive@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz#0c4500e3bcb2141848e89382c05b5a31c23033a3" + integrity sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-calc@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== + dependencies: + postcss-selector-parser "^6.0.11" + postcss-value-parser "^4.2.0" + +postcss-clamp@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" + integrity sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-functional-notation@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.8.tgz#b62a253d478f69b41e9343c983876a592578581c" + integrity sha512-S/TpMKVKofNvsxfau/+bw+IA6cSfB6/kmzFj5szUofHOVnFFMB2WwK+Zu07BeMD8T0n+ZnTO5uXiMvAKe2dPkA== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +postcss-color-hex-alpha@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz#5dd3eba1f8facb4ea306cba6e3f7712e876b0c76" + integrity sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-color-rebeccapurple@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz#5ada28406ac47e0796dff4056b0a9d5a6ecead98" + integrity sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-colormin@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz#076e8d3fb291fbff7b10e6b063be9da42ff6488d" + integrity sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + colord "^2.9.3" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz#3498387f8efedb817cbc63901d45bd1ceaa40f48" + integrity sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w== + dependencies: + browserslist "^4.23.0" + postcss-value-parser "^4.2.0" + +postcss-custom-media@^11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz#2fcd88a9b1d4da41c67dac6f2def903063a3377d" + integrity sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/media-query-list-parser" "^4.0.2" + +postcss-custom-properties@^14.0.4: + version "14.0.4" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz#de9c663285a98833a946d7003a34369d3ce373a9" + integrity sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-custom-selectors@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz#95ef8268fdbbbd84f34cf84a4517c9d99d419c5a" + integrity sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg== + dependencies: + "@csstools/cascade-layer-name-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + postcss-selector-parser "^7.0.0" + +postcss-dir-pseudo-class@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz#80d9e842c9ae9d29f6bf5fd3cf9972891d6cc0ca" + integrity sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-discard-comments@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz#e768dcfdc33e0216380623652b0a4f69f4678b6c" + integrity sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw== + +postcss-discard-duplicates@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz#d121e893c38dc58a67277f75bb58ba43fce4c3eb" + integrity sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw== + +postcss-discard-empty@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz#ee39c327219bb70473a066f772621f81435a79d9" + integrity sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ== + +postcss-discard-overridden@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz#4e9f9c62ecd2df46e8fdb44dc17e189776572e2d" + integrity sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ== + +postcss-discard-unused@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz#c1b0e8c032c6054c3fbd22aaddba5b248136f338" + integrity sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA== + dependencies: + postcss-selector-parser "^6.0.16" + +postcss-double-position-gradients@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz#eddd424ec754bb543d057d4d2180b1848095d4d2" + integrity sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-focus-visible@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz#1f7904904368a2d1180b220595d77b6f8a957868" + integrity sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-focus-within@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz#ac01ce80d3f2e8b2b3eac4ff84f8e15cd0057bc7" + integrity sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-font-variant@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" + integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== + +postcss-gap-properties@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz#d5ff0bdf923c06686499ed2b12e125fe64054fed" + integrity sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw== + +postcss-image-set-function@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz#538e94e16716be47f9df0573b56bbaca86e1da53" + integrity sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA== + dependencies: + "@csstools/utilities" "^2.0.0" + postcss-value-parser "^4.2.0" + +postcss-lab-function@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-7.0.8.tgz#ab0b210c5f6552347efa0311f7a7dfe34af9e6b4" + integrity sha512-plV21I86Hg9q8omNz13G9fhPtLopIWH06bt/Cb5cs1XnaGU2kUtEitvVd4vtQb/VqCdNUHK5swKn3QFmMRbpDg== + dependencies: + "@csstools/css-color-parser" "^3.0.8" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/utilities" "^2.0.0" + +postcss-loader@^7.3.3: + version "7.3.4" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.4.tgz#aed9b79ce4ed7e9e89e56199d25ad1ec8f606209" + integrity sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A== + dependencies: + cosmiconfig "^8.3.5" + jiti "^1.20.0" + semver "^7.5.4" + +postcss-logical@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-8.1.0.tgz#4092b16b49e3ecda70c4d8945257da403d167228" + integrity sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-merge-idents@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz#7b9c31c7bc823c94bec50f297f04e3c2b838ea65" + integrity sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g== + dependencies: + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-merge-longhand@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz#ba8a8d473617c34a36abbea8dda2b215750a065a" + integrity sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^6.1.1" + +postcss-merge-rules@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz#7aa539dceddab56019469c0edd7d22b64c3dea9d" + integrity sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + cssnano-utils "^4.0.2" + postcss-selector-parser "^6.0.16" + +postcss-minify-font-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz#a0e574c02ee3f299be2846369211f3b957ea4c59" + integrity sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz#ca3eb55a7bdb48a1e187a55c6377be918743dbd6" + integrity sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q== + dependencies: + colord "^2.9.3" + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz#54551dec77b9a45a29c3cb5953bf7325a399ba08" + integrity sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA== + dependencies: + browserslist "^4.23.0" + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz#197f7d72e6dd19eed47916d575d69dc38b396aff" + integrity sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ== + dependencies: + postcss-selector-parser "^6.0.16" + +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== + +postcss-modules-local-by-default@^4.0.5: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz#d150f43837831dae25e4085596e84f6f5d6ec368" + integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c" + integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-nesting@^13.0.1: + version "13.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-13.0.1.tgz#c405796d7245a3e4c267a9956cacfe9670b5d43e" + integrity sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ== + dependencies: + "@csstools/selector-resolve-nested" "^3.0.0" + "@csstools/selector-specificity" "^5.0.0" + postcss-selector-parser "^7.0.0" + +postcss-normalize-charset@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz#1ec25c435057a8001dac942942a95ffe66f721e1" + integrity sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ== + +postcss-normalize-display-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz#54f02764fed0b288d5363cbb140d6950dbbdd535" + integrity sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz#e982d284ec878b9b819796266f640852dbbb723a" + integrity sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz#f8006942fd0617c73f049dd8b6201c3a3040ecf3" + integrity sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz#e3cc6ad5c95581acd1fc8774b309dd7c06e5e363" + integrity sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz#40cb8726cef999de984527cbd9d1db1f3e9062c0" + integrity sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz#aaf8bbd34c306e230777e80f7f12a4b7d27ce06e" + integrity sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg== + dependencies: + browserslist "^4.23.0" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz#292792386be51a8de9a454cb7b5c58ae22db0f79" + integrity sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz#fbb009e6ebd312f8b2efb225c2fcc7cf32b400cd" + integrity sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-opacity-percentage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz#0b0db5ed5db5670e067044b8030b89c216e1eb0a" + integrity sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ== + +postcss-ordered-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz#366bb663919707093451ab70c3f99c05672aaae5" + integrity sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q== + dependencies: + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + +postcss-overflow-shorthand@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz#f5252b4a2ee16c68cd8a9029edb5370c4a9808af" + integrity sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-page-break@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" + integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== + +postcss-place@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-10.0.0.tgz#ba36ee4786ca401377ced17a39d9050ed772e5a9" + integrity sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-preset-env@^10.1.0: + version "10.1.5" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-10.1.5.tgz#1e12d050a5dbebc4230cc73c0d2e122c30a6a937" + integrity sha512-LQybafF/K7H+6fAs4SIkgzkSCixJy0/h0gubDIAP3Ihz+IQBRwsjyvBnAZ3JUHD+A/ITaxVRPDxn//a3Qy4pDw== + dependencies: + "@csstools/postcss-cascade-layers" "^5.0.1" + "@csstools/postcss-color-function" "^4.0.8" + "@csstools/postcss-color-mix-function" "^3.0.8" + "@csstools/postcss-content-alt-text" "^2.0.4" + "@csstools/postcss-exponential-functions" "^2.0.7" + "@csstools/postcss-font-format-keywords" "^4.0.0" + "@csstools/postcss-gamut-mapping" "^2.0.8" + "@csstools/postcss-gradients-interpolation-method" "^5.0.8" + "@csstools/postcss-hwb-function" "^4.0.8" + "@csstools/postcss-ic-unit" "^4.0.0" + "@csstools/postcss-initial" "^2.0.1" + "@csstools/postcss-is-pseudo-class" "^5.0.1" + "@csstools/postcss-light-dark-function" "^2.0.7" + "@csstools/postcss-logical-float-and-clear" "^3.0.0" + "@csstools/postcss-logical-overflow" "^2.0.0" + "@csstools/postcss-logical-overscroll-behavior" "^2.0.0" + "@csstools/postcss-logical-resize" "^3.0.0" + "@csstools/postcss-logical-viewport-units" "^3.0.3" + "@csstools/postcss-media-minmax" "^2.0.7" + "@csstools/postcss-media-queries-aspect-ratio-number-values" "^3.0.4" + "@csstools/postcss-nested-calc" "^4.0.0" + "@csstools/postcss-normalize-display-values" "^4.0.0" + "@csstools/postcss-oklab-function" "^4.0.8" + "@csstools/postcss-progressive-custom-properties" "^4.0.0" + "@csstools/postcss-random-function" "^1.0.3" + "@csstools/postcss-relative-color-syntax" "^3.0.8" + "@csstools/postcss-scope-pseudo-class" "^4.0.1" + "@csstools/postcss-sign-functions" "^1.1.2" + "@csstools/postcss-stepped-value-functions" "^4.0.7" + "@csstools/postcss-text-decoration-shorthand" "^4.0.2" + "@csstools/postcss-trigonometric-functions" "^4.0.7" + "@csstools/postcss-unset-value" "^4.0.0" + autoprefixer "^10.4.19" + browserslist "^4.24.4" + css-blank-pseudo "^7.0.1" + css-has-pseudo "^7.0.2" + css-prefers-color-scheme "^10.0.0" + cssdb "^8.2.3" + postcss-attribute-case-insensitive "^7.0.1" + postcss-clamp "^4.1.0" + postcss-color-functional-notation "^7.0.8" + postcss-color-hex-alpha "^10.0.0" + postcss-color-rebeccapurple "^10.0.0" + postcss-custom-media "^11.0.5" + postcss-custom-properties "^14.0.4" + postcss-custom-selectors "^8.0.4" + postcss-dir-pseudo-class "^9.0.1" + postcss-double-position-gradients "^6.0.0" + postcss-focus-visible "^10.0.1" + postcss-focus-within "^9.0.1" + postcss-font-variant "^5.0.0" + postcss-gap-properties "^6.0.0" + postcss-image-set-function "^7.0.0" + postcss-lab-function "^7.0.8" + postcss-logical "^8.1.0" + postcss-nesting "^13.0.1" + postcss-opacity-percentage "^3.0.0" + postcss-overflow-shorthand "^6.0.0" + postcss-page-break "^3.0.4" + postcss-place "^10.0.0" + postcss-pseudo-class-any-link "^10.0.1" + postcss-replace-overflow-wrap "^4.0.0" + postcss-selector-not "^8.0.1" + +postcss-pseudo-class-any-link@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz#06455431171bf44b84d79ebaeee9fd1c05946544" + integrity sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-reduce-idents@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz#b0d9c84316d2a547714ebab523ec7d13704cd486" + integrity sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz#4401297d8e35cb6e92c8e9586963e267105586ba" + integrity sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz#6fa2c586bdc091a7373caeee4be75a0f3e12965d" + integrity sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-replace-overflow-wrap@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" + integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== + +postcss-selector-not@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz#f2df9c6ac9f95e9fe4416ca41a957eda16130172" + integrity sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA== + dependencies: + postcss-selector-parser "^7.0.0" + +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.16: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-selector-parser@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz#4d6af97eba65d73bc4d84bcb343e865d7dd16262" + integrity sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-sort-media-queries@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz#4556b3f982ef27d3bac526b99b6c0d3359a6cf97" + integrity sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA== + dependencies: + sort-css-media-queries "2.2.0" + +postcss-svgo@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz#1d6e180d6df1fa8a3b30b729aaa9161e94f04eaa" + integrity sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^3.2.0" + +postcss-unique-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz#983ab308896b4bf3f2baaf2336e14e52c11a2088" + integrity sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg== + dependencies: + postcss-selector-parser "^6.0.16" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss-zindex@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-6.0.2.tgz#e498304b83a8b165755f53db40e2ea65a99b56e1" + integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== + +postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.33, postcss@^8.4.38: + version "8.5.3" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" + integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +pretty-time@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" + integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== + +prism-react-renderer@^2.3.0, prism-react-renderer@^2.3.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz#ac63b7f78e56c8f2b5e76e823a976d5ede77e35f" + integrity sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig== + dependencies: + "@types/prismjs" "^1.26.0" + clsx "^2.0.0" + +prismjs@^1.29.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9" + integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-information@^6.0.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== + +property-information@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.0.0.tgz#3508a6d6b0b8eb3ca6eb2c6623b164d2ed2ab112" + integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg== + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pupa@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-3.1.0.tgz#f15610274376bbcc70c9a3aa8b505ea23f41c579" + integrity sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug== + dependencies: + escape-goat "^4.0.0" + +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-dev-utils@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" + integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== + dependencies: + "@babel/code-frame" "^7.16.0" + address "^1.1.2" + browserslist "^4.18.1" + chalk "^4.1.2" + cross-spawn "^7.0.3" + detect-port-alt "^1.1.6" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" + global-modules "^2.0.0" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" + is-root "^2.1.0" + loader-utils "^3.2.0" + open "^8.4.0" + pkg-up "^3.1.0" + prompts "^2.4.2" + react-error-overlay "^6.0.11" + recursive-readdir "^2.2.2" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +react-dom@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react-error-overlay@^6.0.11: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.1.0.tgz#22b86256beb1c5856f08a9a228adb8121dd985f2" + integrity sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ== + +react-fast-compare@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + +"react-helmet-async@npm:@slorber/react-helmet-async@*", "react-helmet-async@npm:@slorber/react-helmet-async@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz#11fbc6094605cf60aa04a28c17e0aab894b4ecff" + integrity sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A== + dependencies: + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" + prop-types "^15.7.2" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" + +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-json-view-lite@^1.2.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz#377cc302821717ac79a1b6d099e1891df54c8662" + integrity sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw== + +react-loadable-ssr-addon-v5-slorber@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" + integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A== + dependencies: + "@babel/runtime" "^7.10.3" + +"react-loadable@npm:@docusaurus/react-loadable@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz#de6c7f73c96542bd70786b8e522d535d69069dc4" + integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ== + dependencies: + "@types/react" "*" + +react-router-config@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" + integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg== + dependencies: + "@babel/runtime" "^7.1.2" + +react-router-dom@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" + integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.3.4" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.3.4, react-router@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" + integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== + dependencies: + "@babel/runtime" "^7.12.13" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reading-time@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" + integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +recma-build-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz#c02f29e047e103d2fab2054954e1761b8ea253c4" + integrity sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew== + dependencies: + "@types/estree" "^1.0.0" + estree-util-build-jsx "^3.0.0" + vfile "^6.0.0" + +recma-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-jsx/-/recma-jsx-1.0.0.tgz#f7bef02e571a49d6ba3efdfda8e2efab48dbe3aa" + integrity sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q== + dependencies: + acorn-jsx "^5.0.0" + estree-util-to-js "^2.0.0" + recma-parse "^1.0.0" + recma-stringify "^1.0.0" + unified "^11.0.0" + +recma-parse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-parse/-/recma-parse-1.0.0.tgz#c351e161bb0ab47d86b92a98a9d891f9b6814b52" + integrity sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ== + dependencies: + "@types/estree" "^1.0.0" + esast-util-from-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +recma-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-stringify/-/recma-stringify-1.0.0.tgz#54632030631e0c7546136ff9ef8fde8e7b44f130" + integrity sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g== + dependencies: + "@types/estree" "^1.0.0" + estree-util-to-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +recursive-readdir@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372" + integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA== + dependencies: + minimatch "^3.0.5" + +regenerate-unicode-properties@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" + integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" + integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.0" + regjsgen "^0.8.0" + regjsparser "^0.12.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +registry-auth-token@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-5.1.0.tgz#3c659047ecd4caebd25bc1570a3aa979ae490eca" + integrity sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw== + dependencies: + "@pnpm/npm-conf" "^2.1.0" + +registry-url@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-6.0.1.tgz#056d9343680f2f64400032b1e199faa692286c58" + integrity sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q== + dependencies: + rc "1.2.8" + +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" + integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== + dependencies: + jsesc "~3.0.2" + +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + +rehype-recma@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rehype-recma/-/rehype-recma-1.0.0.tgz#d68ef6344d05916bd96e25400c6261775411aa76" + integrity sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + hast-util-to-estree "^3.0.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + +remark-directive@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/remark-directive/-/remark-directive-3.0.1.tgz#689ba332f156cfe1118e849164cc81f157a3ef0a" + integrity sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-directive "^3.0.0" + micromark-extension-directive "^3.0.0" + unified "^11.0.0" + +remark-emoji@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-4.0.1.tgz#671bfda668047689e26b2078c7356540da299f04" + integrity sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg== + dependencies: + "@types/mdast" "^4.0.2" + emoticon "^4.0.1" + mdast-util-find-and-replace "^3.0.1" + node-emoji "^2.1.0" + unified "^11.0.4" + +remark-frontmatter@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz#b68d61552a421ec412c76f4f66c344627dc187a2" + integrity sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-frontmatter "^2.0.0" + micromark-extension-frontmatter "^2.0.0" + unified "^11.0.0" + +remark-gfm@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b" + integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-gfm "^3.0.0" + micromark-extension-gfm "^3.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" + +remark-mdx@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-3.1.0.tgz#f979be729ecb35318fa48e2135c1169607a78343" + integrity sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA== + dependencies: + mdast-util-mdx "^3.0.0" + micromark-extension-mdxjs "^3.0.0" + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.1.tgz#f864dd2947889a11997c0a2667cd6b38f685bca7" + integrity sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +repeat-string@^1.0.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +"require-like@>= 0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa" + integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-alpn@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve@^1.1.6, resolve@^1.14.2: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-3.0.0.tgz#20decb6c298aff0dbee1c355ca95461d42823626" + integrity sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg== + dependencies: + lowercase-keys "^3.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rtlcss@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-4.3.0.tgz#f8efd4d5b64f640ec4af8fa25b65bacd9e07cc97" + integrity sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + postcss "^8.4.21" + strip-json-comments "^3.1.1" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0, schema-utils@^4.0.1, schema-utils@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0" + integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +section-matter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.1.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + +semver-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-4.0.0.tgz#3afcf5ed6d62259f5c72d0d5d50dffbdc9680df5" + integrity sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA== + dependencies: + semver "^7.3.5" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0, serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +serve-handler@^6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1" + integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + mime-types "2.1.18" + minimatch "3.1.2" + path-is-inside "1.0.2" + path-to-regexp "3.3.0" + range-parser "1.2.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.7.3, shell-quote@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.2.tgz#d2d83e057959d53ec261311e9e9b8f51dcb2934a" + integrity sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA== + +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sirv@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" + integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ== + dependencies: + "@polka/url" "^1.0.0-next.24" + mrmime "^2.0.0" + totalist "^3.0.0" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +sitemap@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.2.tgz#6ce1deb43f6f177c68bc59cf93632f54e3ae6b72" + integrity sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw== + dependencies: + "@types/node" "^17.0.5" + "@types/sax" "^1.2.1" + arg "^5.0.0" + sax "^1.2.4" + +skin-tone@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/skin-tone/-/skin-tone-2.0.0.tgz#4e3933ab45c0d4f4f781745d64b9f4c208e41237" + integrity sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA== + dependencies: + unicode-emoji-modifier-base "^1.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +sort-css-media-queries@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz#aa33cf4a08e0225059448b6c40eddbf9f1c8334c" + integrity sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA== + +source-map-js@^1.0.1, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.0: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +srcset@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/srcset/-/srcset-4.0.0.tgz#336816b665b14cd013ba545b6fe62357f86e65f4" + integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +std-env@^3.7.0: + version "3.8.1" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.1.tgz#2b81c631c62e3d0b964b87f099b8dcab6c9a5346" + integrity sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +style-to-js@^1.0.0: + version "1.1.16" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.16.tgz#e6bd6cd29e250bcf8fa5e6591d07ced7575dbe7a" + integrity sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw== + dependencies: + style-to-object "1.0.8" + +style-to-object@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292" + integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g== + dependencies: + inline-style-parser "0.2.4" + +stylehacks@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6" + integrity sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg== + dependencies: + browserslist "^4.23.0" + postcss-selector-parser "^6.0.16" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^3.0.2, svgo@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.11, terser-webpack-plugin@^5.3.9: + version "5.3.14" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz#9031d48e57ab27567f02ace85c7d690db66c3e06" + integrity sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + jest-worker "^27.4.5" + schema-utils "^4.3.0" + serialize-javascript "^6.0.2" + terser "^5.31.1" + +terser@^5.10.0, terser@^5.15.1, terser@^5.31.1: + version "5.39.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a" + integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +tiny-invariant@^1.0.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tiny-warning@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + +tslib@^2.0.3, tslib@^2.6.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + +type-fest@^2.13.0, type-fest@^2.5.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^5.4.5: + version "5.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" + integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" + integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== + +unicode-emoji-modifier-base@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz#dbbd5b54ba30f287e2a8d5a249da6c0cef369459" + integrity sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71" + integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +unified@^11.0.0, unified@^11.0.3, unified@^11.0.4: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unique-string@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-3.0.0.tgz#84a1c377aff5fd7a8bc6b55d8244b2bd90d75b9a" + integrity sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ== + dependencies: + crypto-random-string "^4.0.0" + +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz#d94da4df596529d1faa3de506202f0c9a23f2200" + integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +update-notifier@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-6.0.2.tgz#a6990253dfe6d5a02bd04fbb6a61543f55026b60" + integrity sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og== + dependencies: + boxen "^7.0.0" + chalk "^5.0.1" + configstore "^6.0.0" + has-yarn "^3.0.0" + import-lazy "^4.0.0" + is-ci "^3.0.1" + is-installed-globally "^0.4.0" + is-npm "^6.0.0" + is-yarn-global "^0.4.0" + latest-version "^7.0.0" + pupa "^3.1.0" + semver "^7.3.7" + semver-diff "^4.0.0" + xdg-basedir "^5.1.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-loader@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== + +utility-types@^3.10.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" + integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0, vfile@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + +webpack-bundle-analyzer@^4.10.2: + version "4.10.2" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz#633af2862c213730be3dbdf40456db171b60d5bd" + integrity sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw== + dependencies: + "@discoveryjs/json-ext" "0.5.7" + acorn "^8.0.4" + acorn-walk "^8.0.0" + commander "^7.2.0" + debounce "^1.2.1" + escape-string-regexp "^4.0.0" + gzip-size "^6.0.0" + html-escaper "^2.0.2" + opener "^1.5.2" + picocolors "^1.0.0" + sirv "^2.0.3" + ws "^7.3.1" + +webpack-dev-middleware@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.15.2: + version "4.15.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" + integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.4" + ws "^8.13.0" + +webpack-merge@^5.9.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-merge@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-6.0.1.tgz#50c776868e080574725abc5869bd6e4ef0a16c6a" + integrity sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.1" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.88.1, webpack@^5.95.0: + version "5.98.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.98.0.tgz#44ae19a8f2ba97537978246072fb89d10d1fbd17" + integrity sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA== + dependencies: + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.6" + "@webassemblyjs/ast" "^1.14.1" + "@webassemblyjs/wasm-edit" "^1.14.1" + "@webassemblyjs/wasm-parser" "^1.14.1" + acorn "^8.14.0" + browserslist "^4.24.0" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^4.3.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.11" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +webpackbar@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-6.0.1.tgz#5ef57d3bf7ced8b19025477bc7496ea9d502076b" + integrity sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q== + dependencies: + ansi-escapes "^4.3.2" + chalk "^4.1.2" + consola "^3.2.3" + figures "^3.2.0" + markdown-table "^2.0.0" + pretty-time "^1.1.0" + std-env "^3.7.0" + wrap-ansi "^7.0.0" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + +wildcard@^2.0.0, wildcard@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.3.1: + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== + +ws@^8.13.0: + version "8.18.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" + integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== + +xdg-basedir@^5.0.1, xdg-basedir@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-5.1.0.tgz#1efba19425e73be1bc6f2a6ceb52a3d2c884c0c9" + integrity sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ== + +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yaml@^1.7.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yocto-queue@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.1.tgz#36d7c4739f775b3cbc28e6136e21aa057adec418" + integrity sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg== + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== diff --git a/example/update_logger/src/main.rs b/example/update_logger/src/main.rs index e51725491..0a87e3d36 100644 --- a/example/update_logger/src/main.rs +++ b/example/update_logger/src/main.rs @@ -20,7 +20,7 @@ async fn app() -> anyhow::Result<()> { let komodo = KomodoClient::new_from_env()?.with_healthcheck().await?; - let (mut rx, _) = komodo.subscribe_to_updates(1000, 5)?; + let (mut rx, _) = komodo.subscribe_to_updates()?; loop { let update = match rx.recv().await { diff --git a/frontend/Dockerfile b/frontend/Dockerfile index de0de06f4..835be0803 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -18,6 +18,6 @@ FROM scratch COPY --from=builder /builder/frontend/dist /frontend -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo +LABEL org.opencontainers.image.source=https://github.com/moghtech/komodo LABEL org.opencontainers.image.description="Komodo Periphery" LABEL org.opencontainers.image.licenses=GPL-3.0 \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 8058071a1..5971c5936 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,11 +4,12 @@ - + + + - - + Komodo diff --git a/frontend/package.json b/frontend/package.json index 116ee8eb3..8188ca955 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,58 +11,59 @@ "build-client": "cd ../client/core/ts && yarn && yarn build && yarn link" }, "dependencies": { - "@monaco-editor/react": "^4.6.0", - "@radix-ui/react-checkbox": "^1.1.2", - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-hover-card": "^1.1.2", - "@radix-ui/react-icons": "1.3.0", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", - "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-select": "^2.1.2", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-switch": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toast": "^1.2.2", - "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-toggle-group": "^1.1.0", - "@tanstack/react-query": "5.59.15", - "@tanstack/react-table": "8.20.5", + "@floating-ui/react": "0.27.5", + "@monaco-editor/react": "4.7.0", + "@radix-ui/react-checkbox": "1.1.4", + "@radix-ui/react-dialog": "1.1.6", + "@radix-ui/react-dropdown-menu": "2.1.6", + "@radix-ui/react-hover-card": "1.1.6", + "@radix-ui/react-icons": "1.3.2", + "@radix-ui/react-label": "2.1.2", + "@radix-ui/react-popover": "1.1.6", + "@radix-ui/react-progress": "1.1.2", + "@radix-ui/react-select": "2.1.6", + "@radix-ui/react-separator": "1.1.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-switch": "1.1.3", + "@radix-ui/react-tabs": "1.1.3", + "@radix-ui/react-toast": "1.2.6", + "@radix-ui/react-toggle": "1.1.2", + "@radix-ui/react-toggle-group": "1.1.2", + "@tanstack/react-query": "5.67.3", + "@tanstack/react-table": "8.21.2", "ansi-to-html": "0.7.2", - "class-variance-authority": "0.7.0", + "class-variance-authority": "0.7.1", "clsx": "2.1.1", - "cmdk": "1.0.0", - "jotai": "2.10.1", - "lucide-react": "0.453.0", - "monaco-editor": "^0.52.0", - "prettier": "3.3.3", - "react": "18.3.1", - "react-charts": "^3.0.0-beta.57", - "react-dom": "18.3.1", - "react-minimal-pie-chart": "8.4.0", - "react-router-dom": "6.27.0", - "sanitize-html": "2.13.1", - "tailwind-merge": "2.5.4", + "cmdk": "1.0.4", + "jotai": "2.12.2", + "lucide-react": "0.479.0", + "monaco-editor": "0.52.2", + "prettier": "3.5.3", + "react": "19.0.0", + "react-charts": "3.0.0-beta.57", + "react-dom": "19.0.0", + "react-minimal-pie-chart": "9.1.0", + "react-router-dom": "7.3.0", + "sanitize-html": "2.14.0", + "tailwind-merge": "2.6.0", "tailwindcss-animate": "1.0.7" }, "devDependencies": { - "@types/react": "18.3.11", - "@types/react-dom": "18.3.1", + "@types/react": "19.0.10", + "@types/react-dom": "19.0.4", "@types/sanitize-html": "2.13.0", - "@typescript-eslint/eslint-plugin": "8.10.0", - "@typescript-eslint/parser": "8.0.1", - "@vitejs/plugin-react": "4.3.3", + "@typescript-eslint/eslint-plugin": "8.26.1", + "@typescript-eslint/parser": "8.26.1", + "@vitejs/plugin-react": "4.3.4", "autoprefixer": "10.4.20", - "eslint": "9.13.0", - "eslint-plugin-react-hooks": "5.0.0", - "eslint-plugin-react-refresh": "0.4.13", - "postcss": "8.4.47", - "tailwindcss": "3.4.14", - "typescript": "5.6.3", - "vite": "5.4.9", - "vite-tsconfig-paths": "5.0.1" + "eslint": "9.22.0", + "eslint-plugin-react-hooks": "5.2.0", + "eslint-plugin-react-refresh": "0.4.19", + "postcss": "8.5.3", + "tailwindcss": "3.4.17", + "typescript": "5.8.2", + "vite": "6.0.7", + "vite-tsconfig-paths": "5.1.4" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png index b182988e1b40b9ac981cee5a6f7696589fe89781..040b5efb3d8bc981abbbbc86edbb7ca2c9ddacdc 100644 GIT binary patch literal 33199 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 rSFU00000NkvXXu0mjfRw)c& literal 15823 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 Z{|DdCd7UXwZkYf8002ovPDHLkV1hoU1{VMT diff --git a/frontend/public/client/lib.d.ts b/frontend/public/client/lib.d.ts index dd65cdb78..57efaad4b 100644 --- a/frontend/public/client/lib.d.ts +++ b/frontend/public/client/lib.d.ts @@ -1,5 +1,5 @@ import { AuthResponses, ExecuteResponses, ReadResponses, UserResponses, WriteResponses } from "./responses.js"; -import { AuthRequest, ExecuteRequest, ReadRequest, UserRequest, WriteRequest } from "./types.js"; +import { AuthRequest, ExecuteRequest, ReadRequest, UpdateListItem, UserRequest, WriteRequest } from "./types.js"; export * as Types from "./types.js"; type InitOptions = { type: "jwt"; @@ -13,6 +13,11 @@ type InitOptions = { secret: string; }; }; +export declare class CancelToken { + cancelled: boolean; + constructor(); + cancel(): void; +} /** Initialize a new client for Komodo */ export declare function KomodoClient(url: string, options: InitOptions): { /** @@ -88,4 +93,18 @@ export declare function KomodoClient(url: string, options: InitOptions): { }>>(type: T, params: Req["params"]) => Promise; /** Returns the version of Komodo Core the client is calling to. */ core_version: () => Promise; + /** + * Subscribes to the update websocket with automatic reconnect loop. + * + * Note. Awaiting this method will never finish. + */ + subscribe_to_update_websocket: ({ on_update, on_login, on_close, retry_timeout_ms, cancel, on_cancel, }: { + on_update: (update: UpdateListItem) => void; + on_login?: () => void; + on_open?: () => void; + on_close?: () => void; + retry_timeout_ms?: number; + cancel?: CancelToken; + on_cancel?: () => void; + }) => Promise; }; diff --git a/frontend/public/client/lib.js b/frontend/public/client/lib.js index 567e0623a..bf6dd90e7 100644 --- a/frontend/public/client/lib.js +++ b/frontend/public/client/lib.js @@ -1,4 +1,13 @@ export * as Types from "./types.js"; +export class CancelToken { + cancelled; + constructor() { + this.cancelled = false; + } + cancel() { + this.cancelled = true; + } +} /** Initialize a new client for Komodo */ export function KomodoClient(url, options) { const state = { @@ -66,6 +75,56 @@ export function KomodoClient(url, options) { const write = async (type, params) => await request("/write", { type, params }); const execute = async (type, params) => await request("/execute", { type, params }); const core_version = () => read("GetVersion", {}).then((res) => res.version); + const subscribe_to_update_websocket = async ({ on_update, on_login, on_close, retry_timeout_ms = 5_000, cancel = new CancelToken(), on_cancel, }) => { + while (true) { + if (cancel.cancelled) { + on_cancel?.(); + return; + } + try { + const ws = new WebSocket(url.replace("http", "ws") + "/ws/update"); + // Handle login on websocket open + ws.addEventListener("open", () => { + const login_msg = options.type === "jwt" + ? { + type: "Jwt", + params: { + jwt: options.params.jwt, + }, + } + : { + type: "ApiKeys", + params: { + key: options.params.key, + secret: options.params.secret, + }, + }; + ws.send(JSON.stringify(login_msg)); + }); + ws.addEventListener("message", ({ data }) => { + if (data == "LOGGED_IN") + return on_login?.(); + on_update(JSON.parse(data)); + }); + if (on_close) { + ws.addEventListener("close", on_close); + } + // This while loop will end when the socket is closed + while (ws.readyState !== WebSocket.CLOSING && + ws.readyState !== WebSocket.CLOSED) { + if (cancel.cancelled) + ws.close(); + // Sleep for a bit before checking for websocket closed + await new Promise((resolve) => setTimeout(resolve, 500)); + } + } + catch (error) { + console.error(error); + // Sleep for a bit before retrying, maybe Komodo Core is down temporarily. + await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms)); + } + } + }; return { /** * Call the `/auth` api. @@ -130,5 +189,11 @@ export function KomodoClient(url, options) { execute, /** Returns the version of Komodo Core the client is calling to. */ core_version, + /** + * Subscribes to the update websocket with automatic reconnect loop. + * + * Note. Awaiting this method will never finish. + */ + subscribe_to_update_websocket, }; } diff --git a/frontend/public/client/responses.d.ts b/frontend/public/client/responses.d.ts index da8f01725..d697895ba 100644 --- a/frontend/public/client/responses.d.ts +++ b/frontend/public/client/responses.d.ts @@ -28,7 +28,6 @@ export type ReadResponses = { ListUserTargetPermissions: Types.ListUserTargetPermissionsResponse; GetUserGroup: Types.GetUserGroupResponse; ListUserGroups: Types.ListUserGroupsResponse; - FindResources: Types.FindResourcesResponse; GetProceduresSummary: Types.GetProceduresSummaryResponse; GetProcedure: Types.GetProcedureResponse; GetProcedureActionState: Types.GetProcedureActionStateResponse; @@ -100,8 +99,8 @@ export type ReadResponses = { GetStack: Types.GetStackResponse; GetStackActionState: Types.GetStackActionStateResponse; GetStackWebhooksEnabled: Types.GetStackWebhooksEnabledResponse; - GetStackServiceLog: Types.GetStackServiceLogResponse; - SearchStackServiceLog: Types.SearchStackServiceLogResponse; + GetStackLog: Types.GetStackLogResponse; + SearchStackLog: Types.SearchStackLogResponse; ListStacks: Types.ListStacksResponse; ListFullStacks: Types.ListFullStacksResponse; ListStackServices: Types.ListStackServicesResponse; @@ -227,6 +226,7 @@ export type WriteResponses = { CreateTag: Types.Tag; DeleteTag: Types.Tag; RenameTag: Types.Tag; + UpdateTagColor: Types.Tag; UpdateTagsOnResource: Types.UpdateTagsOnResourceResponse; CreateVariable: Types.CreateVariableResponse; UpdateVariableValue: Types.UpdateVariableValueResponse; @@ -307,4 +307,5 @@ export type ExecuteResponses = { PauseStackService: Types.Update; UnpauseStackService: Types.Update; DestroyStackService: Types.Update; + TestAlerter: Types.Update; }; diff --git a/frontend/public/client/types.d.ts b/frontend/public/client/types.d.ts index 191dba903..4ae34316c 100644 --- a/frontend/public/client/types.d.ts +++ b/frontend/public/client/types.d.ts @@ -256,8 +256,8 @@ export interface BuildConfig { */ image_name?: string; /** - * An extra tag put before the build version, for the image pushed to the repository. - * Eg. in image tag of `aarch64` would push to mbecker20/komodo:1.13.2-aarch64. + * An extra tag put after the build version, for the image pushed to the repository. + * Eg. in image tag of `aarch64` would push to moghtech/komodo-core:1.13.2-aarch64. * If this is empty, the image tag will just be the build version. * * Can be used in conjunction with `image_name` to direct multiple builds @@ -603,6 +603,9 @@ export type Execution = } | { type: "BatchDestroyStack"; params: BatchDestroyStack; +} | { + type: "TestAlerter"; + params: TestAlerter; } | { type: "Sleep"; params: Sleep; @@ -978,8 +981,20 @@ export interface DeploymentListItemInfo { } export type DeploymentListItem = ResourceListItem; export interface DeploymentQuerySpecifics { + /** + * Query only for Deployments on these Servers. + * If empty, does not filter by Server. + * Only accepts Server id (not name). + */ server_ids?: string[]; + /** + * Query only for Deployments with these Builds attached. + * If empty, does not filter by Build. + * Only accepts Build id (not name). + */ build_ids?: string[]; + /** Query only for Deployments with available image updates. */ + update_available?: boolean; } export type DeploymentQuery = ResourceQuery; /** Response for [ExchangeForJwt]. */ @@ -1013,6 +1028,19 @@ export type AlertData = type: "None"; data: {}; } +/** + * The user triggered a test of the + * Alerter configuration. + */ + | { + type: "Test"; + data: { + /** The id of the alerter */ + id: string; + /** The name of the alerter */ + name: string; + }; +} /** A server could not be reached. */ | { type: "ServerUnreachable"; @@ -1452,11 +1480,20 @@ export interface ResourceSyncConfig { * not declared in the resource files */ delete?: boolean; + /** + * Whether sync should include resources. + * Default: true + */ + include_resources: boolean; /** * When using `managed` resource sync, will only export resources * matching all of the given tags. If none, will match all resources. */ match_tags?: string[]; + /** Whether sync should include variables. */ + include_variables?: boolean; + /** Whether sync should include user groups. */ + include_user_groups?: boolean; /** Manage the file contents in the UI. */ file_contents?: string; } @@ -1649,6 +1686,7 @@ export interface StackActionState { destroying: boolean; } export type GetStackActionStateResponse = StackActionState; +export type GetStackLogResponse = Log; /** The compose file configuration. */ export interface StackConfig { /** The server to deploy the stack on. */ @@ -1683,6 +1721,13 @@ export interface StackConfig { * enable both. */ auto_update?: boolean; + /** + * If auto update is enabled, Komodo will + * by default only update the specific services + * with image updates. If this parameter is set to true, + * Komodo will redeploy the whole Stack (all services). + */ + auto_update_all_services?: boolean; /** Whether to run `docker compose down` before `compose up`. */ destroy_before_deploy?: boolean; /** Whether to skip secret interpolation into the stack environment variables. */ @@ -1759,6 +1804,8 @@ export interface StackConfig { registry_account?: string; /** The optional command to run before the Stack is deployed. */ pre_deploy?: SystemCommand; + /** The optional command to run after the Stack is deployed. */ + post_deploy?: SystemCommand; /** * The extra arguments to pass after `docker compose up -d`. * If empty, no extra arguments will be passed. @@ -1839,13 +1886,21 @@ export interface StackInfo { deployed_hash?: string; /** Deployed commit message, or null. Only for repo based stacks */ deployed_message?: string; - /** The deployed compose file contents. This is updated whenever Komodo successfully deploys the stack. */ + /** + * The deployed compose file contents. + * This is updated whenever Komodo successfully deploys the stack. + */ deployed_contents?: FileContents[]; /** * The deployed service names. * This is updated whenever it is empty, or deployed contents is updated. */ deployed_services?: StackServiceNames[]; + /** + * The output of `docker compose config`. + * This is updated whenever Komodo successfully deploys the stack. + */ + deployed_config?: string; /** * The latest service names. * This is updated whenever the stack cache refreshes, using the latest file contents (either db defined or remote). @@ -1866,7 +1921,6 @@ export interface StackInfo { } export type Stack = Resource; export type GetStackResponse = Stack; -export type GetStackServiceLogResponse = Log; /** System information of a server */ export interface SystemInformation { /** The system name */ @@ -1894,15 +1948,6 @@ export interface SingleDiskUsage { /** Total size of the disk in GB */ total_gb: number; } -/** Info for network interface usage. */ -export interface SingleNetworkInterfaceUsage { - /** The network interface name */ - name: string; - /** The ingress in bytes */ - ingress_bytes: number; - /** The egress in bytes */ - egress_bytes: number; -} export declare enum Timelength { OneSecond = "1-sec", FiveSeconds = "5-sec", @@ -1947,8 +1992,6 @@ export interface SystemStats { network_ingress_bytes?: number; /** Network egress usage in MB */ network_egress_bytes?: number; - /** Network usage by interface name (ingress, egress in bytes) */ - network_usage_interface?: SingleNetworkInterfaceUsage[]; /** The rate the system stats are being polled from the system */ polling_rate: Timelength; /** Unix timestamp in milliseconds when stats were last polled */ @@ -1957,6 +2000,62 @@ export interface SystemStats { refresh_list_ts: I64; } export type GetSystemStatsResponse = SystemStats; +export declare enum TagColor { + LightSlate = "LightSlate", + Slate = "Slate", + DarkSlate = "DarkSlate", + LightRed = "LightRed", + Red = "Red", + DarkRed = "DarkRed", + LightOrange = "LightOrange", + Orange = "Orange", + DarkOrange = "DarkOrange", + LightAmber = "LightAmber", + Amber = "Amber", + DarkAmber = "DarkAmber", + LightYellow = "LightYellow", + Yellow = "Yellow", + DarkYellow = "DarkYellow", + LightLime = "LightLime", + Lime = "Lime", + DarkLime = "DarkLime", + LightGreen = "LightGreen", + Green = "Green", + DarkGreen = "DarkGreen", + LightEmerald = "LightEmerald", + Emerald = "Emerald", + DarkEmerald = "DarkEmerald", + LightTeal = "LightTeal", + Teal = "Teal", + DarkTeal = "DarkTeal", + LightCyan = "LightCyan", + Cyan = "Cyan", + DarkCyan = "DarkCyan", + LightSky = "LightSky", + Sky = "Sky", + DarkSky = "DarkSky", + LightBlue = "LightBlue", + Blue = "Blue", + DarkBlue = "DarkBlue", + LightIndigo = "LightIndigo", + Indigo = "Indigo", + DarkIndigo = "DarkIndigo", + LightViolet = "LightViolet", + Violet = "Violet", + DarkViolet = "DarkViolet", + LightPurple = "LightPurple", + Purple = "Purple", + DarkPurple = "DarkPurple", + LightFuchsia = "LightFuchsia", + Fuchsia = "Fuchsia", + DarkFuchsia = "DarkFuchsia", + LightPink = "LightPink", + Pink = "Pink", + DarkPink = "DarkPink", + LightRose = "LightRose", + Rose = "Rose", + DarkRose = "DarkRose" +} export interface Tag { /** * The Mongo ID of the tag. @@ -1965,6 +2064,8 @@ export interface Tag { */ _id?: MongoId; name: string; + /** Hex color code with alpha for UI display */ + color?: TagColor; owner?: string; } export type GetTagResponse = Tag; @@ -2062,6 +2163,7 @@ export declare enum Operation { UpdateAlerter = "UpdateAlerter", RenameAlerter = "RenameAlerter", DeleteAlerter = "DeleteAlerter", + TestAlerter = "TestAlerter", CreateServerTemplate = "CreateServerTemplate", UpdateServerTemplate = "UpdateServerTemplate", RenameServerTemplate = "RenameServerTemplate", @@ -3029,8 +3131,8 @@ export interface ProviderAccount { export interface DockerRegistry { /** The docker provider domain. Default: `docker.io`. */ domain: string; - /** The account username. Required. */ - accounts?: ProviderAccount[]; + /** The accounts on the registry. Required. */ + accounts: ProviderAccount[]; /** * Available organizations on the registry provider. * Used to push an image under an organization's repo rather than an account's repo. @@ -3069,7 +3171,7 @@ export interface GitProvider { domain: string; /** Whether to use https. Default: true. */ https: boolean; - /** The account username. Required. */ + /** The accounts on the git provider. Required. */ accounts: ProviderAccount[]; } export type ListGitProvidersFromConfigResponse = GitProvider[]; @@ -3360,7 +3462,7 @@ export interface ResourceSyncQuerySpecifics { export type ResourceSyncQuery = ResourceQuery; export type SearchContainerLogResponse = Log; export type SearchDeploymentLogResponse = Log; -export type SearchStackServiceLogResponse = Log; +export type SearchStackLogResponse = Log; export interface ServerQuerySpecifics { } /** Server-specific query */ @@ -3371,8 +3473,16 @@ export interface ServerTemplateQuerySpecifics { export type ServerTemplateQuery = ResourceQuery; export type SetLastSeenUpdateResponse = NoData; export interface StackQuerySpecifics { + /** + * Query only for Stacks on these Servers. + * If empty, does not filter by Server. + * Only accepts Server id (not name). + */ + server_ids?: string[]; /** Filter syncs by their repo. */ - repos: string[]; + repos?: string[]; + /** Query only for Stack with available image updates. */ + update_available?: boolean; } export type StackQuery = ResourceQuery; export type UpdateDescriptionResponse = NoData; @@ -4427,8 +4537,11 @@ export interface Deploy { export interface DeployStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to "compose up" */ - service?: string; + /** + * Filter to only deploy specific services. + * If empty, will deploy all services. + */ + services?: string[]; /** * Override the default termination max time. * Only used if the stack needs to be taken down first. @@ -4483,8 +4596,11 @@ export interface DestroyDeployment { export interface DestroyStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to destroy */ - service?: string; + /** + * Filter to only destroy specific services. + * If empty, will destroy all services. + */ + services?: string[]; /** Pass `--remove-orphans` */ remove_orphans?: boolean; /** Override the default termination max time. */ @@ -4514,8 +4630,27 @@ export interface ExchangeForJwt { * Response: [TomlResponse]. */ export interface ExportAllResourcesToToml { - /** Tag name or id. Empty array will not filter by tag. */ + /** + * Whether to include any resources (servers, stacks, etc.) + * in the exported contents. + * Default: `true` + */ + include_resources: boolean; + /** + * Filter resources by tag. + * Accepts tag name or id. Empty array will not filter by tag. + */ tags?: string[]; + /** + * Whether to include variables in the exported contents. + * Default: false + */ + include_variables?: boolean; + /** + * Whether to include user groups in the exported contents. + * Default: false + */ + include_user_groups?: boolean; } /** * Get pretty formatted monrun sync toml for specific resources and user groups. @@ -4529,26 +4664,6 @@ export interface ExportResourcesToToml { /** Whether to include variables */ include_variables?: boolean; } -/** Find resources matching a common query. Response: [FindResourcesResponse]. */ -export interface FindResources { - /** The mongo query as JSON */ - query?: MongoDocument; - /** The resource variants to include in the response. */ - resources?: ResourceTarget["type"][]; -} -/** Response for [FindResources]. */ -export interface FindResourcesResponse { - /** The matching servers. */ - servers: ServerListItem[]; - /** The matching deployments. */ - deployments: DeploymentListItem[]; - /** The matching builds. */ - builds: BuildListItem[]; - /** The matching repos. */ - repos: RepoListItem[]; - /** The matching procedures. */ - procedures: ProcedureListItem[]; -} /** * **Admin only.** * Find a user. @@ -4855,14 +4970,12 @@ export interface SystemStatsRecord { disk_used_gb: number; /** Total disk size in GB */ disk_total_gb: number; - /** Breakdown of individual disks, ie their usages, sizes, and mount points */ + /** Breakdown of individual disks, including their usage, total size, and mount point */ disks: SingleDiskUsage[]; - /** Network ingress usage in bytes */ + /** Total network ingress in bytes */ network_ingress_bytes?: number; - /** Network egress usage in bytes */ + /** Total network egress in bytes */ network_egress_bytes?: number; - /** Network usage by interface name (ingress, egress in bytes) */ - network_usage_interface?: SingleNetworkInterfaceUsage[]; } /** Response to [GetHistoricalServerStats]. */ export interface GetHistoricalServerStatsResponse { @@ -5099,12 +5212,19 @@ export interface GetStackActionState { /** Id or name */ stack: string; } -/** Get a stack service's log. Response: [GetStackServiceLogResponse]. */ -export interface GetStackServiceLog { +/** + * Get a stack's logs. Filter down included services. Response: [GetStackLogResponse]. + * + * Note. This call will hit the underlying server directly for most up to date log. + */ +export interface GetStackLog { /** Id or name */ stack: string; - /** The service to get the log for. */ - service: string; + /** + * Filter the logs to only ones from specific services. + * If empty, will include logs from all services. + */ + services: string[]; /** * The number of lines of the log tail to include. * Default: 100. @@ -5743,7 +5863,7 @@ export interface ListStackServices { } /** List stacks matching optional query. Response: [ListStacksResponse]. */ export interface ListStacks { - /** optional structured query to filter syncs. */ + /** optional structured query to filter stacks. */ query?: StackQuery; } /** @@ -5897,8 +6017,11 @@ export interface PauseDeployment { export interface PauseStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to pause */ - service?: string; + /** + * Filter to only pause specific services. + * If empty, will pause all services. + */ + services?: string[]; } export interface PermissionToml { /** @@ -6016,8 +6139,11 @@ export interface PullRepo { export interface PullStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to start */ - service?: string; + /** + * Filter to only pull specific services. + * If empty, will pull all services. + */ + services?: string[]; } /** * Push a resource to the front of the users 10 most recently viewed resources. @@ -6262,8 +6388,11 @@ export interface RestartDeployment { export interface RestartStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to restart */ - service?: string; + /** + * Filter to only restart specific services. + * If empty, will restart all services. + */ + services?: string[]; } /** Runs the target Action. Response: [Update] */ export interface RunAction { @@ -6356,16 +6485,19 @@ export interface SearchDeploymentLog { timestamps?: boolean; } /** - * Search the deployment log's tail using `grep`. All lines go to stdout. - * Response: [Log]. + * Search the stack log's tail using `grep`. All lines go to stdout. + * Response: [SearchStackLogResponse]. * * Note. This call will hit the underlying server directly for most up to date log. */ -export interface SearchStackServiceLog { +export interface SearchStackLog { /** Id or name */ stack: string; - /** The service to get the log for. */ - service: string; + /** + * Filter the logs to only ones from specific services. + * If empty, will include logs from all services. + */ + services: string[]; /** The terms to search for. */ terms: string[]; /** @@ -6414,6 +6546,15 @@ export interface SetUsersInUserGroup { /** The user ids or usernames to hard set as the group's users. */ users: string[]; } +/** Info for network interface usage. */ +export interface SingleNetworkInterfaceUsage { + /** The network interface name */ + name: string; + /** The ingress in bytes */ + ingress_bytes: number; + /** The egress in bytes */ + egress_bytes: number; +} /** Configuration for a Slack alerter. */ export interface SlackAlerterEndpoint { /** The Slack app webhook url */ @@ -6451,8 +6592,11 @@ export interface StartDeployment { export interface StartStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to start */ - service?: string; + /** + * Filter to only start specific services. + * If empty, will start all services. + */ + services?: string[]; } /** Stops all containers on the target server. Response: [Update] */ export interface StopAllContainers { @@ -6493,13 +6637,21 @@ export interface StopStack { stack: string; /** Override the default termination max time. */ stop_time?: number; - /** Optionally specify a specific service to stop */ - service?: string; + /** + * Filter to only stop specific services. + * If empty, will stop all services. + */ + services?: string[]; } export interface TerminationSignalLabel { signal: TerminationSignal; label: string; } +/** Tests an Alerters ability to reach the configured endpoint. Response: [Update] */ +export interface TestAlerter { + /** Name or id */ + alerter: string; +} /** Info for the all system disks combined. */ export interface TotalDiskUsage { /** Used portion in GB */ @@ -6544,8 +6696,11 @@ export interface UnpauseDeployment { export interface UnpauseStack { /** Id or name */ stack: string; - /** Optionally specify a specific service to unpause */ - service?: string; + /** + * Filter to only unpause specific services. + * If empty, will unpause all services. + */ + services?: string[]; } /** * Update the action at the given id, and return the updated action. @@ -6794,6 +6949,13 @@ export interface UpdateStack { /** The partial config update to apply. */ config: _PartialStackConfig; } +/** Update color for tag. Response: [Tag]. */ +export interface UpdateTagColor { + /** The name or id of the tag to update. */ + tag: string; + /** The new color for the tag. */ + color: TagColor; +} /** * Update the tags on a resource. * Response: [NoData] @@ -7085,6 +7247,9 @@ export type ExecuteRequest = { } | { type: "LaunchServer"; params: LaunchServer; +} | { + type: "TestAlerter"; + params: TestAlerter; } | { type: "RunSync"; params: RunSync; @@ -7146,9 +7311,6 @@ export type ReadRequest = { } | { type: "ListUserGroups"; params: ListUserGroups; -} | { - type: "FindResources"; - params: FindResources; } | { type: "GetProceduresSummary"; params: GetProceduresSummary; @@ -7363,11 +7525,11 @@ export type ReadRequest = { type: "GetStackWebhooksEnabled"; params: GetStackWebhooksEnabled; } | { - type: "GetStackServiceLog"; - params: GetStackServiceLog; + type: "GetStackLog"; + params: GetStackLog; } | { - type: "SearchStackServiceLog"; - params: SearchStackServiceLog; + type: "SearchStackLog"; + params: SearchStackLog; } | { type: "ListStacks"; params: ListStacks; @@ -7748,6 +7910,9 @@ export type WriteRequest = { } | { type: "RenameTag"; params: RenameTag; +} | { + type: "UpdateTagColor"; + params: UpdateTagColor; } | { type: "UpdateTagsOnResource"; params: UpdateTagsOnResource; diff --git a/frontend/public/client/types.js b/frontend/public/client/types.js index 6b32498ad..7e82547ff 100644 --- a/frontend/public/client/types.js +++ b/frontend/public/client/types.js @@ -110,6 +110,63 @@ export var Timelength; Timelength["TwoWeeks"] = "2-wk"; Timelength["ThirtyDays"] = "30-day"; })(Timelength || (Timelength = {})); +export var TagColor; +(function (TagColor) { + TagColor["LightSlate"] = "LightSlate"; + TagColor["Slate"] = "Slate"; + TagColor["DarkSlate"] = "DarkSlate"; + TagColor["LightRed"] = "LightRed"; + TagColor["Red"] = "Red"; + TagColor["DarkRed"] = "DarkRed"; + TagColor["LightOrange"] = "LightOrange"; + TagColor["Orange"] = "Orange"; + TagColor["DarkOrange"] = "DarkOrange"; + TagColor["LightAmber"] = "LightAmber"; + TagColor["Amber"] = "Amber"; + TagColor["DarkAmber"] = "DarkAmber"; + TagColor["LightYellow"] = "LightYellow"; + TagColor["Yellow"] = "Yellow"; + TagColor["DarkYellow"] = "DarkYellow"; + TagColor["LightLime"] = "LightLime"; + TagColor["Lime"] = "Lime"; + TagColor["DarkLime"] = "DarkLime"; + TagColor["LightGreen"] = "LightGreen"; + TagColor["Green"] = "Green"; + TagColor["DarkGreen"] = "DarkGreen"; + TagColor["LightEmerald"] = "LightEmerald"; + TagColor["Emerald"] = "Emerald"; + TagColor["DarkEmerald"] = "DarkEmerald"; + TagColor["LightTeal"] = "LightTeal"; + TagColor["Teal"] = "Teal"; + TagColor["DarkTeal"] = "DarkTeal"; + TagColor["LightCyan"] = "LightCyan"; + TagColor["Cyan"] = "Cyan"; + TagColor["DarkCyan"] = "DarkCyan"; + TagColor["LightSky"] = "LightSky"; + TagColor["Sky"] = "Sky"; + TagColor["DarkSky"] = "DarkSky"; + TagColor["LightBlue"] = "LightBlue"; + TagColor["Blue"] = "Blue"; + TagColor["DarkBlue"] = "DarkBlue"; + TagColor["LightIndigo"] = "LightIndigo"; + TagColor["Indigo"] = "Indigo"; + TagColor["DarkIndigo"] = "DarkIndigo"; + TagColor["LightViolet"] = "LightViolet"; + TagColor["Violet"] = "Violet"; + TagColor["DarkViolet"] = "DarkViolet"; + TagColor["LightPurple"] = "LightPurple"; + TagColor["Purple"] = "Purple"; + TagColor["DarkPurple"] = "DarkPurple"; + TagColor["LightFuchsia"] = "LightFuchsia"; + TagColor["Fuchsia"] = "Fuchsia"; + TagColor["DarkFuchsia"] = "DarkFuchsia"; + TagColor["LightPink"] = "LightPink"; + TagColor["Pink"] = "Pink"; + TagColor["DarkPink"] = "DarkPink"; + TagColor["LightRose"] = "LightRose"; + TagColor["Rose"] = "Rose"; + TagColor["DarkRose"] = "DarkRose"; +})(TagColor || (TagColor = {})); export var Operation; (function (Operation) { Operation["None"] = "None"; @@ -205,6 +262,7 @@ export var Operation; Operation["UpdateAlerter"] = "UpdateAlerter"; Operation["RenameAlerter"] = "RenameAlerter"; Operation["DeleteAlerter"] = "DeleteAlerter"; + Operation["TestAlerter"] = "TestAlerter"; Operation["CreateServerTemplate"] = "CreateServerTemplate"; Operation["UpdateServerTemplate"] = "UpdateServerTemplate"; Operation["RenameServerTemplate"] = "RenameServerTemplate"; diff --git a/frontend/public/favicon-16x16.png b/frontend/public/favicon-16x16.png deleted file mode 100644 index d114e55973cb124c3b7053217166d176dd0b2bdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 869 zcmV-r1DgDaP)Px&A4x<(R5(vnlTC;mMHI*Xud92uJ8L%L2fAh^UDMq&6IXGJrz}}K#1&N5kK__B zioz~|1WzJ@7(XBZ6(u=H3=6rKi-@cQ!$$Dq6o{ac2(pA_UgNaaStmu388?3wdWkSQnYG8%&Gw zdU658(2?}gZ^}XCi+;cNlwpZ`JDpD7wjC9P&91(K=N6a2cv`SI*Zeh%ZaEW5CEr(l z-Uq@l&eVO*_HBi=Yioa(+~VV5EBS^3H`v3-Y7AOP)|1ug>FI5@ZQq5A=0SJ};FpQ9 z&IYpqaXd`J&-KrI&zlF)pX5pZ3?ZvVX*xSIGjm(Fy_qr@n>ow;t2gKup!m*Q&WiiJ zfepbuA*_!YVdJ!v(iTi7v_OM^vvC@|@9pyUk-#M|HWX*~4D;VA1rZD(HaHu66}8eO zedibDMe<#*&%mL$6|V$Exu_JKvU0ii27~TvtY~UjNWp)AjdS*dEj!sO4>y273; z*2&-H^+9wfZbd7e^xh&8&+oWnYWAOYTVFT zw@L;#spJNGG7M^fu@JZ7)gjp`F)P^gShjxUTL9)mm-Aoyp^VMb;b;oILX^bW(kn&+g v#u60o2iP4AyGR-8dziEwN69a@W|8qP@YP`yr#ooF00000NkvXXu0mjf7QUZO diff --git a/frontend/public/favicon-32x32.png b/frontend/public/favicon-32x32.png deleted file mode 100644 index 07aac79615b5d1f6367b556238e05fdb09cb18d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2120 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 XML^`ciCYAT00000NkvXXu0mjfm0pXr literal 0 HcmV?d00001 diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico index 2ef3d3ad21606fab31bd30ae7718c0ecc5f80a68..1c900b85f3ba4aaec30296def7d2bc2ca9c973f5 100644 GIT binary patch literal 15086 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 Dtny7a diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 000000000..ed4edc02d --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,71 @@ + + + + + + + + \ No newline at end of file diff --git a/frontend/public/komodo-192x192.png b/frontend/public/komodo-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..e138e72c7a42dfe796b31ef0d5d12b40132f54c2 GIT binary patch literal 36895 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 literal 0 HcmV?d00001 diff --git a/frontend/public/komodo-2q2code.png b/frontend/public/komodo-2q2code.png new file mode 100644 index 0000000000000000000000000000000000000000..b15f606998280c1cf9e0c9fbcd3fa140878d8abf GIT binary patch literal 197511 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