mirror of
https://github.com/moghtech/komodo.git
synced 2026-10-30 21:26:18 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf3f2e0503 | ||
|
|
780ac68b99 | ||
|
|
221ad252e1 | ||
|
|
7406c292a1 | ||
|
|
427475651c | ||
|
|
ae0c8bf98a | ||
|
|
a7592f4561 | ||
|
|
a199c8fe27 | ||
|
|
f7296c73b1 | ||
|
|
0eaf8b5f01 | ||
|
|
6919582f69 |
Generated
+118
-812
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -9,7 +9,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "2.3.2"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/moghtech/komodo"
|
||||
@@ -41,7 +41,7 @@ mogh_rate_limit = "1.0.1"
|
||||
partial_derive2 = "0.5.0"
|
||||
mongo_indexed = "2.1.0"
|
||||
mogh_resolver = "1.0.0"
|
||||
mogh_config = "1.1.0"
|
||||
mogh_config = "1.4.2"
|
||||
mogh_logger = "1.3.4"
|
||||
mogh_server = "1.5.0"
|
||||
toml_pretty = "2.0.0"
|
||||
@@ -96,7 +96,7 @@ dotenvy = "0.15.7"
|
||||
envy = "0.4.2"
|
||||
|
||||
# CRYPTO / AUTH
|
||||
uuid = { version = "1.24.0", features = ["v4", "fast-rng", "serde"] }
|
||||
uuid = { version = "1.25.0", features = ["v4", "fast-rng", "serde"] }
|
||||
rustls = { version = "0.23.43", features = ["aws-lc-rs"] }
|
||||
data-encoding = "2.11.1"
|
||||
urlencoding = "2.1.3"
|
||||
@@ -112,20 +112,20 @@ hickory-resolver = "0.26.1"
|
||||
portable-pty = "0.9.0"
|
||||
shell-escape = "0.1.5"
|
||||
crossterm = "0.29.0"
|
||||
bollard = "0.21.0"
|
||||
bollard = "0.21.1"
|
||||
sysinfo = "0.39.6"
|
||||
shlex = "2.0.1"
|
||||
|
||||
# CLOUD
|
||||
aws-config = "1.10.1"
|
||||
aws-sdk-ec2 = "1.246.0"
|
||||
aws-config = "1.11.0"
|
||||
aws-sdk-ec2 = "1.252.0"
|
||||
aws-credential-types = "1.3.0"
|
||||
|
||||
## CRON
|
||||
english-to-cron = "0.1.7"
|
||||
chrono-tz = "0.10.4"
|
||||
chrono = "0.4.45"
|
||||
croner = "3.0.1"
|
||||
croner = "4.0.0"
|
||||
|
||||
# MISC
|
||||
async-compression = { version = "0.4.43", features = ["tokio", "gzip"] }
|
||||
|
||||
+1
-5
@@ -1,5 +1 @@
|
||||
{
|
||||
"imports": {
|
||||
"@std/toml": "jsr:@std/toml"
|
||||
}
|
||||
}
|
||||
{}
|
||||
+1
-42
@@ -1,5 +1,3 @@
|
||||
import * as TOML from "@std/toml";
|
||||
|
||||
export const run = async (action: string) => {
|
||||
const branch = await new Deno.Command("bash", {
|
||||
args: ["-c", "git rev-parse --abbrev-ref HEAD"],
|
||||
@@ -7,46 +5,7 @@ export const run = async (action: string) => {
|
||||
.output()
|
||||
.then((r) => new TextDecoder("utf-8").decode(r.stdout).trim());
|
||||
|
||||
const cargo_toml_str = await Deno.readTextFile("Cargo.toml");
|
||||
const prev_version = (
|
||||
TOML.parse(cargo_toml_str) as {
|
||||
workspace: { package: { version: string } };
|
||||
}
|
||||
).workspace.package.version;
|
||||
|
||||
const [version, tag, count] = prev_version.split("-");
|
||||
const next_count = Number(count) + 1;
|
||||
|
||||
const next_version = `${version}-${tag}-${next_count}`;
|
||||
|
||||
await Deno.writeTextFile(
|
||||
"Cargo.toml",
|
||||
cargo_toml_str.replace(
|
||||
`version = "${prev_version}"`,
|
||||
`version = "${next_version}"`
|
||||
)
|
||||
);
|
||||
|
||||
// Cargo check first here to make sure lock file is updated before commit.
|
||||
const cmd = `
|
||||
cargo check
|
||||
echo ""
|
||||
|
||||
git add --all
|
||||
git commit --all --message "deploy ${version}-${tag}-${next_count}"
|
||||
|
||||
echo ""
|
||||
git push
|
||||
echo ""
|
||||
|
||||
km run -y action ${action} "KOMODO_BRANCH=${branch}&KOMODO_VERSION=${version}&KOMODO_TAG=${tag}-${next_count}"
|
||||
`
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0 && !line.startsWith("//"))
|
||||
.join(" && ");
|
||||
|
||||
new Deno.Command("bash", {
|
||||
args: ["-c", cmd],
|
||||
args: ["-c", `km run -y action ${action} "BRANCH=${branch}"`],
|
||||
}).spawn();
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## Builds the Komodo Core, Periphery, and Util binaries
|
||||
## for a specific architecture. Requires OpenSSL 3 or later.
|
||||
|
||||
FROM rust:1.97.1-bookworm AS builder
|
||||
RUN cargo install cargo-strip
|
||||
FROM rust:1.98.0-bookworm AS builder
|
||||
RUN cargo install cargo-strip cargo-edit
|
||||
|
||||
WORKDIR /builder
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
@@ -14,6 +14,11 @@ COPY ./bin/periphery ./bin/periphery
|
||||
COPY ./bin/cli ./bin/cli
|
||||
COPY ./xtask ./xtask
|
||||
|
||||
# Set Version
|
||||
ARG VERSION="0.0.0"
|
||||
ARG IMAGE_TAG=""
|
||||
RUN cargo set-version -p komodo_core ${VERSION}${IMAGE_TAG:+-${IMAGE_TAG}}
|
||||
|
||||
# Compile bin
|
||||
RUN \
|
||||
cargo build -p komodo_core --release && \
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## Uses chef for dependency caching to help speed up back-to-back builds.
|
||||
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.97.1-bookworm AS chef
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1.98.0-bookworm AS chef
|
||||
WORKDIR /builder
|
||||
|
||||
# Plan just the RECIPE to see if things have changed
|
||||
@@ -12,12 +12,20 @@ COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM chef AS builder
|
||||
RUN cargo install cargo-strip
|
||||
COPY --from=planner /builder/recipe.json recipe.json
|
||||
RUN cargo install cargo-strip cargo-edit
|
||||
|
||||
# Build JUST dependencies - cached layer
|
||||
COPY --from=planner /builder/recipe.json recipe.json
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
# NOW copy again (this time into builder) and build app
|
||||
|
||||
# NOW copy again (this time into builder), set version, and build app
|
||||
COPY . .
|
||||
|
||||
# Set Version
|
||||
ARG VERSION="0.0.0"
|
||||
ARG IMAGE_TAG=""
|
||||
RUN cargo set-version -p komodo_core ${VERSION}${IMAGE_TAG:+-${IMAGE_TAG}}
|
||||
|
||||
RUN \
|
||||
cargo build --release --bin core && \
|
||||
cargo build --release --bin periphery && \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM rust:1.97.1-trixie AS builder
|
||||
RUN cargo install cargo-strip
|
||||
FROM rust:1.98.0-trixie AS builder
|
||||
RUN cargo install cargo-strip cargo-edit
|
||||
|
||||
WORKDIR /builder
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
@@ -8,6 +8,11 @@ COPY ./client/core/rs ./client/core/rs
|
||||
COPY ./client/periphery ./client/periphery
|
||||
COPY ./bin/cli ./bin/cli
|
||||
|
||||
# Set Version
|
||||
ARG VERSION="0.0.0"
|
||||
ARG IMAGE_TAG=""
|
||||
RUN cargo set-version -p komodo_cli ${VERSION}${IMAGE_TAG:+-${IMAGE_TAG}}
|
||||
|
||||
# Compile bin
|
||||
RUN cargo build -p komodo_cli --release && cargo strip
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
# Build Core
|
||||
FROM rust:1.97.1-trixie AS core-builder
|
||||
RUN cargo install cargo-strip
|
||||
FROM rust:1.98.0-trixie AS core-builder
|
||||
RUN cargo install cargo-strip cargo-edit
|
||||
|
||||
WORKDIR /builder
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
@@ -13,6 +13,11 @@ COPY ./bin/core ./bin/core
|
||||
COPY ./bin/cli ./bin/cli
|
||||
COPY ./xtask ./xtask
|
||||
|
||||
# Set Version
|
||||
ARG VERSION="0.0.0"
|
||||
ARG IMAGE_TAG=""
|
||||
RUN cargo set-version -p komodo_core ${VERSION}${IMAGE_TAG:+-${IMAGE_TAG}}
|
||||
|
||||
# Compile app
|
||||
RUN cargo build -p komodo_core --release && \
|
||||
cargo build -p komodo_cli --release && \
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use axum::{Router, extract::Path, http::HeaderMap, routing::post};
|
||||
use derive_variants::{EnumVariants, ExtractVariant};
|
||||
use komodo_client::{api::auth::*, entities::user::User};
|
||||
use reqwest::StatusCode;
|
||||
use resolver_api::Resolve;
|
||||
use response::Response;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use serror::{AddStatusCode, Json};
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::{
|
||||
get_user_id_from_headers,
|
||||
github::{self, client::github_oauth_client},
|
||||
google::{self, client::google_oauth_client},
|
||||
oidc::{self, client::oidc_client},
|
||||
},
|
||||
config::core_config,
|
||||
helpers::query::get_user,
|
||||
state::jwt_client,
|
||||
};
|
||||
|
||||
use super::Variant;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AuthArgs {
|
||||
pub headers: HeaderMap,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[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 {
|
||||
GetLoginOptions(GetLoginOptions),
|
||||
SignUpLocalUser(SignUpLocalUser),
|
||||
LoginLocalUser(LoginLocalUser),
|
||||
ExchangeForJwt(ExchangeForJwt),
|
||||
GetUser(GetUser),
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
let mut router = Router::new()
|
||||
.route("/", post(handler))
|
||||
.route("/{variant}", post(variant_handler));
|
||||
|
||||
if core_config().local_auth {
|
||||
info!("🔑 Local Login Enabled");
|
||||
}
|
||||
|
||||
if github_oauth_client().is_some() {
|
||||
info!("🔑 Github Login Enabled");
|
||||
router = router.nest("/github", github::router())
|
||||
}
|
||||
|
||||
if google_oauth_client().is_some() {
|
||||
info!("🔑 Google Login Enabled");
|
||||
router = router.nest("/google", google::router())
|
||||
}
|
||||
|
||||
if core_config().oidc_enabled {
|
||||
info!("🔑 OIDC Login Enabled");
|
||||
router = router.nest("/oidc", oidc::router())
|
||||
}
|
||||
|
||||
router
|
||||
}
|
||||
|
||||
async fn variant_handler(
|
||||
headers: HeaderMap,
|
||||
Path(Variant { variant }): Path<Variant>,
|
||||
Json(params): Json<serde_json::Value>,
|
||||
) -> serror::Result<axum::response::Response> {
|
||||
let req: AuthRequest = serde_json::from_value(json!({
|
||||
"type": variant,
|
||||
"params": params,
|
||||
}))?;
|
||||
handler(headers, Json(req)).await
|
||||
}
|
||||
|
||||
#[instrument(name = "AuthHandler", level = "debug", skip(headers))]
|
||||
async fn handler(
|
||||
headers: HeaderMap,
|
||||
Json(request): Json<AuthRequest>,
|
||||
) -> serror::Result<axum::response::Response> {
|
||||
let timer = Instant::now();
|
||||
let req_id = Uuid::new_v4();
|
||||
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.error);
|
||||
}
|
||||
let elapsed = timer.elapsed();
|
||||
debug!("/auth request {req_id} | resolve time: {elapsed:?}");
|
||||
res.map(|res| res.0)
|
||||
}
|
||||
|
||||
fn login_options_response() -> GetLoginOptionsResponse {
|
||||
let config = core_config();
|
||||
GetLoginOptionsResponse {
|
||||
local: config.local_auth,
|
||||
github: github_oauth_client().is_some(),
|
||||
google: google_oauth_client().is_some(),
|
||||
oidc: oidc_client().load().is_some(),
|
||||
registration_disabled: config.disable_user_registration,
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<AuthArgs> for GetLoginOptions {
|
||||
#[instrument(name = "GetLoginOptions", level = "debug", skip(self))]
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &AuthArgs,
|
||||
) -> serror::Result<GetLoginOptionsResponse> {
|
||||
Ok(login_options_response())
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<AuthArgs> for ExchangeForJwt {
|
||||
#[instrument(name = "ExchangeForJwt", level = "debug", skip(self))]
|
||||
async fn resolve(
|
||||
self,
|
||||
_: &AuthArgs,
|
||||
) -> serror::Result<ExchangeForJwtResponse> {
|
||||
jwt_client()
|
||||
.redeem_exchange_token(&self.token)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<AuthArgs> for GetUser {
|
||||
#[instrument(name = "GetUser", level = "debug", skip(self))]
|
||||
async fn resolve(
|
||||
self,
|
||||
AuthArgs { headers }: &AuthArgs,
|
||||
) -> serror::Result<User> {
|
||||
let user_id = get_user_id_from_headers(headers)
|
||||
.await
|
||||
.status_code(StatusCode::UNAUTHORIZED)?;
|
||||
get_user(&user_id)
|
||||
.await
|
||||
.status_code(StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use komodo_client::{
|
||||
permission::PermissionLevel,
|
||||
procedure::{Procedure, ProcedureStage},
|
||||
repo::Repo,
|
||||
resource::ResourceQuery,
|
||||
stack::Stack,
|
||||
update::Update,
|
||||
user::procedure_user,
|
||||
@@ -33,7 +34,10 @@ use crate::{
|
||||
state::{all_resources_cache, db_client},
|
||||
};
|
||||
|
||||
use super::update::{init_execution_update, update_update};
|
||||
use super::{
|
||||
query::get_all_tags,
|
||||
update::{init_execution_update, update_update},
|
||||
};
|
||||
|
||||
pub async fn execute_procedure(
|
||||
procedure: &Procedure,
|
||||
@@ -109,95 +113,41 @@ async fn execute_procedure_stage(
|
||||
update: &Mutex<Update>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut executions = Vec::with_capacity(_executions.capacity());
|
||||
for execution in _executions {
|
||||
match execution {
|
||||
Execution::BatchRunAction(exec) => {
|
||||
extend_batch_exection::<BatchRunAction>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
// Expands the batch executions into their matching
|
||||
// single executions, and passes through the rest.
|
||||
macro_rules! expand_batch_executions {
|
||||
($($Variant:ident),* $(,)?) => {
|
||||
for execution in _executions {
|
||||
match execution {
|
||||
$(
|
||||
Execution::$Variant(exec) => {
|
||||
extend_batch_execution::<$Variant>(
|
||||
&exec.pattern,
|
||||
&exec.tags,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
)*
|
||||
execution => executions.push(execution),
|
||||
}
|
||||
}
|
||||
Execution::BatchRunProcedure(exec) => {
|
||||
extend_batch_exection::<BatchRunProcedure>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchRunBuild(exec) => {
|
||||
extend_batch_exection::<BatchRunBuild>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchCloneRepo(exec) => {
|
||||
extend_batch_exection::<BatchCloneRepo>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchPullRepo(exec) => {
|
||||
extend_batch_exection::<BatchPullRepo>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchBuildRepo(exec) => {
|
||||
extend_batch_exection::<BatchBuildRepo>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchDeploy(exec) => {
|
||||
extend_batch_exection::<BatchDeploy>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchDestroyDeployment(exec) => {
|
||||
extend_batch_exection::<BatchDestroyDeployment>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchDeployStack(exec) => {
|
||||
extend_batch_exection::<BatchDeployStack>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchDeployStackIfChanged(exec) => {
|
||||
extend_batch_exection::<BatchDeployStackIfChanged>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchPullStack(exec) => {
|
||||
extend_batch_exection::<BatchPullStack>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Execution::BatchDestroyStack(exec) => {
|
||||
extend_batch_exection::<BatchDestroyStack>(
|
||||
&exec.pattern,
|
||||
&mut executions,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
execution => executions.push(execution),
|
||||
}
|
||||
};
|
||||
}
|
||||
expand_batch_executions!(
|
||||
BatchRunAction,
|
||||
BatchRunProcedure,
|
||||
BatchRunBuild,
|
||||
BatchCloneRepo,
|
||||
BatchPullRepo,
|
||||
BatchBuildRepo,
|
||||
BatchDeploy,
|
||||
BatchDestroyDeployment,
|
||||
BatchDeployStack,
|
||||
BatchDeployStackIfChanged,
|
||||
BatchPullStack,
|
||||
BatchDestroyStack,
|
||||
);
|
||||
let futures = executions.into_iter().map(|execution| async move {
|
||||
let now = Instant::now();
|
||||
add_line_to_update(
|
||||
@@ -550,18 +500,27 @@ async fn add_line_to_update(update: &Mutex<Update>, line: &str) {
|
||||
};
|
||||
}
|
||||
|
||||
async fn extend_batch_exection<E: ExtendBatch>(
|
||||
async fn extend_batch_execution<E: ExtendBatch>(
|
||||
pattern: &str,
|
||||
tags: &[String],
|
||||
executions: &mut Vec<Execution>,
|
||||
) -> anyhow::Result<()> {
|
||||
let all_tags = if tags.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
get_all_tags(None).await?
|
||||
};
|
||||
let more = list_full_for_user_using_pattern::<E::Resource>(
|
||||
pattern,
|
||||
Default::default(),
|
||||
ResourceQuery {
|
||||
tags: tags.to_vec(),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
procedure_user(),
|
||||
PermissionLevel::Read.into(),
|
||||
&[],
|
||||
&all_tags,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
|
||||
+116
-119
@@ -9,24 +9,26 @@
|
||||
//! - Automatic container environment detection
|
||||
//! - Interface validation (existence and UP state)
|
||||
//! - Gateway discovery from routing tables or network configuration
|
||||
//! - Safe default route modification with privilege checking
|
||||
//! - Atomic default route replacement (never leaves the container without a default route)
|
||||
//! - Comprehensive error handling and logging
|
||||
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use tokio::process::Command;
|
||||
use tracing::{debug, info, trace, warn};
|
||||
|
||||
/// Standard gateway addresses to test for Docker networks
|
||||
const DOCKER_GATEWAY_CANDIDATES: &[&str] = &[".1", ".254"];
|
||||
|
||||
/// Container environment detection files
|
||||
const DOCKERENV_FILE: &str = "/.dockerenv";
|
||||
const CONTAINERENV_FILE: &str = "/run/.containerenv";
|
||||
const CGROUP_FILE: &str = "/proc/1/cgroup";
|
||||
|
||||
/// Check if running in container environment
|
||||
fn is_container_environment() -> bool {
|
||||
// Check for Docker-specific indicators
|
||||
if std::path::Path::new(DOCKERENV_FILE).exists() {
|
||||
// Check for Docker / Podman specific indicators
|
||||
if std::path::Path::new(DOCKERENV_FILE).exists()
|
||||
|| std::path::Path::new(CONTAINERENV_FILE).exists()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,7 +37,9 @@ fn is_container_environment() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check cgroup for container runtime indicators
|
||||
// Check cgroup for container runtime indicators.
|
||||
// Only reliable on cgroup v1 - on cgroup v2 the file
|
||||
// is usually just `0::/`, so the checks above carry detection.
|
||||
if let Ok(content) = std::fs::read_to_string(CGROUP_FILE)
|
||||
&& (content.contains("docker") || content.contains("containerd"))
|
||||
{
|
||||
@@ -51,23 +55,24 @@ pub async fn configure_internet_gateway() {
|
||||
|
||||
let config = core_config();
|
||||
|
||||
if config.internet_interface.is_empty() {
|
||||
debug!("No interface specified, using default routing");
|
||||
return;
|
||||
}
|
||||
|
||||
if !is_container_environment() {
|
||||
debug!("Not in container, skipping network configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
if !config.internet_interface.is_empty() {
|
||||
debug!(
|
||||
"Configuring internet interface: {}",
|
||||
config.internet_interface
|
||||
);
|
||||
if let Err(e) =
|
||||
configure_manual_interface(&config.internet_interface).await
|
||||
{
|
||||
warn!("Failed to configure internet gateway: {e:#}");
|
||||
}
|
||||
} else {
|
||||
debug!("No interface specified, using default routing");
|
||||
debug!(
|
||||
"Configuring internet interface: {}",
|
||||
config.internet_interface
|
||||
);
|
||||
if let Err(e) =
|
||||
configure_manual_interface(&config.internet_interface).await
|
||||
{
|
||||
warn!("Failed to configure internet gateway: {e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +96,10 @@ async fn configure_manual_interface(
|
||||
|
||||
let interface_info =
|
||||
String::from_utf8_lossy(&interface_check.stdout);
|
||||
if !interface_info.contains("state UP") {
|
||||
// tun / wireguard style interfaces report `state UNKNOWN` while operational
|
||||
if !interface_info.contains("state UP")
|
||||
&& !interface_info.contains("state UNKNOWN")
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"Interface '{}' is not UP. Please ensure the interface is enabled and connected",
|
||||
interface_name
|
||||
@@ -159,7 +167,7 @@ async fn find_gateway(
|
||||
.context("Failed to get routes for interface")?;
|
||||
|
||||
if route_output.status.success() {
|
||||
let routes = String::from_utf8(route_output.stdout)?;
|
||||
let routes = String::from_utf8_lossy(&route_output.stdout);
|
||||
trace!("Routes for {}: {}", interface_name, routes.trim());
|
||||
|
||||
// Look for routes with gateway
|
||||
@@ -179,60 +187,75 @@ async fn find_gateway(
|
||||
}
|
||||
}
|
||||
|
||||
// Derive gateway from network configuration (Docker standard: .1)
|
||||
if let Some(network_base) = ip_cidr.split('/').next() {
|
||||
let ip_parts: Vec<&str> = network_base.split('.').collect();
|
||||
if ip_parts.len() == 4 {
|
||||
let potential_gateways: Vec<String> = DOCKER_GATEWAY_CANDIDATES
|
||||
.iter()
|
||||
.map(|suffix| {
|
||||
format!(
|
||||
"{}.{}.{}{}",
|
||||
ip_parts[0], ip_parts[1], ip_parts[2], suffix
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
// Derive candidates from the interface subnet
|
||||
let candidates = derive_gateway_candidates(ip_cidr)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Could not determine gateway for interface '{interface_name}' in network '{ip_cidr}'"
|
||||
)
|
||||
})?;
|
||||
|
||||
for gateway in potential_gateways {
|
||||
trace!(
|
||||
"Testing potential gateway {} for {}",
|
||||
gateway, interface_name
|
||||
);
|
||||
for gateway in &candidates {
|
||||
trace!(
|
||||
"Testing potential gateway {} for {}",
|
||||
gateway, interface_name
|
||||
);
|
||||
|
||||
// Check if gateway is reachable
|
||||
let route_test = Command::new("ip")
|
||||
.args(["route", "get", &gateway, "dev", interface_name])
|
||||
.output()
|
||||
.await;
|
||||
// Note: `ip route get` only confirms the address is on-link
|
||||
// for this interface, not that a router actually answers there.
|
||||
let route_test = Command::new("ip")
|
||||
.args(["route", "get", gateway, "dev", interface_name])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
if let Ok(output) = route_test
|
||||
&& output.status.success()
|
||||
{
|
||||
trace!(
|
||||
"Gateway {} is reachable via {}",
|
||||
gateway, interface_name
|
||||
);
|
||||
return Ok(gateway.to_string());
|
||||
}
|
||||
|
||||
// Fallback: assume .1 is gateway (Docker standard)
|
||||
if gateway.ends_with(".1") {
|
||||
trace!(
|
||||
"Assuming Docker gateway {} for {}",
|
||||
gateway, interface_name
|
||||
);
|
||||
return Ok(gateway.to_string());
|
||||
}
|
||||
}
|
||||
if let Ok(output) = route_test
|
||||
&& output.status.success()
|
||||
{
|
||||
trace!("Gateway {} is on-link via {}", gateway, interface_name);
|
||||
return Ok(gateway.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"Could not determine gateway for interface '{}' in network '{}'. \
|
||||
Ensure the interface is properly configured with a valid gateway",
|
||||
interface_name,
|
||||
ip_cidr
|
||||
))
|
||||
// Fall back to the first host address (Docker standard)
|
||||
let gateway = candidates
|
||||
.into_iter()
|
||||
.next()
|
||||
.context("No gateway candidates derived")?;
|
||||
trace!(
|
||||
"Assuming Docker gateway {} for {}",
|
||||
gateway, interface_name
|
||||
);
|
||||
Ok(gateway)
|
||||
}
|
||||
|
||||
/// Derive candidate gateway addresses for the interface subnet:
|
||||
/// the first and last usable host addresses. Docker assigns the
|
||||
/// gateway the first host address of the subnet regardless of prefix length.
|
||||
fn derive_gateway_candidates(
|
||||
ip_cidr: &str,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let (ip, prefix) = ip_cidr
|
||||
.split_once('/')
|
||||
.context("Address is not in CIDR (ip/prefix) format")?;
|
||||
let ip: Ipv4Addr =
|
||||
ip.parse().context("Failed to parse IPv4 address")?;
|
||||
let prefix: u32 =
|
||||
prefix.parse().context("Failed to parse CIDR prefix")?;
|
||||
if !(1..=30).contains(&prefix) {
|
||||
return Err(anyhow!(
|
||||
"Subnet /{prefix} cannot contain a distinct gateway address"
|
||||
));
|
||||
}
|
||||
|
||||
let mask = u32::MAX << (32 - prefix);
|
||||
let network = u32::from(ip) & mask;
|
||||
let first_host = network + 1;
|
||||
let last_host = (network | !mask) - 1;
|
||||
|
||||
Ok(vec![
|
||||
Ipv4Addr::from(first_host).to_string(),
|
||||
Ipv4Addr::from(last_host).to_string(),
|
||||
])
|
||||
}
|
||||
|
||||
/// Set default gateway to use specified interface
|
||||
@@ -245,48 +268,33 @@ async fn set_default_gateway(
|
||||
gateway, interface_name
|
||||
);
|
||||
|
||||
// Check if we have network privileges
|
||||
if !check_network_privileges().await {
|
||||
warn!(
|
||||
"⚠️ Container lacks network privileges (NET_ADMIN capability required)"
|
||||
);
|
||||
warn!(
|
||||
"Add 'cap_add: [\"NET_ADMIN\"]' to your docker-compose.yaml"
|
||||
);
|
||||
return Err(anyhow!(
|
||||
"Insufficient network privileges to modify routing table. \
|
||||
Container needs NET_ADMIN capability to configure network interfaces"
|
||||
));
|
||||
}
|
||||
|
||||
// Remove existing default routes
|
||||
let remove_default = Command::new("sh")
|
||||
.args(["-c", "ip route del default 2>/dev/null || true"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
if let Ok(output) = remove_default
|
||||
&& output.status.success()
|
||||
{
|
||||
trace!("Removed existing default routes");
|
||||
}
|
||||
|
||||
// Add new default route
|
||||
let add_default_cmd = format!(
|
||||
"ip route add default via {gateway} dev {interface_name}"
|
||||
);
|
||||
trace!("Adding default route: {}", add_default_cmd);
|
||||
|
||||
let add_default = Command::new("sh")
|
||||
.args(["-c", &add_default_cmd])
|
||||
// `replace` swaps the default route atomically, so a failure
|
||||
// never leaves the container without any default route.
|
||||
let replace = Command::new("ip")
|
||||
.args([
|
||||
"route",
|
||||
"replace",
|
||||
"default",
|
||||
"via",
|
||||
gateway,
|
||||
"dev",
|
||||
interface_name,
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to add default route")?;
|
||||
.context("Failed to run ip route replace")?;
|
||||
|
||||
if !add_default.status.success() {
|
||||
let error = String::from_utf8_lossy(&add_default.stderr)
|
||||
.trim()
|
||||
.to_string();
|
||||
if !replace.status.success() {
|
||||
let error =
|
||||
String::from_utf8_lossy(&replace.stderr).trim().to_string();
|
||||
if error.contains("Operation not permitted") {
|
||||
warn!(
|
||||
"⚠️ Container lacks network privileges (NET_ADMIN capability required)"
|
||||
);
|
||||
warn!(
|
||||
"Add 'cap_add: [\"NET_ADMIN\"]' to your docker-compose.yaml"
|
||||
);
|
||||
}
|
||||
return Err(anyhow!(
|
||||
"❌ Failed to set default gateway via '{}': {}. \
|
||||
Verify interface configuration and network permissions",
|
||||
@@ -298,14 +306,3 @@ async fn set_default_gateway(
|
||||
trace!("Default gateway set to {} via {}", gateway, interface_name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if we have sufficient network privileges
|
||||
async fn check_network_privileges() -> bool {
|
||||
// Try to test NET_ADMIN capability with a harmless route operation
|
||||
let capability_test = Command::new("sh")
|
||||
.args(["-c", "ip route add 198.51.100.1/32 dev lo 2>/dev/null && ip route del 198.51.100.1/32 dev lo 2>/dev/null"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
matches!(capability_test, Ok(output) if output.status.success())
|
||||
}
|
||||
|
||||
@@ -145,16 +145,18 @@ impl super::KomodoResource for Stack {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let default_git = (
|
||||
stack.config.git_provider,
|
||||
stack.config.repo,
|
||||
stack.config.branch,
|
||||
stack.config.git_https,
|
||||
String::new(),
|
||||
);
|
||||
let default_git = || {
|
||||
(
|
||||
stack.config.git_provider,
|
||||
stack.config.repo,
|
||||
stack.config.branch,
|
||||
stack.config.git_https,
|
||||
String::new(),
|
||||
)
|
||||
};
|
||||
let (git_provider, repo, branch, git_https, linked_repo_name) =
|
||||
if stack.config.linked_repo.is_empty() {
|
||||
default_git
|
||||
default_git()
|
||||
} else {
|
||||
all_resources_cache()
|
||||
.load()
|
||||
@@ -169,7 +171,7 @@ impl super::KomodoResource for Stack {
|
||||
r.name.clone(),
|
||||
)
|
||||
})
|
||||
.unwrap_or(default_git)
|
||||
.unwrap_or_else(default_git)
|
||||
};
|
||||
|
||||
// This is only true if it is KNOWN to be true. so other cases are false.
|
||||
|
||||
@@ -109,14 +109,19 @@ pub async fn on_startup() {
|
||||
// Configure manual network interface if specified
|
||||
network::configure_internet_gateway().await;
|
||||
|
||||
// Run legacy database fixes
|
||||
tokio::join!(
|
||||
clean_up_server_templates(),
|
||||
v2_init_missing_resource_info(),
|
||||
);
|
||||
|
||||
// Standard startup tasks
|
||||
tokio::join!(
|
||||
in_progress_update_cleanup(),
|
||||
open_alert_cleanup(),
|
||||
action_api_key_cleanup(),
|
||||
ensure_first_server_and_builder(),
|
||||
ensure_init_user_and_resources(),
|
||||
clean_up_server_templates(),
|
||||
v2_init_missing_resource_info(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## All in one, multi stage compile + runtime Docker build for your architecture.
|
||||
|
||||
FROM rust:1.97.1-trixie AS builder
|
||||
RUN cargo install cargo-strip
|
||||
FROM rust:1.98.0-trixie AS builder
|
||||
RUN cargo install cargo-strip cargo-edit
|
||||
|
||||
WORKDIR /builder
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
@@ -11,6 +11,11 @@ COPY ./client/periphery ./client/periphery
|
||||
COPY ./bin/periphery ./bin/periphery
|
||||
COPY ./xtask ./xtask
|
||||
|
||||
# Set Version
|
||||
ARG VERSION="0.0.0"
|
||||
ARG IMAGE_TAG=""
|
||||
RUN cargo set-version -p komodo_periphery ${VERSION}${IMAGE_TAG:+-${IMAGE_TAG}}
|
||||
|
||||
# Compile app
|
||||
RUN cargo build -p komodo_periphery --release && cargo strip
|
||||
|
||||
|
||||
@@ -304,15 +304,36 @@ impl Resolve<crate::api::Args> for build::Build {
|
||||
// Do this in fallible block to ensure
|
||||
// cancel token removed from cache on failure.
|
||||
let command = async {
|
||||
// Add VERSION to build args (if not already there)
|
||||
let mut build_args = environment_vars_from_str(build_args)
|
||||
.context("Invalid build_args")?;
|
||||
.context("Invalid build_args")?;
|
||||
|
||||
// Add IMAGE_NAME to build args
|
||||
if !build_args.iter().any(|a| a.variable == "IMAGE_NAME") {
|
||||
build_args.push(EnvironmentVar {
|
||||
variable: String::from("IMAGE_NAME"),
|
||||
value: if build.config.image_name.is_empty() {
|
||||
build.name.clone()
|
||||
} else {
|
||||
build.config.image_name.clone()
|
||||
},
|
||||
});
|
||||
}
|
||||
// Maybe Add IMAGE_TAG to build args
|
||||
if !build.config.image_tag.is_empty()
|
||||
&& !build_args.iter().any(|a| a.variable == "IMAGE_TAG") {
|
||||
build_args.push(EnvironmentVar {
|
||||
variable: String::from("IMAGE_TAG"),
|
||||
value: build.config.image_tag.clone(),
|
||||
});
|
||||
}
|
||||
// Add VERSION to build args
|
||||
if !build_args.iter().any(|a| a.variable == "VERSION") {
|
||||
build_args.push(EnvironmentVar {
|
||||
variable: String::from("VERSION"),
|
||||
value: build.config.version.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let build_args = parse_build_args(&build_args);
|
||||
|
||||
let secret_args = environment_vars_from_str(secret_args)
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Resolve<crate::api::Args> for GetComposeLog {
|
||||
run_komodo_standard_command(
|
||||
"Get Stack Log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -101,7 +101,7 @@ impl Resolve<crate::api::Args> for GetComposeLogSearch {
|
||||
run_komodo_shell_command(
|
||||
"Search Stack Log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ impl Resolve<crate::api::Args> for GetContainerLog {
|
||||
run_komodo_standard_command(
|
||||
"Get container log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -104,7 +104,7 @@ impl Resolve<crate::api::Args> for GetContainerLogSearch {
|
||||
run_komodo_shell_command(
|
||||
"Get container log grep",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
|
||||
@@ -90,7 +90,7 @@ impl Resolve<crate::api::Args> for GetSwarmServiceLog {
|
||||
run_komodo_standard_command(
|
||||
"Get Swarm Service Log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
@@ -141,7 +141,7 @@ impl Resolve<crate::api::Args> for GetSwarmServiceLogSearch {
|
||||
run_komodo_shell_command(
|
||||
"Search Swarm Service Log",
|
||||
command,
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ pub async fn list_compose_projects()
|
||||
let res = run_komodo_standard_command(
|
||||
"List Projects",
|
||||
format!("{docker_compose} ls --all --format json"),
|
||||
CommandOptions::default().timeout(Duration::from_secs(1)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(5)),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ pub async fn list_swarm_configs(
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Configs",
|
||||
"docker config ls --format json",
|
||||
CommandOptions::default().timeout(Duration::from_secs(1)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -73,7 +73,7 @@ pub async fn inspect_swarm_config(
|
||||
// `--` so a name beginning with `-` is not parsed as a flag.
|
||||
// The quotes alone give no protection, as they are stripped when lexed.
|
||||
format!(r#"docker config inspect -- "{config}""#),
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ pub async fn list_swarm_stacks(
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Stacks",
|
||||
"docker stack ls --format json",
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -133,7 +133,7 @@ pub async fn list_swarm_stack_service_ids(
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Stack Services",
|
||||
format!("docker stack services --format json -- {stack}"),
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -163,7 +163,7 @@ pub async fn list_swarm_stack_tasks(
|
||||
let res = run_komodo_standard_command(
|
||||
"List Swarm Stack Tasks",
|
||||
format!("docker stack ps --format json --no-trunc -- {stack}"),
|
||||
CommandOptions::default().timeout(Duration::from_secs(3)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(10)),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use anyhow::Context;
|
||||
use bollard::query_parameters::ListTasksOptions;
|
||||
use komodo_client::entities::docker::task::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "komodo_client"
|
||||
description = "Client for the Komodo build and deployment system"
|
||||
version.workspace = true
|
||||
version = "2.3.3"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "komodo_client",
|
||||
"version": "2.3.2",
|
||||
"version": "2.3.3",
|
||||
"description": "Komodo client package",
|
||||
"homepage": "https://komo.do",
|
||||
"main": "dist/lib.js",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM rust:1.97.1-trixie as builder
|
||||
FROM rust:1.98.0-trixie as builder
|
||||
WORKDIR /builder
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM rust:1.97.1-trixie as builder
|
||||
FROM rust:1.98.0-trixie as builder
|
||||
WORKDIR /builder
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -0,0 +1,836 @@
|
||||
import { KOMODO_BASE_URL } from "@main";
|
||||
import { KomodoClient, Types } from "komodo_client";
|
||||
import {
|
||||
AuthResponses,
|
||||
ExecuteResponses,
|
||||
ReadResponses,
|
||||
UserResponses,
|
||||
WriteResponses,
|
||||
} from "komodo_client/dist/responses";
|
||||
import {
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { UsableResource } from "@types";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { atomFamily } from "jotai/utils";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { has_minimum_permissions, RESOURCE_TARGETS } from "./utils";
|
||||
|
||||
export const atomWithStorage = <T>(key: string, init: T) => {
|
||||
const stored = localStorage.getItem(key);
|
||||
const inner = atom(stored ? JSON.parse(stored) : init);
|
||||
|
||||
return atom(
|
||||
(get) => get(inner),
|
||||
(_, set, newValue) => {
|
||||
set(inner, newValue);
|
||||
localStorage.setItem(key, JSON.stringify(newValue));
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
type LoginTokens = {
|
||||
/** Current User ID */
|
||||
current: string | undefined;
|
||||
/** Array of logged in user ids / tokens */
|
||||
tokens: Array<Types.JwtResponse>;
|
||||
};
|
||||
|
||||
const LOGIN_TOKENS_KEY = "komodo-auth-tokens-v1";
|
||||
|
||||
export const LOGIN_TOKENS = (() => {
|
||||
const stored = localStorage.getItem(LOGIN_TOKENS_KEY);
|
||||
|
||||
let tokens: LoginTokens = stored
|
||||
? JSON.parse(stored)
|
||||
: { current: undefined, tokens: [] };
|
||||
|
||||
const update_local_storage = () => {
|
||||
localStorage.setItem(LOGIN_TOKENS_KEY, JSON.stringify(tokens));
|
||||
};
|
||||
|
||||
const accounts = () => {
|
||||
const current = tokens.tokens.find((t) => t.user_id === tokens.current);
|
||||
const filtered = tokens.tokens.filter((t) => t.user_id !== tokens.current);
|
||||
return current ? [current, ...filtered] : filtered;
|
||||
};
|
||||
|
||||
const add_and_change = (user_id: string, jwt: string) => {
|
||||
const filtered = tokens.tokens.filter((t) => t.user_id !== user_id);
|
||||
filtered.push({ user_id, jwt });
|
||||
filtered.sort();
|
||||
tokens = {
|
||||
current: user_id,
|
||||
tokens: filtered,
|
||||
};
|
||||
update_local_storage();
|
||||
};
|
||||
|
||||
const remove = (user_id: string) => {
|
||||
const filtered = tokens.tokens.filter((t) => t.user_id !== user_id);
|
||||
tokens = {
|
||||
current:
|
||||
tokens.current === user_id ? filtered[0]?.user_id : tokens.current,
|
||||
tokens: filtered,
|
||||
};
|
||||
update_local_storage();
|
||||
};
|
||||
|
||||
const remove_all = () => {
|
||||
tokens = {
|
||||
current: undefined,
|
||||
tokens: [],
|
||||
};
|
||||
update_local_storage();
|
||||
};
|
||||
|
||||
const change = (to_id: string) => {
|
||||
tokens = {
|
||||
current: to_id,
|
||||
tokens: tokens.tokens,
|
||||
};
|
||||
update_local_storage();
|
||||
};
|
||||
|
||||
return {
|
||||
jwt: () =>
|
||||
tokens.current
|
||||
? (tokens.tokens.find((t) => t.user_id === tokens.current)?.jwt ?? "")
|
||||
: "",
|
||||
accounts,
|
||||
add_and_change,
|
||||
remove,
|
||||
remove_all,
|
||||
change,
|
||||
};
|
||||
})();
|
||||
|
||||
export const komodo_client = () =>
|
||||
KomodoClient(KOMODO_BASE_URL, {
|
||||
type: "jwt",
|
||||
params: { jwt: LOGIN_TOKENS.jwt() },
|
||||
});
|
||||
|
||||
// ============== RESOLVER ==============
|
||||
|
||||
export const useLoginOptions = () => {
|
||||
return useQuery({
|
||||
queryKey: ["GetLoginOptions"],
|
||||
queryFn: () => komodo_client().auth("GetLoginOptions", {}),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUser = () => {
|
||||
const userReset = useUserReset();
|
||||
const hasJwt = !!LOGIN_TOKENS.jwt();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["GetUser"],
|
||||
queryFn: () => komodo_client().auth("GetUser", {}),
|
||||
refetchInterval: 30_000,
|
||||
enabled: hasJwt,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.data && query.error) {
|
||||
userReset();
|
||||
}
|
||||
}, [query.data, query.error]);
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
export const useUserInvalidate = () => {
|
||||
const qc = useQueryClient();
|
||||
return () => {
|
||||
qc.invalidateQueries({ queryKey: ["GetUser"] });
|
||||
};
|
||||
};
|
||||
|
||||
export const useUserReset = () => {
|
||||
const qc = useQueryClient();
|
||||
return () => {
|
||||
qc.resetQueries({ queryKey: ["GetUser"] });
|
||||
};
|
||||
};
|
||||
|
||||
export const useRead = <
|
||||
T extends Types.ReadRequest["type"],
|
||||
R extends Extract<Types.ReadRequest, { type: T }>,
|
||||
P extends R["params"],
|
||||
C extends Omit<
|
||||
UseQueryOptions<
|
||||
ReadResponses[R["type"]],
|
||||
unknown,
|
||||
ReadResponses[R["type"]],
|
||||
(T | P)[]
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>,
|
||||
>(
|
||||
type: T,
|
||||
params: P,
|
||||
config?: C
|
||||
) => {
|
||||
const hasJwt = !!LOGIN_TOKENS.jwt();
|
||||
return useQuery({
|
||||
queryKey: [type, params],
|
||||
queryFn: () => komodo_client().read<T, R>(type, params),
|
||||
enabled: hasJwt && (config?.enabled !== false),
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const useInvalidate = () => {
|
||||
const qc = useQueryClient();
|
||||
return <
|
||||
Type extends Types.ReadRequest["type"],
|
||||
Params extends Extract<Types.ReadRequest, { type: Type }>["params"],
|
||||
>(
|
||||
...keys: Array<[Type] | [Type, Params]>
|
||||
) => keys.forEach((key) => qc.invalidateQueries({ queryKey: key }));
|
||||
};
|
||||
|
||||
export const useManageUser = <
|
||||
T extends Types.UserRequest["type"],
|
||||
R extends Extract<Types.UserRequest, { type: T }>,
|
||||
P extends R["params"],
|
||||
C extends Omit<
|
||||
UseMutationOptions<UserResponses[T], unknown, P, unknown>,
|
||||
"mutationKey" | "mutationFn"
|
||||
>,
|
||||
>(
|
||||
type: T,
|
||||
config?: C
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => komodo_client().user<T, R>(type, params),
|
||||
onError: (e: { result: { error?: string; trace?: string[] } }, v, c) => {
|
||||
console.log("Auth error:", e);
|
||||
const msg = e.result?.error ?? "Unknown error. See console.";
|
||||
const detail = e.result?.trace
|
||||
?.map((msg) => msg[0].toUpperCase() + msg.slice(1))
|
||||
.join(" | ");
|
||||
let msg_log = msg ? msg[0].toUpperCase() + msg.slice(1) + " | " : "";
|
||||
if (detail) {
|
||||
msg_log += detail + " | ";
|
||||
}
|
||||
toast({
|
||||
title: `Request ${type} Failed`,
|
||||
description: `${msg_log}See console for details`,
|
||||
variant: "destructive",
|
||||
});
|
||||
config?.onError && config.onError(e, v, c);
|
||||
},
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const useWrite = <
|
||||
T extends Types.WriteRequest["type"],
|
||||
R extends Extract<Types.WriteRequest, { type: T }>,
|
||||
P extends R["params"],
|
||||
C extends Omit<
|
||||
UseMutationOptions<WriteResponses[R["type"]], unknown, P, unknown>,
|
||||
"mutationKey" | "mutationFn"
|
||||
>,
|
||||
>(
|
||||
type: T,
|
||||
config?: C
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => komodo_client().write<T, R>(type, params),
|
||||
onError: (e: { result: { error?: string; trace?: string[] } }, v, c) => {
|
||||
console.log("Write error:", e);
|
||||
const msg = e.result.error ?? "Unknown error. See console.";
|
||||
const detail = e.result?.trace
|
||||
?.map((msg) => msg[0].toUpperCase() + msg.slice(1))
|
||||
.join(" | ");
|
||||
let msg_log = msg ? msg[0].toUpperCase() + msg.slice(1) + " | " : "";
|
||||
if (detail) {
|
||||
msg_log += detail + " | ";
|
||||
}
|
||||
toast({
|
||||
title: `Write request ${type} failed`,
|
||||
description: `${msg_log}See console for details`,
|
||||
variant: "destructive",
|
||||
});
|
||||
config?.onError && config.onError(e, v, c);
|
||||
},
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const useExecute = <
|
||||
T extends Types.ExecuteRequest["type"],
|
||||
R extends Extract<Types.ExecuteRequest, { type: T }>,
|
||||
P extends R["params"],
|
||||
C extends Omit<
|
||||
UseMutationOptions<ExecuteResponses[T], unknown, P, unknown>,
|
||||
"mutationKey" | "mutationFn"
|
||||
>,
|
||||
>(
|
||||
type: T,
|
||||
config?: C
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => komodo_client().execute<T, R>(type, params),
|
||||
onError: (e: { result: { error?: string; trace?: string[] } }, v, c) => {
|
||||
console.log("Execute error:", e);
|
||||
const msg = e.result.error ?? "Unknown error. See console.";
|
||||
const detail = e.result?.trace
|
||||
?.map((msg) => msg[0].toUpperCase() + msg.slice(1))
|
||||
.join(" | ");
|
||||
let msg_log = msg ? msg[0].toUpperCase() + msg.slice(1) + " | " : "";
|
||||
if (detail) {
|
||||
msg_log += detail + " | ";
|
||||
}
|
||||
toast({
|
||||
title: `Execute request ${type} failed`,
|
||||
description: `${msg_log}See console for details`,
|
||||
variant: "destructive",
|
||||
});
|
||||
config?.onError && config.onError(e, v, c);
|
||||
},
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAuth = <
|
||||
T extends Types.AuthRequest["type"],
|
||||
R extends Extract<Types.AuthRequest, { type: T }>,
|
||||
P extends R["params"],
|
||||
C extends Omit<
|
||||
UseMutationOptions<AuthResponses[T], unknown, P, unknown>,
|
||||
"mutationKey" | "mutationFn"
|
||||
>,
|
||||
>(
|
||||
type: T,
|
||||
config?: C
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => komodo_client().auth<T, R>(type, params),
|
||||
onError: (e: { result: { error?: string; trace?: string[] } }, v, c) => {
|
||||
console.log("Auth error:", e);
|
||||
const msg = e.result.error ?? "Unknown error. See console.";
|
||||
const detail = e.result?.trace
|
||||
?.map((msg) => msg[0].toUpperCase() + msg.slice(1))
|
||||
.join(" | ");
|
||||
let msg_log = msg ? msg[0].toUpperCase() + msg.slice(1) + " | " : "";
|
||||
if (detail) {
|
||||
msg_log += detail + " | ";
|
||||
}
|
||||
toast({
|
||||
title: `Auth request ${type} failed`,
|
||||
description: `${msg_log}See console for details`,
|
||||
variant: "destructive",
|
||||
});
|
||||
config?.onError && config.onError(e, v, c);
|
||||
},
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
// ============== UTILITY ==============
|
||||
|
||||
export const useResourceParamType = () => {
|
||||
const type = useParams().type;
|
||||
if (!type) return undefined;
|
||||
if (type === "resource-syncs") return "ResourceSync";
|
||||
return (type[0].toUpperCase() + type.slice(1, -1)) as UsableResource;
|
||||
};
|
||||
|
||||
type ResourceMap = {
|
||||
[Resource in UsableResource]: Types.ResourceListItem<unknown>[] | undefined;
|
||||
};
|
||||
|
||||
export const useAllResources = (): ResourceMap => {
|
||||
return {
|
||||
Server: useRead("ListServers", {}).data,
|
||||
Stack: useRead("ListStacks", {}).data,
|
||||
Deployment: useRead("ListDeployments", {}).data,
|
||||
Build: useRead("ListBuilds", {}).data,
|
||||
Repo: useRead("ListRepos", {}).data,
|
||||
Procedure: useRead("ListProcedures", {}).data,
|
||||
Action: useRead("ListActions", {}).data,
|
||||
Builder: useRead("ListBuilders", {}).data,
|
||||
Alerter: useRead("ListAlerters", {}).data,
|
||||
ResourceSync: useRead("ListResourceSyncs", {}).data,
|
||||
};
|
||||
};
|
||||
|
||||
// Returns true if Komodo has no resources.
|
||||
export const useNoResources = () => {
|
||||
const resources = useAllResources();
|
||||
for (const target of RESOURCE_TARGETS) {
|
||||
if (resources[target] && resources[target].length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/** returns function that takes a resource target and checks if it exists */
|
||||
export const useCheckResourceExists = () => {
|
||||
const resources = useAllResources();
|
||||
return (target: Types.ResourceTarget) => {
|
||||
return (
|
||||
resources[target.type as UsableResource]?.some(
|
||||
(resource) => resource.id === target.id
|
||||
) || false
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export const useFilterResources = <Info>(
|
||||
resources?: Types.ResourceListItem<Info>[],
|
||||
search?: string
|
||||
) => {
|
||||
const tags = useTagsFilter();
|
||||
const searchSplit = search?.toLowerCase()?.split(" ") || [];
|
||||
return (
|
||||
resources?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag: string) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) =>
|
||||
resource.name.toLowerCase().includes(search)
|
||||
)
|
||||
: true)
|
||||
) ?? []
|
||||
);
|
||||
};
|
||||
|
||||
export const usePushRecentlyViewed = ({ type, id }: Types.ResourceTarget) => {
|
||||
const userInvalidate = useUserInvalidate();
|
||||
|
||||
const push = useManageUser("PushRecentlyViewed", {
|
||||
onSuccess: userInvalidate,
|
||||
}).mutate;
|
||||
|
||||
const exists = useRead(`List${type as UsableResource}s`, {}).data?.find(
|
||||
(r) => r.id === id
|
||||
)
|
||||
? true
|
||||
: false;
|
||||
|
||||
useEffect(() => {
|
||||
exists && push({ resource: { type, id } });
|
||||
}, [exists, push]);
|
||||
|
||||
return () => push({ resource: { type, id } });
|
||||
};
|
||||
|
||||
export const useSetTitle = (more?: string) => {
|
||||
const info = useRead("GetCoreInfo", {}).data;
|
||||
const title = more ? `${more} | ${info?.title}` : info?.title;
|
||||
useEffect(() => {
|
||||
if (title) {
|
||||
document.title = title;
|
||||
}
|
||||
}, [title]);
|
||||
};
|
||||
|
||||
const tagsAtom = atomWithStorage<string[]>("tags-v0", []);
|
||||
|
||||
export const useTags = () => {
|
||||
const [tags, setTags] = useAtom<string[]>(tagsAtom);
|
||||
|
||||
const add_tag = (tag_id: string) => setTags([...tags, tag_id]);
|
||||
const remove_tag = (tag_id: string) =>
|
||||
setTags(tags.filter((id) => id !== tag_id));
|
||||
const toggle_tag = (tag_id: string) => {
|
||||
if (tags.includes(tag_id)) {
|
||||
remove_tag(tag_id);
|
||||
} else {
|
||||
add_tag(tag_id);
|
||||
}
|
||||
};
|
||||
const clear_tags = () => setTags([]);
|
||||
|
||||
return {
|
||||
tags,
|
||||
add_tag,
|
||||
remove_tag,
|
||||
toggle_tag,
|
||||
clear_tags,
|
||||
};
|
||||
};
|
||||
|
||||
export const useTagsFilter = () => {
|
||||
const [tags] = useAtom<string[]>(tagsAtom);
|
||||
return tags;
|
||||
};
|
||||
|
||||
export type LocalStorageSetter<T> = (state: T) => T;
|
||||
|
||||
export const useLocalStorage = <T>(
|
||||
key: string,
|
||||
init: T
|
||||
): [T, (state: T | LocalStorageSetter<T>) => void] => {
|
||||
const stored = localStorage.getItem(key);
|
||||
const parsed = stored ? (JSON.parse(stored) as T) : undefined;
|
||||
const [state, inner_set] = useState<T>(parsed ?? init);
|
||||
const set = (state: T | LocalStorageSetter<T>) => {
|
||||
inner_set((prev_state) => {
|
||||
const new_val =
|
||||
typeof state === "function"
|
||||
? (state as LocalStorageSetter<T>)(prev_state)
|
||||
: state;
|
||||
localStorage.setItem(key, JSON.stringify(new_val));
|
||||
return new_val;
|
||||
});
|
||||
};
|
||||
return [state, set];
|
||||
};
|
||||
|
||||
export const useKeyListener = (listenKey: string, onPress: () => void) => {
|
||||
useEffect(() => {
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
// This will ignore Shift + listenKey if it is sent from input / textarea
|
||||
const target = e.target as any;
|
||||
if (target.matches("input") || target.matches("textarea")) return;
|
||||
|
||||
if (e.key === listenKey) {
|
||||
e.preventDefault();
|
||||
onPress();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", keydown);
|
||||
return () => document.removeEventListener("keydown", keydown);
|
||||
});
|
||||
};
|
||||
|
||||
export const useShiftKeyListener = (listenKey: string, onPress: () => void) => {
|
||||
useEffect(() => {
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
// This will ignore Shift + listenKey if it is sent from input / textarea
|
||||
const target = e.target as any;
|
||||
if (target.matches("input") || target.matches("textarea")) return;
|
||||
|
||||
if (e.shiftKey && e.key === listenKey) {
|
||||
e.preventDefault();
|
||||
onPress();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", keydown);
|
||||
return () => document.removeEventListener("keydown", keydown);
|
||||
});
|
||||
};
|
||||
|
||||
/** Listens for ctrl (or CMD on mac) + the listenKey */
|
||||
export const useCtrlKeyListener = (listenKey: string, onPress: () => void) => {
|
||||
useEffect(() => {
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === listenKey) {
|
||||
e.preventDefault();
|
||||
onPress();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", keydown);
|
||||
return () => document.removeEventListener("keydown", keydown);
|
||||
});
|
||||
};
|
||||
|
||||
export interface PromptHotkeysConfig {
|
||||
/** Function to call when Enter is pressed (confirm action) */
|
||||
onConfirm?: () => void;
|
||||
/** Function to call when Escape is pressed (cancel/close action) */
|
||||
onCancel?: () => void;
|
||||
/** Whether the hotkeys are enabled. Defaults to true */
|
||||
enabled?: boolean;
|
||||
/** Whether to ignore hotkeys when inside input/textarea elements. Defaults to true */
|
||||
ignoreInputs?: boolean;
|
||||
/** Whether the confirm action is disabled (e.g., form validation failed) */
|
||||
confirmDisabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that provides standard prompt/dialog hotkey behavior:
|
||||
* - Enter: Confirm/submit action
|
||||
* - Escape: Cancel/close action
|
||||
*/
|
||||
export const usePromptHotkeys = ({
|
||||
enabled = true,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
ignoreInputs = true,
|
||||
confirmDisabled = false,
|
||||
}: PromptHotkeysConfig) => {
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
const findConfirmButton = (): HTMLButtonElement | null => {
|
||||
const dialogContainers = document.querySelectorAll('[role="dialog"], [data-state="open"], .dialog-content');
|
||||
for (const container of dialogContainers) {
|
||||
const button = container.querySelector('[data-confirm-button]:not([disabled])') as HTMLButtonElement;
|
||||
if (button) return button;
|
||||
}
|
||||
|
||||
return document.querySelector('[data-confirm-button]:not([disabled])') as HTMLButtonElement;
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (ignoreInputs) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === "INPUT" ||
|
||||
target.tagName === "TEXTAREA" ||
|
||||
target.tagName === "SELECT" ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "Enter":
|
||||
if (onConfirm && !confirmDisabled) {
|
||||
e.preventDefault();
|
||||
const confirmButton = findConfirmButton();
|
||||
if (confirmButton) {
|
||||
confirmButton.click();
|
||||
} else {
|
||||
onConfirm();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Escape":
|
||||
if (onCancel) {
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [enabled, onConfirm, onCancel, ignoreInputs, confirmDisabled]);
|
||||
};
|
||||
|
||||
export type WebhookIntegration = "Github" | "Gitlab";
|
||||
export type WebhookIntegrations = {
|
||||
[key: string]: WebhookIntegration;
|
||||
};
|
||||
|
||||
const WEBHOOK_INTEGRATIONS_ATOM = atomWithStorage<WebhookIntegrations>(
|
||||
"webhook-integrations-v2",
|
||||
{}
|
||||
);
|
||||
|
||||
export const useWebhookIntegrations = () => {
|
||||
const [integrations, setIntegrations] = useAtom<WebhookIntegrations>(
|
||||
WEBHOOK_INTEGRATIONS_ATOM
|
||||
);
|
||||
return {
|
||||
integrations,
|
||||
setIntegration: (provider: string, integration: WebhookIntegration) =>
|
||||
setIntegrations({
|
||||
...integrations,
|
||||
[provider]: integration,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const getWebhookIntegration = (
|
||||
integrations: WebhookIntegrations,
|
||||
git_provider: string
|
||||
) => {
|
||||
return integrations[git_provider]
|
||||
? integrations[git_provider]
|
||||
: git_provider.includes("gitlab")
|
||||
? "Gitlab"
|
||||
: "Github";
|
||||
};
|
||||
|
||||
export type WebhookIdOrName = "Id" | "Name";
|
||||
|
||||
const WEBHOOK_ID_OR_NAME_ATOM = atomWithStorage<WebhookIdOrName>(
|
||||
"webhook-id-or-name-v1",
|
||||
"Id"
|
||||
);
|
||||
|
||||
export const useWebhookIdOrName = () => {
|
||||
return useAtom<WebhookIdOrName>(WEBHOOK_ID_OR_NAME_ATOM);
|
||||
};
|
||||
|
||||
export type Dimensions = { width: number; height: number };
|
||||
export const useWindowDimensions = () => {
|
||||
const [dimensions, setDimensions] = useState<Dimensions>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
useEffect(() => {
|
||||
const callback = () => {
|
||||
setDimensions({
|
||||
width: window.screen.availWidth,
|
||||
height: window.screen.availHeight,
|
||||
});
|
||||
};
|
||||
callback();
|
||||
window.addEventListener("resize", callback);
|
||||
return () => {
|
||||
window.removeEventListener("resize", callback);
|
||||
};
|
||||
}, []);
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
const selected_resources = atomFamily((_: UsableResource) =>
|
||||
atom<string[]>([])
|
||||
);
|
||||
export const useSelectedResources = (type: UsableResource) =>
|
||||
useAtom(selected_resources(type));
|
||||
|
||||
const filter_by_update_available = atomWithStorage<boolean>(
|
||||
"update-available-filter-v1",
|
||||
false
|
||||
);
|
||||
export const useFilterByUpdateAvailable: () => [boolean, () => void] = () => {
|
||||
const [filter, set] = useAtom<boolean>(filter_by_update_available);
|
||||
return [filter, () => set(!filter)];
|
||||
};
|
||||
|
||||
export const usePermissions = ({ type, id }: Types.ResourceTarget) => {
|
||||
const user = useUser().data;
|
||||
const perms = useRead("GetPermission", { target: { type, id } }).data as
|
||||
| Types.PermissionLevelAndSpecifics
|
||||
| Types.PermissionLevel
|
||||
| undefined;
|
||||
const info = useRead("GetCoreInfo", {}).data;
|
||||
const ui_write_disabled = info?.ui_write_disabled ?? false;
|
||||
const disable_non_admin_create = info?.disable_non_admin_create ?? false;
|
||||
|
||||
const level =
|
||||
(perms && typeof perms === "string" ? perms : perms?.level) ??
|
||||
Types.PermissionLevel.None;
|
||||
const specific =
|
||||
(perms && typeof perms === "string" ? [] : perms?.specific) ?? [];
|
||||
|
||||
const canWrite = !ui_write_disabled && level === Types.PermissionLevel.Write;
|
||||
const canExecute = has_minimum_permissions(
|
||||
{ level, specific },
|
||||
Types.PermissionLevel.Execute
|
||||
);
|
||||
|
||||
const [
|
||||
specificLogs,
|
||||
specificInspect,
|
||||
specificTerminal,
|
||||
specificAttach,
|
||||
specificProcesses,
|
||||
] = [
|
||||
specific.includes(Types.SpecificPermission.Logs),
|
||||
specific.includes(Types.SpecificPermission.Inspect),
|
||||
specific.includes(Types.SpecificPermission.Terminal),
|
||||
specific.includes(Types.SpecificPermission.Attach),
|
||||
specific.includes(Types.SpecificPermission.Processes),
|
||||
];
|
||||
|
||||
const canCreate =
|
||||
type === "Server"
|
||||
? user?.admin ||
|
||||
(!disable_non_admin_create && user?.create_server_permissions)
|
||||
: type === "Build"
|
||||
? user?.admin ||
|
||||
(!disable_non_admin_create && user?.create_build_permissions)
|
||||
: type === "Alerter" ||
|
||||
type === "Builder" ||
|
||||
type === "Procedure" ||
|
||||
type === "Action"
|
||||
? user?.admin
|
||||
: user?.admin || !disable_non_admin_create;
|
||||
|
||||
return {
|
||||
canWrite,
|
||||
canExecute,
|
||||
canCreate,
|
||||
specific,
|
||||
specificLogs,
|
||||
specificInspect,
|
||||
specificTerminal,
|
||||
specificAttach,
|
||||
specificProcesses,
|
||||
};
|
||||
};
|
||||
|
||||
const templatesQueryBehaviorAtom =
|
||||
atomWithStorage<Types.TemplatesQueryBehavior>(
|
||||
"templates-query-behavior-v0",
|
||||
Types.TemplatesQueryBehavior.Exclude
|
||||
);
|
||||
|
||||
export const useTemplatesQueryBehavior = () =>
|
||||
useAtom<Types.TemplatesQueryBehavior>(templatesQueryBehaviorAtom);
|
||||
|
||||
export type SettingsView =
|
||||
| "Variables"
|
||||
| "Tags"
|
||||
| "Providers"
|
||||
| "Users"
|
||||
| "Profile";
|
||||
|
||||
const viewAtom = atomWithStorage<SettingsView>("settings-view-v2", "Variables");
|
||||
|
||||
export const useSettingsView = () => useAtom<SettingsView>(viewAtom);
|
||||
|
||||
/**
|
||||
* Map of unique host ports to array of formatted full port map spec
|
||||
* Formatted ex: 0.0.0.0:3000:3000/tcp
|
||||
*/
|
||||
export type PortsMap = { [host_port: string]: Array<Types.Port> };
|
||||
|
||||
export const useContainerPortsMap = (ports: Types.Port[]) => {
|
||||
return useMemo(() => {
|
||||
const map: PortsMap = {};
|
||||
for (const port of ports) {
|
||||
if (!port.PublicPort || !port.PrivatePort) continue;
|
||||
if (map[port.PublicPort]) {
|
||||
map[port.PublicPort].push(port);
|
||||
} else {
|
||||
map[port.PublicPort] = [port];
|
||||
}
|
||||
}
|
||||
for (const key in map) {
|
||||
map[key].sort();
|
||||
}
|
||||
return map;
|
||||
}, [ports]);
|
||||
};
|
||||
|
||||
/**
|
||||
* A custom React hook that debounces a value, delaying its update until after
|
||||
* a specified period of inactivity. This is useful for performance optimization
|
||||
* in scenarios like search inputs, form validation, or API calls.
|
||||
*/
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use command::{CommandOptions, run_standard_command};
|
||||
pub async fn check_installed() -> anyhow::Result<()> {
|
||||
if run_standard_command(
|
||||
"which git",
|
||||
CommandOptions::default().timeout(Duration::from_secs(1)),
|
||||
CommandOptions::default().timeout(Duration::from_secs(2)),
|
||||
)
|
||||
.await
|
||||
.success()
|
||||
|
||||
+3
-3
@@ -31,7 +31,7 @@ pub async fn get_commit_hash_info(
|
||||
"git rev-parse --short HEAD",
|
||||
CommandOptions::default()
|
||||
.path(repo_dir)
|
||||
.timeout(Duration::from_secs(1)),
|
||||
.timeout(Duration::from_secs(2)),
|
||||
)
|
||||
.await;
|
||||
let hash = if hash.status.success() {
|
||||
@@ -46,7 +46,7 @@ pub async fn get_commit_hash_info(
|
||||
"git log -1 --pretty=%B",
|
||||
CommandOptions::default()
|
||||
.path(repo_dir)
|
||||
.timeout(Duration::from_secs(1)),
|
||||
.timeout(Duration::from_secs(2)),
|
||||
)
|
||||
.await;
|
||||
let message = if message.status.success() {
|
||||
@@ -93,7 +93,7 @@ pub async fn get_remote_url(path: &Path) -> anyhow::Result<String> {
|
||||
"git remote show origin",
|
||||
CommandOptions::default()
|
||||
.path(path)
|
||||
.timeout(Duration::from_secs(1)),
|
||||
.timeout(Duration::from_secs(2)),
|
||||
)
|
||||
.await;
|
||||
if output.success() {
|
||||
|
||||
+15
-15
@@ -10,23 +10,23 @@
|
||||
"build-client": "cd ../client/core/ts && yarn && yarn build && yarn link"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/core": "^9.5.1",
|
||||
"@mantine/form": "^9.5.1",
|
||||
"@mantine/hooks": "^9.5.1",
|
||||
"@mantine/notifications": "^9.5.1",
|
||||
"@mantine/spotlight": "^9.5.1",
|
||||
"@mantine/core": "^9.5.2",
|
||||
"@mantine/form": "^9.5.2",
|
||||
"@mantine/hooks": "^9.5.2",
|
||||
"@mantine/notifications": "^9.5.2",
|
||||
"@mantine/spotlight": "^9.5.2",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"@tanstack/react-query": "^5.102.4",
|
||||
"@tanstack/react-table": "^9.1.2",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"jotai": "^2.20.2",
|
||||
"jotai": "^2.20.3",
|
||||
"jotai-family": "^1.1.0",
|
||||
"jotai-location": "^0.6.2",
|
||||
"lucide-react": "^1.31.0",
|
||||
"lucide-react": "^1.34.0",
|
||||
"mogh_auth_client": "^1.7.1",
|
||||
"mogh_ui": "^1.1.1",
|
||||
"mogh_ui": "^1.2.7",
|
||||
"monaco-editor": "^0.56.0",
|
||||
"monaco-yaml": "^5.5.1",
|
||||
"react": "^19.2.8",
|
||||
@@ -35,23 +35,23 @@
|
||||
"react-router-dom": "^7.18.2",
|
||||
"react-xtermjs": "^1.0.12",
|
||||
"recharts": "^3.10.1",
|
||||
"sanitize-html": "^2.17.6",
|
||||
"sanitize-html": "^2.17.7",
|
||||
"shell-quote": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/node": "^26.3.0",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@types/react-dom": "^19.2.5",
|
||||
"@types/sanitize-html": "^2.16.1",
|
||||
"@types/shell-quote": "^1.7.5",
|
||||
"@vitejs/plugin-react": "^6.0.5",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"postcss": "^8.5.26",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"sass-embedded": "^1.100.0",
|
||||
"sass-embedded": "^1.103.1",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "^8.2.1"
|
||||
"vite": "^8.2.2"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useRead } from "@/lib/hooks";
|
||||
import { Group, Pagination } from "@mantine/core";
|
||||
import {
|
||||
ListPaginationProps,
|
||||
ListPagination as MoghListPagination,
|
||||
} from "mogh_ui";
|
||||
|
||||
/**
|
||||
* The server side default page size on `List<Resource>` calls,
|
||||
@@ -18,29 +21,9 @@ export function usePageSize() {
|
||||
* Only renders when needed, ie the results fill the page
|
||||
* or the user is past the first page.
|
||||
*/
|
||||
export default function ListPagination({
|
||||
page,
|
||||
setPage,
|
||||
count,
|
||||
}: {
|
||||
page: number;
|
||||
setPage: (page: number) => void;
|
||||
count: number;
|
||||
}) {
|
||||
export default function ListPagination(
|
||||
props: Omit<ListPaginationProps, "pageSize">,
|
||||
) {
|
||||
const pageSize = usePageSize();
|
||||
if (count < pageSize && page === 0) return null;
|
||||
return (
|
||||
<Pagination.Root
|
||||
total={count >= pageSize ? page + 2 : page + 1}
|
||||
value={page + 1}
|
||||
onChange={(page) => setPage(page - 1)}
|
||||
>
|
||||
<Group gap="0.2rem" justify="center">
|
||||
<Pagination.First />
|
||||
<Pagination.Previous />
|
||||
<Pagination.Items />
|
||||
<Pagination.Next />
|
||||
</Group>
|
||||
</Pagination.Root>
|
||||
);
|
||||
return <MoghListPagination {...props} pageSize={pageSize} />;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Types } from "komodo_client";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
import ConvertAnsiToHtml from "ansi-to-html";
|
||||
import { RowSelectionState } from "@tanstack/react-table";
|
||||
import { Crumb } from "mogh_ui";
|
||||
|
||||
export const EXECUTION_ACTION_STATE_REQUERY_MS = 500;
|
||||
|
||||
@@ -320,3 +321,9 @@ export function parseVersion(version: string): Types.Version {
|
||||
patch,
|
||||
};
|
||||
}
|
||||
|
||||
/** Crumb linking to the list page for a resource type, eg. `Servers` -> /servers */
|
||||
export function resourceTypeCrumb(type: UsableResource): Crumb {
|
||||
const name = type === "ResourceSync" ? "Resource Sync" : type;
|
||||
return { label: `${name}s`, to: `/${usableResourcePath(type)}` };
|
||||
}
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import "mogh_ui/index.scss";
|
||||
import("@/monaco").then(({ default: initMonaco }) => initMonaco());
|
||||
|
||||
export const KOMODO_BASE_URL =
|
||||
import.meta.env.VITE_KOMODO_HOST ?? location.origin;
|
||||
import.meta.env.VITE_KOMODO_HOST || location.origin;
|
||||
export const UPDATE_WS_URL =
|
||||
KOMODO_BASE_URL.replace("http", "ws") + "/ws/update";
|
||||
const client = new QueryClient({
|
||||
|
||||
@@ -14,13 +14,12 @@ import {
|
||||
UsableResource,
|
||||
} from "@/resources";
|
||||
import { AddResourceTags, ResourceTags } from "@/resources/tags";
|
||||
import { DividedChildren } from "mogh_ui";
|
||||
import { Section } from "mogh_ui";
|
||||
import { DividedChildren, EntityPage } from "mogh_ui";
|
||||
import { Section, PageBreadcrumbs } from "mogh_ui";
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { Types } from "komodo_client";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { EntityPage } from "mogh_ui";
|
||||
import { usableResourcePath } from "@/lib/utils";
|
||||
import { resourceTypeCrumb, usableResourcePath } from "@/lib/utils";
|
||||
import ResourceDescription from "@/resources/description";
|
||||
import ResourceNotFound from "@/resources/not-found";
|
||||
import NewResource from "@/resources/new";
|
||||
@@ -67,6 +66,17 @@ function ResourceInner({ type, id }: { type: UsableResource; id: string }) {
|
||||
? "settings"
|
||||
: usableResourcePath(type))
|
||||
}
|
||||
breadcrumbs={
|
||||
<PageBreadcrumbs
|
||||
items={[
|
||||
...(SETTINGS_RESOURCES.includes(type)
|
||||
? [{ label: "Settings", to: "/settings" }]
|
||||
: []),
|
||||
resourceTypeCrumb(type),
|
||||
{ label: resource.name },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
{canCreate && <NewResource type={type} copyId={id} />}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import UserTable from "@/components/user/table";
|
||||
import { useInvalidate, useRead, useUser, useWrite } from "@/lib/hooks";
|
||||
import { ICONS } from "@/lib/icons";
|
||||
import { DividedChildren } from "mogh_ui";
|
||||
import { EntityHeader } from "mogh_ui";
|
||||
import { EntityPage } from "mogh_ui";
|
||||
import { PageGuard } from "mogh_ui";
|
||||
import { Section } from "mogh_ui";
|
||||
import {
|
||||
DividedChildren,
|
||||
EntityHeader,
|
||||
EntityPage,
|
||||
PageBreadcrumbs,
|
||||
PageGuard,
|
||||
Section,
|
||||
} from "mogh_ui";
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { useParams } from "react-router-dom";
|
||||
@@ -66,7 +69,17 @@ export default function UserGroup() {
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<EntityPage backTo="/settings">
|
||||
<EntityPage
|
||||
backTo="/settings"
|
||||
breadcrumbs={
|
||||
<PageBreadcrumbs
|
||||
items={[
|
||||
{ label: "Settings", to: "/settings" },
|
||||
{ label: group?.name },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Stack gap="md" pb="md" className="bordered-light" bdrs="md">
|
||||
<EntityHeader
|
||||
name={group?.name}
|
||||
|
||||
+20
-12
@@ -1,9 +1,12 @@
|
||||
import { useInvalidate, useRead, useUser, useWrite } from "@/lib/hooks";
|
||||
import { ConfirmButton } from "mogh_ui";
|
||||
import { EntityPage } from "mogh_ui";
|
||||
import { LabelledSwitch } from "mogh_ui";
|
||||
import { PageGuard } from "mogh_ui";
|
||||
import { Section } from "mogh_ui";
|
||||
import {
|
||||
ConfirmButton,
|
||||
EntityPage,
|
||||
LabelledSwitch,
|
||||
PageGuard,
|
||||
PageBreadcrumbs,
|
||||
Section,
|
||||
} from "mogh_ui";
|
||||
import { Group, Stack } from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { UserCheck, UserMinus } from "lucide-react";
|
||||
@@ -48,17 +51,22 @@ export default function User() {
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<EntityPage backTo="/settings">
|
||||
<EntityPage
|
||||
backTo="/settings"
|
||||
breadcrumbs={
|
||||
<PageBreadcrumbs
|
||||
items={[
|
||||
{ label: "Settings", to: "/settings" },
|
||||
{ label: user?.username },
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<UserHeader user={user} />
|
||||
<Stack mt="lg" gap="xl">
|
||||
{user?._id?.$oid !== adminUser?._id?.$oid &&
|
||||
(!user?.admin || (!user.super_admin && adminUser?.super_admin)) && (
|
||||
<Section
|
||||
title="User Permissions"
|
||||
titleFz="h3"
|
||||
|
||||
withBorder
|
||||
>
|
||||
<Section title="User Permissions" titleFz="h3" withBorder>
|
||||
<Group>
|
||||
<ConfirmButton
|
||||
icon={
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { UsableResource } from ".";
|
||||
import { ResourceComponents, UsableResource } from ".";
|
||||
import {
|
||||
EntityHeader,
|
||||
EntityHeaderProps,
|
||||
EntityPage,
|
||||
EntityPageProps,
|
||||
PageBreadcrumbs,
|
||||
} from "mogh_ui";
|
||||
import { ReactNode } from "react";
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { DividedChildren } from "mogh_ui";
|
||||
import ResourceLink from "./link";
|
||||
import ResourceDescription from "./description";
|
||||
import { usableResourcePath } from "@/lib/utils";
|
||||
import { resourceTypeCrumb, usableResourcePath } from "@/lib/utils";
|
||||
import ResourceUpdates from "@/components/updates/resource";
|
||||
import { usePermissions } from "@/lib/hooks";
|
||||
import { Section } from "mogh_ui";
|
||||
@@ -37,6 +38,7 @@ export default function ResourceSubPage({
|
||||
...headerProps
|
||||
}: ResourceSubPageProps) {
|
||||
const { canExecute } = usePermissions({ type: parentType, id: parentId });
|
||||
const parent = ResourceComponents[parentType].useListItem(parentId);
|
||||
const Header = (
|
||||
<Stack justify="space-between">
|
||||
<Stack gap="md" pb="md" className="bordered-light" bdrs="md">
|
||||
@@ -56,6 +58,20 @@ export default function ResourceSubPage({
|
||||
backTo={
|
||||
pageProps?.backTo ?? `/${usableResourcePath(parentType)}/${parentId}`
|
||||
}
|
||||
breadcrumbs={
|
||||
pageProps?.breadcrumbs ?? (
|
||||
<PageBreadcrumbs
|
||||
items={[
|
||||
resourceTypeCrumb(parentType),
|
||||
{
|
||||
label: parent?.name ?? "Unknown",
|
||||
to: `/${usableResourcePath(parentType)}/${parentId}`,
|
||||
},
|
||||
{ label: headerProps.name },
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Stack hiddenFrom="lg" w="100%">
|
||||
{Header}
|
||||
|
||||
+39
-1
@@ -46,7 +46,45 @@ export default defineConfig({
|
||||
// scanner doesn't traverse `?worker` graphs, so without this it gets
|
||||
// served raw ("module is not defined" inside the worker). Force it
|
||||
// through prebundling to get CJS -> ESM interop.
|
||||
include: ["path-browserify"],
|
||||
// @mantine/form and @tanstack/react-table are runtime-imported only via
|
||||
// the excluded (linked) mogh_ui — app src imports of react-table are
|
||||
// type-only, which the scanner erases — so they get served raw and their
|
||||
// nested CJS deps (fast-deep-equal, use-sync-external-store) break with
|
||||
// "does not provide an export named ...". Prebundling the packages
|
||||
// inlines those CJS deps with proper interop.
|
||||
include: ["path-browserify", "@mantine/form", "@tanstack/react-table"],
|
||||
},
|
||||
build: {
|
||||
// The only chunks above the default 500 kB warning limit are
|
||||
// monaco-editor's core (~2.7 MB) and prettier's typescript parser
|
||||
// (~900 kB). Both are already async — mogh_ui lazy-loads the editor
|
||||
// implementation, so they only download when an editor mounts — and
|
||||
// neither can be split further (each is one static import graph).
|
||||
// Raise the limit above them so the warning still catches an eager
|
||||
// chunk regression instead of firing on every build.
|
||||
chunkSizeWarningLimit: 2800,
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
// Split stable vendor code out of the main chunk so app-code
|
||||
// changes don't invalidate the browser cache for react/mantine.
|
||||
codeSplitting: {
|
||||
groups: [
|
||||
{
|
||||
name: "react",
|
||||
test: /node_modules[\\/](react|react-dom|scheduler|react-router)[\\/]/,
|
||||
},
|
||||
{
|
||||
name: "mantine",
|
||||
test: /node_modules[\\/](@mantine|@floating-ui|react-transition-group|react-remove-scroll)[\\/]/,
|
||||
},
|
||||
{
|
||||
name: "tanstack",
|
||||
test: /node_modules[\\/]@tanstack[\\/]/,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
|
||||
+260
-254
@@ -48,10 +48,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.11.tgz#a269e055e40e2f45873bae9d1a2fdccbd314ea3f"
|
||||
integrity sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==
|
||||
|
||||
"@mantine/core@^9.5.1":
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-9.5.1.tgz#954587a496244accf0b7d388f15b8e42dbcecfa2"
|
||||
integrity sha512-3olDOYJBfW4kR37Aqdy/+FnYG7iNb5SPzOGeCp9dDxnt65K94sEqETDnXGJptOMO2xLm4KLJ/eBt3IIhJA7Z4Q==
|
||||
"@mantine/core@^9.5.2":
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-9.5.2.tgz#36baf9017388e1d522a2624edf051d0c9dfe00d3"
|
||||
integrity sha512-yhnR+XVGmy7S66abvj/tF9SxzNJ+k60w+UZlC/vGOztrU2Cxpbr95gtrBrin4fpWyiE80qzVC2AY0WzaHPufWw==
|
||||
dependencies:
|
||||
"@floating-ui/react" "^0.27.19"
|
||||
clsx "^2.1.1"
|
||||
@@ -59,39 +59,39 @@
|
||||
react-remove-scroll "^2.7.2"
|
||||
type-fest "^5.8.0"
|
||||
|
||||
"@mantine/form@^9.5.1":
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-9.5.1.tgz#bfab7ec8a09c0e77ca3eebf84c68e15a2bab25d7"
|
||||
integrity sha512-WsMZGBTsoG2Y/K+6QK0+GmsJxq5KzWHIzo65lRpkq/SwdenV9G+3m0LstlhI9i2/wQF83GfwP7mCbKMfzf00Eg==
|
||||
"@mantine/form@^9.5.2":
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-9.5.2.tgz#78d234f14d58d84d1c4cd405dcbcbcfb8f29148d"
|
||||
integrity sha512-9zWyAvEIn5QGfDa7Mxf+iCeEnUHtIam2avY8Csx6PsHqWmRSl9eYfQFrofv6FcR6eAbVUzQHzL/gpqZ3BzPt9g==
|
||||
dependencies:
|
||||
"@standard-schema/spec" "^1.1.0"
|
||||
fast-deep-equal "^3.1.3"
|
||||
klona "^2.0.6"
|
||||
|
||||
"@mantine/hooks@^9.5.1":
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-9.5.1.tgz#54b94e76ca957636d54cf764037408485a65a899"
|
||||
integrity sha512-2sK9OdWvrzKBOuWxLfQA5qcAJzxpzqNlKumaP7Uq0i7LDBQeY/sYgNiBWLwtW+iZw6fFXwPf0nI7q5VVpvqnNQ==
|
||||
"@mantine/hooks@^9.5.2":
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-9.5.2.tgz#f8bb6dffcc62cf2adad8a01056f8d66ceb687f36"
|
||||
integrity sha512-CsANdaF07VRhcvDCupIvAPtIqU1NxYSc+bhFCCasDHpgJOUQxmqeMOGCCGSjtOOEIs+E+pt2ZbbdfnygDYPybA==
|
||||
|
||||
"@mantine/notifications@^9.5.1":
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-9.5.1.tgz#2a09c8027ec06365213869fadb3057d85df17e82"
|
||||
integrity sha512-/MIGWag8U20HTM4gOGSNVhmQ0n6YM2gdSR1NbtBffSh77nuzwPjr68UmpMc2WHFMi8OBlrSIcw9Xpe3YPfgnvQ==
|
||||
"@mantine/notifications@^9.5.2":
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-9.5.2.tgz#cdd0daa8fcf9da751e0c7990fa694ee91bd305f4"
|
||||
integrity sha512-2DQW2i6BlTGI7Vqp1a8IN03Q2CI/nMd7joJu7RGMTDhnnD0rhAhQtu/NwGdmq5gl4vFCpzzwHtH359Do2MlRGg==
|
||||
dependencies:
|
||||
"@mantine/store" "9.5.1"
|
||||
"@mantine/store" "9.5.2"
|
||||
react-transition-group "4.4.5"
|
||||
|
||||
"@mantine/spotlight@^9.5.1":
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-9.5.1.tgz#8474b951aca217c026e109a82e768ff95c3bb758"
|
||||
integrity sha512-fXiaFrlIjhoFh61IbM+tRP7mg1V20mSTVh2AJi4rm/BnoBvnd4fPw33Wdg+O3c5L9HGzPQdA9SHJ6rdDNueoRw==
|
||||
"@mantine/spotlight@^9.5.2":
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-9.5.2.tgz#499ba5c035f2eee0fa414fb78ad0a3689b5062d4"
|
||||
integrity sha512-+YrHe0pqGrx+sTZKSV9v2K/OMyBow4u1Id5zI4jevTGlENKFqsKPn94CIx6Ryn48ODFzJyiiAAXYmvRT4Sw3xA==
|
||||
dependencies:
|
||||
"@mantine/store" "9.5.1"
|
||||
"@mantine/store" "9.5.2"
|
||||
|
||||
"@mantine/store@9.5.1":
|
||||
version "9.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/store/-/store-9.5.1.tgz#873cf6350998ffb8cc96e97f32689def7a69f539"
|
||||
integrity sha512-nDmA9S+qnQQ61KYgph06Wt2cx1tj3Z6eeyK3HvFeuLb5FcGycbITsVy/+3LltH2qdqkpNeAA9wQ926NFjn5hvA==
|
||||
"@mantine/store@9.5.2":
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/store/-/store-9.5.2.tgz#cf25c09ed51116b9cc8650fdd279a8e7816056f7"
|
||||
integrity sha512-vpOS9QwqaJ1KOPr8MdQXYXFDIPkDg1KCr5O8FOGVZIGfsei1vvQsvvmMGGEKGmEE/4FCYwEfhCUWaAVw2psVzw==
|
||||
|
||||
"@monaco-editor/loader@^1.5.0":
|
||||
version "1.7.0"
|
||||
@@ -107,10 +107,10 @@
|
||||
dependencies:
|
||||
"@monaco-editor/loader" "^1.5.0"
|
||||
|
||||
"@oxc-project/types@=0.143.0":
|
||||
version "0.143.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.143.0.tgz#c3e4f3178b7b54e4dd194eac6d45258a60f0092b"
|
||||
integrity sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==
|
||||
"@oxc-project/types@=0.146.0":
|
||||
version "0.146.0"
|
||||
resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.146.0.tgz#d57a2591abbf1f6e50981b07ee24ab269530d87a"
|
||||
integrity sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==
|
||||
|
||||
"@parcel/watcher-android-arm64@2.5.6":
|
||||
version "2.5.6"
|
||||
@@ -213,75 +213,80 @@
|
||||
redux-thunk "^3.1.0"
|
||||
reselect "^5.1.0"
|
||||
|
||||
"@rolldown/binding-android-arm64@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz#001b8b0b01844701efda1bb6bed84b681c4a488b"
|
||||
integrity sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==
|
||||
"@rolldown/binding-android-arm-eabi@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz#165b80910de7cd33f772d5b7b045b259acb7420c"
|
||||
integrity sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==
|
||||
|
||||
"@rolldown/binding-darwin-arm64@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz#5e87c602ed634a6fef092e2162e24fbfb881c4ec"
|
||||
integrity sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==
|
||||
"@rolldown/binding-android-arm64@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz#5ed74d4b8fa56c68eb1aeb81d0d207a85b6de05c"
|
||||
integrity sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==
|
||||
|
||||
"@rolldown/binding-darwin-x64@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz#f32e0b286714bd03a421d693415d05d97d265b77"
|
||||
integrity sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==
|
||||
"@rolldown/binding-darwin-arm64@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz#6f27c7060e58ca03061fa7d50f9dc409bc377fe1"
|
||||
integrity sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==
|
||||
|
||||
"@rolldown/binding-freebsd-x64@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz#5f38ad5761b6b7b21b57a99566bb52634c60ab19"
|
||||
integrity sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==
|
||||
"@rolldown/binding-darwin-x64@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz#74ab897d134ede4072fdc6108f00193e6167ee28"
|
||||
integrity sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz#ab4dcd07f1bd88e8d659ae0c3bb9d2f290adb897"
|
||||
integrity sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==
|
||||
"@rolldown/binding-freebsd-x64@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz#7d337f9ae4b739674e1938747118ab0676c8ef8a"
|
||||
integrity sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz#d279b7016039a725fb66d82784b9841f42df83da"
|
||||
integrity sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==
|
||||
"@rolldown/binding-linux-arm-gnueabihf@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz#a8195dc1091ade0f912b0613ef6500941e5615f4"
|
||||
integrity sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz#d08bbc93d2742214548c5adf7df7788944e5a89a"
|
||||
integrity sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==
|
||||
"@rolldown/binding-linux-arm64-gnu@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz#40fbbb97072b1acaa3e0e8fe9774fe524e860bba"
|
||||
integrity sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz#6418e63745b3193f26ab3bb88744b3a4a1356d7c"
|
||||
integrity sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==
|
||||
"@rolldown/binding-linux-arm64-musl@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz#3cfe8b0f7c13de29dace9a9fc6c03081164176ab"
|
||||
integrity sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz#77ec30d0704cf4eb1cb4a63f501c9852c6728cf4"
|
||||
integrity sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==
|
||||
"@rolldown/binding-linux-ppc64-gnu@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz#3bd890d96ae29f93718aa142ed0982f2ee2978ad"
|
||||
integrity sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz#3b9b6e0dd3e86c597f42858748ca25f1dfd58ed8"
|
||||
integrity sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==
|
||||
"@rolldown/binding-linux-s390x-gnu@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz#0959a0d5af22741d5787918e27aef70dc8602804"
|
||||
integrity sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==
|
||||
|
||||
"@rolldown/binding-linux-x64-musl@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz#f78033c592c8bd2af48284a45f8e4baaa0befbf5"
|
||||
integrity sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==
|
||||
"@rolldown/binding-linux-x64-gnu@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz#7baa94e826328ac6f457d9e1abacffa0353e8d22"
|
||||
integrity sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==
|
||||
|
||||
"@rolldown/binding-openharmony-arm64@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz#36e951f5a6fca922a5205e283d0a82b9f98199ca"
|
||||
integrity sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==
|
||||
"@rolldown/binding-linux-x64-musl@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz#5f37597eaf0e22313d1e3d4be7d1be1fd332904e"
|
||||
integrity sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz#c1e494ac47e13bd857fca0b3ad59c33580241f7e"
|
||||
integrity sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==
|
||||
"@rolldown/binding-openharmony-arm64@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz#d096567af3f738cbe6aa858ab0a01aae9f357ef4"
|
||||
integrity sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz#b0effffcd6872f8a021373eb437916b1b52283a4"
|
||||
integrity sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==
|
||||
"@rolldown/binding-win32-arm64-msvc@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz#d53b911aa4e6b547b789c07747fabab2ac8c237d"
|
||||
integrity sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc@1.2.5":
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz#7bbd08cfda6a98de9b756472c772a36ebb7229bc"
|
||||
integrity sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==
|
||||
|
||||
"@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1":
|
||||
version "1.0.1"
|
||||
@@ -298,17 +303,17 @@
|
||||
resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b"
|
||||
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
|
||||
|
||||
"@tanstack/query-core@5.101.4":
|
||||
version "5.101.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.101.4.tgz#c01b4289b3094144d8ef96235aa506242cf83efa"
|
||||
integrity sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==
|
||||
"@tanstack/query-core@5.102.4":
|
||||
version "5.102.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.102.4.tgz#05fea44fdc900438210a432dd77a3893e26bb4cf"
|
||||
integrity sha512-6eXkL+KFyVAZdpKoThCjPQQylZK8/ZOMYTJEVoPtHLkLs2PYks6vUiim9qzgvFXakSkRIAZwAZTeZLIJmpgRpQ==
|
||||
|
||||
"@tanstack/react-query@^5.101.4":
|
||||
version "5.101.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.101.4.tgz#044137754d79aaa30a82f6e6c42488be79c86ea7"
|
||||
integrity sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==
|
||||
"@tanstack/react-query@^5.102.4":
|
||||
version "5.102.4"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.102.4.tgz#1cece9f2f06e8a34e33a5147e625fd54a02c07e4"
|
||||
integrity sha512-Cn6X2eqFHjCtA/sfM/3eiT6/YmcPqo30twNHUlqkfz9LBqRzdt8GN/mFMWXn/qTacYUo3d4nycMzn3LWZmJ7MQ==
|
||||
dependencies:
|
||||
"@tanstack/query-core" "5.101.4"
|
||||
"@tanstack/query-core" "5.102.4"
|
||||
|
||||
"@tanstack/react-store@^0.11.0":
|
||||
version "0.11.1"
|
||||
@@ -389,17 +394,17 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
|
||||
integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
|
||||
|
||||
"@types/node@^26.2.0":
|
||||
version "26.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.2.0.tgz#5a4875a862fda8fdc57de8faa579bb81ecba1685"
|
||||
integrity sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==
|
||||
"@types/node@^26.3.0":
|
||||
version "26.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.3.0.tgz#757c33b17fe06db5a356582e9658603a1bd80caf"
|
||||
integrity sha512-L3fgrnchriRC2ExBflb8j4uZZURHZfQsmQeyVzhjcHW4kkwVyo8/0h1B2MVzMTrYUJYu6G7EWs14hW/L9putqw==
|
||||
dependencies:
|
||||
undici-types "~8.3.0"
|
||||
|
||||
"@types/react-dom@^19.2.4":
|
||||
version "19.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.4.tgz#165ab5e97dfb081847b0f6755060cf40a3f6666b"
|
||||
integrity sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==
|
||||
"@types/react-dom@^19.2.5":
|
||||
version "19.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.5.tgz#d4ecf8d704cab59901d3fbb0975b6f855306595f"
|
||||
integrity sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==
|
||||
|
||||
"@types/react@^19.2.18":
|
||||
version "19.2.18"
|
||||
@@ -530,10 +535,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz#cf3b7b0d6ce5635daca4c8e01c189cdcde47ec3c"
|
||||
integrity sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==
|
||||
|
||||
"@vitejs/plugin-react@^6.0.5":
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz#48ee7951093ff9054952cff9d64e874355cde372"
|
||||
integrity sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==
|
||||
"@vitejs/plugin-react@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz#a3d7a3b998c618385d076e390b74398c3d8c65bc"
|
||||
integrity sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==
|
||||
dependencies:
|
||||
"@rolldown/pluginutils" "^1.0.1"
|
||||
|
||||
@@ -576,10 +581,10 @@ clsx@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
|
||||
colorjs.io@^0.5.0:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/colorjs.io/-/colorjs.io-0.5.2.tgz#63b20139b007591ebc3359932bef84628eb3fcef"
|
||||
integrity sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==
|
||||
colorjs.io@^0.7.0:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/colorjs.io/-/colorjs.io-0.7.1.tgz#311b356a325c2c835c5aed17f298b238d7d9a5a1"
|
||||
integrity sha512-LY7OHnJZxHwT5UlzNa9bbhHHDbzB6yE5+3MIPwJEQKRvSCt/T4G7epsj+9j2BExUIIfXFIJGsIXUKdfrK9Q5tA==
|
||||
|
||||
cookie@^1.0.1:
|
||||
version "1.1.1"
|
||||
@@ -899,10 +904,10 @@ jotai-location@^0.6.2:
|
||||
resolved "https://registry.yarnpkg.com/jotai-location/-/jotai-location-0.6.2.tgz#7f90ec768d9aca62ce7d509baa771848225edb39"
|
||||
integrity sha512-D+fqGNNgPHpehOsGBsYsS7F3kNgml099MJrGYt8YlLumcnoTVrBdcO+Y11KyMlyzwPihEN0HPUgke9Iihz9OXw==
|
||||
|
||||
jotai@^2.20.2:
|
||||
version "2.20.2"
|
||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.20.2.tgz#fccdc02ee68b314c2a723a8b9c35de6fcca5e644"
|
||||
integrity sha512-aHB4CNb9qRcyf0mwSB6EO5bCGAjx8cTwFgOFCE2leOnTzqACbnSWG8XoWB3LxCT1Qoj03I1OWAHszDmN4uHb/w==
|
||||
jotai@^2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.20.3.tgz#862ce7289f07ae1991dca9cba93bff94f8c35285"
|
||||
integrity sha512-N8L9FUbeGDP+0qNJw1FebOxt+5hk+jTOwUA2LlRE4C081jUcY6F1T/FSETp4DT2/INMtiSRZyNm2D+yZdzrSgA==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
@@ -1012,10 +1017,10 @@ loose-envify@^1.4.0:
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
lucide-react@^1.31.0:
|
||||
version "1.31.0"
|
||||
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-1.31.0.tgz#1e758f3f86bc0456957d447e6e550b645d96e255"
|
||||
integrity sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==
|
||||
lucide-react@^1.34.0:
|
||||
version "1.34.0"
|
||||
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-1.34.0.tgz#12155b6f950d63b57e29d7ce592e65c116ea95aa"
|
||||
integrity sha512-vnjGJNI7Htk5+oWW8gXGuaLgwgAb0T6/iZbBrp9JCfRFwdNWZ0YTm3eyxjOLgwN6r8iyAf3UA70zNmBRBNv7yg==
|
||||
|
||||
marked@14.0.0:
|
||||
version "14.0.0"
|
||||
@@ -1029,10 +1034,10 @@ mogh_auth_client@^1.7.1:
|
||||
dependencies:
|
||||
jwt-decode "^4.0.0"
|
||||
|
||||
mogh_ui@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mogh_ui/-/mogh_ui-1.1.1.tgz#762623631587474e5532648b4d35c0cbe4bf3bfd"
|
||||
integrity sha512-hxVg9cuuRTm9e9UEBB9fNPi/NARa5h/pSl+FvPM9mzNfDIsimQ6YWg1g7lNfkd/EdKg9W9UX+9Ud3HactLLxFA==
|
||||
mogh_ui@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/mogh_ui/-/mogh_ui-1.2.7.tgz#17a3994a928cbfc8baf79031b2bcd7121fd1d29a"
|
||||
integrity sha512-kQZuStgvzx2Qpis/fF0lHIa5ptlHIzrxjL1suajqcjxIcHCeLCeIeRvExeo0/vQECYbx+PEU6UboACOSlIzIow==
|
||||
|
||||
monaco-editor@^0.56.0:
|
||||
version "0.56.0"
|
||||
@@ -1191,7 +1196,7 @@ postcss@^8.3.11:
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
postcss@^8.5.25, postcss@^8.5.26:
|
||||
postcss@^8.5.26:
|
||||
version "8.5.26"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.26.tgz#6e75135780c7e10df3433bf2266c552d35c8c620"
|
||||
integrity sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==
|
||||
@@ -1355,28 +1360,29 @@ reselect@^5.1.0:
|
||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e"
|
||||
integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
|
||||
|
||||
rolldown@~1.2.1:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.3.tgz#103bdcbbd575d51265277b8b510f080827b6eb6f"
|
||||
integrity sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==
|
||||
rolldown@~1.2.4:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.2.5.tgz#1f504a7d05260a769e617d950410bbb051498c50"
|
||||
integrity sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==
|
||||
dependencies:
|
||||
"@oxc-project/types" "=0.143.0"
|
||||
"@oxc-project/types" "=0.146.0"
|
||||
"@rolldown/pluginutils" "^1.0.0"
|
||||
optionalDependencies:
|
||||
"@rolldown/binding-android-arm64" "1.2.3"
|
||||
"@rolldown/binding-darwin-arm64" "1.2.3"
|
||||
"@rolldown/binding-darwin-x64" "1.2.3"
|
||||
"@rolldown/binding-freebsd-x64" "1.2.3"
|
||||
"@rolldown/binding-linux-arm-gnueabihf" "1.2.3"
|
||||
"@rolldown/binding-linux-arm64-gnu" "1.2.3"
|
||||
"@rolldown/binding-linux-arm64-musl" "1.2.3"
|
||||
"@rolldown/binding-linux-ppc64-gnu" "1.2.3"
|
||||
"@rolldown/binding-linux-s390x-gnu" "1.2.3"
|
||||
"@rolldown/binding-linux-x64-gnu" "1.2.3"
|
||||
"@rolldown/binding-linux-x64-musl" "1.2.3"
|
||||
"@rolldown/binding-openharmony-arm64" "1.2.3"
|
||||
"@rolldown/binding-win32-arm64-msvc" "1.2.3"
|
||||
"@rolldown/binding-win32-x64-msvc" "1.2.3"
|
||||
"@rolldown/binding-android-arm-eabi" "1.2.5"
|
||||
"@rolldown/binding-android-arm64" "1.2.5"
|
||||
"@rolldown/binding-darwin-arm64" "1.2.5"
|
||||
"@rolldown/binding-darwin-x64" "1.2.5"
|
||||
"@rolldown/binding-freebsd-x64" "1.2.5"
|
||||
"@rolldown/binding-linux-arm-gnueabihf" "1.2.5"
|
||||
"@rolldown/binding-linux-arm64-gnu" "1.2.5"
|
||||
"@rolldown/binding-linux-arm64-musl" "1.2.5"
|
||||
"@rolldown/binding-linux-ppc64-gnu" "1.2.5"
|
||||
"@rolldown/binding-linux-s390x-gnu" "1.2.5"
|
||||
"@rolldown/binding-linux-x64-gnu" "1.2.5"
|
||||
"@rolldown/binding-linux-x64-musl" "1.2.5"
|
||||
"@rolldown/binding-openharmony-arm64" "1.2.5"
|
||||
"@rolldown/binding-win32-arm64-msvc" "1.2.5"
|
||||
"@rolldown/binding-win32-x64-msvc" "1.2.5"
|
||||
|
||||
rxjs@^7.4.0:
|
||||
version "7.8.2"
|
||||
@@ -1385,10 +1391,10 @@ rxjs@^7.4.0:
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
sanitize-html@^2.17.6:
|
||||
version "2.17.6"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.17.6.tgz#c62a2e2e55d03004f3e0ecd6dbb9126d4b985d82"
|
||||
integrity sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA==
|
||||
sanitize-html@^2.17.7:
|
||||
version "2.17.7"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.17.7.tgz#b16417c348ea5f99c2451964b6e6621ca6b1da94"
|
||||
integrity sha512-PGtEkc9cbnedU3s9TmzDbpsZ8w086g/0Q8k8/oIO1NLNU3i5k9yn835CrjJSajp1KMmkisbO1qPXxNKO3welAg==
|
||||
dependencies:
|
||||
deepmerge "^4.2.2"
|
||||
escape-string-regexp "^4.0.0"
|
||||
@@ -1398,136 +1404,136 @@ sanitize-html@^2.17.6:
|
||||
parse-srcset "^1.0.2"
|
||||
postcss "^8.3.11"
|
||||
|
||||
sass-embedded-all-unknown@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.100.0.tgz#efee7111ec5e2d6c34a2f57d090e6416c01890ff"
|
||||
integrity sha512-auFtXY/kwYILmSVjtBDwyj0axcLbYYiffOKWoaXHnI5bsYwiRbBh3EneR1rpbX2ZIZCrwX93i5pxKLTZF/662Q==
|
||||
sass-embedded-all-unknown@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.103.1.tgz#a898f9af761f6619c6f7cf7ba6040cbfc3dd6ab4"
|
||||
integrity sha512-MwX2ap06TbImAqlbnH6EndgBBejk6Eq2QB6Ae2hukCn9wmXf9cRu27um3mz3tyB4oJOOku0xfHqFH/+ckYj6YA==
|
||||
dependencies:
|
||||
sass "1.100.0"
|
||||
sass "1.103.1"
|
||||
|
||||
sass-embedded-android-arm64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.100.0.tgz#0a4456a751f76a378ac9f3516a034f56eca9b171"
|
||||
integrity sha512-W+Ru9JwTnfU0UX3jSZcbqFdtKFMcYdfFwytc57h2DgnqCOIiAqI2E06mABZBZC+r3LwXCBuS5GbXAGeVgvVDkA==
|
||||
sass-embedded-android-arm64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.103.1.tgz#f57e5adf9a1fc97e51ac1268a6c6fc19481b5cac"
|
||||
integrity sha512-jBXWMksyz55XeLOWvQs64BqiG5DcqflmMxcvMoA2oHtQT/7XZ02hBn502qpcF/q8Qd+qqG/hU5ccM+hGXrbQZw==
|
||||
|
||||
sass-embedded-android-arm@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.100.0.tgz#7de61d65cfa997c58df426a8b20a096a60497313"
|
||||
integrity sha512-70f3HgX2pFNmzpGQ86n5e6QfWn2fP4QUQGfFQK0P1XH73ZLIzLo2YqygrGKGKeeqtc5eU2Wl1/xQzhzuKnO4kw==
|
||||
sass-embedded-android-arm@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.103.1.tgz#e675976d0e963d87a85d643ca325e736cde2ed80"
|
||||
integrity sha512-p36WHpsu5HEo2+NVNbI2Nmb8VgVH0xwOQyzghvZhF7ikGCzEVYI5BP28vHUZlCcgj2TUvMEJKI2lV336a+F/sg==
|
||||
|
||||
sass-embedded-android-riscv64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.100.0.tgz#cc6721c1526ef5b10d9da02f1e44e7dbaffc11e0"
|
||||
integrity sha512-icU3o0V/uCSytSpf+tX5Lf51BvyQEbLzDUJfUi9etSauYBGHpPKkdtdZH0si4v98phq11Kl8rSV1SggksxF1Hg==
|
||||
sass-embedded-android-riscv64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.103.1.tgz#09ae1946aac97032dc91e2faa27d1b496b56836e"
|
||||
integrity sha512-rzrH7RNntk0rx+zCE9xOiAIUt37D1cQKvFfcoX0xiePdGbNHBN8DWKaWN9vaCRfbgKCmxPjevzaqFNI/G/8E1A==
|
||||
|
||||
sass-embedded-android-x64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.100.0.tgz#dc38f6c633f8bf61b470ac00452ebb50e36f421f"
|
||||
integrity sha512-mevF9VQk6gEYByy8+jusaHGmd7Usb2ytX/DsEOd0JtOGCtcf1kh575xJ6OUBDIcJ15uLnbau/0iy1eP6WVBvWA==
|
||||
sass-embedded-android-x64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.103.1.tgz#bf2feddef3459e549cb437d0d052a940a8d32273"
|
||||
integrity sha512-EQglAJXJutuIcqgZf7YOwzao2ND6ow/OgqN+cM48/Qaeb6AimYmZiUJmM9FFcmS3Q2n1k6/2OU3l3Hlin7bFpw==
|
||||
|
||||
sass-embedded-darwin-arm64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.100.0.tgz#7773c0586e78caab599d53370626c28ca08110eb"
|
||||
integrity sha512-1PVlYi61POo93IT/FfrG1mc1tAHxeSTyUALF2aOFmXGWjVXr3bQzEQiBGCOvQbj/ix+5hNyXFXcEMEyKvtUJJA==
|
||||
sass-embedded-darwin-arm64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.103.1.tgz#be130b070ea07ec1ee64e4b2d8b4659f7c2fe668"
|
||||
integrity sha512-rlaBeCul8pLbDRKdBAxdDxSBwFU5fbX42ci6CxW2Vbs34jWTMQ72+nE2v2KkGGce+JnYbb7UV6xDP/4uDWWi+w==
|
||||
|
||||
sass-embedded-darwin-x64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.100.0.tgz#91066f64875b87eba0cb699ba8254b4863d33901"
|
||||
integrity sha512-x97o3JnGyImZNCIVs9wQHJUE5QCvmVIKaH1cwrz/5dK7OT1FpeNiW+u9TUomP9hG6Ekjd8EL8NBHpxTfIhdjmg==
|
||||
sass-embedded-darwin-x64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.103.1.tgz#82b074bb1b6b975c7fbc0e8fbf32570df1e497d3"
|
||||
integrity sha512-VSKFfhI//AOct6ppFmAaSH7tlW2LOFECMGs77jIoWf0ZjvGch/3sc9lUTTdIaYIOnTS4qpZck+8OcI2D0G92QA==
|
||||
|
||||
sass-embedded-linux-arm64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.100.0.tgz#88d2f51a0882f399c327a1d6cd06757d13de4f82"
|
||||
integrity sha512-Dwjmj8Z6VRy7rAi53JAdEwIyUjpfl7PhpSc2/LpQPQx+aO5Dp7Spaipkax0ufJl1SoDUdchCsM4y/88YaluorQ==
|
||||
sass-embedded-linux-arm64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.103.1.tgz#b3d05cd82927c94d397e04e5cc8681d315d4253f"
|
||||
integrity sha512-rC+80/Xr9svo0ka2Zr/sjD+N1zEHTw2Urm/nsEjaVp5HSH2i22jwhTKTIqie2OLl6ti+qdYnbI0sSfxOYRtgrg==
|
||||
|
||||
sass-embedded-linux-arm@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.100.0.tgz#9721dca177348c1448847ceee1d031a692df0771"
|
||||
integrity sha512-9Ul7O1eKrc5YlhwWjkp8tZPSe3UEwSZ1uwUZOQom1HL0pRlBA6F/IlGZYFTLwnHMIP1fc77MMNaBRfc05mKMpw==
|
||||
sass-embedded-linux-arm@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.103.1.tgz#8064cb1ef7a94ab73f4a6709157a8eeb5abe6c0a"
|
||||
integrity sha512-tJRLPUtBwXHTnBnG7I39gQvCLreuOHLLCMyKHPR1hQ7aFNwiZjADYzKMRQPNMUqti/Os9z+6I7V13FqgQHfuzg==
|
||||
|
||||
sass-embedded-linux-musl-arm64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.100.0.tgz#63a1e0618a2a3acf10bab915585c4dc495327581"
|
||||
integrity sha512-XpACJB2KjSLjf2e9uuvGVdOURsoNrFqgRiihhXyUHK9W0t3LIHb7z5MA/7XGPIT9bWSOO2zyw+rH/FHtDV/Yrg==
|
||||
sass-embedded-linux-musl-arm64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.103.1.tgz#b5eff900406647ae33839861f3f577b5cae41e7d"
|
||||
integrity sha512-jMgG/C/VMo7+ShMFdk0xjJmQmYkj1PbJu/yrQiaQHpuo5pE2G+vnkEgsuM5EM3lTQ4MbMfnf715qy1fkbNPiUQ==
|
||||
|
||||
sass-embedded-linux-musl-arm@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.100.0.tgz#44f9472ae663411de82b7792a918e162c7bcbc02"
|
||||
integrity sha512-sl0JgbGloPyJg66XXx5UDSDScZ0oU85DpMQU4JU/sCUCFj1Z8zZ69SJWKTCNE4/jwnce7WI2zPCV5AG+RHOZJw==
|
||||
sass-embedded-linux-musl-arm@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.103.1.tgz#d82b3b96881ebad7e939293c265b146d93e9f392"
|
||||
integrity sha512-N8L/kgVzXpnH+8d6ErIzqvb0l8kHODR3OfPaf0Azw6DOLGtEjwDRUbHno+G7iogGzyPOjKsM8OdyqHJj/bpTdw==
|
||||
|
||||
sass-embedded-linux-musl-riscv64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.100.0.tgz#21954e480cfb55d4ba8167626cde1cc8560d7eee"
|
||||
integrity sha512-ShvI0Kx04mwoCARwZ0UjiT97isQvzO80tAt91zmFyHLN9kelc/IrQi940farSm2xQVPCKdeVyeG0ekBsokSpYQ==
|
||||
sass-embedded-linux-musl-riscv64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.103.1.tgz#6221464b2d11e5628259524624863b7c0dca99f0"
|
||||
integrity sha512-LZm8rEvI6sKU87qPOlODsYeAs7CmWy0B4ShYkjG4OMcGCcV4Ffxn25XW4TFqmruThITQvJ8WT+WGblzdoxYgbA==
|
||||
|
||||
sass-embedded-linux-musl-x64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.100.0.tgz#a6c93d51999a8b22795d383b67e61962d32c96cd"
|
||||
integrity sha512-TDBCRWNuS4RDLQXvRc1gjZlWiWTWaWGp0Bwu/IKwJxov81lsvrCs3TihTyNXtW7V5aoN4Ky3r0QOkNb3mwmBnA==
|
||||
sass-embedded-linux-musl-x64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.103.1.tgz#483b0e570a27197a84cf75db64974ad579c1695f"
|
||||
integrity sha512-rmKzgk4t6RpaDhSefbbnk5yrA4pk1nHlVhJECGay8yMfzK8eglATeY9fzPgt89xCuQk6rkOiy11e+m8tLYOW5Q==
|
||||
|
||||
sass-embedded-linux-riscv64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.100.0.tgz#01f20471a1be16d18e150993e5ac771d618abd34"
|
||||
integrity sha512-j4ENJGOheO+fm3j/yorLxCjBP6/XskrZx7dTLlT+lXYwN/qqCqoA/gsNLI0McS3DFM6GBwPiffzWsdWS8t6sEQ==
|
||||
sass-embedded-linux-riscv64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.103.1.tgz#7e0929030101d8c059c47da43500536cb9bd5575"
|
||||
integrity sha512-xydQmtxla31uMrmN6z+mAogVGdEMDmkUElVq2+udomX/TPZDYJBkApEyJl237M2VLWVv29v1N4U4i2Z4xa7SDA==
|
||||
|
||||
sass-embedded-linux-x64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.100.0.tgz#809649a0df17f7afb1dfd02e76dbf0676dd8471e"
|
||||
integrity sha512-0vUSN8j0WGtCJIOPh//EmUvYGHW0QOe5iul8qyhPk50MAcw49MA0r34AhftjDdx94ILPF6vApFs0gwHPQRlpVA==
|
||||
sass-embedded-linux-x64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.103.1.tgz#c9818e082004bb94ce615e5918c0192f3d4f9e54"
|
||||
integrity sha512-Rln1oWm0MWKzzemOgQWrRFzfVBARwS9qxAidZPpCQHSohzPnWizFkmY1CUJad/YJNsA7sscueMdX63OEvxoM8w==
|
||||
|
||||
sass-embedded-unknown-all@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.100.0.tgz#fdc210a25a06e9d79655a02c1c77d656fa6f2ca0"
|
||||
integrity sha512-c+naBgWId4MIpToXcI0DgqetjdAkwTTAxFAuOaBz7HUXLdyG1oZRrEvSsbe41nEdQOKH0vgofVFCeSQgoXOG9A==
|
||||
sass-embedded-unknown-all@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.103.1.tgz#1df7de73d83b44ce6395eb060d9506ef79b26f85"
|
||||
integrity sha512-p7kI4US8+n+YguKczXh+4KcodJnk+8cwv+QFdvMaz3OfPrdHV0EnhPsaVDWD9qXb1XXCEdUhpp6vQwGk3eKc3A==
|
||||
dependencies:
|
||||
sass "1.100.0"
|
||||
sass "1.103.1"
|
||||
|
||||
sass-embedded-win32-arm64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.100.0.tgz#fd8cec45b41390c3db40d13c4d291be7f35b6b0b"
|
||||
integrity sha512-iE+yxj+hUXwwbqpHkXxgAWTzeRfcWxJ7SSTQEPMk48lwq3oCrWLlz5sQuWHbuTK/i0GKQfROdP+hOmPi89yjUg==
|
||||
sass-embedded-win32-arm64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.103.1.tgz#5e388ed9b58109cd696ed5fc00350e14372b2c70"
|
||||
integrity sha512-/0renOj3SpZGu4TA8CZJ6qZbTxGZk1khkPfiBTmV1uFB40ESYM5VsdXNj76P07bsAjIj9X2fCjTafLQFLSd9Nw==
|
||||
|
||||
sass-embedded-win32-x64@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.100.0.tgz#a758862b1455d6616be301b363e7e1c93e6361b7"
|
||||
integrity sha512-qI4F8MI7/KYoy9NdjJfhSspG42WPkADSNDvwEV7qWvCSFC83koJssRsKO2/PfY+niZz6BG65Ic/D+A11h959hw==
|
||||
sass-embedded-win32-x64@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.103.1.tgz#5ae50ecb547e6dc17619ee9870bd8e6195ff8ec1"
|
||||
integrity sha512-fIg60j9u5YlLUU3W8FvySMR4HHIo9R12HHFQTW7njyQ8nXqgR+t+XfCzGJloQka8g583BlAfu31Q76Ljr5o+9g==
|
||||
|
||||
sass-embedded@^1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded/-/sass-embedded-1.100.0.tgz#fe4742f2f80c21b287e3b90399b9a3f7ca1dcf6f"
|
||||
integrity sha512-Ut8wlQSk19tm7jMK6mz6cF1+e+E7tUnW2tM02zQDPnOTcVbV8qCQG8UWxZkkNlY50+hV3hqP24OOkUlMz8xBpw==
|
||||
sass-embedded@^1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-embedded/-/sass-embedded-1.103.1.tgz#bcf8edcc9467213f8963a4505384fd34e38c31d1"
|
||||
integrity sha512-UVIuFZPzz+4DXYbHzQ5gKaKkyP2IH3Sc/WoF63N2V7QIfnC2E9UpQAtiKlCVJTfBe43HRgHDBLnXpZkYAEBsNQ==
|
||||
dependencies:
|
||||
"@bufbuild/protobuf" "^2.5.0"
|
||||
colorjs.io "^0.5.0"
|
||||
colorjs.io "^0.7.0"
|
||||
immutable "^5.1.5"
|
||||
rxjs "^7.4.0"
|
||||
supports-color "^8.1.1"
|
||||
sync-child-process "^1.0.2"
|
||||
varint "^6.0.0"
|
||||
optionalDependencies:
|
||||
sass-embedded-all-unknown "1.100.0"
|
||||
sass-embedded-android-arm "1.100.0"
|
||||
sass-embedded-android-arm64 "1.100.0"
|
||||
sass-embedded-android-riscv64 "1.100.0"
|
||||
sass-embedded-android-x64 "1.100.0"
|
||||
sass-embedded-darwin-arm64 "1.100.0"
|
||||
sass-embedded-darwin-x64 "1.100.0"
|
||||
sass-embedded-linux-arm "1.100.0"
|
||||
sass-embedded-linux-arm64 "1.100.0"
|
||||
sass-embedded-linux-musl-arm "1.100.0"
|
||||
sass-embedded-linux-musl-arm64 "1.100.0"
|
||||
sass-embedded-linux-musl-riscv64 "1.100.0"
|
||||
sass-embedded-linux-musl-x64 "1.100.0"
|
||||
sass-embedded-linux-riscv64 "1.100.0"
|
||||
sass-embedded-linux-x64 "1.100.0"
|
||||
sass-embedded-unknown-all "1.100.0"
|
||||
sass-embedded-win32-arm64 "1.100.0"
|
||||
sass-embedded-win32-x64 "1.100.0"
|
||||
sass-embedded-all-unknown "1.103.1"
|
||||
sass-embedded-android-arm "1.103.1"
|
||||
sass-embedded-android-arm64 "1.103.1"
|
||||
sass-embedded-android-riscv64 "1.103.1"
|
||||
sass-embedded-android-x64 "1.103.1"
|
||||
sass-embedded-darwin-arm64 "1.103.1"
|
||||
sass-embedded-darwin-x64 "1.103.1"
|
||||
sass-embedded-linux-arm "1.103.1"
|
||||
sass-embedded-linux-arm64 "1.103.1"
|
||||
sass-embedded-linux-musl-arm "1.103.1"
|
||||
sass-embedded-linux-musl-arm64 "1.103.1"
|
||||
sass-embedded-linux-musl-riscv64 "1.103.1"
|
||||
sass-embedded-linux-musl-x64 "1.103.1"
|
||||
sass-embedded-linux-riscv64 "1.103.1"
|
||||
sass-embedded-linux-x64 "1.103.1"
|
||||
sass-embedded-unknown-all "1.103.1"
|
||||
sass-embedded-win32-arm64 "1.103.1"
|
||||
sass-embedded-win32-x64 "1.103.1"
|
||||
|
||||
sass@1.100.0:
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.100.0.tgz#b4cab1bed286fe22ac6c879c514f71cd36aa06c8"
|
||||
integrity sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==
|
||||
sass@1.103.1:
|
||||
version "1.103.1"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.103.1.tgz#13b70f5ff69288db956dc27d27c8fb79f3a27c61"
|
||||
integrity sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==
|
||||
dependencies:
|
||||
chokidar "^5.0.0"
|
||||
immutable "^5.1.5"
|
||||
@@ -1713,15 +1719,15 @@ victory-vendor@^37.0.2:
|
||||
d3-time "^3.0.0"
|
||||
d3-timer "^3.0.1"
|
||||
|
||||
vite@^8.2.1:
|
||||
version "8.2.1"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-8.2.1.tgz#6fc8d8bb843bd52353091fac978e194d4de5b31d"
|
||||
integrity sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==
|
||||
vite@^8.2.2:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-8.2.2.tgz#399aefad3656145145be110d137a07ea5bb55014"
|
||||
integrity sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==
|
||||
dependencies:
|
||||
lightningcss "^1.33.0"
|
||||
picomatch "^4.0.5"
|
||||
postcss "^8.5.25"
|
||||
rolldown "~1.2.1"
|
||||
postcss "^8.5.26"
|
||||
rolldown "~1.2.4"
|
||||
tinyglobby "^0.2.17"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
Reference in New Issue
Block a user