From cd1711cf03894e65e81ee74631b0e04d7dae76d2 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Tue, 7 May 2024 15:57:41 +0200 Subject: [PATCH 01/35] feat: disable response logs based on env var (#3685) --- README.md | 1 + backend/windmill-api/src/lib.rs | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index da45e240a5..53fbdfde3f 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,7 @@ you to have it being synced automatically everyday. | TIMEOUT_WAIT_RESULT | 20 | The number of seconds to wait before timeout on the 'run_wait_result' endpoint | Worker | | QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker | | DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker | +| DISABLE_RESPONSE_LOGS | false | Disable response logs | Server | ## Run a local dev setup ### only Frontend diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 29ad935447..5aee47e568 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -141,14 +141,12 @@ pub async fn run_server( )); let argon2 = Arc::new(Argon2::default()); + let disable_response_logs = std::env::var("DISABLE_RESPONSE_LOGS") + .ok() + .map(|x| x == "true") + .unwrap_or(false); + let middleware_stack = ServiceBuilder::new() - .layer( - TraceLayer::new_for_http() - .on_response(MyOnResponse {}) - .make_span_with(MyMakeSpan {}) - .on_request(()) - .on_failure(MyOnFailure {}), - ) .layer(Extension(db.clone())) .layer(Extension(rsmq)) .layer(Extension(user_db)) @@ -285,6 +283,18 @@ pub async fn run_server( .fallback(static_assets::static_handler) .layer(middleware_stack); + let app = if disable_response_logs { + app + } else { + app.layer( + TraceLayer::new_for_http() + .on_response(MyOnResponse {}) + .make_span_with(MyMakeSpan {}) + .on_request(()) + .on_failure(MyOnFailure {}), + ) + }; + let instance_name = rd_string(5); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); From 010662d3d0d05a7f6167ceeb592c97cec4c9e804 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Tue, 7 May 2024 17:17:43 +0200 Subject: [PATCH 02/35] fix: can cancel only if can disable schedule + stop worker on force cancel + soft cancel job parent on job cancel (#3670) * fix: can cancel only if can disable schedule + stop worker on force cancel + cancel job parents * fix: sqlx build * fix: flow reschedule check + soft cancel only direct parent * fix: sqlx prepare * fix: only soft cancel parent if force cancel and job is a flow step --- backend/windmill-common/src/error.rs | 2 + backend/windmill-queue/src/jobs.rs | 122 +++++++++++++----- backend/windmill-worker/src/common.rs | 41 ++++-- backend/windmill-worker/src/worker.rs | 11 +- .../src/lib/components/FlowMetadata.svelte | 4 +- .../(root)/(logged)/run/[...run]/+page.svelte | 19 ++- .../[job]/[resume]/[hmac]/+page.svelte | 7 +- 7 files changed, 161 insertions(+), 45 deletions(-) diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index 32e9740d78..b26f074abb 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -58,6 +58,8 @@ pub enum Error { JsonErr(serde_json::Value), #[error("{0}")] OpenAIError(String), + #[error("{0}")] + AlreadyCompleted(String), } impl Error { diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 0b3bd7a791..f829fca51b 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -133,24 +133,16 @@ pub struct CanceledBy { pub reason: Option, } -#[async_recursion] -pub async fn cancel_job<'c: 'async_recursion>( +pub async fn cancel_single_job<'c>( username: &str, reason: Option, - id: Uuid, + job_running: &QueuedJob, w_id: &str, mut tx: Transaction<'c, Postgres>, db: &Pool, rsmq: Option, force_cancel: bool, ) -> error::Result<(Transaction<'c, Postgres>, Option)> { - let job_running = get_queued_job_tx(id, &w_id, &mut tx).await?; - - if job_running.is_none() { - return Ok((tx, None)); - } - let job_running = job_running.unwrap(); - if ((job_running.running || job_running.root_job.is_some()) || (job_running.is_flow())) && !force_cancel { @@ -158,7 +150,7 @@ pub async fn cancel_job<'c: 'async_recursion>( "UPDATE queue SET canceled = true, canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = $3 AND workspace_id = $4 RETURNING id", username, reason, - id, + job_running.id, w_id ) .fetch_optional(&mut *tx) @@ -172,7 +164,7 @@ pub async fn cancel_job<'c: 'async_recursion>( .unwrap_or_else(|| "unexplicited reasons".to_string()); let e = serde_json::json!({"message": format!("Job canceled: {reason} by {username}"), "name": "Canceled", "reason": reason, "canceler": username}); append_logs( - id, + job_running.id, w_id.to_string(), format!("canceled by {username}: (force cancel: {force_cancel})"), db, @@ -180,7 +172,7 @@ pub async fn cancel_job<'c: 'async_recursion>( .await; let add_job = add_completed_job_error( &db, - &job_running, + job_running, job_running.mem_peak.unwrap_or(0), Some(CanceledBy { username: Some(username.to_string()), reason: Some(reason) }), e, @@ -189,16 +181,73 @@ pub async fn cancel_job<'c: 'async_recursion>( false, ) .await; + if let Err(e) = add_job { tracing::error!("Failed to add canceled job: {}", e); } } if let Some(mut rsmq) = rsmq.clone() { - rsmq.change_message_visibility(&job_running.tag, &id.to_string(), 0) + rsmq.change_message_visibility(&job_running.tag, &job_running.id.to_string(), 0) .await .map_err(|e| anyhow::anyhow!(e))?; } + Ok((tx, Some(job_running.id))) +} + +pub async fn cancel_job<'c>( + username: &str, + reason: Option, + id: Uuid, + w_id: &str, + mut tx: Transaction<'c, Postgres>, + db: &Pool, + rsmq: Option, + force_cancel: bool, +) -> error::Result<(Transaction<'c, Postgres>, Option)> { + let job = get_queued_job_tx(id, &w_id, &mut tx).await?; + + if job.is_none() { + return Ok((tx, None)); + } + + let job = job.unwrap(); + + let (ntx, _) = cancel_single_job( + username, + reason.clone(), + &job, + w_id, + tx, + db, + rsmq.clone(), + force_cancel, + ) + .await?; + tx = ntx; + + // soft cancel parent if force cancel and job is a flow step + if force_cancel && job.is_flow_step { + if let Some(parent_job_id) = job.parent_job { + let job = get_queued_job_tx(parent_job_id, &w_id, &mut tx).await?; + if let Some(job) = job { + let (ntx, _) = cancel_single_job( + username, + reason.clone(), + &job, + w_id, + tx, + db, + rsmq.clone(), + false, + ) + .await?; + tx = ntx; + } + } + } + + // cancel children let mut jobs = vec![id]; let mut jobs_to_cancel = vec![]; while !jobs.is_empty() { @@ -213,19 +262,23 @@ pub async fn cancel_job<'c: 'async_recursion>( jobs.extend(new_jobs.clone()); jobs_to_cancel.extend(new_jobs); } - for job in jobs_to_cancel { - let (ntx, _) = cancel_job( - username, - reason.clone(), - job, - w_id, - tx, - db, - rsmq.clone(), - force_cancel, - ) - .await?; - tx = ntx; + for job_id in jobs_to_cancel { + let job = get_queued_job_tx(job_id, &w_id, &mut tx).await?; + + if let Some(job) = job { + let (ntx, _) = cancel_single_job( + username, + reason.clone(), + &job, + w_id, + tx, + db, + rsmq.clone(), + force_cancel, + ) + .await?; + tx = ntx; + } } Ok((tx, Some(id))) } @@ -633,8 +686,19 @@ pub async fn add_completed_job< if let Some(schedule) = schedule { skip_downstream_error_handlers = schedule.ws_error_handler_muted; - if !queued_job.is_flow() { - // script only + // script or flow that failed on start and might not have been rescheduled + let schedule_next_tick = !queued_job.is_flow() + || { + let flow_status = queued_job.parse_flow_status(); + flow_status.is_some_and(|fs| { + fs.step == 0 + && fs.modules.get(0).is_some_and(|m| { + matches!(m, FlowStatusModule::WaitingForPriorSteps { .. }) || matches!(m, FlowStatusModule::Failure { job, ..} if job == &Uuid::nil()) + }) + }) + }; + + if schedule_next_tick { if let Err(err) = handle_maybe_scheduled_job( rsmq.clone(), db, diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index 836765a8fd..22d0b5efd9 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -482,7 +482,7 @@ pub async fn run_future_with_polling_update_job_poller( result_f: Fut, worker_name: &str, w_id: &str, -) -> anyhow::Result +) -> error::Result where Fut: Future>, { @@ -514,12 +514,22 @@ where tracing::error!("Query timeout: {}", e); Error::ExecutionErr(format!("Query timeout after (>{}s)", timeout_ms/1000)) })?, - _ = update_job, if job_id != Uuid::nil() => Err(Error::ExecutionErr("Job cancelled".to_string())).map_err(to_anyhow)?, + ex = update_job, if job_id != Uuid::nil() => { + match ex { + UpdateJobPollingExit::Done => Err(Error::ExecutionErr("Job cancelled".to_string())).map_err(to_anyhow)?, + UpdateJobPollingExit::AlreadyCompleted => Err(Error::AlreadyCompleted("Job already completed".to_string())).map_err(to_anyhow)?, + } + } }?; drop(tx); Ok(rows) } +pub enum UpdateJobPollingExit { + Done, + AlreadyCompleted, +} + pub async fn update_job_poller( job_id: Uuid, db: &DB, @@ -529,13 +539,14 @@ pub async fn update_job_poller( worker_name: &str, w_id: &str, mut rx: broadcast::Receiver<()>, -) where +) -> UpdateJobPollingExit +where F: Fn() -> Fut, Fut: Future, { let update_job_interval = Duration::from_millis(500); if job_id == Uuid::nil() { - return; + return UpdateJobPollingExit::Done; } let db = db.clone(); @@ -593,28 +604,36 @@ pub async fn update_job_poller( } } - let (canceled, canceled_by, canceled_reason) = sqlx::query_as::<_, (bool, Option, Option)>("UPDATE queue SET mem_peak = $1, last_ping = now() WHERE id = $2 RETURNING canceled, canceled_by, canceled_reason") + let (canceled, canceled_by, canceled_reason, already_completed) = sqlx::query_as::<_, (bool, Option, Option, bool)>("UPDATE queue SET mem_peak = $1, last_ping = now() WHERE id = $2 RETURNING canceled, canceled_by, canceled_reason, false") .bind(*mem_peak) .bind(job_id) .fetch_optional(&db) .await .unwrap_or_else(|e| { tracing::error!(%e, "error updating job {job_id}: {e}"); - Some((false, None, None)) + Some((false, None, None, false)) }) - .unwrap_or((false, None, None)); + .unwrap_or_else(|| { + // if the job is not in queue, it can only be in the completed_job so it is already complete + (false, None, None, true) + }); + if already_completed { + return UpdateJobPollingExit::AlreadyCompleted + } if canceled { canceled_by_ref.replace(CanceledBy { username: canceled_by.clone(), reason: canceled_reason.clone(), }); - break; + break } } }, ); } tracing::info!("job {job_id} finished"); + + UpdateJobPollingExit::Done } pub enum CompactLogs { @@ -897,6 +916,7 @@ pub async fn handle_child( TooManyLogs, Timeout, Cancelled, + AlreadyCompleted, } let (timeout_duration, timeout_warn_msg) = @@ -914,7 +934,10 @@ pub async fn handle_child( result = child.wait() => return result.map(Ok), Ok(()) = too_many_logs.changed() => KillReason::TooManyLogs, _ = sleep(timeout_duration) => KillReason::Timeout, - _ = update_job, if job_id != Uuid::nil() => KillReason::Cancelled, + ex = update_job, if job_id != Uuid::nil() => match ex { + UpdateJobPollingExit::Done => KillReason::Cancelled, + UpdateJobPollingExit::AlreadyCompleted => KillReason::AlreadyCompleted, + }, }; tx.send(()).expect("rx should never be dropped"); drop(tx); diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 95dcaf41fa..b6abef7fcb 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -1671,7 +1671,7 @@ pub async fn run_worker( if job.as_ref().workspace_id == "" { return Ok(()); } + + if result + .as_ref() + .is_err_and(|err| matches!(err, &Error::AlreadyCompleted(_))) + { + return Ok(()); + } + process_result( job, result, diff --git a/frontend/src/lib/components/FlowMetadata.svelte b/frontend/src/lib/components/FlowMetadata.svelte index b6810b335c..6ff197b199 100644 --- a/frontend/src/lib/components/FlowMetadata.svelte +++ b/frontend/src/lib/components/FlowMetadata.svelte @@ -11,11 +11,9 @@ export let job: Job const SMALL_ICON_SIZE = 14 - let scheduleEditor: ScheduleEditor + export let scheduleEditor: ScheduleEditor - -
+ + {#if (job?.job_kind == 'flow' || job?.job_kind == 'flowpreview') && job?.['running'] && job?.parent_job == undefined} @@ -449,6 +454,18 @@ {/if} {/if} + {#if job?.schedule_path} + + {/if} {#if job?.type === 'CompletedJob' && job?.job_kind === 'flow' && selectedJobStep !== undefined && selectedJobStepIsTopLevel} {#if selectedJobStepType == 'single'}
diff --git a/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte b/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte index cea6b7b580..c4c5f10432 100644 --- a/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte +++ b/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte @@ -18,6 +18,7 @@ import { getUserExt } from '$lib/user' import { setLicense } from '$lib/enterpriseUtils' import DisplayResult from '$lib/components/DisplayResult.svelte' + import ScheduleEditor from '$lib/components/ScheduleEditor.svelte' $workspaceStore = $page.params.workspace let rd = $page.url.href.replace($page.url.origin, '') @@ -132,8 +133,12 @@ $: if (job?.raw_flow?.modules?.[approvalStep]?.suspend?.user_auth_required && !$userStore) { loadUser() } + + let scheduleEditor: ScheduleEditor + + {#if error}
@@ -186,7 +191,7 @@
{#if job && job.raw_flow} - + {/if}
From ee2a193958b87a7557eadfd7ea8231d109b9d41c Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Tue, 7 May 2024 18:04:12 +0200 Subject: [PATCH 03/35] feat(frontend): Improve app components (#3672) * fix(frontend): wip * fix(frontend): wip * fix(frontend): improve components * fix(frontend): improve components * fix(frontend): improve ranges * fix(frontend): fix build --- frontend/src/lib/assets/app.css | 1 + .../src/lib/components/DisplayResult.svelte | 19 ++-- .../apps/components/display/AppMap.svelte | 2 +- .../apps/components/display/AppPdf.svelte | 14 +-- .../components/inputs/AppDateInput.svelte | 2 +- .../components/inputs/AppMultiSelect.svelte | 106 +++++++++--------- .../components/inputs/AppNumberInput.svelte | 2 +- .../components/inputs/AppRangeInput.svelte | 81 +++++++------ .../apps/components/inputs/AppSelect.svelte | 1 + .../components/inputs/AppSliderInputs.svelte | 58 +++++----- .../components/inputs/AppTextInput.svelte | 1 + .../inputs/currency/AppCurrencyInput.svelte | 2 +- .../apps/editor/component/Component.svelte | 1 + .../apps/editor/component/components.ts | 14 ++- .../common/fileInput/FileInput.svelte | 2 +- .../common/fileUpload/FileUpload.svelte | 2 +- .../components/scriptEditor/LogPanel.svelte | 9 +- 17 files changed, 169 insertions(+), 148 deletions(-) diff --git a/frontend/src/lib/assets/app.css b/frontend/src/lib/assets/app.css index 8602ed3479..4a58b1c648 100644 --- a/frontend/src/lib/assets/app.css +++ b/frontend/src/lib/assets/app.css @@ -157,4 +157,5 @@ .app-editor-input { @apply rounded-component border border-gray-300 dark:border-gray-500 focus:border-gray-300 focus:dark:border-gray-500 focus:!ring-1 focus:!ring-blue-300; + @apply placeholder:text-gray-400 dark:placeholder:text-gray-600; } diff --git a/frontend/src/lib/components/DisplayResult.svelte b/frontend/src/lib/components/DisplayResult.svelte index d665f557b2..73c510bf42 100644 --- a/frontend/src/lib/components/DisplayResult.svelte +++ b/frontend/src/lib/components/DisplayResult.svelte @@ -11,7 +11,8 @@ PanelRightOpen, Table2, Braces, - Highlighter + Highlighter, + InfoIcon } from 'lucide-svelte' import Portal from 'svelte-portal' import ObjectViewer from './propertyPicker/ObjectViewer.svelte' @@ -26,7 +27,7 @@ import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte' import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte' import MapResult from './MapResult.svelte' - import Tooltip from '$lib/components/Tooltip.svelte' + import Popover from './Popover.svelte' export let result: any export let requireHtmlApproval = false @@ -304,13 +305,17 @@
{#if !disableExpand && !noControls} - - The result renderer in Windmill supports rich display rendering, allowing you to - customize the display format of your results. - + + The result renderer in Windmill supports rich display rendering, allowing you to + customize the display format of your results. + +
+ +
+ diff --git a/frontend/src/lib/components/apps/components/display/AppMap.svelte b/frontend/src/lib/components/apps/components/display/AppMap.svelte index 7aa941e7ff..cdf0560445 100644 --- a/frontend/src/lib/components/apps/components/display/AppMap.svelte +++ b/frontend/src/lib/components/apps/components/display/AppMap.svelte @@ -255,7 +255,7 @@ {/each} {#if render} -
+
{#if render} -
+
{#if source && zoom} {#if pages?.length}
diff --git a/frontend/src/lib/components/apps/components/inputs/AppDateInput.svelte b/frontend/src/lib/components/apps/components/inputs/AppDateInput.svelte index a5445ad09c..2ffab7236d 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppDateInput.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppDateInput.svelte @@ -99,7 +99,7 @@ max={resolvedConfig.maxDate} placeholder="Type..." class={twMerge( - 'windmillapp w-full py-1.5 text-sm px-2 app-editor-input', + 'windmillapp w-full py-1.5 text-sm px-1 app-editor-input', css?.input?.class, 'wm-date-input' )} diff --git a/frontend/src/lib/components/apps/components/inputs/AppMultiSelect.svelte b/frontend/src/lib/components/apps/components/inputs/AppMultiSelect.svelte index 181a2dc731..b58f7d528d 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppMultiSelect.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppMultiSelect.svelte @@ -20,6 +20,7 @@ export let configuration: RichConfigurations export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined export let render: boolean + export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined const [floatingRef, floatingContent] = createFloatingActions({ strategy: 'absolute', @@ -104,7 +105,6 @@ } let w = 0 - let h = 0 let open = false @@ -129,68 +129,74 @@ - +
{ + $selectedComponent = [id] + if (!e.shiftKey) { e.stopPropagation() } + selectedComponent.set([id]) }} use:floatingRef bind:clientWidth={w} - bind:clientHeight={h} > {#if !value || Array.isArray(value)} -
- { - if (event?.detail?.type === 'removeAll') { - outputs?.result.set([]) - } else { - outputs?.result.set([...(value ?? [])]) - } + { + if (event?.detail?.type === 'removeAll') { + outputs?.result.set([]) + } else { + outputs?.result.set([...(value ?? [])]) + } + }} + on:open={() => { + $selectedComponent = [id] + open = true + }} + on:close={() => { + open = false + }} + let:option + > + + +
{ + let newe = new MouseEvent('mouseup') + e.target?.['parentElement']?.dispatchEvent(newe) }} - on:open={() => { - $selectedComponent = [id] - open = true - }} - on:close={() => { - open = false - }} - let:option > - + {option} +
+
+ + + {:else} Value {value} is not an array {/if} diff --git a/frontend/src/lib/components/apps/components/inputs/AppNumberInput.svelte b/frontend/src/lib/components/apps/components/inputs/AppNumberInput.svelte index 78f234dbd9..e4496c9849 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppNumberInput.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppNumberInput.svelte @@ -90,7 +90,7 @@ on:pointerdown|stopPropagation={() => ($selectedComponent = [id])} on:focus={() => ($selectedComponent = [id])} class={twMerge( - 'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2', + 'windmillapp w-full py-1.5 px-2 text-sm app-editor-input', css?.input?.class ?? '', 'wm-number-input' )} diff --git a/frontend/src/lib/components/apps/components/inputs/AppRangeInput.svelte b/frontend/src/lib/components/apps/components/inputs/AppRangeInput.svelte index 0e1af0c103..fbea05a6d4 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppRangeInput.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppRangeInput.svelte @@ -74,6 +74,10 @@ handles.forEach((handle) => (handle.style.cssText = css?.handles?.style ?? '')) } } + + const format = (v, i, p) => { + return `${v}` + } {#each Object.keys(components['rangecomponent'].initialData.configuration) as key (key)} @@ -99,13 +103,7 @@
-
- - {+(resolvedConfig.min ?? 0)} - +
-
- - {+(resolvedConfig.max ?? 1)} - -
-
- - {values[0]} - - - {values[1]} -
+ + diff --git a/frontend/src/lib/components/apps/components/inputs/AppSelect.svelte b/frontend/src/lib/components/apps/components/inputs/AppSelect.svelte index c16f0d5bd4..0a186dab86 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppSelect.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppSelect.svelte @@ -221,6 +221,7 @@ { - dispatch('focus') - }} - type="number" - class={twMerge( - valid && error == '' - ? '' - : 'border !border-red-700 !border-opacity-70 focus:!border-red-700 focus:!border-opacity-30' - )} - placeholder={defaultValue ?? ''} - bind:value - min={extra['min']} - max={extra['max']} - /> - {/if} - {:else if inputCat == 'boolean'} - { - e?.stopPropagation() - }} - class={valid && error == '' - ? '' - : 'border !border-red-700 !border-opacity-70 focus:!border-red-700 focus:!border-opacity-30'} - bind:checked={value} +{#if render} + {#if !(hideResourceInput && inputCat === 'resource-object')} +
+
+ {#if displayHeader} + - {#if type == 'boolean' && value == undefined} -   Not set - {/if} - {:else if inputCat == 'list'} -
- {#if Array.isArray(itemsType?.multiselect) && Array.isArray(value)} -
- + {/if} + + {#if description} +
+
{description}
+
+ {/if} + +
+ {#if inputCat == 'number'} + {#if extra['min'] != undefined && extra['max'] != undefined} +
+ {extra['min']} +
+ +
+ {extra['max']} + {value}
- {:else if Array.isArray(itemsType?.enum) && Array.isArray(value)} -
- -
- {:else if Array.isArray(enum_) && Array.isArray(value)} -
- + {:else} + { + dispatch('focus') + }} + type="number" + class={twMerge( + valid && error == '' + ? '' + : 'border !border-red-700 !border-opacity-70 focus:!border-red-700 focus:!border-opacity-30' + )} + placeholder={defaultValue ?? ''} + bind:value + min={extra['min']} + max={extra['max']} + /> + {/if} + {:else if inputCat == 'boolean'} + { + e?.stopPropagation() + }} + class={valid && error == '' + ? '' + : 'border !border-red-700 !border-opacity-70 focus:!border-red-700 focus:!border-opacity-30'} + bind:checked={value} + /> + {#if type == 'boolean' && value == undefined} +   Not set + {/if} + {:else if inputCat == 'list'} +
+ {#if Array.isArray(itemsType?.multiselect) && Array.isArray(value)} +
+ +
+ {:else if Array.isArray(itemsType?.enum) && Array.isArray(value)} +
+ +
+ {:else if Array.isArray(enum_) && Array.isArray(value)} +
+ +
+ {:else} +
+ {#if Array.isArray(value)} + {#each value ?? [] as v, i} +
+ {#if itemsType?.type == 'number'} + + {:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'} + fileChanged(x, (val) => (value[i] = val))} + multiple={false} + /> + {:else if Array.isArray(itemsType?.enum)} + + {:else} + + {/if} + +
+ {/each} + {:else if value != undefined} + List is not an array + {/if} +
+
+ +
+ + {(value ?? []).length} item{(value ?? []).length != 1 ? 's' : ''} + + {/if} +
+ {:else if inputCat == 'resource-object'} + + {:else if inputCat == 'object'} + {#if properties && Object.keys(properties).length > 0} +
+
{:else} -
- {#if Array.isArray(value)} - {#each value ?? [] as v, i} -
- {#if itemsType?.type == 'number'} - - {:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'} - fileChanged(x, (val) => (value[i] = val))} - multiple={false} - /> - {:else if Array.isArray(itemsType?.enum)} - - {:else} - - {/if} - -
- {/each} - {:else if value != undefined} - List is not an array - {/if} -
-
- -
- - {(value ?? []).length} item{(value ?? []).length != 1 ? 's' : ''} - - {/if} -
- {:else if inputCat == 'resource-object'} - - {:else if inputCat == 'object'} - {#if properties && Object.keys(properties).length > 0} -
- { + dispatch('focus') + }} + use:autosize + style="min-height: 5px;" + class="col-span-10 {valid && error == '' + ? '' + : 'border !border-red-700 !border-opacity-70 focus:!border-red-700 focus:!border-opacity-30'}" + placeholder={defaultValue ? JSON.stringify(defaultValue, null, 4) : ''} + bind:value={rawValue} /> -
- {:else} -