From ebfde197fc11d243bbc9ba9b36d4f8a5e72d1f71 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Mon, 13 Jan 2025 14:45:38 +0100 Subject: [PATCH] fix(ui): capture v2 UX (#4954) * feat: captures * flow UI and improvements * fix: build * fix sqlx * Move Capture WIP * Add capture to webhook and websocket * Move connection status viewer in the head * change trigger section label * Add popover capture picker using melt ui * Add shortcut to triggers capture from input form * remove capture tab in input * Allways show capture * remove useless logs * Add email capture * Add kafka capture into triggers * Add edit option in capture table * use light header for arg input * Add prefilled group id * Change button label * fix logic * Open resource drawer if prototype has fields * fix default completion * Change name Prototype * Dissociate Editor Mode for triggers * Fix bug for script * Fix apply args * fix apply schema * Add capture table to script * fix apply args * Add capture button for script * Delete capture tab * Set capture on when opening triggers capture * fix connection indicator * fix minor issues * Add preprocessor logic * Use slot in log Panel for captures * handle capture refresh in script * Delete capture tab from script editor * reset kafka resource on toggle static * fix minor issue * Allow resource in kafka capture * use simple capture button in flow * Remove capture panel * Polish route trigger editor * Fix resource saving * Remove excessive padding * merge nits * Add history tab # Conflicts: # frontend/src/lib/components/EditableSchemaForm.svelte * add workflow_dispatch to build * add workflow_dispatch to build * Move input copy from in a panel * better capture UI * Add smoothing animation on open input editor * fix bad wrapping in input editor menu * clean code * restore captureTable * clean * fix sqlx * fix build * fix build * build * make initial_messages optional in line with db * fix npm check * better handle args for capture webhook and http * add capture pagination * Add capture table * Add table for history inputs * nit * simplify capture table * stop capturing on closing capture section * show only 10 items per page for captures * set focus to logs on script test * Fix collapsable section * nit * open captures when applying args from triggers * update edit input drawer * fix all EditableSchemaForm * fix panel init animation * Add capture popover * change open tab button * clean code * fix bad table display * Fix JSON input bad sync * make capture icon bigger * nit * nit * revert unwanted change * Change capture name and nits * infinite scroll and cleaning * Add toast when failing to retreive first step input * add icons to dropdown edit input list * remove unused log * Add click outside to all schema pickers * fix click oustide * Fix migrations * solve run button not refreshing properly * Add a capture drawer * change apply args and schema logic * change apply args and schema logic * fix toggle display * fix schema display * improve preview mode * Add first step panel * nit * clean * Add hint for captures * clean * nit * nit * fix script node page height * improve animation on redirecting to capture * improve saved input picker * nit * add Json placeholder * nit * add list animations * delete useless logs * add animated list to captures history and saved inputs * Delete unused component * clean * nit * clean code * improve tab scroll experience * nit * nits * nits * fix script args update * nit * nit: include preview in history inputs in edit mode --------- Co-authored-by: HugoCasa Co-authored-by: Ruben Fiszel Co-authored-by: Ruben Fiszel --- ...928e7a158bd29d9035bee136f9952fb96bad4.json | 77 ++ backend/windmill-api/openapi.yaml | 89 +- backend/windmill-api/src/capture.rs | 12 +- backend/windmill-api/src/inputs.rs | 20 +- frontend/src/lib/components/ArgInput.svelte | 28 +- .../src/lib/components/CapturesInputs.svelte | 18 + frontend/src/lib/components/Dev.svelte | 10 +- .../lib/components/EditableSchemaForm.svelte | 678 +++++----- .../src/lib/components/FirstStepInputs.svelte | 122 ++ .../src/lib/components/FlowBuilder.svelte | 34 +- .../lib/components/FlowPreviewContent.svelte | 82 +- .../src/lib/components/HistoricInputs.svelte | 218 ++++ .../src/lib/components/InfiniteList.svelte | 172 +++ .../lib/components/LightweightArgInput.svelte | 28 +- .../lib/components/SaveInputsButton.svelte | 60 + .../src/lib/components/SavedInputs.svelte | 99 +- .../lib/components/SavedInputsPicker.svelte | 288 +++++ .../src/lib/components/ScriptBuilder.svelte | 1133 +++++++++-------- .../src/lib/components/ScriptEditor.svelte | 14 +- .../src/lib/components/ScriptSchema.svelte | 3 +- frontend/src/lib/components/Section.svelte | 10 +- .../src/lib/components/SimpleEditor.svelte | 31 +- .../lib/components/StringTypeNarrowing.svelte | 4 +- .../components/common/button/Button.svelte | 79 +- .../common/button/PulseButton.svelte | 82 ++ .../common/button/RoundIconButton.svelte | 13 + .../src/lib/components/common/button/model.ts | 65 + .../lib/components/common/layout/List.svelte | 12 +- .../details/DetailPageDetailPanel.svelte | 38 +- .../details/DetailPageLayout.svelte | 3 +- .../details/DetailPageTriggerPanel.svelte | 144 +-- .../details/EmailTriggerConfigSection.svelte | 1 + .../details/EmailTriggerPanel.svelte | 2 + .../lib/components/flows/FlowEditor.svelte | 10 +- .../components/flows/common/FlowCard.svelte | 18 +- .../flows/content/FlowEditorPanel.svelte | 36 +- .../components/flows/content/FlowInput.svelte | 579 +++++++-- .../flows/content/FlowInputEditor.svelte | 51 + .../flows/content/FlowModuleComponent.svelte | 767 ++++++----- .../flows/content/FlowPathViewer.svelte | 3 +- .../src/lib/components/flows/flowStore.ts | 47 + .../flows/header/FlowPreviewButtons.svelte | 13 +- .../flows/map/FlowModuleSchemaItem.svelte | 66 +- .../map/FlowModuleSchemaItemViewer.svelte | 70 + .../lib/components/flows/map/MapItem.svelte | 8 +- frontend/src/lib/components/flows/types.ts | 14 + .../meltComponents/ButtonDropDown.svelte | 39 + .../components/meltComponents/Popover.svelte | 12 +- .../schema/AddPropertyFormV2.svelte | 51 + .../components/schema/AddPropertyV2.svelte | 174 +++ .../schema/EditableSchemaDrawer.svelte | 25 +- .../schema/EditableSchemaWrapper.svelte | 74 +- .../schema/FlowPropertyEditor.svelte | 4 +- .../components/schema/JobSchemaPicker.svelte | 34 + .../components/schema/PropertyEditor.svelte | 10 +- .../schema/RunningJobSchemaPicker.svelte | 70 + .../components/schema/SchemaFormDND.svelte | 2 +- .../lib/components/schema/SchemaPicker.svelte | 96 ++ .../components/schema/SchemaPickerRow.svelte | 101 ++ .../components/scriptEditor/LogPanel.svelte | 2 +- .../src/lib/components/table/DataTable.svelte | 50 +- frontend/src/lib/components/table/Head.svelte | 2 +- frontend/src/lib/components/table/Row.svelte | 12 +- frontend/src/lib/components/table/index.ts | 3 + frontend/src/lib/components/triggers.ts | 1 + .../components/triggers/CaptureButton.svelte | 32 +- .../components/triggers/CaptureIcon.svelte | 11 +- .../components/triggers/CaptureSection.svelte | 75 +- .../components/triggers/CaptureTable.svelte | 456 ++++--- .../components/triggers/CaptureWrapper.svelte | 15 +- .../KafkaTriggersConfigSection.svelte | 2 + .../triggers/KafkaTriggersPanel.svelte | 2 + .../triggers/RouteEditorConfigSection.svelte | 10 + .../triggers/RouteEditorInner.svelte | 5 + .../components/triggers/RoutesPanel.svelte | 4 + .../components/triggers/TriggersEditor.svelte | 250 ++-- .../triggers/TriggersEditorSection.svelte | 30 +- .../triggers/TriggersWrapper.svelte | 8 +- .../triggers/WebhooksConfigSection.svelte | 1 + .../components/triggers/WebhooksPanel.svelte | 2 + .../WebsocketEditorConfigSection.svelte | 1 + .../triggers/WebsocketTriggersPanel.svelte | 2 + .../(root)/(logged)/resources/+page.svelte | 37 +- frontend/src/routes/flows/dev/+page.svelte | 16 +- 84 files changed, 4893 insertions(+), 2179 deletions(-) create mode 100644 backend/.sqlx/query-e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4.json create mode 100644 frontend/src/lib/components/CapturesInputs.svelte create mode 100644 frontend/src/lib/components/FirstStepInputs.svelte create mode 100644 frontend/src/lib/components/HistoricInputs.svelte create mode 100644 frontend/src/lib/components/InfiniteList.svelte create mode 100644 frontend/src/lib/components/SaveInputsButton.svelte create mode 100644 frontend/src/lib/components/SavedInputsPicker.svelte create mode 100644 frontend/src/lib/components/common/button/PulseButton.svelte create mode 100644 frontend/src/lib/components/common/button/RoundIconButton.svelte create mode 100644 frontend/src/lib/components/flows/content/FlowInputEditor.svelte create mode 100644 frontend/src/lib/components/flows/map/FlowModuleSchemaItemViewer.svelte create mode 100644 frontend/src/lib/components/meltComponents/ButtonDropDown.svelte create mode 100644 frontend/src/lib/components/schema/AddPropertyFormV2.svelte create mode 100644 frontend/src/lib/components/schema/AddPropertyV2.svelte create mode 100644 frontend/src/lib/components/schema/JobSchemaPicker.svelte create mode 100644 frontend/src/lib/components/schema/RunningJobSchemaPicker.svelte create mode 100644 frontend/src/lib/components/schema/SchemaPicker.svelte create mode 100644 frontend/src/lib/components/schema/SchemaPickerRow.svelte create mode 100644 frontend/src/lib/components/table/index.ts diff --git a/backend/.sqlx/query-e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4.json b/backend/.sqlx/query-e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4.json new file mode 100644 index 0000000000..ff34048ea9 --- /dev/null +++ b/backend/.sqlx/query-e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4.json @@ -0,0 +1,77 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, created_at, trigger_kind as \"trigger_kind: _\", payload as \"payload: _\", trigger_extra as \"trigger_extra: _\"\n FROM capture\n WHERE workspace_id = $1\n AND path = $2 AND is_flow = $3\n AND ($4::trigger_kind IS NULL OR trigger_kind = $4)\n ORDER BY created_at DESC\n OFFSET $5\n LIMIT $6", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "trigger_kind: _", + "type_info": { + "Custom": { + "name": "trigger_kind", + "kind": { + "Enum": [ + "webhook", + "http", + "websocket", + "kafka", + "email" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "payload: _", + "type_info": "Jsonb" + }, + { + "ordinal": 4, + "name": "trigger_extra: _", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Bool", + { + "Custom": { + "name": "trigger_kind", + "kind": { + "Enum": [ + "webhook", + "http", + "websocket", + "kafka", + "email" + ] + } + } + }, + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + true + ] + }, + "hash": "e08dddf6af2656b561c453460ff928e7a158bd29d9035bee136f9952fb96bad4" +} diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index d95fddf2c7..0ccba9807c 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -283,7 +283,7 @@ paths: text/plain: schema: type: string - + /users/set_password_of/{user}: post: summary: set password for a specific user (require super admin) @@ -348,7 +348,6 @@ paths: schema: type: string - /users/create: post: summary: create user @@ -530,7 +529,7 @@ paths: text/plain: schema: type: string - + /users/export: get: summary: global export users (require super admin and EE) @@ -877,7 +876,7 @@ paths: alerts: type: array items: - $ref: '#/components/schemas/CriticalAlert' + $ref: "#/components/schemas/CriticalAlert" total_rows: type: integer description: Total number of rows matching the query. @@ -998,7 +997,6 @@ paths: schema: type: string - /settings/latest_key_renewal_attempt: get: summary: get latest key renewal attempt @@ -1084,7 +1082,6 @@ paths: schema: type: string - /settings/list_global: get: summary: list global settings @@ -2808,7 +2805,7 @@ paths: alerts: type: array items: - $ref: '#/components/schemas/CriticalAlert' + $ref: "#/components/schemas/CriticalAlert" total_rows: type: integer description: Total number of rows matching the query. @@ -2879,7 +2876,7 @@ paths: description: Whether critical alerts should be muted. example: true responses: - '200': + "200": description: Successfully updated mute critical alert settings. content: application/json: @@ -2958,7 +2955,6 @@ paths: schema: type: string - /oauth/connect_slack_callback: post: summary: connect slack callback instance @@ -3175,7 +3171,7 @@ paths: type: string responses: "200": - description: get + description: get content: application/json: schema: @@ -3188,9 +3184,6 @@ paths: items: type: string - - - /w/{workspace}/resources/create: post: summary: create resource @@ -3481,7 +3474,6 @@ paths: application/json: schema: {} - /w/{workspace}/resources/type/delete/{path}: delete: summary: delete resource_type @@ -4469,7 +4461,6 @@ paths: description: Script version/hash content: application/json: - required: false schema: @@ -5040,7 +5031,6 @@ paths: schema: type: string - /w/{workspace}/flows/get/{path}: get: summary: get flow by path @@ -5098,7 +5088,6 @@ paths: items: $ref: "#/components/schemas/TruncatedToken" - /w/{workspace}/flows/toggle_workspace_error_handler/{path}: post: summary: Toggle ON and OFF the workspace error handler for a given flow @@ -5269,7 +5258,6 @@ paths: schema: type: string - /w/{workspace}/raw_apps/list: get: summary: list all raw apps @@ -6319,7 +6307,6 @@ paths: schema: type: integer - /w/{workspace}/jobs/queue/list_filtered_uuids: get: summary: get the ids of all jobs matching the given filters @@ -6631,7 +6618,6 @@ paths: schema: type: string - /w/{workspace}/jobs_u/get_args/{id}: get: summary: get job args @@ -7156,7 +7142,6 @@ paths: schema: type: string - /w/{workspace}/jobs_u/cancel/{id}/{resume_id}/{signature}: get: summary: cancel a job for a suspended flow @@ -7527,7 +7512,6 @@ paths: "201": description: default error handler set - /w/{workspace}/http_triggers/create: post: summary: create http trigger @@ -7580,7 +7564,7 @@ paths: summary: delete http trigger operationId: deleteHttpTrigger tags: - - http_trigger + - http_trigger parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" @@ -7609,7 +7593,6 @@ paths: schema: $ref: "#/components/schemas/HttpTrigger" - /w/{workspace}/http_triggers/list: get: summary: list http triggers @@ -7644,7 +7627,6 @@ paths: items: $ref: "#/components/schemas/HttpTrigger" - /w/{workspace}/http_triggers/exists/{path}: get: summary: does http trigger exists @@ -7747,7 +7729,7 @@ paths: summary: delete websocket trigger operationId: deleteWebsocketTrigger tags: - - websocket_trigger + - websocket_trigger parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" @@ -7776,7 +7758,6 @@ paths: schema: $ref: "#/components/schemas/WebsocketTrigger" - /w/{workspace}/websocket_triggers/list: get: summary: list websocket triggers @@ -7811,7 +7792,6 @@ paths: items: $ref: "#/components/schemas/WebsocketTrigger" - /w/{workspace}/websocket_triggers/exists/{path}: get: summary: does websocket trigger exists @@ -7910,7 +7890,7 @@ paths: summary: delete kafka trigger operationId: deleteKafkaTrigger tags: - - kafka_trigger + - kafka_trigger parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" @@ -7939,7 +7919,6 @@ paths: schema: $ref: "#/components/schemas/KafkaTrigger" - /w/{workspace}/kafka_triggers/list: get: summary: list kafka triggers @@ -7974,7 +7953,6 @@ paths: items: $ref: "#/components/schemas/KafkaTrigger" - /w/{workspace}/kafka_triggers/exists/{path}: get: summary: does kafka trigger exists @@ -8880,7 +8858,7 @@ paths: properties: id: type: string - values: + values: type: array items: type: object @@ -8907,12 +8885,11 @@ paths: description: queue counts content: application/json: - schema: + schema: type: object additionalProperties: type: integer - /configs/list_worker_groups: get: summary: list worker groups @@ -9025,7 +9002,6 @@ paths: items: $ref: "#/components/schemas/AutoscalingEvent" - /w/{workspace}/acls/get/{kind}/{path}: get: summary: get granular acls @@ -9184,11 +9160,11 @@ paths: properties: trigger_kind: $ref: "#/components/schemas/CaptureTriggerKind" - path: + path: type: string is_flow: type: boolean - trigger_config: + trigger_config: type: object required: - trigger_kind @@ -9198,7 +9174,6 @@ paths: "200": description: capture config set - /w/{workspace}/capture/ping_config/{trigger_kind}/{runnable_kind}/{path}: post: summary: ping capture config @@ -9252,6 +9227,8 @@ paths: in: query schema: $ref: "#/components/schemas/CaptureTriggerKind" + - $ref: "#/components/parameters/Page" + - $ref: "#/components/parameters/PerPage" responses: "200": description: list of captures for a script or flow @@ -9262,7 +9239,6 @@ paths: items: $ref: "#/components/schemas/Capture" - /w/{workspace}/capture/{id}: delete: summary: delete a capture @@ -9338,6 +9314,10 @@ paths: - $ref: "#/components/parameters/RunnableTypeQuery" - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" + - name: include_preview + in: query + schema: + type: boolean responses: "200": description: Input history for completed jobs @@ -10265,7 +10245,7 @@ paths: tags: - service_logs parameters: - - $ref: "#/components/parameters/Path" + - $ref: "#/components/parameters/Path" responses: "200": description: log stream @@ -11082,7 +11062,6 @@ components: - no_main_func - has_preprocessor - NewScript: type: object properties: @@ -12439,7 +12418,7 @@ components: type: boolean requires_auth: type: boolean - + required: - path - edited_by @@ -12488,7 +12467,7 @@ components: type: boolean requires_auth: type: boolean - + required: - path - script_path @@ -12614,7 +12593,7 @@ components: $ref: "#/components/schemas/WebsocketTriggerInitialMessage" url_runnable_args: $ref: "#/components/schemas/ScriptArgs" - + required: - path - edited_by @@ -12658,7 +12637,7 @@ components: $ref: "#/components/schemas/WebsocketTriggerInitialMessage" url_runnable_args: $ref: "#/components/schemas/ScriptArgs" - + required: - path - script_path @@ -12694,7 +12673,7 @@ components: $ref: "#/components/schemas/WebsocketTriggerInitialMessage" url_runnable_args: $ref: "#/components/schemas/ScriptArgs" - + required: - path - script_path @@ -12706,7 +12685,7 @@ components: anyOf: - type: object properties: - raw_message: + raw_message: type: string required: - raw_message @@ -12715,7 +12694,7 @@ components: runnable_result: type: object properties: - path: + path: type: string args: $ref: "#/components/schemas/ScriptArgs" @@ -12725,7 +12704,7 @@ components: - path - args - is_flow - required: + required: - runnable_result KafkaTrigger: @@ -12767,7 +12746,7 @@ components: type: string enabled: type: boolean - + required: - path - edited_by @@ -12801,7 +12780,7 @@ components: type: string enabled: type: boolean - + required: - path - script_path @@ -12827,7 +12806,7 @@ components: type: string is_flow: type: boolean - + required: - path - script_path @@ -13794,14 +13773,14 @@ components: obscured_jobs: type: array items: - $ref : "#/components/schemas/ObscuredJob" + $ref: "#/components/schemas/ObscuredJob" omitted_obscured_jobs: description: "Obscured jobs omitted for security because of too specific filtering" type: boolean required: - jobs - obscured_jobs - + ExportedUser: type: object properties: @@ -13847,7 +13826,7 @@ components: type: object required: - name - + ExportedInstanceGroup: type: object properties: diff --git a/backend/windmill-api/src/capture.rs b/backend/windmill-api/src/capture.rs index 2778c8ec4e..e50b83fe5e 100644 --- a/backend/windmill-api/src/capture.rs +++ b/backend/windmill-api/src/capture.rs @@ -27,7 +27,7 @@ use windmill_common::error::Error; use windmill_common::{ db::UserDB, error::{JsonResult, Result}, - utils::{not_found_if_none, StripPath}, + utils::{not_found_if_none, paginate, Pagination, StripPath}, worker::{to_raw_value, CLOUD_HOSTED}, }; use windmill_queue::{PushArgs, PushArgsOwned}; @@ -280,6 +280,8 @@ enum RunnableKind { #[derive(Deserialize)] struct ListCapturesQuery { trigger_kind: Option, + page: Option, + per_page: Option, } async fn list_captures( @@ -290,6 +292,8 @@ async fn list_captures( ) -> JsonResult> { let mut tx = user_db.begin(&authed).await?; + let (per_page, offset) = paginate(Pagination { page: query.page, per_page: query.per_page }); + let captures = sqlx::query_as!( Capture, r#"SELECT id, created_at, trigger_kind as "trigger_kind: _", payload as "payload: _", trigger_extra as "trigger_extra: _" @@ -297,11 +301,15 @@ async fn list_captures( WHERE workspace_id = $1 AND path = $2 AND is_flow = $3 AND ($4::trigger_kind IS NULL OR trigger_kind = $4) - ORDER BY created_at DESC"#, + ORDER BY created_at DESC + OFFSET $5 + LIMIT $6"#, &w_id, &path.to_path(), matches!(runnable_kind, RunnableKind::Flow), query.trigger_kind as Option, + offset as i64, + per_page as i64, ) .fetch_all(&mut *tx) .await?; diff --git a/backend/windmill-api/src/inputs.rs b/backend/windmill-api/src/inputs.rs index e971056372..1271be1620 100644 --- a/backend/windmill-api/src/inputs.rs +++ b/backend/windmill-api/src/inputs.rs @@ -115,12 +115,18 @@ pub struct CompletedJobMini { success: bool, } +#[derive(Deserialize)] +struct GetInputHistory { + include_preview: Option, +} + async fn get_input_history( authed: ApiAuthed, Extension(user_db): Extension, Path(w_id): Path, Query(pagination): Query, Query(r): Query, + Query(g): Query, ) -> JsonResult> { let (per_page, offset) = paginate(pagination); @@ -128,7 +134,7 @@ async fn get_input_history( let sql = &format!( "select id, created_at, created_by, 'null'::jsonb as args, success from completed_job \ - where {} = $1 and job_kind = $2 and workspace_id = $3 \ + where {} = $1 and job_kind = any($2) and workspace_id = $3 \ order by created_at desc limit $4 offset $5", r.runnable_type.column_name() ); @@ -140,8 +146,18 @@ async fn get_input_history( _ => query.bind(&r.runnable_id), }; + let job_kinds = match r.runnable_type.job_kind() { + kind @ JobKind::Script if g.include_preview.unwrap_or(false) => { + vec![kind, JobKind::Preview] + } + kind @ JobKind::Flow if g.include_preview.unwrap_or(false) => { + vec![kind, JobKind::FlowPreview] + } + kind => vec![kind], + }; + let rows = query - .bind(r.runnable_type.job_kind()) + .bind(job_kinds) .bind(&w_id) .bind(per_page as i32) .bind(offset as i32) diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 632b115863..c2a45265a3 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -395,19 +395,21 @@ {/if} {:else if inputCat == 'boolean'} - { - e?.stopPropagation() - }} - {disabled} - class={valid - ? '' - : 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'} - bind:checked={value} - /> - {#if type == 'boolean' && value == undefined} -   Not set - {/if} +
+ { + e?.stopPropagation() + }} + {disabled} + class={valid + ? '' + : 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'} + bind:checked={value} + /> + {#if type == 'boolean' && value == undefined} +   Not set + {/if} +
{:else if inputCat == 'list' && !isListJson}
diff --git a/frontend/src/lib/components/CapturesInputs.svelte b/frontend/src/lib/components/CapturesInputs.svelte new file mode 100644 index 0000000000..c7c9225c4f --- /dev/null +++ b/frontend/src/lib/components/CapturesInputs.svelte @@ -0,0 +1,18 @@ + + +
+ +
diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index 9a86f8680b..ccdda7524e 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -33,7 +33,7 @@ import { writable } from 'svelte/store' import type { FlowState } from './flows/flowState' import { initHistory } from '$lib/history' - import type { FlowEditorContext, FlowInput } from './flows/types' + import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types' import { dfs } from './flows/dfs' import { loadSchemaFromModule } from './flows/flowInfers' import { CornerDownLeft, Play } from 'lucide-svelte' @@ -490,7 +490,8 @@ triggersCount: triggersCount, simplifiedPoll: writable(false), defaultValues: writable(undefined), - captureOn: writable(undefined) + captureOn: writable(undefined), + showCaptureHint: writable(undefined) }) setContext('FlowEditorContext', { selectedId: selectedIdStore, @@ -508,6 +509,11 @@ customUi: {}, insertButtonOpen: writable(false), executionCount: writable(0), + flowInputEditorState: writable({ + selectedTab: undefined, + editPanelSize: undefined, + payloadData: undefined + }) }) setContext('PropPickerContext', { flowPropPickerConfig: writable(undefined), diff --git a/frontend/src/lib/components/EditableSchemaForm.svelte b/frontend/src/lib/components/EditableSchemaForm.svelte index dbf5e2e79d..102ce4b6db 100644 --- a/frontend/src/lib/components/EditableSchemaForm.svelte +++ b/frontend/src/lib/components/EditableSchemaForm.svelte @@ -5,7 +5,6 @@ import { Button } from './common' import ItemPicker from './ItemPicker.svelte' import VariableEditor from './VariableEditor.svelte' - import { Pen, Plus, X } from 'lucide-svelte' import { Pane, Splitpanes } from 'svelte-splitpanes' import { twMerge } from 'tailwind-merge' @@ -23,6 +22,7 @@ import Popup from './common/popup/Popup.svelte' import SchemaFormDnd from './schema/SchemaFormDND.svelte' import { deepEqual } from 'fast-equals' + import { tweened } from 'svelte/motion' export let schema: Schema | any export let schemaSkippedValues: string[] = [] @@ -33,12 +33,22 @@ export let uiOnly: boolean = false export let isFlowInput: boolean = false export let noPreview: boolean = false - export let offset = 48 + 31 + 31 + 16 + 1 export let jsonEnabled: boolean = true export let isAppInput: boolean = false export let lightweightMode: boolean = false export let displayWebhookWarning: boolean = false export let dndType: string | undefined = undefined + export let editTab: + | 'inputEditor' + | 'history' + | 'savedInputs' + | 'json' + | 'captures' + | 'firstStepInputs' + | undefined + export let previewSchema: Record | undefined = undefined + export let editPanelInitialSize: number | undefined = undefined + export let editPanelSize = 0 const dispatch = createEventDispatcher() @@ -134,13 +144,15 @@ } } - $: if (opened && schema.properties[opened]) { + $: opened && updateSelected(schema.properties[opened]) + function updateSelected(property: any) { + if (!property) return selected = opened - ? schema.properties[opened].type !== 'object' - ? schema.properties[opened].type - : schema.properties[opened].format === 'resource-s3_object' + ? property.type !== 'object' + ? property.type + : property.format === 'resource-s3_object' ? 'S3' - : schema.properties[opened].oneOf && schema.properties[opened].oneOf.length >= 2 + : property.oneOf && property.oneOf.length >= 2 ? 'oneOf' : 'object' : '' @@ -189,15 +201,63 @@ let schemaString: string = JSON.stringify(schema, null, '\t') let error: string | undefined = undefined let editor: SimpleEditor | undefined = undefined + + const editTabDefaultSize = noPreview ? 100 : 50 + editPanelSize = editTab ? editPanelInitialSize ?? editTabDefaultSize : 0 + let inputPanelSize = 100 - editPanelSize + let editPanelSizeSmooth = tweened(editPanelSize, { + duration: 150 + }) + let inputPanelSizeSmooth = tweened(inputPanelSize, { duration: 150 }) + + function openEditTab() { + if (editPanelSize > 0) return + editPanelSizeSmooth.set(editTabDefaultSize) + inputPanelSizeSmooth.set(100 - editTabDefaultSize) + } + + function closeEditTab() { + editPanelSizeSmooth.set(0) + inputPanelSizeSmooth.set(100) + } + + function updatePanelSizes(editSize: number, inputSize: number) { + editPanelSize = editSize + inputPanelSize = inputSize + dispatch('editPanelSizeChanged', editSize) + } + $: updatePanelSizes($editPanelSizeSmooth, $inputPanelSizeSmooth) + + $: !!editTab ? openEditTab() : closeEditTab() + + let pannelButtonWidth: number = 0 + export let pannelExtraButtonWidth: number = 0 -
- +
+
+
+ +
+
+ {#if !noPreview} - -
+ +
+ {#if $$slots.addProperty} +
+
+ +
+
+ {/if} + { @@ -214,314 +274,336 @@ }} {lightweightMode} prettifyHeader={isAppInput} + disabled={!!previewSchema} /> + +
{/if} - - {#if jsonEnabled} -
- { - schemaString = JSON.stringify(schema, null, '\t') - editor?.setCode(schemaString) - }} - /> -
- {/if} - {#if !jsonView} -
- {#if keys.length > 0} - {#each keys as argName, i (argName)} -
- - -
{ - if (opened === argName) { - opened = undefined - } else { - opened = argName - } - }} - > -
- {argName} - {#if !uiOnly} -
- - - -
- - -
- {/if} -
+ {#if editPanelSize > 0} + 0 ? 'rounded-tl-none' : '')} + > + {#if editTab !== 'inputEditor'} + + {:else} + + {#if jsonEnabled} +
+ { + schemaString = JSON.stringify(schema, null, '\t') + editor?.setCode(schemaString) + }} + /> +
+ {/if} - {#if schema.required?.includes(argName)} - Required - {/if} - - {#if !uiOnly} - - {/if} -
- {#if opened === argName} -
- {#if !schemaSkippedValues.includes(argName) && Object.keys(schema?.properties ?? {}).includes(argName)} - {#if typeof args == 'object' && schema?.properties[argName]} - { - schema = schema - // console.log('schema', schema) - dispatch('change', schema) +
+ {argName} + {#if !uiOnly} +
+ + + +
+ + +
+ {/if} +
+ + {#if schema.required?.includes(argName)} + Required + {/if} + + {#if !uiOnly} + + {/if} +
+ {#if opened === argName} +
+ {#if !schemaSkippedValues.includes(argName) && Object.keys(schema?.properties ?? {}).includes(argName)} + {#if typeof args == 'object' && schema?.properties[argName]} + { + schema = schema + // console.log('schema', schema) + dispatch('change', schema) + }} + > + + {#if isFlowInput || isAppInput} + + {/if} + + + {#if isFlowInput || isAppInput} + { + if (event.detail.required) { + schema.required = schema.required ?? [] + schema.required.push(argName) } else { - schema.properties[argName] = { - ...emptyProperty, - format: undefined, - type: e.detail - } + schema.required = schema.required?.filter( + (x) => x !== argName + ) } + dispatch('change', schema) + }} + on:schemaChange={(e) => { schema = schema dispatch('change', schema) }} - > - {#each [['String', 'string'], ['Number', 'number'], ['Integer', 'integer'], ['Object', 'object'], ['OneOf', 'oneOf'], ['Array', 'array'], ['Boolean', 'boolean'], ['S3 Object', 'S3']] as x} - - {/each} - - - {/if} - - - {#if isFlowInput || isAppInput} - { - if (event.detail.required) { - schema.required = schema.required ?? [] - schema.required.push(argName) - } else { - schema.required = schema.required?.filter((x) => x !== argName) - } - dispatch('change', schema) - }} - on:schemaChange={(e) => { - schema = schema - dispatch('change', schema) - }} - /> + /> + {/if} + {/if} - - {/if} + {/if} +
{/if}
- {/if} -
- {/each} - {:else if !shouldHideNoInputs} -
No inputs
- {/if} -
- {:else} -
-
- { - try { - schema = JSON.parse(schemaString) - dispatch('change', schema) - error = '' - } catch (err) { - error = err.message - } - }} - bind:code={schemaString} - lang="json" - autoHeight - automaticLayout - /> -
- {#if !emptyString(error)} -
{error}
+ {/each} + {:else if !shouldHideNoInputs} +
No inputs
+ {/if} +
{:else} -

+
+
+ { + try { + schema = JSON.parse(schemaString) + dispatch('change', schema) + error = '' + } catch (err) { + error = err.message + } + }} + bind:code={schemaString} + lang="json" + autoHeight + automaticLayout + /> +
+ {#if !emptyString(error)} +
{error}
+ {:else} +

+ {/if} +
{/if} -
- {/if} - + {/if} + + {/if}
@@ -558,3 +640,11 @@ {/if} + + diff --git a/frontend/src/lib/components/FirstStepInputs.svelte b/frontend/src/lib/components/FirstStepInputs.svelte new file mode 100644 index 0000000000..6f7b690c3f --- /dev/null +++ b/frontend/src/lib/components/FirstStepInputs.svelte @@ -0,0 +1,122 @@ + + + + +
+ {#if schema && mod} + + {:else} + + {error} + + {/if} +
diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 97c0bce350..4cdfceb49f 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -44,7 +44,7 @@ import { dfs, getPreviousIds } from './flows/previousResults' import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte' import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte' - import type { FlowEditorContext, FlowInput } from './flows/types' + import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types' import { cleanInputs, emptyFlowModuleState } from './flows/utils' import { Calendar, @@ -490,7 +490,13 @@ const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined) const history = initHistory($flowStore) const pathStore = writable(pathStoreInit ?? initialPath) - + const captureOn = writable(false) + const showCaptureHint = writable(undefined) + const flowInputEditorStateStore = writable({ + selectedTab: undefined, + editPanelSize: 0, + payloadData: undefined + }) $: initialPath && ($pathStore = initialPath) const testStepStore = writable>({}) @@ -529,7 +535,8 @@ flowInputsStore: writable({}), customUi, insertButtonOpen, - executionCount: writable(0) + executionCount: writable(0), + flowInputEditorState: flowInputEditorStateStore }) setContext('TriggerContext', { @@ -538,7 +545,8 @@ triggersCount, simplifiedPoll, defaultValues: writable(undefined), - captureOn: writable(undefined) + captureOn, + showCaptureHint }) async function loadTriggers() { @@ -1206,6 +1214,7 @@ let deploymentMsg = '' let msgInput: HTMLInputElement | undefined = undefined + let flowPreviewButtons: FlowPreviewButtons @@ -1418,7 +1427,15 @@ {abortController} /> {/if} - + { + select('triggers') + selectTrigger(e.detail.kind) + captureOn.set(true) + showCaptureHint.set(true) + }} + bind:this={flowPreviewButtons} + /> + +
+ {#if typeof selectedCapture == 'string' && selectedCapture == 'WINDMILL_TOO_BIG'} +
+ Payload too big to preview but can still be loaded
+ {:else if Object.keys(selectedCapture || {}).length > 0} +
+ +
+ {:else} +
+ Select an Input to preview scripts arguments +
+ {/if} +
+ + + + + +
@@ -280,6 +352,14 @@
{/if}
+ {#if initialPath != ''} diff --git a/frontend/src/lib/components/SavedInputs.svelte b/frontend/src/lib/components/SavedInputs.svelte index 2aa4248a12..7cb00f671e 100644 --- a/frontend/src/lib/components/SavedInputs.svelte +++ b/frontend/src/lib/components/SavedInputs.svelte @@ -4,22 +4,22 @@ InputService, type Input, type RunnableType, - type CreateInput, type Job, JobService } from '$lib/gen/index.js' import { userStore, workspaceStore } from '$lib/stores.js' import { base } from '$lib/base' - import { classNames, displayDate, displayDateOnly, sendUserToast } from '$lib/utils.js' + import { classNames, displayDateOnly, sendUserToast } from '$lib/utils.js' import { createEventDispatcher } from 'svelte' import { Pane, Splitpanes } from 'svelte-splitpanes' import ObjectViewer from './propertyPicker/ObjectViewer.svelte' - import { ArrowLeftIcon, Edit, ExternalLink, Save, X } from 'lucide-svelte' + import { ArrowLeftIcon, Edit, ExternalLink, X } from 'lucide-svelte' import Toggle from './Toggle.svelte' import Tooltip from './Tooltip.svelte' import TimeAgo from './TimeAgo.svelte' import JobLoader from './runs/JobLoader.svelte' import Skeleton from './common/skeleton/Skeleton.svelte' + import SaveInputsButton from './SaveInputsButton.svelte' export let scriptHash: string | null = null export let scriptPath: string | null = null @@ -40,7 +40,6 @@ let selectedInput: Input | null let jobs: Job[] = [] let loading: boolean = false - let savingInputs = false const dispatch = createEventDispatcher() $: runnableId = scriptHash || scriptPath || flowPath || undefined @@ -80,38 +79,6 @@ }) } - async function saveInput(args: object) { - savingInputs = true - - const requestBody: CreateInput = { - name: 'Saved ' + displayDate(new Date()), - args: args as any - } - - try { - let id = await InputService.createInput({ - workspace: $workspaceStore!, - runnableId, - runnableType, - requestBody - }) - - const input = { - id, - created_by: '', - created_at: new Date().toISOString(), - is_public: false, - ...requestBody - } - savedInputs = [input, ...(savedInputs ?? [])] - } catch (err) { - console.error(err) - sendUserToast(`Failed to save Input: ${err}`, true) - } - - savingInputs = false - } - async function updateInput(input: EditableInput) { input.isSaving = true @@ -176,25 +143,26 @@ } - +{#if runnableId} + +{/if}
@@ -202,21 +170,20 @@
Saved Inputs Saved InputsShared inputs are available to anyone with access to the script {#if canSaveInputs} - + {runnableId} + {runnableType} + on:update={() => { + loadSavedInputs() + }} + /> {/if}
@@ -385,7 +352,7 @@ > {/if} {:else} -
No running runs
+
No job currently running
{/if}
diff --git a/frontend/src/lib/components/SavedInputsPicker.svelte b/frontend/src/lib/components/SavedInputsPicker.svelte new file mode 100644 index 0000000000..139d1f12b7 --- /dev/null +++ b/frontend/src/lib/components/SavedInputsPicker.svelte @@ -0,0 +1,288 @@ + + + + +
{ + selectedInput = null + selectedArgs = undefined + dispatch('select', undefined) + }} +> +
+ + + {#if !flowPath} + Save draft first before you can save inputs + {:else if !previewArgs} + Add inputs before saving + {/if} + + { + infiniteList?.loadData('refresh') + }} + showTooltip={true} + /> + +
+
+ {#if !draft} + handleError(e.detail)} + on:select={(e) => handleSelect(e.detail)} + > + + + + + + + + +
+ +
+
+ +
+
+ {#if isEditing && isEditing.id === item.id} +
{ + updateInput(isEditing) + setEditing(null) + }} + class="w-full" + > + +
+ {:else} + + {item.name} + + {/if} + {#if item.created_by == $userStore?.username || $userStore?.is_admin || $userStore?.is_super_admin} +
+ {#if !isEditing || isEditing?.id !== item.id} + + {/if} + + + +
+ {:else} + By {item.created_by} + {/if} +
+
+
+
+ +
No saved Inputs
+
+
+ {/if} +
+
diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 85a928a2e8..0c5afc41ce 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -147,13 +147,15 @@ const triggerDefaultValuesStore = writable | undefined>(undefined) const captureOn = writable(undefined) + const showCaptureHint = writable(undefined) setContext('TriggerContext', { selectedTrigger: selectedTriggerStore, primarySchedule: primaryScheduleStore, triggersCount, simplifiedPoll, defaultValues: triggerDefaultValuesStore, - captureOn: captureOn + captureOn: captureOn, + showCaptureHint: showCaptureHint }) const enterpriseLangs = ['bigquery', 'snowflake', 'mssql'] @@ -628,7 +630,9 @@ async function applyArgs(e) { selectedInputTab = e.detail.kind metadataOpen = false - args = e.detail.args ?? {} + if (scriptEditor) { + scriptEditor.updateArgs(e.detail.args ?? {}) + } } function openTriggers(ev) { @@ -680,601 +684,604 @@ > (metadataOpen = false)}> - - Metadata - Runtime - - Generated UI - - The arguments are synced with the main signature but you may refine the parts that - cannot be inferred from the type directly. - - - - Triggers - - Configure how this script will be triggered. - - - -
- -
-
- -
- -
-
-
- - - -
-
+
+ + Metadata + Runtime + + Generated UI + + The arguments are synced with the main signature but you may refine the parts that + cannot be inferred from the type directly. + + + + Triggers + + Configure how this script will be triggered. + + -
- - {#if lockedLanguage} -
- As a forked script, the language '{script.language}' cannot be modified. -
- {/if} -
- {#each langs as [label, lang] (lang)} - {@const isPicked = - (lang == script.language && template == 'script') || - (template == 'bunnative' && lang == 'bunnative') || - (template == 'docker' && lang == 'docker')} - - - {label} is only available with an enterprise license - - {/each} -
-
+ elementProps={{ + type: 'text', + placeholder: 'Short summary to be displayed when listed' + }} + /> + + + +
+ -
- - + + {#if lockedLanguage} +
+ As a forked script, the language '{script.language}' cannot be modified. +
+ {/if} +
+ {#each langs as [label, lang] (lang)} + {@const isPicked = + (lang == script.language && template == 'script') || + (template == 'bunnative' && lang == 'bunnative') || + (template == 'docker' && lang == 'docker')} + + + {label} is only available with an enterprise license + + {/each} +
+
+ +
+ + + Tag this script's purpose within flows such that it is available as the + corresponding action. + + + { + template = 'script' + script.kind = detail + initContent(script.language, detail, template) + }} > - Tag this script's purpose within flows such that it is available as the - corresponding action. - - - { - template = 'script' - script.kind = detail - initContent(script.language, detail, template) - }} - > - {#each scriptKindOptions as { value, title, desc, documentationLink, Icon }} - - {/each} - -
-
-
- -
-
- - - Allowed concurrency within a given timeframe - - -
-
+
+
+ +
+
+ + + Allowed concurrency within a given timeframe + + +
+ + +
- - - -
- -
- - - The script will be executed on a worker configured to listen to this worker - group tag (queue). For instance, you could setup an "highmem", or "gpu" tag. - - - -
-
- - - Cache the results for each possible inputs - - -
- { - if (script.cache_ttl && script.cache_ttl != undefined) { - script.cache_ttl = undefined - } else { - script.cache_ttl = 300 - } - }} - options={{ - right: 'Cache the results for each possible inputs' - }} - /> - - How long to the keep cache valid - - {#if script.cache_ttl} - - {:else} - - {/if} -
-
-
- - - Add a custom timeout for this script - - -
- { - if (script.timeout && script.timeout != undefined) { - script.timeout = undefined - } else { - script.timeout = 300 - } - }} - options={{ - right: 'Add a custom timeout for this script' - }} - /> - Timeout duration - {#if script.timeout} - - {:else} - - {/if} -
-
-
- - - Restart the script upon ending unless cancelled - - -
- { - if (script.restart_unless_cancelled) { - script.restart_unless_cancelled = undefined - } else { - script.restart_unless_cancelled = true - } - }} - options={{ - right: 'Restart upon ending unless cancelled' - }} - /> -
-
-
- { - if (script.dedicated_worker) { - script.dedicated_worker = undefined - } else { - script.dedicated_worker = true - } - }} - options={{ - right: 'Script is run on dedicated workers' - }} - /> - {#if script.dedicated_worker} -
- - One worker in a worker group needs to be configured with dedicated worker - set to:
{$workspaceStore}:{script.path}
-
+
- {/if} - - - In this mode, the script is meant to be run on dedicated workers that run the - script at native speed. Can reach >1500rps per dedicated worker. Only - available on enterprise edition and for Python3, Deno and Bun. For other - languages, the efficiency is already on par with deidcated workers since they - do not spawn a full runtime - -
-
- - - WARNING: This settings ONLY applies to synchronous webhooks or when the script - is used within a flow. If used individually, this script must be triggered - using a synchronous endpoint to have the desired effect. -
-
- The logs, arguments and results of the job will be completely deleted from Windmill - once it is complete and the result has been returned. -
-
- The deletion is irreversible. - {#if !$enterpriseLicense} -
-
- This option is only available on Windmill Enterprise Edition. +
+
+ + + The script will be executed on a worker configured to listen to this worker + group tag (queue). For instance, you could setup an "highmem", or "gpu" tag. + + + +
+
+ + + Cache the results for each possible inputs + + +
+ { + if (script.cache_ttl && script.cache_ttl != undefined) { + script.cache_ttl = undefined + } else { + script.cache_ttl = 300 + } + }} + options={{ + right: 'Cache the results for each possible inputs' + }} + /> + + How long to the keep cache valid + + {#if script.cache_ttl} + + {:else} + {/if} - - -
+
+
+
+ + + Add a custom timeout for this script + + +
+ { + if (script.timeout && script.timeout != undefined) { + script.timeout = undefined + } else { + script.timeout = 300 + } + }} + options={{ + right: 'Add a custom timeout for this script' + }} + /> + Timeout duration + {#if script.timeout} + + {:else} + + {/if} +
+
+
+ + + Restart the script upon ending unless cancelled + + +
+ { + if (script.restart_unless_cancelled) { + script.restart_unless_cancelled = undefined + } else { + script.restart_unless_cancelled = true + } + }} + options={{ + right: 'Restart upon ending unless cancelled' + }} + /> +
+
+
{ - if (script.delete_after_use) { - script.delete_after_use = undefined + if (script.dedicated_worker) { + script.dedicated_worker = undefined } else { - script.delete_after_use = true + script.dedicated_worker = true } }} options={{ - right: 'Delete logs, arguments and results after use' + right: 'Script is run on dedicated workers' }} /> -
- - {#if !isCloudHosted()} -
- 0} - on:change={() => { - if (script.priority) { - script.priority = undefined - } else { - script.priority = 100 - } - }} - options={{ - right: 'Label as high priority' - }} - > - - { - if (script.priority && script.priority > 100) { - script.priority = 100 - } else if (script.priority && script.priority < 0) { - script.priority = 0 - } - }} - /> - - + {#if script.dedicated_worker} +
+ + One worker in a worker group needs to be configured with dedicated worker + set to:
{$workspaceStore}:{script.path}
+
+
+ {/if} - + In this mode, the script is meant to be run on dedicated workers that run + the script at native speed. Can reach >1500rps per dedicated worker. Only + available on enterprise edition and for Python3, Deno and Bun. For other + languages, the efficiency is already on par with deidcated workers since + they do not spawn a full runtime - Jobs from script labeled as high priority take precedence over the other - jobs when in the jobs queue. - {#if !$enterpriseLicense}This is a feature only available on enterprise - edition.{/if} -
- {/if} -
- - - When this option is enabled, manual executions of this script are invisible to - users other than the user running it, including the owner(s). This setting can - be overridden when this script is run manually from the advanced menu. - - -
- { - if (script.visible_to_runner_only) { - script.visible_to_runner_only = undefined - } else { - script.visible_to_runner_only = true - } - }} - options={{ - right: 'Make runs invisible to others' - }} - /> -
-
- {#if !isCloudHosted()} -
+
- Additional static custom env variables to pass to the script. + WARNING: This settings ONLY applies to synchronous webhooks or when the + script is used within a flow. If used individually, this script must be + triggered using a synchronous endpoint to have the desired effect. +
+
+ The logs, arguments and results of the job will be completely deleted from Windmill + once it is complete and the result has been returned. +
+
+ The deletion is irreversible. + {#if !$enterpriseLicense} +
+
+ This option is only available on Windmill Enterprise Edition. + {/if}
- {#if script.envs && script.envs.length > 0} - - Static envs variables are not passed in preview but solely on deployed - scripts. - - {/if} -
- Format is: `{'='}` - {#if Array.isArray(script.envs ?? [])} - {#each script.envs ?? [] as v, i} -
- - -
- {/each} - {/if} -
-
-
+ {#if !isCloudHosted()} +
+ 0} + on:change={() => { + if (script.priority) { + script.priority = undefined + } else { + script.priority = 100 + } + }} + options={{ + right: 'Label as high priority' }} > -
- - Add item -
- + + { + if (script.priority && script.priority > 100) { + script.priority = 100 + } else if (script.priority && script.priority < 0) { + script.priority = 0 + } + }} + /> + +
+ + + + Jobs from script labeled as high priority take precedence over the other + jobs when in the jobs queue. + {#if !$enterpriseLicense}This is a feature only available on enterprise + edition.{/if} + + +
+ {/if} +
+ + + When this option is enabled, manual executions of this script are invisible + to users other than the user running it, including the owner(s). This + setting can be overridden when this script is run manually from the advanced + menu. + + +
+ { + if (script.visible_to_runner_only) { + script.visible_to_runner_only = undefined + } else { + script.visible_to_runner_only = true + } + }} + options={{ + right: 'Make runs invisible to others' + }} + />
- {/if} -
- - - - - - { - captureTable?.refreshCaptures() - }} - {initialPath} - schema={script.schema} - noEditor={true} - isFlow={false} - currentPath={script.path} - hash={script.parent_hash} - newItem={initialPath == ''} - canHavePreprocessor={script.language === 'bun' || - script.language === 'deno' || - script.language === 'python3'} - {hasPreprocessor} - /> - - -
- - + {#if !isCloudHosted()} +
+ + + Additional static custom env variables to pass to the script. + + + {#if script.envs && script.envs.length > 0} + + Static envs variables are not passed in preview but solely on deployed + scripts. + + {/if} +
+ Format is: `{'='}` + {#if Array.isArray(script.envs ?? [])} + {#each script.envs ?? [] as v, i} +
+ + +
+ {/each} + {/if} +
+
+ +
+
+ {/if} +
+ + + + + + { + captureTable?.loadCaptures(true) + }} + {initialPath} + schema={script.schema} + noEditor={true} + isFlow={false} + currentPath={script.path} + hash={script.parent_hash} + newItem={initialPath == ''} + canHavePreprocessor={script.language === 'bun' || + script.language === 'deno' || + script.language === 'python3'} + {hasPreprocessor} + /> + + +
+ + + diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index a61402dd0d..9430bc852d 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -118,6 +118,7 @@ selectedTab === 'preprocessor' ? { _ENTRYPOINT_OVERRIDE: 'preprocessor', ...args } : args, tag ) + setFocusToLogs() } async function loadPastTests(): Promise { @@ -249,6 +250,14 @@ $: selectedTab && inferSchema(code) let argsRender = 0 + export async function updateArgs(newArgs: Record) { + if (Object.keys(newArgs).length > 0) { + args = { ...newArgs } + argsRender++ + } + } + + let setFocusToLogs = () => {} -
+
{#key argsRender} - + diff --git a/frontend/src/lib/components/Section.svelte b/frontend/src/lib/components/Section.svelte index 1889fc708a..8a67bf9432 100644 --- a/frontend/src/lib/components/Section.svelte +++ b/frontend/src/lib/components/Section.svelte @@ -14,7 +14,7 @@ export let headless: boolean = false -
+
{#if !headless}

{/if} -
- -
+ {#if !collapsable || !collapsed} +
+ +
+ {/if}

diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index 18a42d8408..21989e362c 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -73,6 +73,7 @@ export let formatAction: (() => void) | undefined = undefined export let automaticLayout = true export let extraLib: string = '' + export let placeholder: string = '' export let shouldBindKey: boolean = true export let autoHeight = false @@ -106,6 +107,12 @@ } } + let placeholderVisible = false + function updatePlaceholderVisibility(value: string) { + if (!value) return + placeholderVisible = value.trim() === '' + } + export function format() { if (editor) { code = getCode() @@ -302,6 +309,13 @@ $tailwindClassesLoaded = true addTailwindClassCompletions() } + + if (placeholder) { + editor.onDidChangeModelContent(() => { + const value = editor.getValue() + updatePlaceholderVisibility(value) + }) + } } function addCSSClassCompletions() { @@ -417,6 +431,8 @@ editor.focus() } } + + updatePlaceholderVisibility(code) @@ -431,9 +447,20 @@ {/if}
+> + {#if placeholder} +
+ {@html placeholder} +
+ {/if} +
{#if allowVim && $vimMode}
{/if} diff --git a/frontend/src/lib/components/StringTypeNarrowing.svelte b/frontend/src/lib/components/StringTypeNarrowing.svelte index 4405bc2fb6..b960653b64 100644 --- a/frontend/src/lib/components/StringTypeNarrowing.svelte +++ b/frontend/src/lib/components/StringTypeNarrowing.svelte @@ -108,9 +108,11 @@ } -
+
{#if allowKindChange} { if (e.detail != 'enum') { diff --git a/frontend/src/lib/components/common/button/Button.svelte b/frontend/src/lib/components/common/button/Button.svelte index 1fef9435a0..264a56dc00 100644 --- a/frontend/src/lib/components/common/button/Button.svelte +++ b/frontend/src/lib/components/common/button/Button.svelte @@ -59,70 +59,6 @@ const dispatch = createEventDispatcher() // Order of classes: border, border modifier, bg, bg modifier, text, text modifier, everything else - const colorVariants: Record> = { - none: { - border: '', - contained: '', - divider: '' - }, - - blue: { - border: - 'border-frost-500 dark:border-frost-300 hover:border-frost-700 dark:hover:border-frost-400 focus-visible:border-frost-700 bg-surface hover:bg-frost-100 dark:hover:bg-frost-900 focus-visible:bg-frost-100 focus-visible:dark:text-frost-100 dark:focus-visible:bg-frost-900 text-frost-500 dark:text-frost-300 dark:hover:text-frost-400 hover:text-frost-700 focus-visible:text-frost-700 focus-visible:ring-frost-300', - contained: - 'bg-frost-500 hover:bg-frost-700 focus-visible:bg-frost-700 text-white focus-visible:ring-frost-300 dark:bg-frost-500/90 dark:hover:bg-frost-600/90', - divider: 'divide-x divide-frost-600' - }, - marine: { - border: - 'border-marine-300 dark:border-marine-200 hover:border-marine-500 dark:hover:border-marine-400 focus-visible:border-marine-500 bg-surface hover:bg-marine-500 dark:hover:bg-marine-400 focus-visible:bg-marine-100 focus-visible:dark:text-marine-50 dark:focus-visible:bg-marine-500 text-marine-50 dark:text-marine-50 dark:hover:text-marine-50 hover:text-marine-50 focus-visible:text-marine-300 focus-visible:ring-marine-200', - contained: - 'bg-marine-300 hover:bg-marine-500 focus-visible:bg-marine-500 text-gray-50 focus-visible:ring-marine-200 dark:bg-marine-200 dark:hover:bg-marine-400', - divider: 'divide-x divide-marine-500' - }, - red: { - border: - 'border-red-600/60 hover:border-red-600 bg-surface hover:bg-red-100 text-red-600 hover:text-red-700 focus-visible:ring-red-300 dark:border-red-400/90 dark:text-red-400 dark:hover:border-red-400 dark:hover:bg-red-500/60 dark:hover:text-red-100', - contained: - 'bg-red-600 hover:bg-red-600 text-white focus-visible:ring-red-300 dark:border-red-400/70 dark:bg-red-700/90 dark:hover:bg-red-900 dark:hover:border-red-300 dark:text-primary', - divider: 'divide-x divide-red-700' - }, - green: { - border: - 'border-green-600 hover:border-green-700 bg-surface hover:bg-green-100 text-green-600 hover:text-green-700 focus-visible:ring-green-300 dark:hover:bg-green-800 dark:border-green-400/90', - contained: - 'bg-green-600 hover:bg-green-700 text-white focus-visible:ring-green-300 dark:bg-green-700/90 dark:hover:bg-green-800', - divider: 'divide-x divide-green-700' - }, - dark: { - border: - 'border-marine-300 bg-surface hover:bg-surface-hover focus-visible:bg-surface-hover text-primary hover:text-secondary focus-visible:text-secondary focus-visible:ring-surface-selected-inverse dark:border-marine-200', - contained: - 'bg-marine-400 hover:bg-marine-200 focus-visible:bg-surface-hover-inverse text-primary-inverse focus-visible:ring-surface-selected-inverse dark:bg-marine-50 dark:hover:bg-marine-50/70 dark:text-primary-inverse', - divider: 'divide-x divide-gray-800 dark:divide-gray-200' - }, - gray: { - border: - 'border-gray-500 dark:border-gray-300 hover:border-gray-700 dark:hover:border-gray-400 focus-visible:border-gray-700 bg-surface hover:bg-gray-100 dark:hover:bg-gray-900 focus-visible:bg-gray-100 focus-visible:dark:text-gray-100 dark:focus-visible:bg-gray-900 text-gray-500 dark:text-gray-300 dark:hover:text-gray-400 hover:text-gray-700 focus-visible:text-gray-700 focus-visible:ring-gray-300', - contained: - 'bg-gray-500 hover:bg-gray-700 focus-visible:bg-gray-700 text-white focus-visible:ring-gray-300', - divider: 'divide-x divide-gray-600' - }, - light: { - border: - 'border bg-surface hover:bg-surface-hover focus-visible:bg-surface-hover text-primary hover:text-secondary focus-visible:text-secondary focus-visible:ring-surface-selected', - contained: - 'bg-surface border-transparent hover:bg-surface-hover focus-visible:bg-surface-hover text-primary focus-visible:ring-surface-selected', - divider: 'divide-x divide-gray-200 dark:divide-gray-700' - }, - nord: { - border: - 'border-nord-200 bg-surface hover:bg-surface-hover focus-visible:bg-surface-hover text-primary hover:text-secondary focus-visible:text-secondary focus-visible:ring-surface-selected-inverse dark:border-nord-200', - contained: - 'bg-nord-300 hover:bg-nord-0 focus-visible:bg-surface-hover-inverse text-primary-inverse focus-visible:ring-surface-selected-inverse dark:bg-nord-400 dark:hover:bg-nord-600 dark:text-primary-inverse', - divider: 'divide-x divide-gray-800 dark:divide-gray-200' - } - } async function onClick(event: MouseEvent) { if (!nonCaptureEvent) { @@ -136,8 +72,8 @@ } function getColorClass(color, variant) { - if (color in colorVariants) { - return colorVariants[color][variant] + if (color in ButtonType.ColorVariants) { + return ButtonType.ColorVariants[color][variant] } else { return color } @@ -182,9 +118,14 @@