diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 94967feb4c..f137f1323c 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -238,4 +238,4 @@ jobs: run: | deno --version && bun -v && node --version && go version && python3 --version && php --version && ruby --version && pwsh --version && dotnet --version cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. - DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp,run_inline --all -- --nocapture --test-threads=10 + DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp --all -- --nocapture --test-threads=10 diff --git a/.github/workflows/discord-notification.yml b/.github/workflows/discord-notification.yml index d731faafd9..5eb48570e7 100644 --- a/.github/workflows/discord-notification.yml +++ b/.github/workflows/discord-notification.yml @@ -9,7 +9,9 @@ on: issue_comment: types: - created - - edited + pull_request_review_comment: + types: + - created jobs: notify_discord_when_pr_opened: @@ -51,7 +53,23 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} COMMENT_AUTHOR: ${{ github.event.comment.user.login }} COMMENT_URL: ${{ github.event.comment.html_url }} - COMMENT_IS_EDIT: ${{ github.event.action == 'edited' }} + DISCORD_CHANNEL_ID: "1372204995868491786" + DISCORD_GUILD_ID: "930051556043276338" + secrets: + DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }} + + notify_discord_on_review_comment: + if: > + github.event_name == 'pull_request_review_comment' + && github.event.comment.user.login != 'cloudflare-workers-and-pages[bot]' + && github.event.comment.user.login != 'ellipsis-dev[bot]' + uses: ./.github/workflows/shareable-discord-notification.yml + with: + PR_STATUS: "comment" + PR_NUMBER: ${{ github.event.pull_request.number }} + COMMENT_BODY: ${{ github.event.comment.body }} + COMMENT_AUTHOR: ${{ github.event.comment.user.login }} + COMMENT_URL: ${{ github.event.comment.html_url }} DISCORD_CHANNEL_ID: "1372204995868491786" DISCORD_GUILD_ID: "930051556043276338" secrets: diff --git a/.github/workflows/shareable-discord-notification.yml b/.github/workflows/shareable-discord-notification.yml index b0d3fed549..cf8c7d9078 100644 --- a/.github/workflows/shareable-discord-notification.yml +++ b/.github/workflows/shareable-discord-notification.yml @@ -36,10 +36,6 @@ on: description: "The comment URL" type: string default: "" - COMMENT_IS_EDIT: - description: "Whether this is an edit of an existing comment" - type: string - default: "false" secrets: DISCORD_WEBHOOK_URL: description: "Discord Webhook URL" @@ -139,7 +135,7 @@ jobs: runs-on: ubuntu-latest if: ${{ inputs.PR_STATUS == 'comment' }} steps: - - name: Post or update comment in Discord thread + - name: Post comment to Discord thread env: BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }} CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }} @@ -148,7 +144,6 @@ jobs: COMMENT_BODY: ${{ inputs.COMMENT_BODY }} COMMENT_AUTHOR: ${{ inputs.COMMENT_AUTHOR }} COMMENT_URL: ${{ inputs.COMMENT_URL }} - COMMENT_IS_EDIT: ${{ inputs.COMMENT_IS_EDIT }} run: | # 1) Find the thread by PR number threads=$(curl -s -H "Authorization: Bot $BOT_TOKEN" \ @@ -177,36 +172,10 @@ jobs: truncated_body="$COMMENT_BODY" fi - # 3) Build the message content - if [ "$COMMENT_IS_EDIT" = "true" ]; then - message=$(printf '**%s** [edited comment](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body") - else - message=$(printf '**%s** [commented](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body") - fi + # 3) Post the comment to the thread + message=$(printf '**%s** [commented](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body") payload=$(jq -n --arg content "$message" '{content: $content, flags: 4, allowed_mentions: {parse: []}}') - # 4) If this is an edit, try to find and update the existing Discord message - if [ "$COMMENT_IS_EDIT" = "true" ]; then - # Search recent messages in the thread for one containing the comment URL - messages=$(curl -s -H "Authorization: Bot $BOT_TOKEN" \ - "https://discord.com/api/v10/channels/${thread_id}/messages?limit=100") - existing_msg_id=$(echo "$messages" | jq -r \ - --arg url "$COMMENT_URL" \ - '[.[] | select(.content | contains($url))] | first | .id // empty') - - if [ -n "$existing_msg_id" ]; then - echo "Updating existing Discord message $existing_msg_id" - curl -s -X PATCH \ - -H "Authorization: Bot $BOT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$payload" \ - "https://discord.com/api/v10/channels/${thread_id}/messages/${existing_msg_id}" - exit 0 - fi - echo "Original Discord message not found, posting as new message" - fi - - # 5) Post a new message to the thread curl -s -X POST \ -H "Authorization: Bot $BOT_TOKEN" \ -H "Content-Type: application/json" \ diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 1238c47b05..90400b04e0 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -16167,7 +16167,6 @@ dependencies = [ "windmill-common", "windmill-native-triggers", "windmill-test-utils", - "windmill-worker", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c08092556d..ef969c8ffe 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -159,12 +159,12 @@ all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", " # For windows we have another set of languages enabled all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql-winauth", "bigquery", "csharp", "nu", "php", "java"] # Edition meta-features: shared groups -run_inline = ["windmill-api/run_inline"] +inline_preview = ["windmill-api/inline_preview"] oss_core = [ "embedding", "parquet", "openidconnect", "license", "http_trigger", "zip", "oauth2", "postgres_trigger", "mqtt_trigger", "websocket", "smtp", "native_trigger", - "static_frontend", "mcp", "bedrock", "run_inline", + "static_frontend", "mcp", "bedrock", "inline_preview", "quickjs" ] ce_core = ["oss_core", "private", "operator"] diff --git a/backend/windmill-api-configs/Cargo.toml b/backend/windmill-api-configs/Cargo.toml index a24e0b9540..bfb702c73b 100644 --- a/backend/windmill-api-configs/Cargo.toml +++ b/backend/windmill-api-configs/Cargo.toml @@ -13,7 +13,7 @@ default = [] enterprise = ["dep:windmill-autoscaling"] private = [] python = [] -run_inline = ["dep:windmill-worker", "dep:itertools"] +inline_preview = ["dep:windmill-worker", "dep:itertools"] [dependencies] windmill-api-auth.workspace = true diff --git a/backend/windmill-api-configs/src/lib.rs b/backend/windmill-api-configs/src/lib.rs index f4e91e554f..59ad1625a4 100644 --- a/backend/windmill-api-configs/src/lib.rs +++ b/backend/windmill-api-configs/src/lib.rs @@ -283,14 +283,14 @@ async fn native_kubernetes_autoscaling_healthcheck() -> Result<(), error::Error> } async fn list_available_python_versions() -> error::JsonResult> { - #[cfg(not(all(feature = "python", feature = "run_inline")))] + #[cfg(not(all(feature = "python", feature = "inline_preview")))] return Err(error::Error::BadRequest( "Python listing available only with 'python' feature enabled".to_string(), )); - #[cfg(all(feature = "python", feature = "run_inline"))] + #[cfg(all(feature = "python", feature = "inline_preview"))] use itertools::Itertools; - #[cfg(all(feature = "python", feature = "run_inline"))] + #[cfg(all(feature = "python", feature = "inline_preview"))] return Ok(Json( windmill_worker::PyV::list_available_python_versions() .await diff --git a/backend/windmill-api-integration-tests/Cargo.toml b/backend/windmill-api-integration-tests/Cargo.toml index c7d608ed28..c27a10775e 100644 --- a/backend/windmill-api-integration-tests/Cargo.toml +++ b/backend/windmill-api-integration-tests/Cargo.toml @@ -14,7 +14,6 @@ private = ["windmill-test-utils/private", "dep:aws-config", "dep:aws-credential- enterprise = ["windmill-test-utils/enterprise", "dep:base64"] deno_core = ["windmill-test-utils/deno_core"] mcp = [] -run_inline = ["dep:windmill-worker", "windmill-test-utils/run_inline", "windmill-test-utils/duckdb"] [dependencies] windmill-test-utils.workspace = true @@ -22,7 +21,6 @@ windmill-api-client.workspace = true windmill-common = { workspace = true, default-features = false } windmill-native-triggers = { workspace = true, features = ["native_trigger"] } windmill-api-auth.workspace = true -windmill-worker = { workspace = true, optional = true } sqlx.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index 7af8137630..66b002043e 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -13,7 +13,7 @@ default = [] private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"] enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"] stripe = [] -run_inline = ["dep:windmill-worker", "windmill-api-configs/run_inline"] +inline_preview = ["dep:windmill-worker", "windmill-api-configs/inline_preview"] agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"] enterprise_saml = ["dep:samael", "dep:libxml"] benchmark = [] diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index dbf058e4cb..dd65cf9e2f 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -9173,54 +9173,6 @@ paths: application/json: schema: {} - /w/{workspace}/jobs/run_inline/p/{path}: - post: - summary: run script by path without starting a new job - operationId: runScriptByPathInline - tags: - - job - parameters: - - $ref: "#/components/parameters/WorkspaceId" - - $ref: "#/components/parameters/ScriptPath" - requestBody: - description: script args - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/InlineScriptArgs" - - responses: - "200": - description: script result - content: - application/json: - schema: {} - - /w/{workspace}/jobs/run_inline/h/{hash}: - post: - summary: run script by hash without starting a new job - operationId: runScriptByHashInline - tags: - - job - parameters: - - $ref: "#/components/parameters/WorkspaceId" - - $ref: "#/components/parameters/ScriptHash" - requestBody: - description: script args - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/InlineScriptArgs" - - responses: - "200": - description: script result - content: - application/json: - schema: {} - /w/{workspace}/jobs/run_wait_result/preview: post: summary: run script preview and wait for result @@ -19859,12 +19811,6 @@ components: $ref: "#/components/schemas/ScriptLang" required: [content, args, language] - InlineScriptArgs: - type: object - properties: - args: - $ref: "#/components/schemas/ScriptArgs" - WorkflowTask: type: object properties: diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 2528e2556f..077ddd2876 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -27,22 +27,20 @@ use url::Url; #[cfg(all(feature = "enterprise", feature = "smtp"))] use windmill_common::auth::is_super_admin_email; use windmill_common::auth::TOKEN_PREFIX_LEN; -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] use windmill_common::client::AuthedClient; use windmill_common::db::UserDbWithAuthed; use windmill_common::error::JsonResult; use windmill_common::flow_status::{JobResult, RestartedFrom}; +#[cfg(feature = "inline_preview")] +use windmill_common::jobs::RunInlinePreviewScriptFnParams; use windmill_common::jobs::{ format_completed_job_result, format_result, DynamicInput, ENTRYPOINT_OVERRIDE, }; -#[cfg(feature = "run_inline")] -use windmill_common::jobs::{ - InlineScriptTarget, RunInlinePreviewScriptFnParams, RunInlineScriptFnParams, -}; use windmill_common::runnable_settings::{ ConcurrencySettings, ConcurrencySettingsWithCustom, DebouncingSettings, }; -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] use windmill_common::runtime_assets::{register_runtime_asset, InsertRuntimeAssetParams}; use windmill_common::scripts::ScriptRunnableSettingsInline; use windmill_common::triggers::TriggerMetadata; @@ -55,15 +53,15 @@ use windmill_common::DYNAMIC_INPUT_CACHE; #[cfg(all(feature = "enterprise", feature = "smtp"))] use windmill_common::{email_oss::send_email_html, server::load_smtp_config}; use windmill_object_store::upload_artifact_to_store; -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] use windmill_parser::asset_parser::AssetKind; use windmill_types::s3::BundleFormat; -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] use windmill_worker::get_worker_internal_server_inline_utils; use windmill_common::variables::get_workspace_key; -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] use crate::db::OptJobAuthed; use crate::triggers::trigger_helpers::{FlowId, ScriptId}; use crate::{ @@ -244,11 +242,6 @@ pub fn workspaced_service() -> Router { ) .route("/run/preview", post(run_preview_script)) .route("/run_inline/preview", post(run_inline_preview_script)) - .route( - "/run_inline/p/*script_path", - post(run_inline_script_by_path), - ) - .route("/run_inline/h/:hash", post(run_inline_script_by_hash)) .route( "/run_wait_result/preview", post(run_wait_result_preview_script), @@ -2860,7 +2853,7 @@ struct Preview { flow_path: Option, } -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] #[derive(Debug, Deserialize)] struct PreviewInline { content: String, @@ -2868,12 +2861,6 @@ struct PreviewInline { language: ScriptLang, } -#[cfg(feature = "run_inline")] -#[derive(Debug, Deserialize)] -struct InlineScriptArgs { - args: Option>>, -} - #[derive(Deserialize)] pub struct WorkflowTask { pub args: Option>>, @@ -4586,7 +4573,7 @@ async fn run_preview_script( Ok((StatusCode::CREATED, uuid.to_string())) } -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] async fn run_inline_preview_script( OptJobAuthed { authed, job_id }: OptJobAuthed, Tokened { token }: Tokened, @@ -4623,120 +4610,14 @@ async fn run_inline_preview_script( Ok(Json(to_raw_value(&result)).into_response()) } -#[cfg(not(feature = "run_inline"))] +#[cfg(not(feature = "inline_preview"))] async fn run_inline_preview_script() -> error::Result { Err(error::Error::InternalErr( "inline preview requires the worker feature".to_string(), )) } -#[cfg(feature = "run_inline")] -async fn run_inline_script_by_path( - OptJobAuthed { authed, .. }: OptJobAuthed, - Tokened { token }: Tokened, - Extension(db): Extension, - Extension(user_db): Extension, - Path((w_id, script_path)): Path<(String, StripPath)>, - Json(body): Json, -) -> error::Result { - let script_path_str = script_path.to_path(); - check_scopes(&authed, || format!("jobs:run:scripts:{script_path_str}"))?; - run_inline_script_inner( - authed, - token, - db, - w_id, - InlineScriptTarget::Path(script_path.to_path().to_string()), - body.args, - Some(user_db), - ) - .await -} - -#[cfg(not(feature = "run_inline"))] -async fn run_inline_script_by_path() -> error::Result { - Err(error::Error::InternalErr( - "inline script by path requires the worker feature".to_string(), - )) -} - -#[cfg(feature = "run_inline")] -async fn run_inline_script_by_hash( - OptJobAuthed { authed, .. }: OptJobAuthed, - Tokened { token }: Tokened, - Extension(db): Extension, - Extension(user_db): Extension, - Path((w_id, script_hash)): Path<(String, ScriptHash)>, - Json(body): Json, -) -> error::Result { - // Resolve the script path from the hash and check scopes properly - let hash = script_hash.0; - let userdb_authed = UserDbWithAuthed { db: user_db.clone(), authed: &authed.to_authed_ref() }; - let ScriptHashInfo { path, .. } = - get_script_info_for_hash(Some(userdb_authed), &db, &w_id, hash) - .await? - .prefetch_cached(&db) - .await?; - - check_scopes(&authed, || format!("jobs:run:scripts:{path}"))?; - - run_inline_script_inner( - authed, - token, - db, - w_id, - InlineScriptTarget::Hash(hash), - body.args, - Some(user_db), - ) - .await -} - -#[cfg(not(feature = "run_inline"))] -async fn run_inline_script_by_hash() -> error::Result { - Err(error::Error::InternalErr( - "inline script by hash requires the worker feature".to_string(), - )) -} - -#[cfg(feature = "run_inline")] -async fn run_inline_script_inner( - authed: ApiAuthed, - token: String, - db: DB, - w_id: String, - target: InlineScriptTarget, - args: Option>>, - user_db: Option, -) -> error::Result { - let utils = get_worker_internal_server_inline_utils()?; - let authed_owned: windmill_common::db::Authed = authed.clone().into(); - let result = utils.run_inline_script.as_ref()(RunInlineScriptFnParams { - target, - args, - workspace_id: w_id.clone(), - base_internal_url: utils.base_internal_url.clone(), - killpill_rx: utils.killpill_rx.resubscribe(), - created_by: authed.display_username().to_string(), - permissioned_as: username_to_permissioned_as(&authed.username), - permissioned_as_email: authed.email.clone(), - job_dir: "".to_string(), - worker_name: "".to_string(), - worker_dir: "".to_string(), - client: AuthedClient { - base_internal_url: utils.base_internal_url.clone(), - force_client: None, - token, - workspace: w_id, - }, - conn: windmill_common::worker::Connection::Sql(db), - user_db: user_db.map(|udb| (udb, authed_owned)), - }) - .await?; - Ok(Json(to_raw_value(&result)).into_response()) -} - -#[cfg(feature = "run_inline")] +#[cfg(feature = "inline_preview")] fn register_potential_assets_on_inline_execution( job_id: Uuid, w_id: &str, diff --git a/backend/windmill-common/src/db.rs b/backend/windmill-common/src/db.rs index da1d4c1cd6..cf938504d3 100644 --- a/backend/windmill-common/src/db.rs +++ b/backend/windmill-common/src/db.rs @@ -17,21 +17,6 @@ pub struct Authed { pub token_prefix: Option, } -impl Authed { - pub fn to_authed_ref(&self) -> AuthedRef<'_> { - AuthedRef { - email: &self.email, - username: &self.username, - is_admin: &self.is_admin, - is_operator: &self.is_operator, - groups: &self.groups, - folders: &self.folders, - scopes: &self.scopes, - token_prefix: &self.token_prefix, - } - } -} - #[derive(Clone, Debug, Hash)] pub struct AuthedRef<'a> { pub email: &'a str, diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index bc40e4cd75..12004b945d 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -326,28 +326,6 @@ pub struct RunInlinePreviewScriptFnParams { pub killpill_rx: tokio::sync::broadcast::Receiver<()>, } -pub enum InlineScriptTarget { - Path(String), - Hash(i64), -} - -pub struct RunInlineScriptFnParams { - pub workspace_id: String, - pub target: InlineScriptTarget, - pub args: Option>>, - pub created_by: String, - pub permissioned_as: String, - pub permissioned_as_email: String, - pub base_internal_url: String, - pub worker_name: String, - pub conn: crate::worker::Connection, - pub client: AuthedClient, - pub job_dir: String, - pub worker_dir: String, - pub killpill_rx: tokio::sync::broadcast::Receiver<()>, - pub user_db: Option<(crate::db::UserDB, crate::db::Authed)>, -} - #[derive(Clone)] pub struct WorkerInternalServerInlineUtils { pub killpill_rx: Arc>, @@ -359,13 +337,6 @@ pub struct WorkerInternalServerInlineUtils { + Send + Sync, >, - pub run_inline_script: Arc< - dyn Fn( - RunInlineScriptFnParams, - ) -> Pin>> + Send>> - + Send - + Sync, - >, } // To run a script inline, bypassing the db and job queue, windmill-api uses these functions. // They should only be called by the internal server of a worker. diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 806e3e99a2..007b398c79 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -2285,59 +2285,6 @@ pub struct MiniPulledJob { pub runnable_settings_handle: Option, } -impl MiniPulledJob { - pub fn new_inline( - workspace_id: String, - args: Option>>, - created_by: String, - permissioned_as: String, - permissioned_as_email: String, - runnable_path: Option, - kind: JobKind, - runnable_id: Option, - tag: String, - script_lang: Option, - ) -> Self { - Self { - workspace_id, - id: Uuid::new_v4(), - args: args.map(Json), - parent_job: None, - created_by, - scheduled_for: chrono::Utc::now(), - started_at: None, - runnable_path, - kind, - runnable_id, - canceled_reason: None, - canceled_by: None, - permissioned_as, - permissioned_as_email, - flow_status: None, - tag, - script_lang, - same_worker: true, - pre_run_error: None, - flow_innermost_root_job: None, - root_job: None, - timeout: None, - flow_step_id: None, - cache_ttl: None, - cache_ignore_s3_path: None, - priority: None, - preprocessed: None, - script_entrypoint_override: None, - trigger: None, - trigger_kind: None, - visible_to_owner: false, - permissioned_as_end_user_email: None, - runnable_settings_handle: None, - concurrent_limit: None, - concurrency_time_window_s: None, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MiniCompletedJob { pub id: Uuid, diff --git a/backend/windmill-test-utils/Cargo.toml b/backend/windmill-test-utils/Cargo.toml index e805ee5a3d..3d253a6772 100644 --- a/backend/windmill-test-utils/Cargo.toml +++ b/backend/windmill-test-utils/Cargo.toml @@ -15,8 +15,6 @@ enterprise = [] python = ["windmill-common/python"] deno_core = ["dep:windmill-runtime-nativets"] agent_worker_server = ["dep:windmill-api-agent-workers"] -run_inline = ["windmill-api/run_inline"] -duckdb = ["windmill-worker/duckdb"] [dependencies] windmill-api = { workspace = true, default-features = false } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index c354d34ee2..6f14512b32 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -14,10 +14,6 @@ use futures::TryFutureExt; use tokio::sync::Mutex; use tokio::time::timeout; use windmill_common::client::AuthedClient; -use windmill_common::db::UserDbWithAuthed; -use windmill_common::get_latest_deployed_hash_for_path; -use windmill_common::jobs::InlineScriptTarget; -use windmill_common::jobs::RunInlineScriptFnParams; use windmill_common::jobs::WorkerInternalServerInlineUtils; use windmill_common::jobs::WORKER_INTERNAL_SERVER_INLINE_UTILS; use windmill_common::runtime_assets::init_runtime_asset_loop; @@ -4709,18 +4705,43 @@ pub fn init_worker_internal_server_inline_utils( base_internal_url, killpill_rx: Arc::new(killpill_rx), run_inline_preview_script: Arc::new(|params| { - let job = MiniPulledJob::new_inline( - params.workspace_id, - params.args, - params.created_by, - params.permissioned_as, - params.permissioned_as_email, - None, - JobKind::Preview, - None, - "inline_preview".to_string(), - Some(params.lang), - ); + let job = MiniPulledJob { + workspace_id: params.workspace_id, + id: Uuid::new_v4(), + args: params.args.map(Json), + parent_job: None, + created_by: params.created_by, + scheduled_for: chrono::Utc::now(), + started_at: None, + runnable_path: None, + kind: JobKind::Preview, + runnable_id: None, + canceled_reason: None, + canceled_by: None, + permissioned_as: params.permissioned_as, + permissioned_as_email: params.permissioned_as_email, + flow_status: None, + tag: "inline_preview".to_string(), + script_lang: Some(params.lang), + same_worker: true, + pre_run_error: None, + flow_innermost_root_job: None, + root_job: None, + timeout: None, + flow_step_id: None, + cache_ttl: None, + cache_ignore_s3_path: None, + priority: None, + preprocessed: None, + script_entrypoint_override: None, + trigger: None, + trigger_kind: None, + visible_to_owner: false, + permissioned_as_end_user_email: None, + runnable_settings_handle: None, + concurrent_limit: None, + concurrency_time_window_s: None, + }; Box::pin(async move { let mut mem_peak: i32 = -1; let mut canceled_by: Option = None; @@ -4757,86 +4778,6 @@ pub fn init_worker_internal_server_inline_utils( .await }) }), - run_inline_script: Arc::new(|params: RunInlineScriptFnParams| { - Box::pin(async move { - let (script_hash, runnable_path) = match params.target { - InlineScriptTarget::Path(ref path) => { - let db = params - .conn - .as_sql() - .ok_or_else(|| { - error::Error::InternalErr( - "run_inline_script by path requires a SQL connection" - .to_string(), - ) - })? - .clone(); - let authed_ref = params.user_db.as_ref().map(|(_, a)| a.to_authed_ref()); - let user_db_authed = - params.user_db.as_ref().zip(authed_ref.as_ref()).map( - |((udb, _), ar)| UserDbWithAuthed { db: udb.clone(), authed: ar }, - ); - let script_hash_info = get_latest_deployed_hash_for_path( - user_db_authed, - db, - ¶ms.workspace_id, - path, - ) - .await?; - (ScriptHash(script_hash_info.hash), Some(path.clone())) - } - InlineScriptTarget::Hash(hash) => (ScriptHash(hash), None), - }; - let content_info = - get_script_content_by_hash(&script_hash, ¶ms.workspace_id, ¶ms.conn) - .await?; - let job = MiniPulledJob::new_inline( - params.workspace_id, - params.args, - params.created_by, - params.permissioned_as, - params.permissioned_as_email, - runnable_path, - JobKind::Script, - Some(script_hash), - "inline_run".to_string(), - content_info.language, - ); - let mut mem_peak: i32 = -1; - let mut canceled_by: Option = None; - let mut column_order: Option> = None; - let mut new_args: Option>> = None; - let mut occupancy_metrics = OccupancyMetrics::new(Instant::now()); - let mut has_stream: bool = false; - let mut killpill_rx = params.killpill_rx; - - run_language_executor( - &job, - ¶ms.conn, - ¶ms.client, - None, - ¶ms.job_dir, - ¶ms.worker_dir, - &mut mem_peak, - &mut canceled_by, - ¶ms.base_internal_url, - ¶ms.worker_name, - &mut column_order, - &mut new_args, - &mut occupancy_metrics, - &mut killpill_rx, - None, - &mut has_stream, - content_info.language, - &content_info.content, - &content_info.envs, - &content_info.codebase, - &content_info.lockfile, - true, - ) - .await - }) - }), }; WORKER_INTERNAL_SERVER_INLINE_UTILS .set(utils) diff --git a/frontend/.devcontainer.json b/frontend/.devcontainer.json index 6b9c4c181e..8fd2c1ea2b 100644 --- a/frontend/.devcontainer.json +++ b/frontend/.devcontainer.json @@ -3,10 +3,7 @@ // If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. { "name": "Frontend container", - "dockerComposeFile": [ - "../docker-compose.yml", - "../.devcontainer/docker-compose.yml" - ], + "dockerComposeFile": ["../docker-compose.yml", "../.devcontainer/docker-compose.yml"], "customizations": { "vscode": { "extensions": [] @@ -14,12 +11,10 @@ }, "service": "front", "workspaceFolder": "/workspace", - "forwardPorts": [ - 8080 - ], + "forwardPorts": [8080], // Uncomment the next line if you want to keep your containers running after VS Code shuts down. // "shutdownAction": "none", "postCreateCommand": "cd frontend && npm install && npm run generate-backend-client && wget https://github.com/caddyserver/caddy/releases/download/v2.6.0-beta.3/caddy_2.6.0-beta.3_linux_amd64.deb -O /tmp/caddy.deb && sudo dpkg -i /tmp/caddy.deb && rm /tmp/caddy.deb", "postStartCommand": "nohup bash -c 'cd frontend && npm run dev &' && nohup bash -c 'caddy run --config frontend/CaddyfileDev &'", "remoteUser": "node" -} \ No newline at end of file +} diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json index 3fa6fab23f..bba9566a9a 100644 --- a/frontend/.vscode/settings.json +++ b/frontend/.vscode/settings.json @@ -5,4 +5,4 @@ "onAutoForward": "openPreview" } } -} \ No newline at end of file +} diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 4ce27aa1ae..abb43c44c1 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -47,6 +47,7 @@ function useLoader(argsGetter: () => Args) { ``` This pattern ensures: + - State responsibility is clearly owned by `useLoader` - Data flows in one direction (parent → child) - No ambiguity about where state can be modified @@ -59,7 +60,10 @@ For async requests, **always use `resource()` from the Runed library** instead o ```typescript import { resource } from 'runed' -let items = resource(() => args, (args) => YourService.route(args)) +let items = resource( + () => args, + (args) => YourService.route(args) +) // Access loading state items.loading @@ -69,6 +73,7 @@ items.current ``` The `resource()` utility: + - Automatically handles loading states - Manages async lifecycle - Provides reactive updates when dependencies change @@ -102,6 +107,7 @@ npm run check:fast ``` At the end of a PR to do final validation, you can do the longer one (2s for fast vs 50s for the slow one): + ```bash npm run check ``` diff --git a/frontend/res.json b/frontend/res.json index 4c89aae894..fd10281324 100644 --- a/frontend/res.json +++ b/frontend/res.json @@ -1 +1 @@ -{"started":true,"success":true,"completed":true,"result":""} \ No newline at end of file +{ "started": true, "success": true, "completed": true, "result": "" } diff --git a/frontend/scripts/untar_ui_builder.js b/frontend/scripts/untar_ui_builder.js index 0991589025..3a7d546904 100644 --- a/frontend/scripts/untar_ui_builder.js +++ b/frontend/scripts/untar_ui_builder.js @@ -1,22 +1,20 @@ import path from 'path' import fs from 'fs' - // Check if we're in node_modules (installed as dependency) if (process.cwd().includes('node_modules')) { - console.log('Skipping postinstall - running as dependency'); - process.exit(0); + console.log('Skipping postinstall - running as dependency') + process.exit(0) } // Check if we're in the root project if (process.env.INIT_CWD && process.env.INIT_CWD !== process.cwd()) { - console.log('Skipping postinstall - not root project'); - process.exit(0); + console.log('Skipping postinstall - not root project') + process.exit(0) } // Your actual postinstall logic here -console.log('Running postinstall for root project'); - +console.log('Running postinstall for root project') import { x } from 'tar' @@ -35,7 +33,6 @@ const response = await fetch(tarUrl) const buffer = await response.arrayBuffer() await fs.promises.writeFile(outputTarPath, Buffer.from(buffer)) - // Create extract directory if it doesn't exist try { await fs.promises.mkdir(extractTo, { recursive: true }) diff --git a/frontend/src/app.html b/frontend/src/app.html index 718800105a..45af24cec8 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -1,4 +1,4 @@ - + diff --git a/frontend/src/lib/ansibleUtils.ts b/frontend/src/lib/ansibleUtils.ts index 762c1793a2..bf3b6d9711 100644 --- a/frontend/src/lib/ansibleUtils.ts +++ b/frontend/src/lib/ansibleUtils.ts @@ -11,11 +11,17 @@ interface DelegateToGitRepoConfig { * @param value - The value to set (or undefined to remove the field) * @returns The modified YAML script content */ -export function updateDelegateToGitRepoField(code: string, fieldName: string, value: string | undefined): string { +export function updateDelegateToGitRepoField( + code: string, + fieldName: string, + value: string | undefined +): string { const lines = code.split('\n') // Find delegate_to_git_repo section - const delegateLineIndex = lines.findIndex(line => line.trim().startsWith('delegate_to_git_repo:')) + const delegateLineIndex = lines.findIndex((line) => + line.trim().startsWith('delegate_to_git_repo:') + ) if (delegateLineIndex === -1) { // If no delegate section exists and we're setting a value, create the whole section @@ -26,8 +32,8 @@ export function updateDelegateToGitRepoField(code: string, fieldName: string, va } // Find the specific field line - const fieldLineIndex = lines.findIndex((line, index) => - index > delegateLineIndex && line.trim().startsWith(`${fieldName}:`) + const fieldLineIndex = lines.findIndex( + (line, index) => index > delegateLineIndex && line.trim().startsWith(`${fieldName}:`) ) if (fieldLineIndex !== -1) { @@ -52,7 +58,10 @@ export function updateDelegateToGitRepoField(code: string, fieldName: string, va * @param config - Configuration object with fields to update * @returns The modified YAML script content */ -export function updateDelegateToGitRepoConfig(code: string, config: DelegateToGitRepoConfig): string { +export function updateDelegateToGitRepoConfig( + code: string, + config: DelegateToGitRepoConfig +): string { let updatedCode = code // Update each field that's provided @@ -121,7 +130,12 @@ function insertDelegateToGitRepoSection(code: string, config: DelegateToGitRepoC // Find the end of inventories section for (let j = i + 1; j < lines.length; j++) { const nextLine = lines[j].trim() - if (nextLine && !nextLine.startsWith('-') && !nextLine.startsWith(' ') && !nextLine.startsWith('#')) { + if ( + nextLine && + !nextLine.startsWith('-') && + !nextLine.startsWith(' ') && + !nextLine.startsWith('#') + ) { insertionIndex = j break } @@ -150,7 +164,9 @@ function extractDelegateToGitRepoField(code: string, fieldName: string): string const lines = code.split('\n') // Find delegate_to_git_repo section - const delegateLineIndex = lines.findIndex(line => line.trim().startsWith('delegate_to_git_repo:')) + const delegateLineIndex = lines.findIndex((line) => + line.trim().startsWith('delegate_to_git_repo:') + ) if (delegateLineIndex === -1) { return undefined @@ -222,7 +238,9 @@ export function insertAdditionalInventories(code: string, inventoryPaths: string const lines = code.split('\n') // Find and update existing additional_inventories section if it exists - const additionalInventoriesIndex = lines.findIndex(line => line.trim().startsWith('additional_inventories:')) + const additionalInventoriesIndex = lines.findIndex((line) => + line.trim().startsWith('additional_inventories:') + ) if (additionalInventoriesIndex !== -1) { // Determine the indentation level of the additional_inventories line const sectionLine = lines[additionalInventoriesIndex] @@ -292,7 +310,7 @@ export function insertAdditionalInventories(code: string, inventoryPaths: string // Format the new options content const optionsIndentation = ' ' // Standard 2-space indentation under additional_inventories - const formattedPaths = inventoryPaths.map(path => `"delegated_git_repository/${path}"`) + const formattedPaths = inventoryPaths.map((path) => `"delegated_git_repository/${path}"`) const inlineFormat = `${optionsIndentation}- options: [${formattedPaths.join(', ')}]` let newOptionsContent: string[] @@ -302,7 +320,7 @@ export function insertAdditionalInventories(code: string, inventoryPaths: string } else { // Use dash format newOptionsContent = [`${optionsIndentation}- options:`] - inventoryPaths.forEach(path => { + inventoryPaths.forEach((path) => { newOptionsContent.push(`${optionsIndentation} - "delegated_git_repository/${path}"`) }) } @@ -319,29 +337,25 @@ export function insertAdditionalInventories(code: string, inventoryPaths: string } // Format the inventory paths based on length - const formattedPaths = inventoryPaths.map(path => `"delegated_git_repository/${path}"`) + const formattedPaths = inventoryPaths.map((path) => `"delegated_git_repository/${path}"`) const inlineFormat = `options: [${formattedPaths.join(', ')}]` let inventorySection: string[] if (inlineFormat.length <= 100) { // Use inline format - inventorySection = [ - 'additional_inventories:', - ` ${inlineFormat}` - ] + inventorySection = ['additional_inventories:', ` ${inlineFormat}`] } else { // Use dash format with each item on new line - inventorySection = [ - 'additional_inventories:', - ' - options:' - ] - inventoryPaths.forEach(path => { + inventorySection = ['additional_inventories:', ' - options:'] + inventoryPaths.forEach((path) => { inventorySection.push(` - "delegated_git_repository/${path}"`) }) } // Find insertion point (after the complete delegate_to_git_repo section) - const delegateLineIndex = lines.findIndex(line => line.trim().startsWith('delegate_to_git_repo:')) + const delegateLineIndex = lines.findIndex((line) => + line.trim().startsWith('delegate_to_git_repo:') + ) if (delegateLineIndex === -1) { // If no delegate_to_git_repo section, insert at the beginning (after document marker if exists) let insertionIndex = 0 @@ -381,4 +395,3 @@ export function insertAdditionalInventories(code: string, inventoryPaths: string return lines.join('\n') } - diff --git a/frontend/src/lib/ata/userFacingTypes.d.ts b/frontend/src/lib/ata/userFacingTypes.d.ts index 5f600cb8aa..daa28b782e 100644 --- a/frontend/src/lib/ata/userFacingTypes.d.ts +++ b/frontend/src/lib/ata/userFacingTypes.d.ts @@ -1,29 +1,29 @@ export interface ATABootstrapConfig { - /** A object you pass in to get callbacks */ - delegate: { - /** The callback which gets called when ATA decides a file needs to be written to your VFS */ - receivedFile?: (code: string, path: string) => void - /** A way to display progress */ - progress?: (downloaded: number, estimatedTotal: number) => void - /** Note: An error message does not mean ATA has stopped! */ - errorMessage?: (userFacingMessage: string, error: Error) => void - /** A callback indicating that ATA actually has work to do */ - started?: () => void - /** The callback when all ATA has finished */ - finished?: (files: Map) => void - } - /** Passed to fetch as the user-agent */ - projectName: string - /** Your local copy of typescript */ - depsParser: (code: string) => string[] + /** A object you pass in to get callbacks */ + delegate: { + /** The callback which gets called when ATA decides a file needs to be written to your VFS */ + receivedFile?: (code: string, path: string) => void + /** A way to display progress */ + progress?: (downloaded: number, estimatedTotal: number) => void + /** Note: An error message does not mean ATA has stopped! */ + errorMessage?: (userFacingMessage: string, error: Error) => void + /** A callback indicating that ATA actually has work to do */ + started?: () => void + /** The callback when all ATA has finished */ + finished?: (files: Map) => void + } + /** Passed to fetch as the user-agent */ + projectName: string + /** Your local copy of typescript */ + depsParser: (code: string) => string[] - /** If you need a custom version of fetch */ - fetcher?: typeof fetch - /** If you need a custom logger instead of the console global */ - logger?: Logger + /** If you need a custom version of fetch */ + fetcher?: typeof fetch + /** If you need a custom logger instead of the console global */ + logger?: Logger } -type ModuleMeta = { state: "loading" } +type ModuleMeta = { state: 'loading' } /** * The function which starts up type acquisition, @@ -34,11 +34,13 @@ type ModuleMeta = { state: "loading" } * basically exported for tests and should be considered * implementation details by consumers. */ -export const setupTypeAcquisition: (config: ATABootstrapConfig) => (initialSourceFile: string) => void +export const setupTypeAcquisition: ( + config: ATABootstrapConfig +) => (initialSourceFile: string) => void interface Logger { - log: (...args: any[]) => void - error: (...args: any[]) => void - groupCollapsed: (...args: any[]) => void - groupEnd: (...args: any[]) => void + log: (...args: any[]) => void + error: (...args: any[]) => void + groupCollapsed: (...args: any[]) => void + groupEnd: (...args: any[]) => void } diff --git a/frontend/src/lib/cloud.ts b/frontend/src/lib/cloud.ts index a684153e7a..cc6319127a 100644 --- a/frontend/src/lib/cloud.ts +++ b/frontend/src/lib/cloud.ts @@ -2,4 +2,4 @@ import { BROWSER } from 'esm-env' export function isCloudHosted(): boolean { return BROWSER && window.location.hostname == 'app.windmill.dev' -} \ No newline at end of file +} diff --git a/frontend/src/lib/components/Badge.svelte b/frontend/src/lib/components/Badge.svelte index 11042b4f1e..8dcad8ef8e 100644 --- a/frontend/src/lib/components/Badge.svelte +++ b/frontend/src/lib/components/Badge.svelte @@ -1,12 +1,22 @@ - + {@render children?.()} {#if tooltip && tooltip != ''} {tooltip} {/if} diff --git a/frontend/src/lib/components/Description.svelte b/frontend/src/lib/components/Description.svelte index c3cd74c7d6..16c5b13f11 100644 --- a/frontend/src/lib/components/Description.svelte +++ b/frontend/src/lib/components/Description.svelte @@ -2,11 +2,17 @@ import { ExternalLink } from 'lucide-svelte' import { twMerge } from 'tailwind-merge' - export let link: string | undefined = undefined + interface Props { + link?: string | undefined + class?: string + children?: import('svelte').Snippet + } + + let { link = undefined, class: c = '', children }: Props = $props() -
- +
+ {@render children?.()} {#if link} Learn more
diff --git a/frontend/src/lib/components/FlowInputViewer.svelte b/frontend/src/lib/components/FlowInputViewer.svelte index 94c25ffcc4..62aef2dd37 100644 --- a/frontend/src/lib/components/FlowInputViewer.svelte +++ b/frontend/src/lib/components/FlowInputViewer.svelte @@ -3,7 +3,11 @@ import FieldHeader from './FieldHeader.svelte' - export let schema: Schema | { [key: string]: unknown } | undefined + interface Props { + schema: Schema | { [key: string]: unknown } | undefined; + } + + let { schema }: Props = $props();
    diff --git a/frontend/src/lib/components/GraphqlSchemaViewer.svelte b/frontend/src/lib/components/GraphqlSchemaViewer.svelte index 7929083d9a..9f84604585 100644 --- a/frontend/src/lib/components/GraphqlSchemaViewer.svelte +++ b/frontend/src/lib/components/GraphqlSchemaViewer.svelte @@ -8,7 +8,12 @@ let divEl: HTMLDivElement | null = null let editor: meditor.IStandaloneCodeEditor - export let code: string = '' + interface Props { + code?: string + class?: string + } + + let { code = '', class: c = '' }: Props = $props() async function loadMonaco() { editor = meditor.create(divEl as HTMLDivElement, { @@ -43,4 +48,4 @@ }) -
    +
    diff --git a/frontend/src/lib/components/HighlightCode.svelte b/frontend/src/lib/components/HighlightCode.svelte index be6e6b6ef0..a90fa3b8a3 100644 --- a/frontend/src/lib/components/HighlightCode.svelte +++ b/frontend/src/lib/components/HighlightCode.svelte @@ -100,6 +100,8 @@ } const lang = $derived(highlightLanguage ?? getLang(language)) + // Cast needed: svelte-highlight types not updated for Svelte 5 snippets + const HighlightWithSnippet = Highlight as any @@ -134,9 +136,11 @@ {#if !lines} {:else} - - - + + {#snippet children({ highlighted })} + + {/snippet} + {/if} {:else}
    -	$: entries = Object.entries(inputTransforms)
    +	interface Props {
    +		inputTransforms: Record;
    +	}
    +
    +	let { inputTransforms }: Props = $props();
    +	let entries = $derived(Object.entries(inputTransforms))
     
     
     {#if entries.length}
    diff --git a/frontend/src/lib/components/ModuleStatus.svelte b/frontend/src/lib/components/ModuleStatus.svelte
    index 36f7b54753..351b3e0d03 100644
    --- a/frontend/src/lib/components/ModuleStatus.svelte
    +++ b/frontend/src/lib/components/ModuleStatus.svelte
    @@ -5,9 +5,13 @@
     	import { displayDate } from '$lib/utils'
     	import { Hourglass } from 'lucide-svelte'
     
    -	export let type: FlowStatusModule['type']
    -	export let scheduled_for: Date | undefined
    -	export let skipped: boolean = false
    +	interface Props {
    +		type: FlowStatusModule['type'];
    +		scheduled_for: Date | undefined;
    +		skipped?: boolean;
    +	}
    +
    +	let { type, scheduled_for, skipped = false }: Props = $props();
     
     
     {#if type == 'WaitingForEvents'}
    diff --git a/frontend/src/lib/components/PageHeader.svelte b/frontend/src/lib/components/PageHeader.svelte
    index 07b02227d4..556ac1b0cc 100644
    --- a/frontend/src/lib/components/PageHeader.svelte
    +++ b/frontend/src/lib/components/PageHeader.svelte
    @@ -1,11 +1,23 @@
     
     
     
    @@ -31,9 +43,9 @@ {/if} - {#if $$slots.default} + {#if children}
    - + {@render children?.()}
    {/if}
    diff --git a/frontend/src/lib/components/PermissionHistory.svelte b/frontend/src/lib/components/PermissionHistory.svelte index 82d646bd53..6a1398b5d4 100644 --- a/frontend/src/lib/components/PermissionHistory.svelte +++ b/frontend/src/lib/components/PermissionHistory.svelte @@ -79,12 +79,14 @@

    No permission changes recorded yet

    {:else} - - Changed By - Change Type - Affected - Date - + {#snippet header_row()} + + Changed By + Change Type + Affected + Date + + {/snippet} {#snippet body()} {#each history as change} diff --git a/frontend/src/lib/components/Popover.model.ts b/frontend/src/lib/components/Popover.model.ts index a4c6e672bc..9f4672a14a 100644 --- a/frontend/src/lib/components/Popover.model.ts +++ b/frontend/src/lib/components/Popover.model.ts @@ -1,3 +1,5 @@ const SIDE = ['auto', 'top', 'bottom', 'left', 'right'] as const const ALIGN = ['start', 'end'] as const -export type PopoverPlacement = `${typeof SIDE[number]}` | `${typeof SIDE[number]}-${typeof ALIGN[number]}` \ No newline at end of file +export type PopoverPlacement = + | `${(typeof SIDE)[number]}` + | `${(typeof SIDE)[number]}-${(typeof ALIGN)[number]}` diff --git a/frontend/src/lib/components/Required.svelte b/frontend/src/lib/components/Required.svelte index 2163efa976..c1e422d740 100644 --- a/frontend/src/lib/components/Required.svelte +++ b/frontend/src/lib/components/Required.svelte @@ -1,12 +1,17 @@ {#if required} - * + * {:else if detail || detail != ''} - ({detail != '' ? `${detail}` : ''}) {/if} diff --git a/frontend/src/lib/components/SchemaEditorProperty.svelte b/frontend/src/lib/components/SchemaEditorProperty.svelte index 611e178981..fa12138c0d 100644 --- a/frontend/src/lib/components/SchemaEditorProperty.svelte +++ b/frontend/src/lib/components/SchemaEditorProperty.svelte @@ -2,7 +2,11 @@ import type { SchemaProperty } from '$lib/common' import Badge from './common/badge/Badge.svelte' - export let property: SchemaProperty + interface Props { + property: SchemaProperty; + } + + let { property }: Props = $props();
    diff --git a/frontend/src/lib/components/Scrollable.svelte b/frontend/src/lib/components/Scrollable.svelte index 37e3ae2933..5826368534 100644 --- a/frontend/src/lib/components/Scrollable.svelte +++ b/frontend/src/lib/components/Scrollable.svelte @@ -2,14 +2,19 @@ import { onMount, onDestroy } from 'svelte' import { twMerge } from 'tailwind-merge' - let isAtBottom: boolean = false - let isScrollable = false + let isAtBottom: boolean = $state(false) + let isScrollable = $state(false) - export let id: string | null | undefined = undefined - export let scrollableClass: string = '' - export let shiftedShadow: boolean = false + interface Props { + id?: string | null | undefined + scrollableClass?: string + shiftedShadow?: boolean + children?: import('svelte').Snippet + } + + let { id = undefined, scrollableClass = '', shiftedShadow = false, children }: Props = $props() let mutationObserver: MutationObserver - let el: HTMLDivElement + let el: HTMLDivElement | undefined = $state() function handleScroll(event) { const scrollableElement = event.target @@ -33,7 +38,7 @@ } export function scrollIntoView(top: number) { - el.scrollTo({ top, behavior: 'smooth' }) + el?.scrollTo({ top, behavior: 'smooth' }) } onMount(() => { observeScrollability(el) @@ -45,8 +50,8 @@
    -
    - +
    + {@render children?.()}
    {#if !isAtBottom && isScrollable}
    - import { createEventDispatcher } from 'svelte' + interface Props { + paginated?: boolean + currentPage?: number + showNext?: boolean + class?: string + header_row?: import('svelte').Snippet + body?: import('svelte').Snippet + onNext?: () => void + onPrevious?: () => void + } - export let paginated = false - export let currentPage = 1 - export let showNext = true - - const dispatch = createEventDispatcher() + let { + paginated = false, + currentPage = 1, + showNext = true, + class: className = '', + header_row, + body, + onNext, + onPrevious + }: Props = $props() - -
    +
    - + {@render header_row?.()} - + {@render body?.()}
    {#if paginated}
    diff --git a/frontend/src/lib/components/TooltipInner.svelte b/frontend/src/lib/components/TooltipInner.svelte index 437fbc1c02..215d58a499 100644 --- a/frontend/src/lib/components/TooltipInner.svelte +++ b/frontend/src/lib/components/TooltipInner.svelte @@ -4,9 +4,19 @@ import { gfmPlugin } from 'svelte-exmarkdown/gfm' import { twMerge } from 'tailwind-merge' - export let documentationLink: string | undefined = undefined - export let markdownTooltip: string | undefined = undefined - export let customBgClass: string | undefined = undefined + interface Props { + documentationLink?: string | undefined; + markdownTooltip?: string | undefined; + customBgClass?: string | undefined; + children?: import('svelte').Snippet; + } + + let { + documentationLink = undefined, + markdownTooltip = undefined, + customBgClass = undefined, + children + }: Props = $props(); const plugins = [gfmPlugin()] @@ -21,7 +31,7 @@
    {:else} - + {@render children?.()} {/if} {#if documentationLink} diff --git a/frontend/src/lib/components/Uptodate.svelte b/frontend/src/lib/components/Uptodate.svelte index de5a459083..40a231e6be 100644 --- a/frontend/src/lib/components/Uptodate.svelte +++ b/frontend/src/lib/components/Uptodate.svelte @@ -3,7 +3,7 @@ import { isCloudHosted } from '$lib/cloud' import Tooltip from './Tooltip.svelte' - let uptodate: string | undefined = undefined + let uptodate: string | undefined = $state(undefined) async function loadVersion() { try { diff --git a/frontend/src/lib/components/Urlize.svelte b/frontend/src/lib/components/Urlize.svelte index 060ec9f141..d682a39ca9 100644 --- a/frontend/src/lib/components/Urlize.svelte +++ b/frontend/src/lib/components/Urlize.svelte @@ -1,8 +1,12 @@ {@html parsed} diff --git a/frontend/src/lib/components/Version.svelte b/frontend/src/lib/components/Version.svelte index 9406bed01f..aa0b58624d 100644 --- a/frontend/src/lib/components/Version.svelte +++ b/frontend/src/lib/components/Version.svelte @@ -1,7 +1,7 @@ {#if flow_status} diff --git a/frontend/src/lib/components/apps/components/buttons/index.ts b/frontend/src/lib/components/apps/components/buttons/index.ts index c192b70b75..282a895a56 100644 --- a/frontend/src/lib/components/apps/components/buttons/index.ts +++ b/frontend/src/lib/components/apps/components/buttons/index.ts @@ -1,3 +1,3 @@ export { default as AppButton } from './AppButton.svelte' export { default as AppForm } from './AppForm.svelte' -export { default as AppFormButton } from './AppFormButton.svelte' \ No newline at end of file +export { default as AppFormButton } from './AppFormButton.svelte' diff --git a/frontend/src/lib/components/apps/components/helpers/executeRunnable.ts b/frontend/src/lib/components/apps/components/helpers/executeRunnable.ts index 133af15153..c879a9f580 100644 --- a/frontend/src/lib/components/apps/components/helpers/executeRunnable.ts +++ b/frontend/src/lib/components/apps/components/helpers/executeRunnable.ts @@ -12,7 +12,7 @@ export async function executeRunnable( id: string, requestBody: ExecuteComponentData['requestBody'], inlineScriptOverride?: InlineScript, - queryParams?: Record, + queryParams?: Record ) { let appPath = defaultIfEmptyString(path, `u/${username ?? 'unknown'}/newapp`) if (isRunnableByName(runnable)) { diff --git a/frontend/src/lib/components/apps/editor/AppInputs.svelte b/frontend/src/lib/components/apps/editor/AppInputs.svelte index a81bd59200..591821ae7a 100644 --- a/frontend/src/lib/components/apps/editor/AppInputs.svelte +++ b/frontend/src/lib/components/apps/editor/AppInputs.svelte @@ -9,7 +9,7 @@ const { app } = getContext('AppViewerContext') - let resourceOnly: boolean = true + let resourceOnly: boolean = $state(true) diff --git a/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts b/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts index 122c42bcf9..f799cb864a 100644 --- a/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts +++ b/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts @@ -1,7 +1,7 @@ -import { base } from "$lib/base" -import { workspaceStore } from "$lib/stores" -import { get } from "svelte/store" +import { base } from '$lib/base' +import { workspaceStore } from '$lib/stores' +import { get } from 'svelte/store' export function computeSecretUrl(secretUrl: string) { - return `${window.location.origin}${base}/public/${get(workspaceStore)}/${secretUrl}` + return `${window.location.origin}${base}/public/${get(workspaceStore)}/${secretUrl}` } diff --git a/frontend/src/lib/components/apps/editor/commonAppUtils.ts b/frontend/src/lib/components/apps/editor/commonAppUtils.ts index 6cc5fbb3b4..ab68dc6080 100644 --- a/frontend/src/lib/components/apps/editor/commonAppUtils.ts +++ b/frontend/src/lib/components/apps/editor/commonAppUtils.ts @@ -1,37 +1,35 @@ -import type { StaticAppInput } from "../inputType" +import type { StaticAppInput } from '../inputType' import { Sha256 } from '@aws-crypto/sha256-js' -export function collectStaticFields( - fields: Record -) { - return Object.fromEntries( - Object.entries(fields ?? {}) - .filter(([k, v]) => v.type == 'static') - .map(([k, v]) => { - return [k, v['value']] - }) - ) +export function collectStaticFields(fields: Record) { + return Object.fromEntries( + Object.entries(fields ?? {}) + .filter(([k, v]) => v.type == 'static') + .map(([k, v]) => { + return [k, v['value']] + }) + ) } export type TriggerableV2 = { - static_inputs: Record - one_of_inputs?: Record - allow_user_resources?: string[] + static_inputs: Record + one_of_inputs?: Record + allow_user_resources?: string[] } export async function hash(message) { - try { - const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array - const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) // hash the message - const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array - const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string - return hashHex - } catch { - //subtle not available, trying pure js - const hash = new Sha256() - hash.update(message ?? '') - const result = Array.from(await hash.digest()) - const hex = result.map((b) => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string - return hex - } -} \ No newline at end of file + try { + const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array + const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) // hash the message + const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string + return hashHex + } catch { + //subtle not available, trying pure js + const hash = new Sha256() + hash.update(message ?? '') + const result = Array.from(await hash.digest()) + const hex = result.map((b) => b.toString(16).padStart(2, '0')).join('') // convert bytes to hex string + return hex + } +} diff --git a/frontend/src/lib/components/apps/editor/component/default-codes.ts b/frontend/src/lib/components/apps/editor/component/default-codes.ts index 6a00a47cd9..c9a3b77c16 100644 --- a/frontend/src/lib/components/apps/editor/component/default-codes.ts +++ b/frontend/src/lib/components/apps/editor/component/default-codes.ts @@ -24,7 +24,7 @@ export const DEFAULT_CODES: Partial< | 'snowflake' | 'mssql' | 'bigquery' - | 'oracledb', + | 'oracledb', string > > diff --git a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/utils.ts b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/utils.ts index c42dd06299..30612799f6 100644 --- a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/utils.ts +++ b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/utils.ts @@ -1,7 +1,15 @@ import type { Schema } from '$lib/common' import { ScriptService } from '$lib/gen' import { sendUserToast } from '$lib/toast' -import { isRunnableByName, isRunnableByPath, type AppInputs, type InlineScript, type Runnable, type RunnableByName, type CtxInput } from '../../inputType' +import { + isRunnableByName, + isRunnableByPath, + type AppInputs, + type InlineScript, + type Runnable, + type RunnableByName, + type CtxInput +} from '../../inputType' import type { GridItem, HiddenRunnable } from '../../types' import { fieldTypeToTsType, schemaToInputsSpec } from '../../utils' import type { AppComponent } from '../component' @@ -14,7 +22,11 @@ export interface AppScriptsList { // When the schema is loaded, we need to update the inputs spec // in order to render the inputs the component panel // Note: fields can include CtxInput for raw apps -export function computeFields(schema: Schema, defaultUserInput: boolean, fields: AppInputs | Record) { +export function computeFields( + schema: Schema, + defaultUserInput: boolean, + fields: AppInputs | Record +) { let schemaCopy: Schema = JSON.parse(JSON.stringify(schema)) const result = {} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/utils.ts b/frontend/src/lib/components/apps/editor/settingsPanel/script/utils.ts index 875c6a9bbd..d2eb14ae24 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/script/utils.ts +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/utils.ts @@ -33,9 +33,13 @@ export function isFrontend(runnable: Runnable): boolean { } export function isTriggerable(componentType: string): boolean { - return ['buttoncomponent', 'formbuttoncomponent', 'formcomponent', 'steppercomponent', 'chatcomponent'].includes( - componentType - ) + return [ + 'buttoncomponent', + 'formbuttoncomponent', + 'formcomponent', + 'steppercomponent', + 'chatcomponent' + ].includes(componentType) } export function isTriggerOnAppLoad(appComponent: AppComponent): boolean { diff --git a/frontend/src/lib/components/apps/gridUtils.ts b/frontend/src/lib/components/apps/gridUtils.ts index 5944755ffa..3682f93782 100644 --- a/frontend/src/lib/components/apps/gridUtils.ts +++ b/frontend/src/lib/components/apps/gridUtils.ts @@ -10,7 +10,6 @@ const Breakpoints = { export const moveMode = writable<'move' | 'insert'>('move') - const WIDE_GRID_COLUMNS = 12 as const const NARROW_GRID_COLUMNS = 3 as const diff --git a/frontend/src/lib/components/apps/svelte-grid/utils/container.ts b/frontend/src/lib/components/apps/svelte-grid/utils/container.ts index 7139bc407d..d3d7d1d7b1 100644 --- a/frontend/src/lib/components/apps/svelte-grid/utils/container.ts +++ b/frontend/src/lib/components/apps/svelte-grid/utils/container.ts @@ -1,5 +1,5 @@ -import { getRowsCount } from "./other"; +import { getRowsCount } from './other' export function getContainerHeight(items, yPerPx, cols) { - return getRowsCount(items, cols) * yPerPx; + return getRowsCount(items, cols) * yPerPx } diff --git a/frontend/src/lib/components/apps/svelte-select/lib/tailwind.css b/frontend/src/lib/components/apps/svelte-select/lib/tailwind.css index 058dac4876..e24dbae1db 100644 --- a/frontend/src/lib/components/apps/svelte-select/lib/tailwind.css +++ b/frontend/src/lib/components/apps/svelte-select/lib/tailwind.css @@ -1,130 +1,130 @@ @import 'tailwindcss/base'; .svelte-select { - @apply border rounded box-border h-10 relative flex items-center px-4 py-0 bg-white m-0 w-full + @apply border rounded box-border h-10 relative flex items-center px-4 py-0 bg-white m-0 w-full hover:border-gray-400; } .svelte-select input { - @apply cursor-default border-none text-tertiary h-10 leading-10 px-4 py-0 bg-transparent text-sm absolute left-0 m-0 w-full + @apply cursor-default border-none text-tertiary h-10 leading-10 px-4 py-0 bg-transparent text-sm absolute left-0 m-0 w-full focus:outline-none hover:border-gray-400; } .svelte-select.focused { - @apply border-blue-600; + @apply border-blue-600; } .svelte-select.disabled { - @apply bg-gray-200 border-gray-200 text-tertiary; + @apply bg-gray-200 border-gray-200 text-tertiary; } .svelte-select.disabled input { - @apply placeholder:text-gray-400 placeholder:opacity-100; + @apply placeholder:text-gray-400 placeholder:opacity-100; } .svelte-select .selected-item { - @apply leading-10 h-10 overflow-x-hidden pr-5 + @apply leading-10 h-10 overflow-x-hidden pr-5 focus:outline-none; } .svelte-select .icons { - @apply absolute flex items-center right-0 translate-y-0 text-gray-200 pointer-events-none top-0 bottom-0; + @apply absolute flex items-center right-0 translate-y-0 text-gray-200 pointer-events-none top-0 bottom-0; } .svelte-select .icons > * { - @apply transition-colors ease-in-out duration-200; + @apply transition-colors ease-in-out duration-200; } .svelte-select .clear-select { - @apply pointer-events-auto; + @apply pointer-events-auto; } .svelte-select.focused .icons, .svelte-select .chevron:hover, .svelte-select .clear-select:hover { - @apply text-tertiary; + @apply text-tertiary; } .svelte-select .clear-select { - @apply px-2 h-5 text-gray-300 flex-none w-9; + @apply px-2 h-5 text-gray-300 flex-none w-9; } .svelte-select .chevron { - @apply flex pt-0 pr-2 pl-2 border-l-2 w-9 h-5 text-gray-300; + @apply flex pt-0 pr-2 pl-2 border-l-2 w-9 h-5 text-gray-300; } .svelte-select.multi { - @apply pr-9 pl-4 h-auto flex-wrap items-stretch; + @apply pr-9 pl-4 h-auto flex-wrap items-stretch; } .svelte-select.multi input { - @apply p-0 relative m-0; + @apply p-0 relative m-0; } .svelte-select.error { - @apply border-red-500 bg-white; + @apply border-red-500 bg-white; } .a11y-text { - @apply sr-only; + @apply sr-only; } .list { - @apply shadow-md rounded-sm max-h-64 overflow-y-auto bg-white border-none absolute z-10 w-full left-0 right-0; + @apply shadow-md rounded-sm max-h-64 overflow-y-auto bg-white border-none absolute z-10 w-full left-0 right-0; } .list .list-group-title { - @apply text-gray-400 cursor-default text-sm font-medium h-10 leading-10 px-5 overflow-ellipsis whitespace-nowrap uppercase; + @apply text-gray-400 cursor-default text-sm font-medium h-10 leading-10 px-5 overflow-ellipsis whitespace-nowrap uppercase; } .list .empty { - @apply text-center py-5 text-tertiary; + @apply text-center py-5 text-tertiary; } .item { - @apply cursor-default h-10 leading-10 px-5 text-gray-800 overflow-ellipsis overflow-hidden whitespace-nowrap; + @apply cursor-default h-10 leading-10 px-5 text-gray-800 overflow-ellipsis overflow-hidden whitespace-nowrap; } .item.group-item { - @apply px-10; + @apply px-10; } .item:active { - @apply bg-blue-200; + @apply bg-blue-200; } .item.active { - @apply bg-blue-600 text-white; + @apply bg-blue-600 text-white; } .item.not-selectable { - @apply text-gray-300; + @apply text-gray-300; } .item.first { - @apply rounded-t-sm; + @apply rounded-t-sm; } .item.hover:not(.active) { - @apply bg-blue-100; + @apply bg-blue-100; } .multi input { - flex: 1 1 40px; + flex: 1 1 40px; } .multi-item { - @apply bg-gray-100 mt-1 border border-gray-200 rounded-sm h-8 leading-8 flex cursor-default pr-1 pl-1 max-w-full items-center mr-1 overflow-hidden overflow-ellipsis whitespace-nowrap; + @apply bg-gray-100 mt-1 border border-gray-200 rounded-sm h-8 leading-8 flex cursor-default pr-1 pl-1 max-w-full items-center mr-1 overflow-hidden overflow-ellipsis whitespace-nowrap; } .multi-item.disabled { - @apply hover:bg-gray-300 hover:text-tertiary; + @apply hover:bg-gray-300 hover:text-tertiary; } .multi-item-clear { - @apply flex items-center justify-center w-5; + @apply flex items-center justify-center w-5; } .list-item { - @apply list-none; -} \ No newline at end of file + @apply list-none; +} diff --git a/frontend/src/lib/components/apps/utils.ts b/frontend/src/lib/components/apps/utils.ts index cc197762b3..e3a720dd72 100644 --- a/frontend/src/lib/components/apps/utils.ts +++ b/frontend/src/lib/components/apps/utils.ts @@ -2,7 +2,14 @@ import type { Schema } from '$lib/common' import { twMerge } from 'tailwind-merge' import { type AppComponent } from './editor/component' -import { isRunnableByName, isRunnableByPath, type AppInput, type InputType, type ResultAppInput, type StaticAppInput } from './inputType' +import { + isRunnableByName, + isRunnableByPath, + type AppInput, + type InputType, + type ResultAppInput, + type StaticAppInput +} from './inputType' import type { Output } from './rx' import type { App, @@ -133,7 +140,7 @@ export function isScriptByNameDefined(appInput: AppInput | undefined): boolean { return false } - if (appInput.type === 'runnable' && isRunnableByName(appInput.runnable)) { + if (appInput.type === 'runnable' && isRunnableByName(appInput.runnable)) { return appInput.runnable?.name != undefined } @@ -402,10 +409,7 @@ export function getAllScriptNames(app: App): string[] { const names = (allItems(app.grid, app?.subgrids) ?? []).reduce((acc, gridItem: GridItem) => { const { componentInput } = gridItem.data - if ( - componentInput?.type === 'runnable' && - isRunnableByName(componentInput.runnable) - ) { + if (componentInput?.type === 'runnable' && isRunnableByName(componentInput.runnable)) { acc.push(componentInput.runnable.name) } diff --git a/frontend/src/lib/components/assets/lib.ts b/frontend/src/lib/components/assets/lib.ts index 2dacedbae3..eb45a04a53 100644 --- a/frontend/src/lib/components/assets/lib.ts +++ b/frontend/src/lib/components/assets/lib.ts @@ -90,6 +90,7 @@ export function formatAssetKind(asset: { case 'datatable': return 'Data table' } + return 'Unknown' } export function formatAssetAccessType(accessType: AssetUsageAccessType | undefined) { diff --git a/frontend/src/lib/components/common/alert/Notification.svelte b/frontend/src/lib/components/common/alert/Notification.svelte index 4beca30071..7c4aadc271 100644 --- a/frontend/src/lib/components/common/alert/Notification.svelte +++ b/frontend/src/lib/components/common/alert/Notification.svelte @@ -1,6 +1,10 @@ {#if notificationCount > 0} diff --git a/frontend/src/lib/components/common/button/AnimatedButton.svelte b/frontend/src/lib/components/common/button/AnimatedButton.svelte index 7248b891dc..3ceb0ebd4c 100644 --- a/frontend/src/lib/components/common/button/AnimatedButton.svelte +++ b/frontend/src/lib/components/common/button/AnimatedButton.svelte @@ -1,12 +1,26 @@ {#if animate} @@ -19,10 +33,10 @@ {ringColor} {darkMode} > - + {@render children?.()} {:else}
    - + {@render children?.()}
    {/if} diff --git a/frontend/src/lib/components/common/button/AnimatedButtonInner.svelte b/frontend/src/lib/components/common/button/AnimatedButtonInner.svelte index 7396b5314b..9ba46928a0 100644 --- a/frontend/src/lib/components/common/button/AnimatedButtonInner.svelte +++ b/frontend/src/lib/components/common/button/AnimatedButtonInner.svelte @@ -1,27 +1,41 @@ @@ -33,7 +47,7 @@ bind:clientWidth bind:clientHeight > - + {@render children?.()}