Compare commits

..
11 Commits
Author SHA1 Message Date
ninjasurge cf3f2e0503 merge upstream 2026-10-02 09:07:18 -05:00
Maxwell BeckerandGitHub 780ac68b99 2.3.3 (#1605)
* 2.4.0-dev-0

* deploy 2.4.0-dev-1

* slightly optimize stack read path

* Add IMAGE_NAME and IMAGE_TAG default build args in addition to VERSION

* deploy 2.4.0-dev-2

* ui use || location.origin for base url

* move to build time versioning

* simplify run action with komodo managed version

* procedure batch executions use  tag filters

* set advanced code splitting options

* improve network module logic / edge cases

* fix startup ordering - apply legacy db fixes before other tasks

* lengthen periphery command timeouts

* update deps

* clients still have explicit versions

* Add page breadcrumbs

* mogh ui 1.2.6

* fix set version – set only specific path

* cargo fmt

* use mogh ui ListPagination

* bump deps, rust 1.98.0

* missing dockerfile rust 1.98.0 bump
2026-09-01 14:34:49 -07:00
ninjasurge 221ad252e1 merge upstream 2026-08-26 21:18:32 -05:00
ninjasurge 7406c292a1 merge upstream 2026-04-04 12:49:28 -05:00
NinjaSurge 427475651c Merge branch 'github-starred-main' 2026-03-25 18:51:08 -05:00
NinjaSurge ae0c8bf98a Merge branch 'fix-issue-1022' 2026-02-12 09:39:30 -06:00
NinjaSurge a7592f4561 Merge branch 'fix-issue-1022' of https://github.com/litlmike/komodo into fix-issue-1022 2026-02-12 09:37:28 -06:00
rootandNinjaSurge a199c8fe27 fix: refresh login options when OIDC client is re-initialized
The GetLoginOptions response was cached in a OnceLock, meaning it was
computed once at startup and never updated. When the OIDC client was
re-initialized (e.g. after config changes), the login options endpoint
would still return the stale cached value, causing the UI to show
outdated OIDC button state.

Replace the static OnceLock cache with a function that reads the
current OIDC client state on each request, so the response always
reflects whether OIDC is currently available.

Also add a 30-second polling interval to the frontend's login options
query so the login page picks up server-side changes without requiring
a hard refresh.

Fixes #1022
2026-02-12 09:37:19 -06:00
ninjasurge f7296c73b1 Merge pull request 'fix: prevent custom registry org input from deselecting' (#1) from fix-issue-1094 into main
Reviewed-on: ComputerSurge/komodo#1
2026-02-11 19:32:25 -06:00
root 0eaf8b5f01 fix: refresh login options when OIDC client is re-initialized
The GetLoginOptions response was cached in a OnceLock, meaning it was
computed once at startup and never updated. When the OIDC client was
re-initialized (e.g. after config changes), the login options endpoint
would still return the stale cached value, causing the UI to show
outdated OIDC button state.

Replace the static OnceLock cache with a function that reads the
current OIDC client state on each request, so the response always
reflects whether OIDC is currently available.

Also add a 30-second polling interval to the frontend's login options
query so the login page picks up server-side changes without requiring
a hard refresh.

Fixes #1022
2026-02-07 20:34:53 -08:00
Voice Typer User 6919582f69 fix: prevent custom registry org input from deselecting
The ImageRegistryConfig component's key included registry.organization,
which caused React to remount the component on every keystroke when
typing a custom organization name. This destroyed the OrganizationSelector's
internal customMode state, causing the input to deselect after each character.

Using a stable index-based key preserves component identity during edits.

Closes #1094
2026-02-06 16:04:35 -08:00
39 changed files with 1789 additions and 1442 deletions
Generated
+118 -812
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -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
View File
@@ -1,5 +1 @@
{
"imports": {
"@std/toml": "jsr:@std/toml"
}
}
{}
+1 -42
View File
@@ -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();
};
+7 -2
View File
@@ -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 && \
+12 -4
View File
@@ -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 && \
+7 -2
View File
@@ -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
+7 -2
View File
@@ -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 && \
+158
View File
@@ -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)
}
}
+50 -91
View File
@@ -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
View File
@@ -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())
}
+11 -9
View File
@@ -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.
+7 -2
View File
@@ -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(),
);
}
+7 -2
View File
@@ -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
+23 -2
View File
@@ -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)
+2 -2
View File
@@ -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,
)
+2 -2
View File
@@ -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,
)
+2 -2
View File
@@ -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,
)
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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;
+3 -3
View File
@@ -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
View File
@@ -1,6 +1,5 @@
use anyhow::Context;
use bollard::query_parameters::ListTasksOptions;
use komodo_client::entities::docker::task::*;
use super::*;
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
FROM rust:1.97.1-trixie as builder
FROM rust:1.98.0-trixie as builder
WORKDIR /builder
COPY . .
+1 -1
View File
@@ -1,4 +1,4 @@
FROM rust:1.97.1-trixie as builder
FROM rust:1.98.0-trixie as builder
WORKDIR /builder
COPY . .
+836
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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"
}
+8 -25
View File
@@ -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} />;
}
+7
View File
@@ -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
View File
@@ -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 -4
View File
@@ -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} />}
+19 -6
View File
@@ -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
View File
@@ -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={
+18 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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"