mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 00:04:10 +00:00
Merge remote-tracking branch 'origin/main' into glm/add-local-dev-proxy-v2
# Conflicts: # cli/src/commands/dev/dev.ts # cli/src/commands/sync/sync.ts # cli/src/guidance/writer.ts
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
---
|
||||
name: adding-a-trigger
|
||||
description: Checklist for adding a new TriggerCrud-based trigger type to Windmill (Azure, GCP, Kafka, etc.). Use when wiring a new trigger kind across backend, frontend, CLI, and capture infrastructure.
|
||||
---
|
||||
|
||||
# Skill: Adding a New Trigger Type
|
||||
|
||||
Use this skill when adding a trigger kind that implements `TriggerCrud` (Kafka, GCP, Azure, MQTT, SQS, NATS, Postgres, Email…). For native triggers (Nextcloud, Google Drive — things wired through `windmill-native-triggers`), use the `native-trigger` skill instead.
|
||||
|
||||
The goal of this doc is to enumerate every file that needs to change. Missing any one of them leads to silent regressions: sync drops the trigger, capture button does nothing, workspace forks lose it, sidebar counters undercount. Follow the checklist top-to-bottom — each section is independent enough to be validated on its own.
|
||||
|
||||
Throughout this doc, substitute `{kind}` for the new trigger kind (`azure`, `kafka`, …), `{Kind}` for PascalCase (`Azure`, `Kafka`), `{KIND}` for SCREAMING (`AZURE`, `KAFKA`).
|
||||
|
||||
## Reference implementations
|
||||
|
||||
- **GCP** — closest analogue to Azure. Has push + pull, OIDC auth, ARM-like resource paths, capture handler. Grep for `gcp_trigger` / `GcpTrigger`.
|
||||
- **Kafka** — simpler (pull-only, streaming). Good for trivial integrations.
|
||||
- **Azure** — most recently added (2026). Shared-secret push auth, Event Grid namespaces + basic topics, ARM resource discovery, Namespace-pull data-plane. Grep for `azure_trigger` / `AzureTrigger`.
|
||||
|
||||
## 1. Database migration
|
||||
|
||||
Create a migration: `cargo sqlx migrate add -r add_{kind}_trigger` from `backend/`. Never write timestamps manually.
|
||||
|
||||
The `up.sql` usually defines:
|
||||
- An optional enum type (e.g. `AZURE_MODE`) if the trigger has sub-kinds
|
||||
- The `{kind}_trigger` table with at minimum these columns (mirrored from kafka/gcp):
|
||||
- primary: `(workspace_id, path)`
|
||||
- `script_path`, `is_flow`, `enabled`, `mode`, `permissioned_as`, `edited_by`, `email`
|
||||
- `edited_at`, `error`, `server_id`, `last_server_ping`
|
||||
- `error_handler_path`, `error_handler_args jsonb`, `retry jsonb`
|
||||
- trigger-specific fields
|
||||
- Indexes on foreign keys + any frequently-filtered columns
|
||||
- Foreign key to `workspace`
|
||||
|
||||
Down migration drops the table and any enum types.
|
||||
|
||||
## 2. Backend crate (`windmill-trigger-{kind}`)
|
||||
|
||||
Create a new crate under `backend/windmill-trigger-{kind}/` with:
|
||||
|
||||
- `Cargo.toml`: features `enterprise`, `private` if EE, standard deps
|
||||
- `src/lib.rs`: `pub use mod_ee::*;` behind `#[cfg(all(feature = "enterprise", feature = "private"))]`
|
||||
- `src/mod_ee.rs`: core types + helpers
|
||||
- `src/handler_ee.rs`: `TriggerCrud` impl + route handlers
|
||||
- `src/listener_ee.rs`: (only if streaming/pull-based) `Listener` trait impl
|
||||
|
||||
Required in `mod_ee.rs`:
|
||||
- `{Kind}Config` struct (persisted shape, `FromRow`)
|
||||
- `{Kind}ConfigRequest` struct (what API receives — usually similar to Config but with validation fields)
|
||||
- `{Kind}Trigger` unit struct (implements the traits)
|
||||
- `impl TriggerJobArgs for {Kind}Trigger` — sets `TRIGGER_KIND`, `Payload`, `v1_payload_fn`
|
||||
|
||||
Required in `handler_ee.rs`:
|
||||
- `#[async_trait] impl TriggerCrud for {Kind}Trigger` with:
|
||||
- `type Trigger = Trigger<{Kind}Config>`
|
||||
- `type TriggerConfigRequest = {Kind}ConfigRequest`
|
||||
- `const ROUTE_PREFIX: &'static str = "/{kind}_triggers";`
|
||||
- `const TABLE_NAME`, `ADDITIONAL_SELECT_FIELDS`
|
||||
- `get_deployed_object`, `validate_config`, `create_trigger`, `update_trigger`, `delete_trigger`, `test_connection`
|
||||
- `additional_routes` (optional — mount extra endpoints for things like ARM resource listing, topic discovery)
|
||||
|
||||
Register the crate in `backend/Cargo.toml` as a workspace member and as a dep of `windmill-api` behind the feature flag.
|
||||
|
||||
## 3. Wire into `windmill-api` (feature-gated everywhere)
|
||||
|
||||
**`backend/windmill-api/src/triggers/handler.rs`** — mount the trigger crate:
|
||||
```rust
|
||||
#[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))]
|
||||
{
|
||||
use crate::triggers::{kind}::{Kind}Trigger;
|
||||
router = router.nest({Kind}Trigger::ROUTE_PREFIX, complete_trigger_routes({Kind}Trigger));
|
||||
}
|
||||
```
|
||||
|
||||
**`backend/windmill-api/src/triggers/{kind}/mod.rs`** — re-export the crate:
|
||||
```rust
|
||||
pub use windmill_trigger_{kind}::*;
|
||||
```
|
||||
|
||||
**`backend/windmill-api/src/lib.rs`** — if the trigger receives inbound pushes, add a webhook route:
|
||||
```rust
|
||||
.nest("/{kind}/w/{workspace_id}", {
|
||||
#[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))]
|
||||
{ triggers::{kind}::handler_oss::{kind}_push_route_handler() }
|
||||
#[cfg(not(...))]
|
||||
{ Router::new() }
|
||||
})
|
||||
```
|
||||
|
||||
## 4. `TriggerKind` enum (`backend/windmill-types/src/triggers.rs`)
|
||||
|
||||
Already has slots for most triggers but verify your variant exists:
|
||||
- Add `{Kind}` to the `TriggerKind` enum
|
||||
- Add match arm in `to_key()`
|
||||
- Add match arm in `from_str`
|
||||
- Add match arm in `JobTriggerKind` (if jobs need kind tagging)
|
||||
|
||||
## 5. OpenAPI (`backend/windmill-api/openapi.yaml`)
|
||||
|
||||
This file is huge and the single most-forgotten place. Add:
|
||||
|
||||
- `/w/{workspace}/{kind}_triggers/create` + `/update/{path}` + `/delete/{path}` + `/get/{path}` + `/list` + `/exists/{path}` + `/setmode/{path}` + `/test` paths (mirror gcp section)
|
||||
- Any `additional_routes` your handler exposes (resource discovery, etc.)
|
||||
- Schemas: `{Kind}Trigger`, `{Kind}TriggerData`, `{Kind}Mode` (if enum), `{Kind}DeliveryConfig`, helper request/response types
|
||||
- Add `{kind}` to `CaptureTriggerKind` enum
|
||||
- Add `{kind}_used: boolean` to the `UsedTriggers` response schema
|
||||
|
||||
Regenerate frontend client: `npm run generate-backend-client` from `frontend/`.
|
||||
|
||||
## 6. `UsedTriggers` + workspace export
|
||||
|
||||
**`backend/windmill-api-workspaces/src/workspaces.rs`** — add `{kind}_used: bool` to the `UsedTriggers` struct and add an `EXISTS(SELECT 1 FROM {kind}_trigger …)` to the `get_used_triggers` query.
|
||||
|
||||
**`backend/windmill-api/src/workspaces_export.rs`** — add export block mirroring gcp's (export lists all triggers, serializes them to YAML/JSON).
|
||||
|
||||
## 6.5 Hardcoded trigger-kind arrays (silent-failure hotspots)
|
||||
|
||||
Several files keep **hardcoded arrays** of trigger kind strings. Miss one and ACL checks / user offboarding / trash drop your kind:
|
||||
|
||||
- **`backend/windmill-api-groups/src/granular_acls.rs`** — `KINDS: [&str; N]`. **Increment N** (the compile error is cryptic otherwise). Controls which kinds accept granular ACL operations.
|
||||
- **`backend/windmill-api-users/src/users.rs`** (`extra_perms_tables`) — which tables get `extra_perms` entries cleaned when a user is deleted.
|
||||
- **`backend/windmill-api/src/offboarding.rs`** — three separate arrays (enumeration, fork-copy, and delete paths). **All three** need the new kind.
|
||||
- **`backend/windmill-api/src/trash.rs`** — `valid_tables` for the trash / restore API.
|
||||
- **`backend/windmill-git-sync/src/lib.rs`** — add a test assertion for `DeployedObject::{Kind}Trigger.get_kind() == "{kind}_trigger"` (the `get_kind` match arm itself lives in the enum impl — already required by the Rust compiler).
|
||||
- **`backend/windmill-api-auth/src/scopes.rs`** — add the `{Kind}Triggers` variant to `ScopeDomain` enum + `as_str` match + `from_str` match. Required for the OAuth/token system to recognise `{kind}_triggers:read|write` scopes.
|
||||
- **`backend/windmill-api/src/token.rs`** (`build_trigger_scope_domains` → `TRIGGER_DOMAINS`) — add `("{kind}_triggers", "{Kind display name}")` so the CreateToken UI's scope selector surfaces the `read` / `write` checkboxes.
|
||||
|
||||
**OpenAPI enums** to extend (do NOT forget — generated client will allow it but server rejects as 400):
|
||||
- `CaptureTriggerKind` enum
|
||||
- Three `kind` enums under `/w/{workspace}/acls/{get,add,remove}/{kind}/{path}` (yes, same list repeated three times)
|
||||
|
||||
After editing any of these, run a full `cargo check` with your feature flag + `gcp_trigger` + other core flags — the `KINDS: [&str; N]` length mismatch only surfaces when the crate compiles.
|
||||
|
||||
## 7. Capture infrastructure (`backend/windmill-api/src/capture.rs`)
|
||||
|
||||
If the trigger supports push delivery, it also needs a capture endpoint so users can test it:
|
||||
|
||||
- `{Kind}TriggerConfig` struct (gated by feature flags)
|
||||
- `TriggerConfig::{Kind}` variant
|
||||
- `set_{kind}_trigger_config` function (creates the subscription/equivalent pointing at the capture URL — use your `manage_{kind}_subscription` helper with `trigger_mode=false`)
|
||||
- Both real + no-op versions behind feature gates
|
||||
- `TriggerKind::{Kind} => set_{kind}_trigger_config(...)` arm in `set_config`
|
||||
- `{kind}_payload` async handler — validates auth (if any), processes payload, calls `insert_capture_payload`
|
||||
- Route: `.route("/{kind}/{runnable_kind}/{*path}", post({kind}_payload))` inside `workspaced_unauthed_service` — and expand the surrounding `#[cfg(any(...))]` to include your feature flag
|
||||
|
||||
## 8. CLI (`cli/`) — easy to miss, breaks sync silently
|
||||
|
||||
Check all of these:
|
||||
|
||||
**`cli/src/types.ts`:**
|
||||
- Add `"{kind}"` to `TRIGGER_TYPES` array
|
||||
- Add `"{kind}_trigger"` to `getTypeStrFromPath` return union
|
||||
- Add match case in `getTypeStrFromPath`'s `typeEnding ===` chain
|
||||
- Add `pushTrigger("{kind}", ...)` branch in `pushObj`
|
||||
|
||||
**`cli/src/commands/trigger/trigger.ts`:**
|
||||
- Import `{Kind}Trigger` type
|
||||
- Add `{kind}: {Kind}Trigger` to the `Trigger` type map
|
||||
- Add `{kind}: wmill.get{Kind}Trigger`, `update{Kind}Trigger`, `create{Kind}Trigger` to each function map
|
||||
- Add `{kind}: { ... }` template to `triggerTemplates`
|
||||
- Add `list{Kind}Triggers` call + spread in the `list` aggregation
|
||||
- Update `--kind` option descriptions to mention the new kind
|
||||
|
||||
**`cli/src/commands/sync/sync.ts`:**
|
||||
- Add `path.endsWith(".{kind}_trigger" + ext)` in the file-type filter
|
||||
- Add `typ == "{kind}_trigger"` in `getTypeOrder`
|
||||
- Add `"{kind}_trigger"` to the delete-suffix regex (~line 3092)
|
||||
- Add a `case "{kind}_trigger"` in the delete switch
|
||||
|
||||
**`cli/src/guidance/skills.ts`** — **DO NOT EDIT DIRECTLY**. It's auto-generated by `system_prompts/generate.py`. Instead:
|
||||
- Edit `system_prompts/utils.py` → append `('{Kind}Trigger', '{kind}_trigger')` to the `SCHEMA_MAPPINGS['triggers']` list (this is the master list — the one in `generate.py` is duplicated and `utils.py` wins)
|
||||
- Then run `python3 system_prompts/generate.py` — it regenerates `cli/src/guidance/skills.ts` with the schema extracted from `backend/windmill-api/openapi.yaml`
|
||||
- Commit the regenerated file
|
||||
|
||||
## 9. Frontend — editor + drawer
|
||||
|
||||
Under `frontend/src/lib/components/triggers/{kind}/`:
|
||||
|
||||
- `{Kind}TriggerPanel.svelte` — the tile shown in the triggers listing
|
||||
- `{Kind}TriggerEditor.svelte` — outer drawer wrapper
|
||||
- `{Kind}TriggerEditorInner.svelte` — state + business logic; must expose:
|
||||
- `openEdit(path, isFlow, defaultValues?)` method
|
||||
- `isEditor` prop, `onConfigChange` + `onCaptureConfigChange` callbacks
|
||||
- `get{Kind}Config()` + `get{Kind}CaptureConfig()` helpers
|
||||
- `captureConfig = $derived.by(untrack(() => isEditor) ? get{Kind}CaptureConfig : () => ({}))`
|
||||
- `$effect(() => { const args = [captureConfig, isValid] as const; untrack(() => onCaptureConfigChange?.(...args)) })`
|
||||
- `{Kind}TriggerEditorConfigSection.svelte` — form fields; use design-system components (`TextInput`, `Select`, `Toggle`, `ToggleButtonGroup`), never raw `<input>`
|
||||
- `{Kind}Capture.svelte` — capture panel; wraps `CaptureSection` with `captureType="{kind}"`
|
||||
- `utils.ts` — `requestBody` builders and any trigger-type-specific helpers
|
||||
|
||||
## 10. Frontend — global integration
|
||||
|
||||
Easy to miss:
|
||||
|
||||
- **`frontend/src/lib/components/triggers.ts`** — add `'{kind}'` to the `TriggerKind` union
|
||||
- **`frontend/src/lib/components/triggers/CaptureWrapper.svelte`**:
|
||||
- Import `{Kind}Capture`
|
||||
- Add to `isStreamingCapture()` array (streaming = pull-style; push-style is typically `false`)
|
||||
- Add `{:else if captureType === '{kind}'}` branch with the `<{Kind}Capture>` render
|
||||
- **`frontend/src/lib/components/sidebar/SidebarContent.svelte`** — import the icon, add the nav entry
|
||||
- **`frontend/src/lib/components/sidebar/OperatorMenu.svelte`** — add the operator-mode entry
|
||||
- **`frontend/src/routes/(root)/(logged)/+layout.svelte`** — destructure `{kind}_used` from `/get_used_triggers` response, push `'{kind}'` into `usedKinds`
|
||||
- **`frontend/src/lib/components/search/GlobalSearchModal.svelte`** — import icon, add "Go to {Kind} ..." entry
|
||||
- **`frontend/src/lib/components/offboarding-utils.ts`** — add mappings `{kind}_trigger: '{kind}_triggers'` and `{kind}_trigger: '{kind} trigger'`
|
||||
- **`frontend/src/lib/components/icons/{Kind}Icon.svelte`** — single-path SVG, `fill={color ?? 'currentColor'}`, `size` prop default 16 (match existing icons — don't hardcode colors, don't use `width`/`height` props)
|
||||
- **`frontend/src/routes/(root)/(logged)/{kind}_triggers/+page.svelte`** — listing page (mirror `gcp_triggers/+page.svelte` for push+pull, `kafka_triggers` for pure streaming)
|
||||
- **`frontend/src/lib/components/CompareWorkspaces.svelte`** — workspace fork / compare tool. Needs: service import, editor import, `{kind}Editor` `$state`, `case '{kind}'` in `openTriggerDetails()`, entry in `triggerServices` object (list/delete/normalize), and `<{Kind}TriggerEditor bind:this={{kind}Editor} />` in the template
|
||||
|
||||
## 10.5 AI system prompts (`system_prompts/`)
|
||||
|
||||
- **`system_prompts/utils.py`** — append `('{Kind}Trigger', '{kind}_trigger')` to `SCHEMA_MAPPINGS['triggers']` (master list used by code generation + CLI skills)
|
||||
- **`system_prompts/generate.py`** — also has a duplicated `schema_types` list (~line 903) for the AI `triggers` skill content. Add `('{Kind}Trigger', '{kind}_trigger')` there too
|
||||
- **`system_prompts/generate.py`** `schema_names` (~line 1192) — add `'{Kind}Trigger'` (add `'New{Kind}Trigger'` only if the OpenAPI declares one; GCP and Azure don't)
|
||||
- Run `python3 system_prompts/generate.py` — this rewrites `cli/src/guidance/skills.ts` and all `auto-generated/` docs. Commit the regenerated files
|
||||
|
||||
## 11. Validation
|
||||
|
||||
Run all of these before declaring done:
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
cd backend
|
||||
cargo check --features enterprise,{kind}_trigger,private # minimal
|
||||
cargo check --features enterprise,azure_trigger,private,gcp_trigger,http_trigger,mqtt_trigger,postgres_trigger,sqs_trigger,kafka,nats,smtp,websocket # full
|
||||
|
||||
# SQLx offline data (never run `cargo sqlx prepare` directly — use the wrapper)
|
||||
./update_sqlx.sh
|
||||
|
||||
# Frontend
|
||||
cd frontend
|
||||
npm run generate-backend-client
|
||||
npm run check:fast
|
||||
```
|
||||
|
||||
Smoke test in the UI: create a trigger, save, check it appears in sidebar + search, delete, re-create via CLI `wmill sync`.
|
||||
|
||||
## 12. Common pitfalls
|
||||
|
||||
- **Forgetting feature gates in `workspaced_unauthed_service()`** — the surrounding `#[cfg(any(...))]` expression must include your feature flag, not just the inner `#[cfg]` on the route
|
||||
- **`.route(path, ...).route(path, ...)` with same path and different methods** — older axum replaced; use `.route(path, post(h1).options(h2))` to chain methods on the same `MethodRouter`
|
||||
- **`on:event` directives** — legacy Svelte 4, no-op in runes mode. Use callback props (`onSelected`, `onConfigChange`)
|
||||
- **`$bindable(default_value)` on optional props** — banned by project CLAUDE.md. Use `$bindable()` + `$derived(prop ?? default)` instead
|
||||
- **CORS layer intercepting OPTIONS** — tower-http CorsLayer short-circuits OPTIONS before reaching your handler. For server-to-server webhook endpoints, drop the CORS layer entirely (CORS is browser-only)
|
||||
- **DeliveryAttributeMappings / custom headers for auth** — prefer HMAC or sha256-hashed shared secrets over opaque JWTs when the provider doesn't support signed tokens natively. Store only the hash; regenerate secret on every save
|
||||
- **ARM / API resource-listing cascades** — if the trigger's resource type is deep (Azure: subscription → RG → namespace → topic), offer dropdowns in the UI populated from the provider's APIs using the user's credential resource
|
||||
- **Clearing stale selections on dependency change** — when a dropdown's underlying data reloads (e.g., user changes SP or edition), clear selections that no longer match the new list
|
||||
- **Workspace-scoped tag compatibility** — if the trigger has tags, verify forked workspaces handle them (see commit `0773b5bc85` for a historical fix)
|
||||
|
||||
## 13. EE file split
|
||||
|
||||
If the trigger is enterprise-only, the code lives in `windmill-ee-private__worktrees/.../windmill-trigger-{kind}/src/*_ee.rs` and is symlinked into the OSS tree. The `windmill-ee-private__worktrees/` directory holds the real files; changes propagate via symlinks. See `docs/enterprise.md` for the workflow.
|
||||
|
||||
## 14. Final checklist before PR
|
||||
|
||||
- [ ] Migration up/down tested (revert + re-apply)
|
||||
- [ ] `./update_sqlx.sh` committed the updated `.sqlx/` offline data
|
||||
- [ ] `cargo check` passes with your feature flag + with all trigger features
|
||||
- [ ] `npm run check:fast` passes
|
||||
- [ ] Trigger visible in sidebar with correct icon weight (not oversized/colored — use `currentColor`)
|
||||
- [ ] Create, edit, delete flow all work in the UI
|
||||
- [ ] Capture button works (if push-capable)
|
||||
- [ ] Trigger appears in `/get_used_triggers` → sidebar pulse
|
||||
- [ ] `wmill sync pull` + `wmill sync push` both round-trip the trigger
|
||||
- [ ] `wmill trigger list` includes it
|
||||
- [ ] OpenAPI schemas are complete (no `null` in generated types)
|
||||
@@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## [1.690.0](https://github.com/windmill-labs/windmill/compare/v1.689.0...v1.690.0) (2026-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ai agent conversation output control ([#8915](https://github.com/windmill-labs/windmill/issues/8915)) ([9a60ff2](https://github.com/windmill-labs/windmill/commit/9a60ff2e77f197786f523755c3a9286a178a245c))
|
||||
* add Azure Event Grid triggers ([#8888](https://github.com/windmill-labs/windmill/issues/8888)) ([d6c642b](https://github.com/windmill-labs/windmill/commit/d6c642b170b9547fe1d8db190affa35b305c9c8a))
|
||||
* add OTEL_HOST_NAME env override for host.name attribute ([#8923](https://github.com/windmill-labs/windmill/issues/8923)) ([f429cb5](https://github.com/windmill-labs/windmill/commit/f429cb5e486aa5d2bd37f84c1fb30b9d350909e4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** use wmill.yaml key consistently for workspace-specific items ([#8900](https://github.com/windmill-labs/windmill/issues/8900)) ([1722a7a](https://github.com/windmill-labs/windmill/commit/1722a7a2af5e00beeae204b78e588cd74a3ceb39))
|
||||
* correct flow conversation pagination ([#8919](https://github.com/windmill-labs/windmill/issues/8919)) ([7fa924e](https://github.com/windmill-labs/windmill/commit/7fa924e67e212458726a839dbe366798b2709cd6))
|
||||
* ensure schema is inferred on script/flow module load ([#8927](https://github.com/windmill-labs/windmill/issues/8927)) ([664d0f8](https://github.com/windmill-labs/windmill/commit/664d0f838d168978d7c27e88d2bb9019531e7ea1))
|
||||
* include endpoint descriptions in mcp tools ([#8925](https://github.com/windmill-labs/windmill/issues/8925)) ([07951e8](https://github.com/windmill-labs/windmill/commit/07951e81ae9a1c26e8fe63bcd7a760b80500ca4c))
|
||||
* load job metadata on approval page via approval token ([#8924](https://github.com/windmill-labs/windmill/issues/8924)) ([dac29e7](https://github.com/windmill-labs/windmill/commit/dac29e7d23d6e980c2b6fc4dd3a05a0d2e0170b3))
|
||||
* slim app ai chat context ([#8922](https://github.com/windmill-labs/windmill/issues/8922)) ([132d8a6](https://github.com/windmill-labs/windmill/commit/132d8a61f9c109b2b447fab1a39565f52864b746))
|
||||
|
||||
## [1.689.0](https://github.com/windmill-labs/windmill/compare/v1.688.0...v1.689.0) (2026-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add s3 stream progress logs to other DB executors ([#8898](https://github.com/windmill-labs/windmill/issues/8898)) ([2d4fadb](https://github.com/windmill-labs/windmill/commit/2d4fadb590590837412d638192fbd62bdc9331e8))
|
||||
* allow hiding catalog picker and raw input on s3 form fields ([#8902](https://github.com/windmill-labs/windmill/issues/8902)) ([05baa4a](https://github.com/windmill-labs/windmill/commit/05baa4ab026a307267d11fb827f8abcc246d1ac0))
|
||||
* async dep endpoints and queue-position logs in cli ([#8895](https://github.com/windmill-labs/windmill/issues/8895)) ([aaf3a19](https://github.com/windmill-labs/windmill/commit/aaf3a1974746be451adf463fd1f0e584b2fa995e))
|
||||
* auto-strip UTF-8 BOM when reading local files in CLI ([#8911](https://github.com/windmill-labs/windmill/issues/8911)) ([99bc96d](https://github.com/windmill-labs/windmill/commit/99bc96d0b231a2af303b28aa87d5de9141ee5cab))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add aws-config to private feature to restore ce build ([18eed92](https://github.com/windmill-labs/windmill/commit/18eed92dd66d635305a72818e2fcf0ee8b9672cc))
|
||||
* add flow conversation token scope ([#8903](https://github.com/windmill-labs/windmill/issues/8903)) ([aea7444](https://github.com/windmill-labs/windmill/commit/aea74445a31d30abb8030763db91e1829db772f0))
|
||||
* add proxy eval coverage for gemini schemas ([#8897](https://github.com/windmill-labs/windmill/issues/8897)) ([fddd8e2](https://github.com/windmill-labs/windmill/commit/fddd8e288fc0fd7af3b1df1ddd4476fb57694ed3))
|
||||
* apply powershell workspace dependencies to deployed scripts ([#8912](https://github.com/windmill-labs/windmill/issues/8912)) ([dc89673](https://github.com/windmill-labs/windmill/commit/dc896737ac1dcd90ab96314b2bc2f044ff833b8a))
|
||||
* detect and clearly label OOM in zombie flow alerts ([#8901](https://github.com/windmill-labs/windmill/issues/8901)) ([680c711](https://github.com/windmill-labs/windmill/commit/680c711f9262683c046a78532b22be5f5a4121a8))
|
||||
* omit default_permissioned_as from tarball export when empty ([bbb564c](https://github.com/windmill-labs/windmill/commit/bbb564c1420593014d17f352ba38d3ed38c248e1))
|
||||
* persist flow groups from AI chat tool calls ([#8906](https://github.com/windmill-labs/windmill/issues/8906)) ([932d183](https://github.com/windmill-labs/windmill/commit/932d18331196ef3e87c45d8a06ae45ac8013bd7a))
|
||||
* push parent resource on fileset child add/delete ([#8910](https://github.com/windmill-labs/windmill/issues/8910)) ([f29badc](https://github.com/windmill-labs/windmill/commit/f29badcf368e7c712f1515fa30a6a0e179a4bdc5))
|
||||
* rust nsjail RUSTUP_HOME mount and arch-aware cache keys ([#8890](https://github.com/windmill-labs/windmill/issues/8890)) ([f8c916c](https://github.com/windmill-labs/windmill/commit/f8c916cb6073f5566289c395ec39ec3919f449e7))
|
||||
* skip opus 4.7 sampling params ([#8904](https://github.com/windmill-labs/windmill/issues/8904)) ([1e83278](https://github.com/windmill-labs/windmill/commit/1e83278fe2ef5a5c6959a9351e7286d9dbf2453a))
|
||||
* support windmill chat answer override ([#8909](https://github.com/windmill-labs/windmill/issues/8909)) ([eeb5d12](https://github.com/windmill-labs/windmill/commit/eeb5d12be3ba2aedf2ebc4843d4395e241ecc8d3))
|
||||
* track dollar-quoted strings in SQL block splitter ([#8891](https://github.com/windmill-labs/windmill/issues/8891)) ([53badf1](https://github.com/windmill-labs/windmill/commit/53badf1a8cff576bb4ccbc75f045efb457b6a07d))
|
||||
* trigger failure_module when branchone predicate throws ([#8905](https://github.com/windmill-labs/windmill/issues/8905)) ([dffb89e](https://github.com/windmill-labs/windmill/commit/dffb89e00632bd4e7bbe9998bccb1f14357d9c07)), closes [#8889](https://github.com/windmill-labs/windmill/issues/8889)
|
||||
|
||||
## [1.688.0](https://github.com/windmill-labs/windmill/compare/v1.687.0...v1.688.0) (2026-04-20)
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -43,7 +43,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at, created_seq, step_name, success\n FROM (\n SELECT id, conversation_id, message_type, content, job_id, created_at, created_seq, step_name, success\n FROM flow_conversation_message\n WHERE conversation_id = $1\n ORDER BY created_seq DESC\n LIMIT $2 OFFSET $3\n ) AS messages\n ORDER BY created_seq ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "conversation_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "message_type: MessageType",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "message_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"user",
|
||||
"assistant",
|
||||
"tool"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "content",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "job_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_seq",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "step_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "success",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1c3473a0f9f6b6148b2c975f9f05bdefedf8a51c4e6ddf0eca367b9cc778d051"
|
||||
}
|
||||
+4
-2
@@ -37,7 +37,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -73,7 +74,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -42,7 +42,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -39,7 +39,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -34,7 +34,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -74,7 +75,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3",
|
||||
"query": "SELECT id, worker_group, event_type::text, desired_workers, reason, (applied_at AT TIME ZONE 'UTC') AS \"applied_at!: chrono::DateTime<chrono::Utc>\" FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -30,8 +30,8 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "applied_at",
|
||||
"type_info": "Timestamp"
|
||||
"name": "applied_at!: chrono::DateTime<chrono::Utc>",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -47,8 +47,8 @@
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a38df5d7dc4577c715d9acdaf87c38535ad388b1948a95efafd71135cfe5e3a6"
|
||||
"hash": "5d26d8145464131740172082584b4e2f32987b67a125981e7122ae5b7b88fa46"
|
||||
}
|
||||
+2
-1
@@ -37,7 +37,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -159,7 +159,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -126,7 +126,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -43,7 +43,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+11
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\",\n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\",\n EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS \"mqtt_used!\",\n EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS \"sqs_used!\",\n EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS \"gcp_used!\",\n EXISTS(SELECT 1 FROM email_trigger WHERE workspace_id = $1) AS \"email_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'nextcloud'::native_trigger_service) AS \"nextcloud_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'google'::native_trigger_service) AS \"google_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'github'::native_trigger_service) AS \"github_used!\"\n ",
|
||||
"query": "\n SELECT\n EXISTS(SELECT 1 FROM websocket_trigger WHERE workspace_id = $1) AS \"websocket_used!\",\n EXISTS(SELECT 1 FROM http_trigger WHERE workspace_id = $1) AS \"http_routes_used!\",\n EXISTS(SELECT 1 FROM kafka_trigger WHERE workspace_id = $1) as \"kafka_used!\",\n EXISTS(SELECT 1 FROM nats_trigger WHERE workspace_id = $1) as \"nats_used!\",\n EXISTS(SELECT 1 FROM postgres_trigger WHERE workspace_id = $1) AS \"postgres_used!\",\n EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS \"mqtt_used!\",\n EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS \"sqs_used!\",\n EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS \"gcp_used!\",\n EXISTS(SELECT 1 FROM azure_trigger WHERE workspace_id = $1) AS \"azure_used!\",\n EXISTS(SELECT 1 FROM email_trigger WHERE workspace_id = $1) AS \"email_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'nextcloud'::native_trigger_service) AS \"nextcloud_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'google'::native_trigger_service) AS \"google_used!\",\n EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'github'::native_trigger_service) AS \"github_used!\"\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -45,21 +45,26 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "email_used!",
|
||||
"name": "azure_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "nextcloud_used!",
|
||||
"name": "email_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "google_used!",
|
||||
"name": "nextcloud_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "google_used!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "github_used!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -81,8 +86,9 @@
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "952f244a06950ccfc70651cb48cdfb7766f7d62d34e68e65b975aaed9e104a5a"
|
||||
"hash": "83d79dd52a708da7c0d55a171744214dc62651c8ae4ffa2be8b5aa68d8f8e791"
|
||||
}
|
||||
+2
-1
@@ -37,7 +37,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -189,7 +189,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO azure_trigger (\n azure_resource_path, azure_mode, scope_resource_id, topic_name,\n subscription_name, event_type_filters,\n push_auth_config, workspace_id, path, script_path, is_flow,\n permissioned_as, mode, edited_by, email,\n error_handler_path, error_handler_args, retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7,\n $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "azure_trigger_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"basic_push",
|
||||
"namespace_push",
|
||||
"namespace_pull"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "trigger_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"enabled",
|
||||
"disabled",
|
||||
"suspended"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ad25201d0eea65972234cade87a95d8cd99fc26e5bd466942423cbd09efcebe4"
|
||||
}
|
||||
+2
-1
@@ -164,7 +164,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -109,7 +109,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT created_at FROM flow_conversation_message WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c67e81985093ff976f1326ff2254585e850f61a787a5b8f4a8d88f88016f1f2b"
|
||||
}
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -109,7 +109,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -249,7 +249,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE azure_trigger SET\n azure_resource_path = $1,\n azure_mode = $2,\n scope_resource_id = $3,\n topic_name = $4,\n subscription_name = $5,\n event_type_filters = $6,\n push_auth_config = $7,\n is_flow = $8,\n edited_by = $9,\n permissioned_as = $10,\n script_path = $11,\n path = $12,\n mode = $13,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE workspace_id = $17 AND path = $18\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "azure_trigger_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"basic_push",
|
||||
"namespace_push",
|
||||
"namespace_pull"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "trigger_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"enabled",
|
||||
"disabled",
|
||||
"suspended"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d9ef1def7044e58722c5c69c31e9b9f6877de8bc7d4714915bbd572946d9270a"
|
||||
}
|
||||
+2
-1
@@ -189,7 +189,8 @@
|
||||
"nextcloud",
|
||||
"google",
|
||||
"ci_test",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at, step_name, success\n FROM (\n SELECT id, conversation_id, message_type, content, job_id, created_at, step_name, success\n FROM flow_conversation_message\n WHERE conversation_id = $1\n ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n LIMIT $2 OFFSET $3\n ) AS messages\n ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END\n ",
|
||||
"query": "SELECT id, conversation_id, message_type as \"message_type: MessageType\", content, job_id, created_at, created_seq, step_name, success\n FROM flow_conversation_message\n WHERE conversation_id = $1\n AND created_seq > $2\n ORDER BY created_seq ASC\n LIMIT $3\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -46,11 +46,16 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_seq",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "step_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "success",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
@@ -69,9 +74,10 @@
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a739af2f72e117acc58374f6ed44f8223efa4826d6c10639f98e624474b247a3"
|
||||
"hash": "e8802be9203c1e88a06e337260ccca029380139f89a01a89033e36a6ed9ac082"
|
||||
}
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@
|
||||
"default_email",
|
||||
"nextcloud",
|
||||
"google",
|
||||
"github"
|
||||
"github",
|
||||
"azure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n azure_resource_path,\n script_path,\n is_flow,\n mode as \"mode: _\",\n workspace_id,\n path,\n edited_by,\n permissioned_as,\n push_auth_config AS \"push_auth_config: _\",\n retry as \"retry: _\",\n error_handler_path,\n error_handler_args as \"error_handler_args: _\"\n FROM\n azure_trigger\n WHERE\n workspace_id = $1 AND\n path = $2 AND\n azure_mode IN ('basic_push'::AZURE_TRIGGER_MODE, 'namespace_push'::AZURE_TRIGGER_MODE)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "azure_resource_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "script_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "is_flow",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "mode: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "trigger_mode",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"enabled",
|
||||
"disabled",
|
||||
"suspended"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "permissioned_as",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "push_auth_config: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "retry: _",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "error_handler_path",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "error_handler_args: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "efdcdf0f8d24a23682bb3792ebaffeb7a8dc5632043b6bf4a3dd680c9b99b5df"
|
||||
}
|
||||
Generated
+572
-398
File diff suppressed because it is too large
Load Diff
+8
-4
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -27,6 +27,7 @@ members = [
|
||||
"./windmill-trigger-nats",
|
||||
"./windmill-trigger-sqs",
|
||||
"./windmill-trigger-gcp",
|
||||
"./windmill-trigger-azure",
|
||||
"./windmill-trigger-http",
|
||||
"./windmill-native-triggers",
|
||||
"./windmill-alerting",
|
||||
@@ -86,7 +87,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -142,6 +143,7 @@ mqtt_trigger = ["windmill-api/mqtt_trigger"]
|
||||
native_trigger = ["windmill-api/native_trigger"]
|
||||
sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"]
|
||||
gcp_trigger = ["windmill-api/gcp_trigger"]
|
||||
azure_trigger = ["windmill-api/azure_trigger"]
|
||||
smtp = ["windmill-api/smtp", "windmill-common/smtp", "windmill-queue/smtp"]
|
||||
license = ["windmill-api/license", "windmill-api-settings/license"]
|
||||
oauth2 = ["windmill-api/oauth2"]
|
||||
@@ -183,7 +185,7 @@ oss_core = [
|
||||
ce_core = ["oss_core", "private", "operator"]
|
||||
ee_core = [
|
||||
"enterprise", "stripe", "prometheus", "cloud",
|
||||
"kafka", "sqs_trigger", "nats", "gcp_trigger",
|
||||
"kafka", "sqs_trigger", "nats", "gcp_trigger", "azure_trigger",
|
||||
"jemalloc", "otel", "operator"
|
||||
]
|
||||
ee_server = ["enterprise_saml", "tantivy", "agent_worker_server", "local_reports"]
|
||||
@@ -197,7 +199,7 @@ ee_rhel = ["ce_core", "ee_core", "kafka-gssapi", "all_languages"]
|
||||
ee_windows = ["ce_core", "ee_core", "all_languages_windows"]
|
||||
all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embedding", "parquet", "prometheus", "flow_testing",
|
||||
"openidconnect", "cloud", "jemalloc", "tantivy", "sqlx", "kafka", "kafka-gssapi", "nats", "otel", "dind", "websocket", "http_trigger",
|
||||
"postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "smtp", "stripe",
|
||||
"postgres_trigger", "mcp", "mqtt_trigger", "sqs_trigger", "gcp_trigger", "azure_trigger", "smtp", "stripe",
|
||||
"license", "oauth2", "zip", "static_frontend", "scoped_cache", "agent_worker_server", "bedrock", "native_trigger", "quickjs",
|
||||
"windmill-git-sync/all_sqlx_features"]
|
||||
|
||||
@@ -277,6 +279,7 @@ windmill-trigger-kafka.workspace = true
|
||||
windmill-trigger-nats.workspace = true
|
||||
windmill-trigger-sqs.workspace = true
|
||||
windmill-trigger-gcp.workspace = true
|
||||
windmill-trigger-azure.workspace = true
|
||||
windmill-api-auth.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -327,6 +330,7 @@ windmill-trigger-email = { path = "./windmill-trigger-email" }
|
||||
windmill-trigger-nats = { path = "./windmill-trigger-nats" }
|
||||
windmill-trigger-sqs = { path = "./windmill-trigger-sqs" }
|
||||
windmill-trigger-gcp = { path = "./windmill-trigger-gcp" }
|
||||
windmill-trigger-azure = { path = "./windmill-trigger-azure" }
|
||||
windmill-trigger-http = { path = "./windmill-trigger-http" }
|
||||
windmill-native-triggers = { path = "./windmill-native-triggers" }
|
||||
windmill-alerting = { path = "./windmill-alerting" }
|
||||
|
||||
@@ -1 +1 @@
|
||||
2c2b8dc99689f54b8cd916fb9472fd5698b09478
|
||||
4128203739a973330599dacfb054203cf9832f3a
|
||||
@@ -384,6 +384,19 @@ def schema_to_rust_value(schema: Optional[Dict[str, Any]]) -> str:
|
||||
return "None"
|
||||
return f"Some(serde_json::json!({json.dumps(schema, indent=8)}))"
|
||||
|
||||
def build_tool_description(operation: Dict[str, Any], method: str, path: str) -> str:
|
||||
"""Build the MCP tool description from OpenAPI summary and description."""
|
||||
summary = operation.get('summary', '').strip()
|
||||
description = operation.get('description', '').strip()
|
||||
|
||||
if summary and description:
|
||||
return f"{summary}: {description}".rstrip('.!? ')
|
||||
if summary:
|
||||
return summary
|
||||
if description:
|
||||
return description.rstrip('.!? ')
|
||||
return f'{method.upper()} {path}'
|
||||
|
||||
def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Find all endpoints marked with x-mcp-tool: true."""
|
||||
tools = []
|
||||
@@ -395,7 +408,7 @@ def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
# Extract tool information
|
||||
tool = {
|
||||
'name': operation.get('operationId', f"{method}_{path.replace('/', '_').replace('{', '').replace('}', '')}"),
|
||||
'description': operation.get('summary', operation.get('description', f'{method.upper()} {path}')),
|
||||
'description': build_tool_description(operation, method, path),
|
||||
'instructions': operation.get('x-mcp-instructions', ''),
|
||||
'path': path,
|
||||
'method': method.upper(),
|
||||
@@ -607,4 +620,4 @@ def main():
|
||||
print("Done!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE IF EXISTS azure_trigger;
|
||||
DROP TYPE IF EXISTS AZURE_TRIGGER_MODE;
|
||||
-- Note: TRIGGER_KIND and JOB_TRIGGER_KIND enums cannot have values removed
|
||||
-- so we leave 'azure' in place as a dead value (matches other trigger migrations)
|
||||
@@ -0,0 +1,95 @@
|
||||
-- Add up migration script here
|
||||
|
||||
ALTER TYPE TRIGGER_KIND ADD VALUE IF NOT EXISTS 'azure';
|
||||
ALTER TYPE JOB_TRIGGER_KIND ADD VALUE IF NOT EXISTS 'azure';
|
||||
|
||||
CREATE TYPE AZURE_TRIGGER_MODE AS ENUM ('basic_push', 'namespace_push', 'namespace_pull');
|
||||
|
||||
CREATE TABLE azure_trigger (
|
||||
azure_resource_path VARCHAR(255) NOT NULL,
|
||||
azure_mode AZURE_TRIGGER_MODE NOT NULL,
|
||||
scope_resource_id TEXT NOT NULL,
|
||||
topic_name VARCHAR(255),
|
||||
subscription_name VARCHAR(255) NOT NULL,
|
||||
event_type_filters JSONB,
|
||||
push_auth_config JSONB,
|
||||
path VARCHAR(255) NOT NULL,
|
||||
script_path VARCHAR(255) NOT NULL,
|
||||
is_flow BOOLEAN NOT NULL,
|
||||
workspace_id VARCHAR(50) NOT NULL,
|
||||
edited_by VARCHAR(50) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
edited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
extra_perms JSONB NOT NULL DEFAULT '{}',
|
||||
server_id VARCHAR(50),
|
||||
last_server_ping TIMESTAMPTZ,
|
||||
error TEXT,
|
||||
mode TRIGGER_MODE NOT NULL DEFAULT 'enabled',
|
||||
permissioned_as VARCHAR(255) NOT NULL,
|
||||
error_handler_path VARCHAR(255),
|
||||
error_handler_args JSONB,
|
||||
retry JSONB,
|
||||
labels TEXT[],
|
||||
PRIMARY KEY (path, workspace_id),
|
||||
CONSTRAINT azure_topic_name_matches_mode CHECK (
|
||||
(azure_mode = 'basic_push' AND topic_name IS NULL) OR
|
||||
(azure_mode IN ('namespace_push', 'namespace_pull') AND topic_name IS NOT NULL)
|
||||
),
|
||||
CONSTRAINT azure_push_auth_config_matches_mode CHECK (
|
||||
(azure_mode IN ('basic_push', 'namespace_push') AND push_auth_config IS NOT NULL) OR
|
||||
(azure_mode = 'namespace_pull' AND push_auth_config IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX unique_subscription_per_azure_scope
|
||||
ON azure_trigger (subscription_name, scope_resource_id, workspace_id);
|
||||
|
||||
CREATE INDEX idx_azure_trigger_labels ON azure_trigger USING GIN (labels) WHERE labels IS NOT NULL;
|
||||
|
||||
GRANT ALL ON azure_trigger TO windmill_user;
|
||||
GRANT ALL ON azure_trigger TO windmill_admin;
|
||||
|
||||
ALTER TABLE azure_trigger ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY admin_policy ON azure_trigger FOR ALL TO windmill_admin USING (true);
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user_select ON azure_trigger FOR SELECT TO windmill_user
|
||||
USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_insert ON azure_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_update ON azure_trigger FOR UPDATE TO windmill_user
|
||||
USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
CREATE POLICY see_folder_extra_perms_user_delete ON azure_trigger FOR DELETE TO windmill_user
|
||||
USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'f' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_own ON azure_trigger FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'u' AND SPLIT_PART(azure_trigger.path, '/', 2) = current_setting('session.user'));
|
||||
CREATE POLICY see_member ON azure_trigger FOR ALL TO windmill_user
|
||||
USING (SPLIT_PART(azure_trigger.path, '/', 1) = 'g' AND SPLIT_PART(azure_trigger.path, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[]));
|
||||
|
||||
CREATE POLICY see_extra_perms_user_select ON azure_trigger FOR SELECT TO windmill_user
|
||||
USING (extra_perms ? CONCAT('u/', current_setting('session.user')));
|
||||
CREATE POLICY see_extra_perms_user_insert ON azure_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
CREATE POLICY see_extra_perms_user_update ON azure_trigger FOR UPDATE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
CREATE POLICY see_extra_perms_user_delete ON azure_trigger FOR DELETE TO windmill_user
|
||||
USING ((extra_perms ->> CONCAT('u/', current_setting('session.user')))::boolean);
|
||||
|
||||
CREATE POLICY see_extra_perms_groups_select ON azure_trigger FOR SELECT TO windmill_user
|
||||
USING (extra_perms ?| regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]);
|
||||
CREATE POLICY see_extra_perms_groups_insert ON azure_trigger FOR INSERT TO windmill_user
|
||||
WITH CHECK (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
CREATE POLICY see_extra_perms_groups_update ON azure_trigger FOR UPDATE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
CREATE POLICY see_extra_perms_groups_delete ON azure_trigger FOR DELETE TO windmill_user
|
||||
USING (exists(
|
||||
SELECT key, value FROM jsonb_each_text(extra_perms)
|
||||
WHERE SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[])
|
||||
AND value::boolean));
|
||||
@@ -0,0 +1,10 @@
|
||||
DROP INDEX IF EXISTS idx_conversation_message_conversation_created_seq;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_conversation_message_conversation_time
|
||||
ON flow_conversation_message(conversation_id, created_at DESC);
|
||||
|
||||
ALTER TABLE flow_conversation_message
|
||||
DROP CONSTRAINT IF EXISTS flow_conversation_message_created_seq_key;
|
||||
|
||||
ALTER TABLE flow_conversation_message
|
||||
DROP COLUMN IF EXISTS created_seq;
|
||||
@@ -0,0 +1,10 @@
|
||||
ALTER TABLE flow_conversation_message
|
||||
ADD COLUMN created_seq BIGINT GENERATED ALWAYS AS IDENTITY;
|
||||
|
||||
ALTER TABLE flow_conversation_message
|
||||
ADD CONSTRAINT flow_conversation_message_created_seq_key UNIQUE (created_seq);
|
||||
|
||||
CREATE INDEX idx_conversation_message_conversation_created_seq
|
||||
ON flow_conversation_message(conversation_id, created_seq);
|
||||
|
||||
DROP INDEX IF EXISTS idx_conversation_message_conversation_time;
|
||||
+24
-24
@@ -6183,7 +6183,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -6263,7 +6263,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6275,7 +6275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"serde",
|
||||
@@ -6284,7 +6284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6296,7 +6296,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6308,7 +6308,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -6320,7 +6320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6332,7 +6332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6344,7 +6344,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -6355,7 +6355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6366,7 +6366,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6378,7 +6378,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6389,7 +6389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -6411,7 +6411,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6423,7 +6423,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6437,7 +6437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case",
|
||||
@@ -6454,7 +6454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6467,7 +6467,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6479,7 +6479,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6497,7 +6497,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -6513,7 +6513,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6529,7 +6529,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.17",
|
||||
@@ -6561,7 +6561,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6572,7 +6572,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
|
||||
@@ -12,7 +12,7 @@ resolver = "2"
|
||||
members = ["."]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.688.0"
|
||||
version = "1.690.0"
|
||||
edition = "2021"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
|
||||
|
||||
@@ -3879,6 +3879,10 @@ pub async fn reload_jwt_secret_setting(db: &DB) -> error::Result<()> {
|
||||
|
||||
JWT_SECRET.store(std::sync::Arc::new(jwt_secret));
|
||||
|
||||
// The debug signing key is derived from JWT_SECRET, so re-derive it here so
|
||||
// rotation propagates to /api/debug/* signing without requiring a restart.
|
||||
windmill_api::reload_debug_signing_key().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ flow: workspace_id(char), path(char), summary(text), description(text), value(js
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
flow_conversation: id(uuid), workspace_id(char), flow_path(char), title(char), created_at(ts), updated_at(ts), created_by(char)
|
||||
FK: (workspace_id) -> workspace(id)
|
||||
flow_conversation_message: id(uuid), conversation_id(uuid), message_type(message_type), content(text), job_id(uuid), created_at(ts), step_name(char), success(bool)
|
||||
flow_conversation_message: id(uuid), conversation_id(uuid), message_type(message_type), content(text), job_id(uuid), created_at(ts), created_seq(int8), step_name(char), success(bool)
|
||||
FK: (conversation_id) -> flow_conversation(id) | (job_id) -> v2_job(id)
|
||||
flow_iterator_data: job_id(uuid), itered(jsonb)
|
||||
flow_node: id(bigint), workspace_id(char), hash(bigint), path(char), lock(text), code(text), flow(jsonb), hash_v2(char(64))
|
||||
|
||||
@@ -259,6 +259,7 @@ pub enum ScopeDomain {
|
||||
MqttTriggers,
|
||||
SqsTriggers,
|
||||
GcpTriggers,
|
||||
AzureTriggers,
|
||||
PostgresTriggers,
|
||||
EmailTriggers,
|
||||
|
||||
@@ -317,6 +318,7 @@ impl ScopeDomain {
|
||||
Self::MqttTriggers => "mqtt_triggers",
|
||||
Self::SqsTriggers => "sqs_triggers",
|
||||
Self::GcpTriggers => "gcp_triggers",
|
||||
Self::AzureTriggers => "azure_triggers",
|
||||
Self::PostgresTriggers => "postgres_triggers",
|
||||
Self::EmailTriggers => "email_triggers",
|
||||
Self::NativeTriggers => "native_triggers",
|
||||
@@ -366,6 +368,7 @@ impl ScopeDomain {
|
||||
"mqtt_triggers" => Some(Self::MqttTriggers),
|
||||
"sqs_triggers" => Some(Self::SqsTriggers),
|
||||
"gcp_triggers" => Some(Self::GcpTriggers),
|
||||
"azure_triggers" => Some(Self::AzureTriggers),
|
||||
"postgres_triggers" => Some(Self::PostgresTriggers),
|
||||
"email_triggers" => Some(Self::EmailTriggers),
|
||||
"audit" => Some(Self::Audit),
|
||||
|
||||
@@ -247,7 +247,7 @@ struct AutoscalingEvent {
|
||||
event_type: Option<String>,
|
||||
desired_workers: i32,
|
||||
reason: Option<String>,
|
||||
applied_at: chrono::NaiveDateTime,
|
||||
applied_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
async fn list_autoscaling_events(
|
||||
@@ -260,9 +260,12 @@ async fn list_autoscaling_events(
|
||||
}
|
||||
let (per_page, offset) = windmill_common::utils::paginate(pagination);
|
||||
|
||||
// applied_at is a naive TIMESTAMP; reinterpret it as UTC so the response
|
||||
// includes a timezone (otherwise the browser parses it as local time and
|
||||
// TimeAgo clamps future timestamps to "0s ago").
|
||||
let events = sqlx::query_as!(
|
||||
AutoscalingEvent,
|
||||
"SELECT id, worker_group, event_type::text, desired_workers, reason, applied_at FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3",
|
||||
r#"SELECT id, worker_group, event_type::text, desired_workers, reason, (applied_at AT TIME ZONE 'UTC') AS "applied_at!: chrono::DateTime<chrono::Utc>" FROM autoscaling_event WHERE worker_group = $1 ORDER BY applied_at DESC LIMIT $2 OFFSET $3"#,
|
||||
worker_group,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
|
||||
@@ -18,7 +18,6 @@ chrono.workspace = true
|
||||
ed25519-dalek.workspace = true
|
||||
hex.workspace = true
|
||||
lazy_static.workspace = true
|
||||
rand.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
@@ -37,7 +37,7 @@ use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::{audit_oss::audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
db::UserDB, error::JsonResult, jobs::JobKind, scripts::ScriptLang,
|
||||
db::UserDB, error::JsonResult, jobs::JobKind, jwt::JWT_SECRET, scripts::ScriptLang,
|
||||
users::username_to_permissioned_as,
|
||||
};
|
||||
|
||||
@@ -48,35 +48,67 @@ pub const DEBUG_TOKEN_TTL_SECS: i64 = 60;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// Ed25519 signing key for debug tokens.
|
||||
/// Generated at startup if not provided via environment variable.
|
||||
///
|
||||
/// Derived deterministically from the instance `JWT_SECRET` so all API
|
||||
/// replicas agree on the same key without coordination. Refreshed via
|
||||
/// [`reload_debug_signing_key`] when `JWT_SECRET` is reloaded.
|
||||
static ref DEBUG_SIGNING_KEY: Arc<RwLock<Option<SigningKey>>> = Arc::new(RwLock::new(None));
|
||||
}
|
||||
|
||||
/// Initialize the debug signing key.
|
||||
/// Call this at server startup.
|
||||
pub async fn init_debug_signing_key() {
|
||||
let mut key_guard = DEBUG_SIGNING_KEY.write().await;
|
||||
/// Domain-separation tag so the debug Ed25519 seed cannot be confused with
|
||||
/// any other HMAC/HS256 usage of `JWT_SECRET`.
|
||||
const DEBUG_KEY_DERIVATION_TAG: &[u8] = b"windmill-debug-signing-key:v1:";
|
||||
|
||||
// Check if key is provided via environment variable (base64-encoded seed)
|
||||
fn derive_signing_key_from_jwt_secret(jwt_secret: &str) -> SigningKey {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(DEBUG_KEY_DERIVATION_TAG);
|
||||
hasher.update(jwt_secret.as_bytes());
|
||||
let seed: [u8; 32] = hasher.finalize().into();
|
||||
SigningKey::from_bytes(&seed)
|
||||
}
|
||||
|
||||
fn compute_debug_signing_key() -> Option<SigningKey> {
|
||||
// Env var override: base64url-encoded 32-byte seed. Useful for tests or
|
||||
// advanced deployments that want to pin the key independently.
|
||||
if let Ok(seed_b64) = std::env::var("DEBUG_SIGNING_KEY_SEED") {
|
||||
if let Ok(seed_bytes) = URL_SAFE_NO_PAD.decode(&seed_b64) {
|
||||
if seed_bytes.len() >= 32 {
|
||||
match URL_SAFE_NO_PAD.decode(&seed_b64) {
|
||||
Ok(seed_bytes) if seed_bytes.len() >= 32 => {
|
||||
let mut seed = [0u8; 32];
|
||||
seed.copy_from_slice(&seed_bytes[..32]);
|
||||
*key_guard = Some(SigningKey::from_bytes(&seed));
|
||||
tracing::info!("Debug signing key loaded from environment");
|
||||
return;
|
||||
tracing::info!("Debug signing key loaded from DEBUG_SIGNING_KEY_SEED");
|
||||
return Some(SigningKey::from_bytes(&seed));
|
||||
}
|
||||
_ => tracing::warn!(
|
||||
"Invalid DEBUG_SIGNING_KEY_SEED (expect base64url-encoded 32+ bytes); falling back to JWT_SECRET derivation"
|
||||
),
|
||||
}
|
||||
tracing::warn!("Invalid DEBUG_SIGNING_KEY_SEED, generating new key");
|
||||
}
|
||||
|
||||
// Generate a new random key using rand
|
||||
let mut seed = [0u8; 32];
|
||||
rand::Rng::fill(&mut rand::rng(), &mut seed);
|
||||
let signing_key = SigningKey::from_bytes(&seed);
|
||||
tracing::info!("Generated new debug signing key");
|
||||
*key_guard = Some(signing_key);
|
||||
let jwt_secret = JWT_SECRET.load();
|
||||
if jwt_secret.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(derive_signing_key_from_jwt_secret(&jwt_secret))
|
||||
}
|
||||
|
||||
/// Initialize the debug signing key. Call once at server startup, after
|
||||
/// `reload_jwt_secret_setting` so `JWT_SECRET` is populated.
|
||||
pub async fn init_debug_signing_key() {
|
||||
reload_debug_signing_key().await;
|
||||
}
|
||||
|
||||
/// Recompute and store the debug signing key. Call after `JWT_SECRET` is
|
||||
/// (re)loaded so rotation propagates without a pod restart.
|
||||
pub async fn reload_debug_signing_key() {
|
||||
let key = compute_debug_signing_key();
|
||||
if key.is_none() {
|
||||
tracing::warn!(
|
||||
"Debug signing key not initialized: JWT_SECRET is empty and DEBUG_SIGNING_KEY_SEED is not set. /api/debug/* endpoints will return an error."
|
||||
);
|
||||
} else {
|
||||
tracing::info!("Debug signing key initialized from JWT_SECRET");
|
||||
}
|
||||
*DEBUG_SIGNING_KEY.write().await = key;
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
|
||||
@@ -33,6 +33,7 @@ pub struct FlowConversationMessage {
|
||||
pub content: String,
|
||||
pub job_id: Option<Uuid>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub created_seq: i64,
|
||||
pub step_name: Option<String>,
|
||||
pub success: bool,
|
||||
}
|
||||
@@ -40,7 +41,11 @@ pub struct FlowConversationMessage {
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListConversationsQuery {
|
||||
pub flow_path: Option<String>,
|
||||
pub after_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListMessagesQuery {
|
||||
pub after_seq: Option<i64>,
|
||||
}
|
||||
|
||||
async fn list_conversations(
|
||||
@@ -68,15 +73,6 @@ async fn list_conversations(
|
||||
if let Some(flow_path) = &query.flow_path {
|
||||
sqlb.and_where_eq("flow_path", "?".bind(flow_path));
|
||||
}
|
||||
if let Some(after_id) = &query.after_id {
|
||||
let message_id_created_at = sqlx::query_scalar!(
|
||||
"SELECT created_at FROM flow_conversation_message WHERE id = $1",
|
||||
after_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.await?;
|
||||
sqlb.and_where_gt("created_at", "?".bind(&message_id_created_at.to_rfc3339()));
|
||||
}
|
||||
|
||||
sqlb.order_by("updated_at", true)
|
||||
.limit(per_page as i64)
|
||||
@@ -157,6 +153,7 @@ async fn list_messages(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, conversation_id)): Path<(String, Uuid)>,
|
||||
Query(pagination): Query<Pagination>,
|
||||
Query(query): Query<ListMessagesQuery>,
|
||||
) -> JsonResult<Vec<FlowConversationMessage>> {
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
@@ -178,25 +175,43 @@ async fn list_messages(
|
||||
)));
|
||||
}
|
||||
|
||||
// Fetch messages for this conversation, oldest first, but reverse the order of the messages for easy rendering on the frontend
|
||||
let messages = sqlx::query_as!(
|
||||
FlowConversationMessage,
|
||||
r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at, step_name, success
|
||||
FROM (
|
||||
SELECT id, conversation_id, message_type, content, job_id, created_at, step_name, success
|
||||
FROM flow_conversation_message
|
||||
WHERE conversation_id = $1
|
||||
ORDER BY created_at DESC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END
|
||||
LIMIT $2 OFFSET $3
|
||||
) AS messages
|
||||
ORDER BY created_at ASC, CASE WHEN message_type = 'user' THEN 0 ELSE 1 END
|
||||
"#,
|
||||
conversation_id,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
let messages = if let Some(after_seq) = query.after_seq {
|
||||
sqlx::query_as!(
|
||||
FlowConversationMessage,
|
||||
r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at, created_seq, step_name, success
|
||||
FROM flow_conversation_message
|
||||
WHERE conversation_id = $1
|
||||
AND created_seq > $2
|
||||
ORDER BY created_seq ASC
|
||||
LIMIT $3
|
||||
"#,
|
||||
conversation_id,
|
||||
after_seq,
|
||||
per_page as i64
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?
|
||||
} else {
|
||||
// Fetch messages for this conversation, oldest first, but reverse the order of the messages for easy rendering on the frontend
|
||||
sqlx::query_as!(
|
||||
FlowConversationMessage,
|
||||
r#"SELECT id, conversation_id, message_type as "message_type: MessageType", content, job_id, created_at, created_seq, step_name, success
|
||||
FROM (
|
||||
SELECT id, conversation_id, message_type, content, job_id, created_at, created_seq, step_name, success
|
||||
FROM flow_conversation_message
|
||||
WHERE conversation_id = $1
|
||||
ORDER BY created_seq DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
) AS messages
|
||||
ORDER BY created_seq ASC
|
||||
"#,
|
||||
conversation_id,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?
|
||||
};
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(messages))
|
||||
|
||||
@@ -24,7 +24,7 @@ use windmill_common::{
|
||||
utils::{not_found_if_none, StripPath},
|
||||
};
|
||||
|
||||
const KINDS: [&str; 19] = [
|
||||
const KINDS: [&str; 20] = [
|
||||
"script",
|
||||
"group_",
|
||||
"resource",
|
||||
@@ -41,6 +41,7 @@ const KINDS: [&str; 19] = [
|
||||
"postgres_trigger",
|
||||
"mqtt_trigger",
|
||||
"gcp_trigger",
|
||||
"azure_trigger",
|
||||
"sqs_trigger",
|
||||
"email_trigger",
|
||||
"volume",
|
||||
|
||||
@@ -883,6 +883,7 @@ async fn create_script_internal<'c>(
|
||||
|| ns.language == ScriptLang::Java
|
||||
|| ns.language == ScriptLang::Ruby
|
||||
|| ns.language == ScriptLang::Rlang
|
||||
|| ns.language == ScriptLang::Powershell
|
||||
// for related places search: ADD_NEW_LANG
|
||||
) {
|
||||
Some(String::new())
|
||||
|
||||
@@ -1690,6 +1690,7 @@ pub async fn delete_workspace_user_internal(
|
||||
"nats_trigger",
|
||||
"sqs_trigger",
|
||||
"gcp_trigger",
|
||||
"azure_trigger",
|
||||
"email_trigger",
|
||||
];
|
||||
for table in &extra_perms_tables {
|
||||
|
||||
@@ -3138,6 +3138,7 @@ struct UsedTriggers {
|
||||
pub mqtt_used: bool,
|
||||
pub sqs_used: bool,
|
||||
pub gcp_used: bool,
|
||||
pub azure_used: bool,
|
||||
pub email_used: bool,
|
||||
pub nextcloud_used: bool,
|
||||
pub google_used: bool,
|
||||
@@ -3162,6 +3163,7 @@ async fn get_used_triggers(
|
||||
EXISTS(SELECT 1 FROM mqtt_trigger WHERE workspace_id = $1) AS "mqtt_used!",
|
||||
EXISTS(SELECT 1 FROM sqs_trigger WHERE workspace_id = $1) AS "sqs_used!",
|
||||
EXISTS(SELECT 1 FROM gcp_trigger WHERE workspace_id = $1) AS "gcp_used!",
|
||||
EXISTS(SELECT 1 FROM azure_trigger WHERE workspace_id = $1) AS "azure_used!",
|
||||
EXISTS(SELECT 1 FROM email_trigger WHERE workspace_id = $1) AS "email_used!",
|
||||
EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'nextcloud'::native_trigger_service) AS "nextcloud_used!",
|
||||
EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = 'google'::native_trigger_service) AS "google_used!",
|
||||
|
||||
@@ -10,8 +10,8 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
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", "windmill-git-sync/private", "windmill-autoscaling?/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-schedule/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"]
|
||||
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-azure?/private", "windmill-trigger-email?/private", "windmill-git-sync/private", "windmill-autoscaling?/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-schedule/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-azure?/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"]
|
||||
agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"]
|
||||
@@ -37,6 +37,7 @@ mqtt_trigger = ["dep:windmill-trigger-mqtt", "windmill-store/mqtt_trigger"]
|
||||
native_trigger = ["dep:windmill-native-triggers", "windmill-native-triggers/native_trigger", "dep:strum", "oauth2"]
|
||||
sqs_trigger = ["dep:windmill-trigger-sqs", "windmill-store/sqs_trigger"]
|
||||
gcp_trigger = ["dep:windmill-trigger-gcp", "windmill-store/gcp_trigger"]
|
||||
azure_trigger = ["dep:windmill-trigger-azure", "windmill-store/azure_trigger"]
|
||||
cloud = ["windmill-common/cloud", "windmill-api-auth/cloud", "windmill-store/cloud", "windmill-api-workspaces/cloud"]
|
||||
mcp = ["dep:windmill-mcp", "windmill-mcp/server", "windmill-mcp/auth", "windmill-api-auth/mcp", "windmill-store/mcp"]
|
||||
bedrock = ["windmill-ai/bedrock", "dep:aws-sdk-bedrock", "dep:aws-sdk-bedrockruntime", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-types"]
|
||||
@@ -145,6 +146,7 @@ windmill-trigger-email = { workspace = true, optional = true }
|
||||
windmill-trigger-nats = { workspace = true, optional = true }
|
||||
windmill-trigger-sqs = { workspace = true, optional = true }
|
||||
windmill-trigger-gcp = { workspace = true, optional = true }
|
||||
windmill-trigger-azure = { workspace = true, optional = true }
|
||||
windmill-trigger-http = { workspace = true, optional = true }
|
||||
windmill-native-triggers = { workspace = true, optional = true }
|
||||
windmill-alerting.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+2108
-1387
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.688.0
|
||||
version: 1.690.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -4575,6 +4575,8 @@ paths:
|
||||
type: boolean
|
||||
gcp_used:
|
||||
type: boolean
|
||||
azure_used:
|
||||
type: boolean
|
||||
sqs_used:
|
||||
type: boolean
|
||||
email_used:
|
||||
@@ -4593,6 +4595,7 @@ paths:
|
||||
- postgres_used
|
||||
- mqtt_used
|
||||
- gcp_used
|
||||
- azure_used
|
||||
- sqs_used
|
||||
- email_used
|
||||
- nextcloud_used
|
||||
@@ -5963,6 +5966,9 @@ paths:
|
||||
- type
|
||||
saml:
|
||||
type: string
|
||||
auto_login:
|
||||
type: string
|
||||
description: provider type to auto-redirect to on login (oauth key or "saml")
|
||||
required:
|
||||
- oauth
|
||||
|
||||
@@ -7406,6 +7412,9 @@ paths:
|
||||
/w/{workspace}/scripts/create:
|
||||
post:
|
||||
summary: create script
|
||||
description: |
|
||||
Creates a new script when the path does not already exist.
|
||||
Creates a new version of an existing script when called with the same path and the current `parent_hash`.
|
||||
operationId: createScript
|
||||
x-mcp-tool: true
|
||||
x-mcp-instructions: "To create a script, specify the path (e.g., 'f/my_folder/my_script'), the content (source code), and the language. For TypeScript, use 'bun' unless deno-specific APIs are needed."
|
||||
@@ -9451,13 +9460,13 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: after_id
|
||||
description: id to fetch only the messages after that id
|
||||
- name: after_seq
|
||||
description: Message sequence cursor to fetch only the messages after that cursor
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
"200":
|
||||
description: conversation messages
|
||||
@@ -11687,6 +11696,11 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: approval_token
|
||||
in: query
|
||||
description: Approval token granting read access to the job when not logged in. The token must be the one issued for this job's flow (i.e. the flow id used when generating the approval URL).
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: job details
|
||||
@@ -15211,6 +15225,296 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/azure_triggers/create:
|
||||
post:
|
||||
summary: create an Azure Event Grid trigger
|
||||
operationId: createAzureTrigger
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AzureTriggerData"
|
||||
responses:
|
||||
"201":
|
||||
description: azure trigger created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/azure_triggers/update/{path}:
|
||||
post:
|
||||
summary: update an Azure Event Grid trigger
|
||||
operationId: updateAzureTrigger
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AzureTriggerData"
|
||||
responses:
|
||||
"200":
|
||||
description: azure trigger updated
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/azure_triggers/delete/{path}:
|
||||
delete:
|
||||
summary: delete an Azure Event Grid trigger
|
||||
operationId: deleteAzureTrigger
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: azure trigger deleted
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/azure_triggers/get/{path}:
|
||||
get:
|
||||
summary: get an Azure Event Grid trigger
|
||||
operationId: getAzureTrigger
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: azure trigger
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AzureTrigger"
|
||||
|
||||
/w/{workspace}/azure_triggers/list:
|
||||
get:
|
||||
summary: list azure triggers
|
||||
operationId: listAzureTriggers
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/PerPage"
|
||||
- name: path
|
||||
description: filter by exact path
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: is_flow
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: path_start
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: azure trigger list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AzureTrigger"
|
||||
|
||||
/w/{workspace}/azure_triggers/exists/{path}:
|
||||
get:
|
||||
summary: check whether an azure trigger exists
|
||||
operationId: existsAzureTrigger
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: true/false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/w/{workspace}/azure_triggers/setmode/{path}:
|
||||
post:
|
||||
summary: set azure trigger mode
|
||||
operationId: setAzureTriggerMode
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
mode:
|
||||
$ref: "#/components/schemas/TriggerMode"
|
||||
required:
|
||||
- mode
|
||||
responses:
|
||||
"200":
|
||||
description: trigger mode updated
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/azure_triggers/test:
|
||||
post:
|
||||
summary: test Azure service principal connection
|
||||
operationId: testAzureConnection
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TestAzureConnection"
|
||||
responses:
|
||||
"200":
|
||||
description: connection successful
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/azure_triggers/namespaces/topics/list/{path}:
|
||||
post:
|
||||
summary: list topics under an Event Grid Namespace
|
||||
operationId: listAzureNamespaceTopics
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AzureListTopics"
|
||||
responses:
|
||||
"200":
|
||||
description: topic list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
|
||||
/w/{workspace}/azure_triggers/namespaces/subscriptions/list/{path}:
|
||||
post:
|
||||
summary: list subscriptions under a Namespace topic
|
||||
operationId: listAzureNamespaceSubscriptions
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AzureListSubscriptions"
|
||||
responses:
|
||||
"200":
|
||||
description: subscription list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
|
||||
/w/{workspace}/azure_triggers/subscriptions/delete/{path}:
|
||||
delete:
|
||||
summary: delete an Event Grid subscription on Azure
|
||||
operationId: deleteAzureSubscription
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AzureDeleteSubscription"
|
||||
responses:
|
||||
"200":
|
||||
description: subscription deleted
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/azure_triggers/namespaces/list/{path}:
|
||||
post:
|
||||
summary: list Event Grid Namespaces the service principal can access
|
||||
operationId: listAzureNamespaces
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: namespace list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AzureArmResource"
|
||||
|
||||
/w/{workspace}/azure_triggers/basic/topics/list/{path}:
|
||||
post:
|
||||
summary: list Basic Event Grid topics + system topics the service principal can access
|
||||
operationId: listAzureBasicTopics
|
||||
tags:
|
||||
- azure_trigger
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"200":
|
||||
description: topic list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/AzureArmResource"
|
||||
|
||||
/w/{workspace}/postgres_triggers/postgres/version/{path}:
|
||||
get:
|
||||
summary: get postgres version
|
||||
@@ -17091,6 +17395,7 @@ paths:
|
||||
postgres_trigger,
|
||||
mqtt_trigger,
|
||||
gcp_trigger,
|
||||
azure_trigger,
|
||||
sqs_trigger,
|
||||
email_trigger,
|
||||
volume,
|
||||
@@ -17137,6 +17442,7 @@ paths:
|
||||
postgres_trigger,
|
||||
mqtt_trigger,
|
||||
gcp_trigger,
|
||||
azure_trigger,
|
||||
sqs_trigger,
|
||||
email_trigger,
|
||||
volume,
|
||||
@@ -17194,6 +17500,7 @@ paths:
|
||||
postgres_trigger,
|
||||
mqtt_trigger,
|
||||
gcp_trigger,
|
||||
azure_trigger,
|
||||
sqs_trigger,
|
||||
email_trigger,
|
||||
volume,
|
||||
@@ -20341,7 +20648,7 @@ components:
|
||||
|
||||
FlowConversationMessage:
|
||||
type: object
|
||||
required: [id, conversation_id, message_type, content, created_at]
|
||||
required: [id, conversation_id, message_type, content, created_at, created_seq]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
@@ -20367,6 +20674,10 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
description: When the message was created
|
||||
created_seq:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Monotonic cursor assigned when the message is inserted
|
||||
step_name:
|
||||
type: string
|
||||
description: The step name that produced that message
|
||||
@@ -22887,6 +23198,7 @@ components:
|
||||
- mqtt
|
||||
- sqs
|
||||
- gcp
|
||||
- azure
|
||||
- google
|
||||
- github
|
||||
|
||||
@@ -23374,6 +23686,8 @@ components:
|
||||
type: number
|
||||
gcp_count:
|
||||
type: number
|
||||
azure_count:
|
||||
type: number
|
||||
sqs_count:
|
||||
type: number
|
||||
nextcloud_count:
|
||||
@@ -24048,6 +24362,171 @@ components:
|
||||
required:
|
||||
- subscription_id
|
||||
|
||||
AzureMode:
|
||||
type: string
|
||||
enum:
|
||||
- basic_push
|
||||
- namespace_push
|
||||
- namespace_pull
|
||||
description: "Azure Event Grid trigger mode."
|
||||
|
||||
AzureArmResource:
|
||||
type: object
|
||||
description: "An ARM resource the service principal can see."
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
location:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- type
|
||||
|
||||
AzureDeleteSubscription:
|
||||
type: object
|
||||
properties:
|
||||
azure_mode:
|
||||
$ref: "#/components/schemas/AzureMode"
|
||||
scope_resource_id:
|
||||
type: string
|
||||
topic_name:
|
||||
type: string
|
||||
nullable: true
|
||||
subscription_name:
|
||||
type: string
|
||||
required:
|
||||
- azure_mode
|
||||
- scope_resource_id
|
||||
- subscription_name
|
||||
|
||||
AzureTrigger:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/TriggerExtraProperty"
|
||||
type: object
|
||||
description: "An Azure Event Grid trigger that executes a script or flow when events arrive."
|
||||
properties:
|
||||
azure_resource_path:
|
||||
type: string
|
||||
azure_mode:
|
||||
$ref: "#/components/schemas/AzureMode"
|
||||
scope_resource_id:
|
||||
type: string
|
||||
description: "ARM resource ID of the topic (basic) or namespace (namespace modes)."
|
||||
topic_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: "Topic name within the namespace (namespace modes only)."
|
||||
subscription_name:
|
||||
type: string
|
||||
event_type_filters:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
server_id:
|
||||
type: string
|
||||
last_server_ping:
|
||||
type: string
|
||||
format: date-time
|
||||
error:
|
||||
type: string
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
required:
|
||||
- azure_resource_path
|
||||
- azure_mode
|
||||
- scope_resource_id
|
||||
- subscription_name
|
||||
|
||||
AzureTriggerData:
|
||||
type: object
|
||||
description: "Data for creating or updating an Azure Event Grid trigger."
|
||||
properties:
|
||||
azure_resource_path:
|
||||
type: string
|
||||
azure_mode:
|
||||
$ref: "#/components/schemas/AzureMode"
|
||||
scope_resource_id:
|
||||
type: string
|
||||
topic_name:
|
||||
type: string
|
||||
nullable: true
|
||||
subscription_name:
|
||||
type: string
|
||||
base_endpoint:
|
||||
type: string
|
||||
description: "Base URL for push delivery endpoints (push modes only)."
|
||||
event_type_filters:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
script_path:
|
||||
type: string
|
||||
is_flow:
|
||||
type: boolean
|
||||
mode:
|
||||
$ref: "#/components/schemas/TriggerMode"
|
||||
error_handler_path:
|
||||
type: string
|
||||
error_handler_args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
retry:
|
||||
$ref: "../../openflow.openapi.yaml#/components/schemas/Retry"
|
||||
permissioned_as:
|
||||
type: string
|
||||
preserve_permissioned_as:
|
||||
type: boolean
|
||||
labels:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- script_path
|
||||
- is_flow
|
||||
- azure_resource_path
|
||||
- azure_mode
|
||||
- scope_resource_id
|
||||
- subscription_name
|
||||
|
||||
TestAzureConnection:
|
||||
type: object
|
||||
properties:
|
||||
azure_resource_path:
|
||||
type: string
|
||||
required:
|
||||
- azure_resource_path
|
||||
|
||||
AzureListTopics:
|
||||
type: object
|
||||
properties:
|
||||
scope_resource_id:
|
||||
type: string
|
||||
required:
|
||||
- scope_resource_id
|
||||
|
||||
AzureListSubscriptions:
|
||||
type: object
|
||||
properties:
|
||||
scope_resource_id:
|
||||
type: string
|
||||
topic_name:
|
||||
type: string
|
||||
required:
|
||||
- scope_resource_id
|
||||
- topic_name
|
||||
|
||||
AwsAuthResourceType:
|
||||
type: string
|
||||
enum:
|
||||
@@ -26349,6 +26828,7 @@ components:
|
||||
sqs,
|
||||
mqtt,
|
||||
gcp,
|
||||
azure,
|
||||
email,
|
||||
]
|
||||
|
||||
|
||||
@@ -114,7 +114,8 @@ pub fn workspaced_unauthed_service() -> Router {
|
||||
|
||||
#[cfg(any(
|
||||
feature = "http_trigger",
|
||||
all(feature = "enterprise", feature = "gcp_trigger")
|
||||
all(feature = "enterprise", feature = "gcp_trigger"),
|
||||
all(feature = "enterprise", feature = "azure_trigger")
|
||||
))]
|
||||
{
|
||||
#[cfg(feature = "http_trigger")]
|
||||
@@ -125,12 +126,16 @@ pub fn workspaced_unauthed_service() -> Router {
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))]
|
||||
let router = router.route("/gcp/{runnable_kind}/{*path}", post(gcp_payload));
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))]
|
||||
let router = router.route("/azure/{runnable_kind}/{*path}", post(azure_payload));
|
||||
|
||||
router
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "http_trigger",
|
||||
all(feature = "enterprise", feature = "gcp_trigger")
|
||||
all(feature = "enterprise", feature = "gcp_trigger"),
|
||||
all(feature = "enterprise", feature = "azure_trigger")
|
||||
)))]
|
||||
{
|
||||
router
|
||||
@@ -170,6 +175,26 @@ pub struct SqsTriggerConfig {
|
||||
pub aws_auth_resource_type: AwsAuthResourceType,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AzureTriggerConfig {
|
||||
pub azure_resource_path: String,
|
||||
pub azure_mode: crate::triggers::azure::AzureMode,
|
||||
pub scope_resource_id: String,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub topic_name: Option<String>,
|
||||
pub subscription_name: String,
|
||||
#[serde(default, deserialize_with = "empty_as_none")]
|
||||
pub base_endpoint: Option<String>,
|
||||
#[serde(default)]
|
||||
pub event_type_filters: Option<Vec<String>>,
|
||||
/// Server-managed. Populated by `set_azure_trigger_config` after
|
||||
/// `manage_azure_subscription` regenerates the secret; skipped on
|
||||
/// (de)serialization so clients never see or send it.
|
||||
#[serde(skip, default)]
|
||||
pub push_auth_config: Option<crate::triggers::azure::PushAuthConfig>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GcpTriggerConfig {
|
||||
@@ -248,6 +273,8 @@ enum TriggerConfig {
|
||||
Mqtt(MqttTriggerConfig),
|
||||
#[cfg(all(feature = "enterprise", feature = "gcp_trigger", feature = "private"))]
|
||||
Gcp(GcpTriggerConfig),
|
||||
#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))]
|
||||
Azure(AzureTriggerConfig),
|
||||
#[cfg(all(feature = "enterprise", feature = "smtp", feature = "private"))]
|
||||
Email(EmailTriggerConfig),
|
||||
}
|
||||
@@ -419,6 +446,78 @@ async fn set_gcp_trigger_config(
|
||||
Ok(capture_config)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))]
|
||||
async fn set_azure_trigger_config(
|
||||
w_id: &str,
|
||||
authed: ApiAuthed,
|
||||
db: &DB,
|
||||
mut capture_config: NewCaptureConfig,
|
||||
) -> Result<NewCaptureConfig> {
|
||||
use crate::triggers::azure::{manage_azure_subscription, AzureConfigRequest};
|
||||
|
||||
let Some(TriggerConfig::Azure(azure_config)) = capture_config.trigger_config else {
|
||||
return Err(Error::BadRequest(
|
||||
"Invalid Azure Event Grid config".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
// Suffix subscription name so capture never clobbers the deployed trigger's
|
||||
// subscription. Azure allows [A-Za-z0-9-]{3,50}; reserve 11 chars for
|
||||
// "-wm-capture" (mirrors Kafka's `_wm_capture` convention — hyphen since
|
||||
// Azure names disallow underscores).
|
||||
let mut sub_name = azure_config.subscription_name;
|
||||
if sub_name.len() > 39 {
|
||||
sub_name.truncate(39);
|
||||
}
|
||||
sub_name.push_str("-wm-capture");
|
||||
|
||||
let mut req = AzureConfigRequest {
|
||||
azure_resource_path: azure_config.azure_resource_path,
|
||||
azure_mode: azure_config.azure_mode,
|
||||
scope_resource_id: azure_config.scope_resource_id,
|
||||
topic_name: azure_config.topic_name,
|
||||
subscription_name: sub_name,
|
||||
base_endpoint: azure_config.base_endpoint,
|
||||
event_type_filters: azure_config.event_type_filters,
|
||||
push_auth_config: azure_config.push_auth_config,
|
||||
};
|
||||
|
||||
manage_azure_subscription(
|
||||
authed,
|
||||
db,
|
||||
w_id,
|
||||
&mut req,
|
||||
&capture_config.path,
|
||||
capture_config.is_flow,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
capture_config.trigger_config = Some(TriggerConfig::Azure(AzureTriggerConfig {
|
||||
azure_resource_path: req.azure_resource_path,
|
||||
azure_mode: req.azure_mode,
|
||||
scope_resource_id: req.scope_resource_id,
|
||||
topic_name: req.topic_name,
|
||||
subscription_name: req.subscription_name,
|
||||
base_endpoint: req.base_endpoint,
|
||||
event_type_filters: req.event_type_filters,
|
||||
push_auth_config: req.push_auth_config,
|
||||
}));
|
||||
|
||||
Ok(capture_config)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(all(feature = "enterprise", feature = "azure_trigger", feature = "private")))]
|
||||
async fn set_azure_trigger_config(
|
||||
_w_id: &str,
|
||||
_authed: ApiAuthed,
|
||||
_db: &DB,
|
||||
capture_config: NewCaptureConfig,
|
||||
) -> Result<NewCaptureConfig> {
|
||||
Ok(capture_config)
|
||||
}
|
||||
|
||||
async fn set_config(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -431,6 +530,7 @@ async fn set_config(
|
||||
set_postgres_trigger_config(&w_id, authed.clone(), &db, user_db.clone(), nc).await?
|
||||
}
|
||||
TriggerKind::Gcp => set_gcp_trigger_config(&w_id, authed.clone(), &db, nc).await?,
|
||||
TriggerKind::Azure => set_azure_trigger_config(&w_id, authed.clone(), &db, nc).await?,
|
||||
_ => nc,
|
||||
};
|
||||
|
||||
@@ -960,6 +1060,70 @@ async fn gcp_payload(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))]
|
||||
async fn azure_payload(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, runnable_kind, path)): Path<(String, RunnableKind, String)>,
|
||||
headers: HeaderMap,
|
||||
request: Request,
|
||||
) -> Result<StatusCode> {
|
||||
use crate::triggers::azure::{
|
||||
cloud_event_to_args, process_azure_push_request, validate_push_secret, AzureTrigger,
|
||||
PushOutcome,
|
||||
};
|
||||
use crate::triggers::trigger_helpers::TriggerJobArgs;
|
||||
|
||||
let is_flow = matches!(runnable_kind, RunnableKind::Flow);
|
||||
let (azure_trigger_config, owner, _email): (AzureTriggerConfig, _, _) =
|
||||
get_capture_trigger_config_and_owner(&db, &w_id, &path, is_flow, &TriggerKind::Azure)
|
||||
.await?;
|
||||
|
||||
// Basic handshake posts don't carry the secret header; let them through.
|
||||
let is_classic_handshake = headers
|
||||
.get("aeg-event-type")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.map(|v| v.eq_ignore_ascii_case("SubscriptionValidation"))
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_classic_handshake {
|
||||
let dc = azure_trigger_config
|
||||
.push_auth_config
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::NotAuthorized("azure capture missing push_auth_config".to_string())
|
||||
})?;
|
||||
validate_push_secret(&headers, dc)?;
|
||||
}
|
||||
|
||||
let outcome = process_azure_push_request(headers, request).await?;
|
||||
let (cloud_events, headers_map) = match outcome {
|
||||
PushOutcome::Handshake(_) => {
|
||||
// Capture path doesn't need to echo validation response — return 200.
|
||||
return Ok(StatusCode::OK);
|
||||
}
|
||||
PushOutcome::Events { cloud_events, headers } => (cloud_events, headers),
|
||||
};
|
||||
|
||||
for event in cloud_events {
|
||||
let (payload, trigger_info) = cloud_event_to_args(&event, &headers_map);
|
||||
let (main_args, preprocessor_args) =
|
||||
AzureTrigger::build_capture_payloads(&payload, trigger_info);
|
||||
let _ = insert_capture_payload(
|
||||
&db,
|
||||
&w_id,
|
||||
&path,
|
||||
is_flow,
|
||||
&TriggerKind::Azure,
|
||||
main_args,
|
||||
preprocessor_args,
|
||||
&owner,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[cfg(feature = "http_trigger")]
|
||||
async fn http_payload(
|
||||
Extension(db): Extension<DB>,
|
||||
|
||||
@@ -935,6 +935,7 @@ async fn list_selected_job_groups(
|
||||
struct GetJobQuery {
|
||||
pub no_logs: Option<bool>,
|
||||
pub no_code: Option<bool>,
|
||||
pub approval_token: Option<String>,
|
||||
}
|
||||
|
||||
async fn get_job(
|
||||
@@ -942,16 +943,36 @@ async fn get_job(
|
||||
opt_tokened: OptTokened,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, id)): Path<(String, Uuid)>,
|
||||
Query(GetJobQuery { no_logs, no_code }): Query<GetJobQuery>,
|
||||
Query(GetJobQuery { no_logs, no_code, approval_token }): Query<GetJobQuery>,
|
||||
) -> error::Result<Response> {
|
||||
let tags = opt_authed
|
||||
.as_ref()
|
||||
.map(|authed| get_scope_tags(authed))
|
||||
.flatten();
|
||||
|
||||
let mut get = GetQuery::new()
|
||||
.with_auth(&opt_authed)
|
||||
.with_in_tags(tags.as_ref());
|
||||
// A valid approval token on the same (workspace, flow) grants read access
|
||||
// so the approval page can render job metadata without login. The approval
|
||||
// URL usually carries the flow id directly — try that first and only
|
||||
// resolve the parent flow if the direct check fails.
|
||||
let has_valid_approval_token = if let Some(ref token) = approval_token {
|
||||
if validate_approval_token(&db, token, id, &w_id).await.is_ok() {
|
||||
true
|
||||
} else if let Ok(flow_id) = get_flow_id_for_job(&db, id).await {
|
||||
flow_id != id
|
||||
&& validate_approval_token(&db, token, flow_id, &w_id)
|
||||
.await
|
||||
.is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut get = GetQuery::new().with_in_tags(tags.as_ref());
|
||||
if !has_valid_approval_token {
|
||||
get = get.with_auth(&opt_authed);
|
||||
}
|
||||
|
||||
if no_code.unwrap_or(false) {
|
||||
get = get.without_code();
|
||||
|
||||
@@ -216,6 +216,8 @@ pub use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT;
|
||||
|
||||
pub use windmill_common::utils::{COOKIE_DOMAIN, IS_SECURE};
|
||||
|
||||
pub use windmill_api_debug::reload_debug_signing_key;
|
||||
|
||||
#[cfg(feature = "oauth2")]
|
||||
pub use windmill_oauth::OAUTH_CLIENTS;
|
||||
|
||||
@@ -898,6 +900,24 @@ pub async fn run_server(
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.nest("/azure/w/{workspace_id}", {
|
||||
#[cfg(all(
|
||||
feature = "enterprise",
|
||||
feature = "azure_trigger",
|
||||
feature = "private"
|
||||
))]
|
||||
{
|
||||
triggers::azure::handler_oss::azure_push_route_handler()
|
||||
}
|
||||
#[cfg(not(all(
|
||||
feature = "enterprise",
|
||||
feature = "azure_trigger",
|
||||
feature = "private"
|
||||
)))]
|
||||
{
|
||||
Router::new()
|
||||
}
|
||||
})
|
||||
.route("/version", get(git_v))
|
||||
.nest("/health/status", health::status_service())
|
||||
.route("/min_keep_alive_version", get(min_keep_alive_version))
|
||||
|
||||
@@ -78,6 +78,12 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"type": "string",
|
||||
"description": "The expiration date of the variable",
|
||||
"format": "date-time"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -157,6 +163,12 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"type": "string",
|
||||
"description": "The new description of the variable"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path__body": {
|
||||
"type": "string",
|
||||
"description": "The path to the variable (body parameter)"
|
||||
@@ -244,6 +256,10 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"per_page": {
|
||||
"type": "integer",
|
||||
"description": "number of items to return for a given page (default 30, max 100)"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Filter by label"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -287,6 +303,12 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"resource_type": {
|
||||
"type": "string",
|
||||
"description": "The resource_type associated with the resource"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -355,6 +377,12 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"type": "string",
|
||||
"description": "The new resource_type to be associated with the resource"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path__body": {
|
||||
"type": "string",
|
||||
"description": "The path to the resource (body parameter)"
|
||||
@@ -437,6 +465,10 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Filter by label"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -544,6 +576,10 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"dedicated_worker": {
|
||||
"type": "boolean",
|
||||
"description": "(default regardless)\nIf true, show only scripts with dedicated_worker enabled.\nIf false, show only scripts with dedicated_worker disabled.\n"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Filter by label"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -555,7 +591,8 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
EndpointTool {
|
||||
name: Cow::Borrowed("createScript"),
|
||||
description: Cow::Borrowed("create script"),
|
||||
description: Cow::Borrowed("create script: Creates a new script when the path does not already exist.
|
||||
Creates a new version of an existing script when called with the same path and the current `parent_hash`"),
|
||||
instructions: Cow::Borrowed("To create a script, specify the path (e.g., 'f/my_folder/my_script'), the content (source code), and the language. For TypeScript, use 'bun' unless deno-specific APIs are needed."),
|
||||
path: Cow::Borrowed("/w/{workspace}/scripts/create"),
|
||||
method: Cow::Borrowed("POST"),
|
||||
@@ -578,7 +615,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative"
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
@@ -772,6 +809,10 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
"dedicated_worker": {
|
||||
"type": "boolean",
|
||||
"description": "(default regardless)\nIf true, show only flows with dedicated_worker enabled.\nIf false, show only flows with dedicated_worker disabled.\n"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Filter by label"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@@ -1095,7 +1136,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative"
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
@@ -1127,7 +1168,7 @@ pub fn all_tools() -> Vec<EndpointTool> {
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, duckdb, bunnative"
|
||||
"description": "Possible values: python3, deno, go, bash, powershell, postgresql, mysql, bigquery, snowflake, mssql, oracledb, graphql, nativets, bun, php, rust, ansible, csharp, nu, java, ruby, rlang, duckdb, bunnative"
|
||||
},
|
||||
"lock": {
|
||||
"type": "string",
|
||||
@@ -1691,6 +1732,12 @@ You should get the schema of the script or flow before creating the schedule to
|
||||
"preserve_permissioned_as": {
|
||||
"type": "boolean",
|
||||
"description": "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it."
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1894,6 +1941,12 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"description": "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2001,6 +2054,10 @@ You should get the schema of the script or flow before updating the schedule to
|
||||
"broad_filter": {
|
||||
"type": "string",
|
||||
"description": "broad search across multiple fields (case-insensitive substring match)"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Filter by label"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
@@ -92,11 +92,12 @@ pub struct TokenResponse {
|
||||
struct Logins {
|
||||
oauth: Vec<String>,
|
||||
saml: Option<String>,
|
||||
auto_login: Option<String>,
|
||||
}
|
||||
#[cfg(not(feature = "private"))]
|
||||
async fn list_logins() -> error::JsonResult<Logins> {
|
||||
// Implementation is not open source
|
||||
return Ok(Json(Logins { oauth: vec![], saml: None }));
|
||||
return Ok(Json(Logins { oauth: vec![], saml: None, auto_login: None }));
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
||||
@@ -189,6 +189,7 @@ async fn get_offboard_preview(
|
||||
"nats_trigger",
|
||||
"sqs_trigger",
|
||||
"gcp_trigger",
|
||||
"azure_trigger",
|
||||
"email_trigger",
|
||||
];
|
||||
let mut triggers = HashMap::new();
|
||||
@@ -748,6 +749,7 @@ async fn check_path_conflicts(
|
||||
"nats_trigger",
|
||||
"sqs_trigger",
|
||||
"gcp_trigger",
|
||||
"azure_trigger",
|
||||
"email_trigger",
|
||||
];
|
||||
|
||||
@@ -1020,6 +1022,7 @@ async fn offboard_user_from_workspace<'c>(
|
||||
"nats_trigger",
|
||||
"sqs_trigger",
|
||||
"gcp_trigger",
|
||||
"azure_trigger",
|
||||
"email_trigger",
|
||||
];
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ fn build_trigger_scope_domains() -> Vec<ScopeDomain> {
|
||||
("mqtt_triggers", "MQTT"),
|
||||
("sqs_triggers", "AWS SQS"),
|
||||
("gcp_triggers", "GCP Pub/Sub"),
|
||||
("azure_triggers", "Azure Event Grid"),
|
||||
("postgres_triggers", "PostgreSQL"),
|
||||
("email_triggers", "Email"),
|
||||
];
|
||||
|
||||
@@ -469,6 +469,7 @@ async fn restore_trigger(tx: &mut sqlx::PgConnection, item: &TrashItemWithData)
|
||||
"mqtt_trigger",
|
||||
"sqs_trigger",
|
||||
"gcp_trigger",
|
||||
"azure_trigger",
|
||||
"email_trigger",
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub use windmill_trigger_azure::*;
|
||||
@@ -81,6 +81,16 @@ pub fn generate_trigger_routers() -> Router {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))]
|
||||
{
|
||||
use crate::triggers::azure::AzureTrigger;
|
||||
|
||||
router = router.nest(
|
||||
AzureTrigger::ROUTE_PREFIX,
|
||||
complete_trigger_routes(AzureTrigger),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "postgres_trigger")]
|
||||
{
|
||||
use crate::triggers::postgres::PostgresTrigger;
|
||||
@@ -135,6 +145,7 @@ pub struct TriggersCount {
|
||||
mqtt_count: i64,
|
||||
sqs_count: i64,
|
||||
gcp_count: i64,
|
||||
azure_count: i64,
|
||||
nextcloud_count: i64,
|
||||
google_count: i64,
|
||||
github_count: i64,
|
||||
@@ -252,6 +263,17 @@ pub async fn get_triggers_count_internal(
|
||||
#[cfg(not(all(feature = "gcp_trigger", feature = "enterprise", feature = "private")))]
|
||||
let gcp_count = 0;
|
||||
|
||||
#[cfg(all(feature = "azure_trigger", feature = "enterprise", feature = "private"))]
|
||||
let azure_count = {
|
||||
use crate::triggers::azure::AzureTrigger;
|
||||
let count = AzureTrigger
|
||||
.trigger_count(&mut tx, w_id, is_flow, path)
|
||||
.await;
|
||||
count
|
||||
};
|
||||
#[cfg(not(all(feature = "azure_trigger", feature = "enterprise", feature = "private")))]
|
||||
let azure_count = 0;
|
||||
|
||||
#[cfg(all(feature = "smtp", feature = "enterprise", feature = "private"))]
|
||||
let email_count = {
|
||||
use crate::triggers::email::EmailTrigger;
|
||||
@@ -341,6 +363,7 @@ pub async fn get_triggers_count_internal(
|
||||
postgres_count,
|
||||
mqtt_count,
|
||||
gcp_count,
|
||||
azure_count,
|
||||
sqs_count,
|
||||
nextcloud_count,
|
||||
google_count,
|
||||
|
||||
@@ -67,6 +67,14 @@ pub fn start_all_listeners(db: DB, killpill_rx: &tokio::sync::broadcast::Receive
|
||||
listen_to(GcpTrigger, db.clone(), gcp_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "azure_trigger", feature = "enterprise", feature = "private"))]
|
||||
{
|
||||
let azure_killpill_rx = killpill_rx.resubscribe();
|
||||
use crate::triggers::azure::AzureTrigger;
|
||||
|
||||
listen_to(AzureTrigger, db.clone(), azure_killpill_rx);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "sqs_trigger", feature = "enterprise", feature = "private"))]
|
||||
{
|
||||
let gcp_killpill_rx = killpill_rx.resubscribe();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// Concrete trigger submodules (feature-gated)
|
||||
#[cfg(all(feature = "azure_trigger", feature = "enterprise", feature = "private"))]
|
||||
pub mod azure;
|
||||
#[cfg(all(feature = "smtp", feature = "private"))]
|
||||
pub mod email;
|
||||
#[cfg(all(feature = "gcp_trigger", feature = "enterprise", feature = "private"))]
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::{apps::AppWithLastVersion, db::DB, folders::Folder};
|
||||
feature = "kafka",
|
||||
feature = "sqs_trigger",
|
||||
feature = "gcp_trigger",
|
||||
feature = "azure_trigger",
|
||||
feature = "nats",
|
||||
feature = "smtp",
|
||||
),
|
||||
@@ -237,6 +238,13 @@ where
|
||||
if !preserve_extra_perms && obj.contains_key("extra_perms") {
|
||||
obj.remove("extra_perms");
|
||||
}
|
||||
if obj
|
||||
.get("default_permissioned_as")
|
||||
.and_then(|v| v.as_array())
|
||||
.is_some_and(|a| a.is_empty())
|
||||
{
|
||||
obj.remove("default_permissioned_as");
|
||||
}
|
||||
|
||||
serde_json::to_string_pretty(&obj).ok()
|
||||
})
|
||||
@@ -761,6 +769,23 @@ pub(crate) async fn tarball_workspace(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "azure_trigger", feature = "private"))]
|
||||
{
|
||||
use crate::triggers::azure::AzureTrigger;
|
||||
let handler = AzureTrigger;
|
||||
let azure_triggers = handler.list_triggers(&mut *tx, &w_id, None).await?;
|
||||
|
||||
for trigger in azure_triggers {
|
||||
let trigger_str = &to_string_without_metadata(&trigger, false, None).unwrap();
|
||||
archive
|
||||
.write_to_archive(
|
||||
&trigger_str,
|
||||
&format!("{}.azure_trigger.json", trigger.base.path),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "nats", feature = "private"))]
|
||||
{
|
||||
use crate::triggers::nats::NatsTrigger;
|
||||
|
||||
@@ -9,7 +9,7 @@ default = []
|
||||
enterprise = ["dep:aws-config"]
|
||||
instance_config_schema = ["dep:schemars"]
|
||||
local_reports = ["dep:rsa", "dep:aes-gcm"]
|
||||
private = ["dep:aws-sdk-rds", "dep:aws-sdk-secretsmanager"]
|
||||
private = ["dep:aws-sdk-rds", "dep:aws-sdk-secretsmanager", "dep:aws-config"]
|
||||
jemalloc = ["dep:tikv-jemalloc-ctl"]
|
||||
tantivy = []
|
||||
prometheus = ["dep:prometheus"]
|
||||
|
||||
@@ -50,6 +50,7 @@ pub const HUB_API_SECRET_SETTING: &str = "hub_api_secret";
|
||||
|
||||
pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation";
|
||||
pub const DISABLE_PASSWORD_LOGIN_SETTING: &str = "disable_password_login";
|
||||
pub const AUTO_LOGIN_PROVIDER_SETTING: &str = "auto_login_provider";
|
||||
pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
|
||||
pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url";
|
||||
pub const DISABLE_HUB_SETTING: &str = "disable_hub";
|
||||
|
||||
@@ -157,7 +157,7 @@ pub enum ObjectType {
|
||||
WorkspaceDependencies,
|
||||
}
|
||||
|
||||
pub const LATEST_GIT_SYNC_SCRIPT_PATH: &str = "hub/28191/sync-script-to-git-repo-windmill";
|
||||
pub const LATEST_GIT_SYNC_SCRIPT_PATH: &str = "hub/28213/sync-script-to-git-repo-windmill";
|
||||
|
||||
/// Prefix used to identify fork workspaces. A workspace whose id starts with this string is a
|
||||
/// fork of another workspace.
|
||||
|
||||
@@ -39,6 +39,7 @@ pub enum DeployedObject {
|
||||
MqttTrigger { path: String, parent_path: Option<String> },
|
||||
SqsTrigger { path: String, parent_path: Option<String> },
|
||||
GcpTrigger { path: String, parent_path: Option<String> },
|
||||
AzureTrigger { path: String, parent_path: Option<String> },
|
||||
EmailTrigger { path: String, parent_path: Option<String> },
|
||||
Settings { setting_type: String },
|
||||
Key { key_type: String },
|
||||
@@ -67,6 +68,7 @@ impl DeployedObject {
|
||||
DeployedObject::MqttTrigger { path, .. } => path.to_owned(),
|
||||
DeployedObject::SqsTrigger { path, .. } => path.to_owned(),
|
||||
DeployedObject::GcpTrigger { path, .. } => path.to_owned(),
|
||||
DeployedObject::AzureTrigger { path, .. } => path.to_owned(),
|
||||
DeployedObject::EmailTrigger { path, .. } => path.to_owned(),
|
||||
DeployedObject::Settings { .. } => "settings.yaml".to_string(),
|
||||
DeployedObject::Key { .. } => "encryption_key.yaml".to_string(),
|
||||
@@ -107,6 +109,7 @@ impl DeployedObject {
|
||||
DeployedObject::MqttTrigger { parent_path, .. } => parent_path.to_owned(),
|
||||
DeployedObject::SqsTrigger { parent_path, .. } => parent_path.to_owned(),
|
||||
DeployedObject::GcpTrigger { parent_path, .. } => parent_path.to_owned(),
|
||||
DeployedObject::AzureTrigger { parent_path, .. } => parent_path.to_owned(),
|
||||
DeployedObject::EmailTrigger { parent_path, .. } => parent_path.to_owned(),
|
||||
DeployedObject::Settings { .. } => None,
|
||||
DeployedObject::Key { .. } => None,
|
||||
@@ -135,6 +138,7 @@ impl DeployedObject {
|
||||
DeployedObject::MqttTrigger { .. } => "mqtt_trigger",
|
||||
DeployedObject::SqsTrigger { .. } => "sqs_trigger",
|
||||
DeployedObject::GcpTrigger { .. } => "gcp_trigger",
|
||||
DeployedObject::AzureTrigger { .. } => "azure_trigger",
|
||||
DeployedObject::EmailTrigger { .. } => "email_trigger",
|
||||
DeployedObject::Settings { .. } => "settings",
|
||||
DeployedObject::Key { .. } => "key",
|
||||
@@ -369,6 +373,10 @@ mod tests {
|
||||
DeployedObject::GcpTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
||||
"gcp_trigger"
|
||||
);
|
||||
assert_eq!(
|
||||
DeployedObject::AzureTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
||||
"azure_trigger"
|
||||
);
|
||||
assert_eq!(
|
||||
DeployedObject::EmailTrigger { path: "t".to_string(), parent_path: None }.get_kind(),
|
||||
"email_trigger"
|
||||
|
||||
@@ -21,6 +21,7 @@ postgres_trigger = []
|
||||
mqtt_trigger = []
|
||||
sqs_trigger = []
|
||||
gcp_trigger = []
|
||||
azure_trigger = []
|
||||
kafka = []
|
||||
nats = []
|
||||
openidconnect = ["windmill-common/openidconnect"]
|
||||
|
||||
@@ -1820,6 +1820,7 @@ async fn update_resource_type(
|
||||
any(
|
||||
feature = "sqs_trigger",
|
||||
feature = "gcp_trigger",
|
||||
feature = "azure_trigger",
|
||||
feature = "kafka",
|
||||
feature = "nats"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "windmill-trigger-azure"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_trigger_azure"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enterprise = ["windmill-common/enterprise", "windmill-store/enterprise", "windmill-trigger/enterprise"]
|
||||
private = ["windmill-common/private", "windmill-store/private"]
|
||||
|
||||
[dependencies]
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-store = { workspace = true, features = ["azure_trigger"] }
|
||||
windmill-trigger.workspace = true
|
||||
windmill-git-sync.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tracing.workspace = true
|
||||
async-trait.workspace = true
|
||||
itertools.workspace = true
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
bytes.workspace = true
|
||||
http.workspace = true
|
||||
quick_cache.workspace = true
|
||||
lazy_static.workspace = true
|
||||
reqwest.workspace = true
|
||||
chrono.workspace = true
|
||||
thiserror.workspace = true
|
||||
sha2.workspace = true
|
||||
rand.workspace = true
|
||||
hex.workspace = true
|
||||
constant_time_eq.workspace = true
|
||||
@@ -0,0 +1,65 @@
|
||||
#[allow(unused)]
|
||||
#[cfg(feature = "private")]
|
||||
pub use super::handler_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use {
|
||||
super::AzureTrigger,
|
||||
async_trait::async_trait,
|
||||
sqlx::PgConnection,
|
||||
windmill_api_auth::ApiAuthed,
|
||||
windmill_common::{
|
||||
error::{Error, Result},
|
||||
DB,
|
||||
},
|
||||
windmill_git_sync::DeployedObject,
|
||||
windmill_trigger::{TriggerCrud, TriggerData},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
#[async_trait]
|
||||
impl TriggerCrud for AzureTrigger {
|
||||
type Trigger = ();
|
||||
type TriggerConfig = ();
|
||||
type TriggerConfigRequest = ();
|
||||
type TestConnectionConfig = ();
|
||||
|
||||
const TABLE_NAME: &'static str = "";
|
||||
const TRIGGER_TYPE: &'static str = "";
|
||||
const SUPPORTS_SERVER_STATE: bool = false;
|
||||
const SUPPORTS_TEST_CONNECTION: bool = false;
|
||||
const ROUTE_PREFIX: &'static str = "/azure_triggers";
|
||||
const DEPLOYMENT_NAME: &'static str = "";
|
||||
const IS_ALLOWED_ON_CLOUD: bool = false;
|
||||
|
||||
fn get_deployed_object(path: String, parent_path: Option<String>) -> DeployedObject {
|
||||
DeployedObject::AzureTrigger { path, parent_path }
|
||||
}
|
||||
|
||||
async fn create_trigger(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_executor: &mut PgConnection,
|
||||
_authed: &ApiAuthed,
|
||||
_w_id: &str,
|
||||
_trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
Err(Error::BadRequest(
|
||||
"Azure triggers are not available in open source version".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn update_trigger(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_executor: &mut PgConnection,
|
||||
_authed: &ApiAuthed,
|
||||
_workspace_id: &str,
|
||||
_path: &str,
|
||||
_trigger: TriggerData<Self::TriggerConfigRequest>,
|
||||
) -> Result<()> {
|
||||
Err(Error::BadRequest(
|
||||
"Azure triggers are not available in open source version".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#[cfg(feature = "private")]
|
||||
mod handler_ee;
|
||||
pub mod handler_oss;
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
mod listener_ee;
|
||||
pub mod listener_oss;
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
mod mod_ee;
|
||||
#[cfg(feature = "private")]
|
||||
pub use mod_ee::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AzureTrigger;
|
||||
@@ -0,0 +1,52 @@
|
||||
#[allow(unused)]
|
||||
#[cfg(feature = "private")]
|
||||
pub use super::listener_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use {
|
||||
super::AzureTrigger,
|
||||
serde_json::{value::RawValue, Value},
|
||||
std::{collections::HashMap, sync::Arc},
|
||||
tokio::sync::RwLock,
|
||||
windmill_common::{error::Result, jobs::JobTriggerKind, triggers::TriggerKind, DB},
|
||||
windmill_trigger::{listener::ListeningTrigger, trigger_helpers::TriggerJobArgs, Listener},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
impl TriggerJobArgs for AzureTrigger {
|
||||
type Payload = Value;
|
||||
const TRIGGER_KIND: TriggerKind = TriggerKind::Azure;
|
||||
fn v1_payload_fn(_payload: &Self::Payload) -> HashMap<String, Box<RawValue>> {
|
||||
HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
#[async_trait::async_trait]
|
||||
impl Listener for AzureTrigger {
|
||||
type Consumer = ();
|
||||
type Extra = ();
|
||||
type ExtraState = ();
|
||||
const JOB_TRIGGER_KIND: JobTriggerKind = JobTriggerKind::Azure;
|
||||
|
||||
async fn get_consumer(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_listening_trigger: &ListeningTrigger<Self::TriggerConfig>,
|
||||
_err_message: Arc<RwLock<Option<String>>>,
|
||||
_killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> Result<Option<Self::Consumer>> {
|
||||
Ok(None)
|
||||
}
|
||||
async fn consume(
|
||||
&self,
|
||||
_db: &DB,
|
||||
_consumer: Self::Consumer,
|
||||
_listening_trigger: &ListeningTrigger<Self::TriggerConfig>,
|
||||
_err_message: Arc<RwLock<Option<String>>>,
|
||||
_killpill_rx: tokio::sync::broadcast::Receiver<()>,
|
||||
_extra_state: Option<&Self::ExtraState>,
|
||||
) {
|
||||
()
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,10 @@ pub fn is_none_or_false(b: &Option<bool>) -> bool {
|
||||
b.is_none() || !b.unwrap()
|
||||
}
|
||||
|
||||
fn is_false(b: &bool) -> bool {
|
||||
!*b
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::FromRow)]
|
||||
pub struct ListableFlow {
|
||||
pub workspace_id: String,
|
||||
@@ -921,6 +925,8 @@ pub enum FlowModuleValue {
|
||||
AIAgent {
|
||||
input_transforms: HashMap<String, InputTransform>,
|
||||
tools: Vec<AgentTool>,
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
omit_output_from_conversation: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -955,6 +961,7 @@ struct UntaggedFlowModuleValue {
|
||||
modules_node: Option<FlowNodeId>,
|
||||
assets: Option<Vec<AssetWithAltAccessType>>,
|
||||
tools: Option<Vec<AgentTool>>,
|
||||
omit_output_from_conversation: Option<bool>,
|
||||
pass_flow_input_directly: Option<bool>,
|
||||
squash: Option<bool>,
|
||||
#[serde(flatten)]
|
||||
@@ -1056,6 +1063,9 @@ impl<'de> Deserialize<'de> for FlowModuleValue {
|
||||
tools: untagged
|
||||
.tools
|
||||
.ok_or_else(|| serde::de::Error::missing_field("tools"))?,
|
||||
omit_output_from_conversation: untagged
|
||||
.omit_output_from_conversation
|
||||
.unwrap_or(false),
|
||||
}),
|
||||
other => Err(serde::de::Error::unknown_variant(
|
||||
other,
|
||||
@@ -1173,4 +1183,37 @@ mod tests {
|
||||
let val: FlowValue = serde_json::from_value(input).unwrap();
|
||||
assert_eq!(val.modules.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_agent_omit_output_from_conversation_defaults_to_false() {
|
||||
let input = json!({
|
||||
"type": "aiagent",
|
||||
"tools": [],
|
||||
"input_transforms": {}
|
||||
});
|
||||
|
||||
let val: FlowModuleValue = serde_json::from_value(input).unwrap();
|
||||
let FlowModuleValue::AIAgent { omit_output_from_conversation, .. } = val else {
|
||||
panic!("expected aiagent module");
|
||||
};
|
||||
|
||||
assert!(!omit_output_from_conversation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_agent_omit_output_from_conversation_preserves_true() {
|
||||
let input = json!({
|
||||
"type": "aiagent",
|
||||
"tools": [],
|
||||
"input_transforms": {},
|
||||
"omit_output_from_conversation": true
|
||||
});
|
||||
|
||||
let val: FlowModuleValue = serde_json::from_value(input).unwrap();
|
||||
let FlowModuleValue::AIAgent { omit_output_from_conversation, .. } = val else {
|
||||
panic!("expected aiagent module");
|
||||
};
|
||||
|
||||
assert!(omit_output_from_conversation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ pub enum JobTriggerKind {
|
||||
Postgres,
|
||||
Schedule,
|
||||
Gcp,
|
||||
Azure,
|
||||
Nextcloud,
|
||||
Google,
|
||||
Github,
|
||||
@@ -58,6 +59,7 @@ impl std::fmt::Display for JobTriggerKind {
|
||||
JobTriggerKind::Postgres => "postgres",
|
||||
JobTriggerKind::Schedule => "schedule",
|
||||
JobTriggerKind::Gcp => "gcp",
|
||||
JobTriggerKind::Azure => "azure",
|
||||
JobTriggerKind::Nextcloud => "nextcloud",
|
||||
JobTriggerKind::Google => "google",
|
||||
JobTriggerKind::Github => "github",
|
||||
|
||||
@@ -19,6 +19,7 @@ pub enum TriggerKind {
|
||||
Sqs,
|
||||
Postgres,
|
||||
Gcp,
|
||||
Azure,
|
||||
Nextcloud,
|
||||
Google,
|
||||
Github,
|
||||
@@ -38,6 +39,7 @@ impl TriggerKind {
|
||||
TriggerKind::Sqs => "sqs".to_string(),
|
||||
TriggerKind::Postgres => "postgres".to_string(),
|
||||
TriggerKind::Gcp => "gcp".to_string(),
|
||||
TriggerKind::Azure => "azure".to_string(),
|
||||
TriggerKind::Nextcloud => "nextcloud".to_string(),
|
||||
TriggerKind::Google => "google".to_string(),
|
||||
TriggerKind::Github => "github".to_string(),
|
||||
@@ -59,6 +61,7 @@ impl fmt::Display for TriggerKind {
|
||||
TriggerKind::Sqs => "sqs",
|
||||
TriggerKind::Postgres => "postgres",
|
||||
TriggerKind::Gcp => "gcp",
|
||||
TriggerKind::Azure => "azure",
|
||||
TriggerKind::Nextcloud => "nextcloud",
|
||||
TriggerKind::Google => "google",
|
||||
TriggerKind::Github => "github",
|
||||
|
||||
@@ -75,6 +75,7 @@ pub struct ToolExecutionContext<'a> {
|
||||
// Optional streaming & chat
|
||||
pub stream_event_processor: Option<&'a StreamEventProcessor>,
|
||||
pub flow_context: &'a mut FlowContext,
|
||||
pub omit_output_from_conversation: bool,
|
||||
pub previous_result: &'a Option<Box<RawValue>>,
|
||||
pub id_context: &'a Option<crate::js_eval::IdContext>,
|
||||
|
||||
@@ -780,6 +781,10 @@ async fn add_tool_message_to_chat(
|
||||
content: &str,
|
||||
success: bool,
|
||||
) {
|
||||
if ctx.omit_output_from_conversation {
|
||||
return;
|
||||
}
|
||||
|
||||
let chat_enabled = ctx
|
||||
.flow_context
|
||||
.flow_status
|
||||
|
||||
@@ -275,7 +275,9 @@ pub async fn handle_ai_agent_job(
|
||||
|
||||
let summary = module.summary.clone();
|
||||
|
||||
let FlowModuleValue::AIAgent { tools, .. } = module.get_value()? else {
|
||||
let FlowModuleValue::AIAgent { tools, omit_output_from_conversation, .. } =
|
||||
module.get_value()?
|
||||
else {
|
||||
return Err(Error::internal_err(
|
||||
"AI agent module is not an AI agent".to_string(),
|
||||
));
|
||||
@@ -504,6 +506,7 @@ pub async fn handle_ai_agent_job(
|
||||
killpill_rx,
|
||||
has_stream,
|
||||
has_websearch,
|
||||
omit_output_from_conversation,
|
||||
cancel_rx,
|
||||
tool_abort_handles.clone(),
|
||||
);
|
||||
@@ -600,6 +603,7 @@ pub async fn run_agent(
|
||||
killpill_rx: &mut tokio::sync::broadcast::Receiver<()>,
|
||||
has_stream: &mut bool,
|
||||
has_websearch: bool,
|
||||
omit_output_from_conversation: bool,
|
||||
|
||||
// cancellation signal from parent
|
||||
cancel_rx: tokio::sync::watch::Receiver<bool>,
|
||||
@@ -860,6 +864,7 @@ pub async fn run_agent(
|
||||
.as_ref()
|
||||
.and_then(|fs| fs.chat_input_enabled)
|
||||
.unwrap_or(false);
|
||||
let persist_output_to_conversation = chat_enabled && !omit_output_from_conversation;
|
||||
|
||||
let step_name = get_step_name_from_flow(summary.as_deref(), effective_flow_step_id);
|
||||
|
||||
@@ -1061,7 +1066,7 @@ pub async fn run_agent(
|
||||
agent_action: Some(AgentAction::WebSearch {}),
|
||||
..Default::default()
|
||||
});
|
||||
if chat_enabled {
|
||||
if persist_output_to_conversation {
|
||||
if let Some(memory_id) = memory_id {
|
||||
let agent_job_id = job.id;
|
||||
let db_clone = db.clone();
|
||||
@@ -1113,7 +1118,7 @@ pub async fn run_agent(
|
||||
content = Some(OpenAIContent::Text(response_content.clone()));
|
||||
|
||||
// Add assistant message to conversation if chat_input_enabled
|
||||
if chat_enabled && !response_content.is_empty() {
|
||||
if persist_output_to_conversation && !response_content.is_empty() {
|
||||
if let Some(memory_id) = memory_id {
|
||||
let agent_job_id = job.id;
|
||||
let db_clone = db.clone();
|
||||
@@ -1195,6 +1200,7 @@ pub async fn run_agent(
|
||||
killpill_rx,
|
||||
stream_event_processor: stream_event_processor.as_ref(),
|
||||
flow_context: &mut flow_context,
|
||||
omit_output_from_conversation,
|
||||
previous_result: &previous_result,
|
||||
id_context: &id_context,
|
||||
tool_abort_handles: tool_abort_handles.clone(),
|
||||
@@ -1230,7 +1236,7 @@ pub async fn run_agent(
|
||||
let content = to_raw_value(&s3_object);
|
||||
|
||||
// Add assistant message to conversation if chat_input_enabled
|
||||
if chat_enabled {
|
||||
if persist_output_to_conversation {
|
||||
if let Some(memory_id) = memory_id {
|
||||
let agent_job_id = job.id;
|
||||
let db_clone = db.clone();
|
||||
|
||||
@@ -6,7 +6,9 @@ use sqlx::types::Json;
|
||||
use tokio::process::Command;
|
||||
use windmill_common::client::AuthedClient;
|
||||
use windmill_common::error::Error;
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
use windmill_common::worker::{to_raw_value, write_file, Connection};
|
||||
use windmill_common::workspace_dependencies::clean_lock_from_annotations;
|
||||
use windmill_queue::{
|
||||
append_logs, CanceledBy, MiniPulledJob, INIT_SCRIPT_PATH_PREFIX, PERIODIC_SCRIPT_PATH_PREFIX,
|
||||
};
|
||||
@@ -417,10 +419,16 @@ pub async fn handle_powershell_job(
|
||||
// Resolve modules from workspace dependencies and/or script imports
|
||||
let all_modules = match &maybe_lock {
|
||||
MaybeLock::Resolved { lock } if !lock.is_empty() => {
|
||||
// Deployed script with lock: parse workspace deps from lock, merge with script imports
|
||||
let ws_modules = parse_modules_json(lock)?;
|
||||
// Deployed script with lock: strip workspace-dependencies annotation header,
|
||||
// parse the modules.json body, and merge with script imports.
|
||||
let cleaned = clean_lock_from_annotations(lock, ScriptLang::Powershell);
|
||||
let script_modules = parse_script_imports(content);
|
||||
merge_module_requests(ws_modules, script_modules)
|
||||
if cleaned.trim().is_empty() {
|
||||
script_modules
|
||||
} else {
|
||||
let ws_modules = parse_modules_json(&cleaned)?;
|
||||
merge_module_requests(ws_modules, script_modules)
|
||||
}
|
||||
}
|
||||
MaybeLock::Unresolved { workspace_dependencies } => {
|
||||
let script_modules = parse_script_imports(content);
|
||||
@@ -1070,6 +1078,19 @@ mod tests {
|
||||
assert!(parse_modules_json("not json").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_modules_json_after_cleaning_header() {
|
||||
// Simulates the deployed-script lock: workspace-dependencies header
|
||||
// prepended to the modules.json content. `clean_lock_from_annotations`
|
||||
// strips header lines so the remaining body can be JSON-parsed.
|
||||
let lock = "# workspace-dependencies-mode: manual\n# workspace-dependencies: default:abc123\n{\"modules\": {\"PSWriteColor\": \"1.0.0\"}}";
|
||||
let cleaned = clean_lock_from_annotations(lock, ScriptLang::Powershell);
|
||||
let modules = parse_modules_json(&cleaned).unwrap();
|
||||
assert_eq!(modules.len(), 1);
|
||||
assert_eq!(modules[0].name, "PSWriteColor");
|
||||
assert_eq!(modules[0].version, Some("1.0.0".to_string()));
|
||||
}
|
||||
|
||||
// --- parse_script_imports tests ---
|
||||
|
||||
#[test]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user