mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-04 08:01:54 +00:00
Merge remote-tracking branch 'upstream/main' into feat/batch-jobs-ui
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
name: Build windows executable for this branch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
SQLX_OFFLINE: true
|
||||
DISABLE_EMBEDDING: true
|
||||
RUST_LOG: info
|
||||
|
||||
jobs:
|
||||
cargo_build_windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Read EE repo commit hash
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ee_repo_ref = Get-Content .\backend\ee-repo-ref.txt
|
||||
echo "ee_repo_ref=$ee_repo_ref" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
|
||||
- name: Checkout windmill-ee-private repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: windmill-labs/windmill-ee-private
|
||||
path: ./windmill-ee-private
|
||||
ref: ${{ env.ee_repo_ref }}
|
||||
token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Substitute EE code
|
||||
shell: bash
|
||||
run: |
|
||||
./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
|
||||
- name: Cargo build windows
|
||||
timeout-minutes: 90
|
||||
run: |
|
||||
vcpkg.exe install openssl-windows:x64-windows
|
||||
vcpkg.exe install openssl:x64-windows-static
|
||||
vcpkg.exe integrate install
|
||||
$env:VCPKGRS_DYNAMIC=1
|
||||
$env:OPENSSL_DIR="${Env:VCPKG_INSTALLATION_ROOT}\installed\x64-windows-static"
|
||||
mkdir frontend/build && cd backend
|
||||
New-Item -Path . -Name "windmill-api/openapi-deref.yaml" -ItemType "File" -Force
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,deno_core
|
||||
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windmill-ee-binary
|
||||
path: ./backend/target/release/windmill-ee.exe
|
||||
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## [1.423.2](https://github.com/windmill-labs/windmill/compare/v1.423.1...v1.423.2) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix intempestive expr type change in flow transform ([84bd06e](https://github.com/windmill-labs/windmill/commit/84bd06e90650e949c11e2b09447a7bad1ab60a95))
|
||||
|
||||
## [1.423.1](https://github.com/windmill-labs/windmill/compare/v1.423.0...v1.423.1) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **autoscaling:** autoscaling thresholds to be >= and not > ([de00944](https://github.com/windmill-labs/windmill/commit/de00944f09699ff33a382f5a14f515ccf90b2454))
|
||||
|
||||
## [1.423.0](https://github.com/windmill-labs/windmill/compare/v1.422.1...v1.423.0) (2024-11-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* s3 input available for public apps ([#4685](https://github.com/windmill-labs/windmill/issues/4685)) ([1671005](https://github.com/windmill-labs/windmill/commit/167100510032ab53cd609fb2c7629e67faceb093))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent underflow for autoscaling scalein ([5e9870a](https://github.com/windmill-labs/windmill/commit/5e9870a8a993032ec7d45c62e0023008da42c74a))
|
||||
* support multiple pip-extra-index-url with commas ([50861fc](https://github.com/windmill-labs/windmill/commit/50861fccc0cd86d4c76120c3306adf94f375330e))
|
||||
|
||||
## [1.422.1](https://github.com/windmill-labs/windmill/compare/v1.422.0...v1.422.1) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix password inputs ([e5e174a](https://github.com/windmill-labs/windmill/commit/e5e174ae9516f4c6b94ceb6e258b467f5c9a1f1a))
|
||||
|
||||
## [1.422.0](https://github.com/windmill-labs/windmill/compare/v1.421.2...v1.422.0) (2024-11-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* expandable subflows in flows ([#4683](https://github.com/windmill-labs/windmill/issues/4683)) ([d44976f](https://github.com/windmill-labs/windmill/commit/d44976f35e45ade510d1ec220b5a1503e11f3db9))
|
||||
* **frontend:** critical alerts UI ([#4653](https://github.com/windmill-labs/windmill/issues/4653)) ([d9148ea](https://github.com/windmill-labs/windmill/commit/d9148eaa78680a93d81d71847a7df67e01f3c110))
|
||||
|
||||
## [1.421.2](https://github.com/windmill-labs/windmill/compare/v1.421.1...v1.421.2) (2024-11-08)
|
||||
|
||||
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO alerts (alert_type, message) VALUES ('recovered_critical_error', $1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "00ce4ed3ca0eac7cb6283b047353a64b9e78c4beb423f04baef9a53fbf87e9f9"
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, alert_type, message, created_at, acknowledged \n FROM alerts \n WHERE acknowledged = $1\n ORDER BY created_at DESC \n LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "alert_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "message",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "acknowledged",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "0b955f2cff82a2d4ba3840588143e08952f029480d4a42503ecc3c5e70437995"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT raw_flow->'failure_module' != 'null'::jsonb\n FROM completed_job\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "31dd23f6768052e486668172e15eba1a3f9b6a8e5678a7ee8e5456ff4405d6f9"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('recovered_critical_error', $1, $2)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3a94ad52c6b7cde844fa868167248cd9ff63e5fdfa1d93d8fbec32a257b6b05e"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO alerts (alert_type, message) VALUES ('critical_error', $1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4c0067c2135a259aea5cc2db60f7375a9a33671be8ef406427d90f67a98c9c9f"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('critical_error', $1, $2)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4d22084a5d9860832f30e8f08cbfa1848ed3c1336fa4790f45b8189c4ac97d91"
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH uuid_table as (\n select gen_random_uuid() as uuid from generate_series(1, $11)\n )\n INSERT INTO queue \n (id, script_hash, script_path, job_kind, language, args, tag, created_by, permissioned_as, email, scheduled_for, workspace_id, concurrent_limit, concurrency_time_window_s, timeout)\n (SELECT uuid, $1, $2, $3, $4, ('{ \"uuid\": \"' || uuid || '\" }')::jsonb, $5, $6, $7, $8, $9, $10, $12, $13, $14 FROM uuid_table) \n RETURNING id",
|
||||
"query": "WITH uuid_table as (\n select gen_random_uuid() as uuid from generate_series(1, $11)\n )\n INSERT INTO queue \n (id, script_hash, script_path, job_kind, language, args, tag, created_by, permissioned_as, email, scheduled_for, workspace_id, concurrent_limit, concurrency_time_window_s, timeout, raw_code, raw_lock, raw_flow, flow_status)\n (SELECT uuid, $1, $2, $3, $4, ('{ \"uuid\": \"' || uuid || '\" }')::jsonb, $5, $6, $7, $8, $9, $10, $12, $13, $14, $15, $16, $17, $18 FROM uuid_table) \n RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -72,12 +72,16 @@
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Int4"
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0a686ca61444d7ad7484071727aa039a6ea6697e5a49a633b767c052aa3e0a18"
|
||||
"hash": "a69ba7471d1a5faa145bebbd17d43e6dbe02e9e92ebbc31a50c99c3a04719284"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts SET acknowledged = true WHERE acknowledged = false",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a7c5008aa7ea43d0afac7d9f19846976ed7af2e90270902f001115e023cb947d"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT raw_flow->'failure_module' != 'null'::jsonb\n FROM queue\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "aa6907b7266ee437dfbd77a9bf6c047fda88f3e72a0d10323a5e4020cf857c42"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT flow_version.value AS \"value: sqlx::types::Json<Box<RawValue>>\" FROM flow \n LEFT JOIN flow_version\n ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.path = $1 AND flow.workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value: sqlx::types::Json<Box<RawValue>>",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ae543dfa106fa6ad4e9bf45cda1110d4702a80f455c63af6fcbd7cf45bbc4f7a"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts SET acknowledged = true WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "be3ae231557e794172336bc27d725f862dcf039bbb3d75ced9d54c86f53d2580"
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, alert_type, message, created_at, acknowledged \n FROM alerts \n ORDER BY created_at DESC \n LIMIT $1 OFFSET $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "alert_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "message",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "acknowledged",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "cc5ab80241b88c5befea279f16c4ec68cec17b31dcd277b321f652917346496b"
|
||||
}
|
||||
Generated
+156
-124
@@ -96,9 +96,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.18"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
@@ -576,7 +576,7 @@ dependencies = [
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -659,7 +659,7 @@ dependencies = [
|
||||
"sha2 0.10.8",
|
||||
"smart-default",
|
||||
"smol_str",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
@@ -692,7 +692,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"log",
|
||||
"pin-project",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -1363,9 +1363,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.5.2"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5327f6c99920069d1fe374aa743be1af0031dea9f250852cdf1ae6a0861ee24"
|
||||
checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03"
|
||||
dependencies = [
|
||||
"borsh-derive",
|
||||
"cfg_aliases",
|
||||
@@ -1373,9 +1373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "borsh-derive"
|
||||
version = "1.5.2"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10aedd8f1a81a8aafbfde924b0e3061cd6fedd6f6bbcfc6a76e6fd426d7bfe26"
|
||||
checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro-crate",
|
||||
@@ -1545,7 +1545,7 @@ dependencies = [
|
||||
"rand_distr",
|
||||
"rayon",
|
||||
"safetensors",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"yoke",
|
||||
"zip",
|
||||
]
|
||||
@@ -1562,7 +1562,7 @@ dependencies = [
|
||||
"rayon",
|
||||
"safetensors",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1598,9 +1598,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.37"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
|
||||
checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -1714,9 +1714,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1724,9 +1724,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.20"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1748,9 +1748,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
@@ -1783,13 +1783,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "comfy-table"
|
||||
version = "7.1.1"
|
||||
version = "7.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
|
||||
checksum = "e0d05af1e006a2407bedef5af410552494ce5be9090444dbbcb57258c1af3d56"
|
||||
dependencies = [
|
||||
"strum 0.26.3",
|
||||
"strum_macros 0.26.4",
|
||||
"unicode-width",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1816,7 +1816,7 @@ dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.14",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -1943,9 +1943,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||
checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -2080,9 +2080,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
|
||||
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
@@ -2553,7 +2553,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"sourcemap 9.0.1",
|
||||
"sourcemap 9.1.0",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_config",
|
||||
@@ -2576,8 +2576,8 @@ dependencies = [
|
||||
"swc_visit",
|
||||
"swc_visit_macros",
|
||||
"text_lines",
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
"thiserror 1.0.69",
|
||||
"unicode-width 0.1.14",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -2715,7 +2715,7 @@ dependencies = [
|
||||
"strum 0.25.0",
|
||||
"strum_macros 0.25.3",
|
||||
"syn 2.0.87",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2725,7 +2725,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4889646c1ce8437a6fde3acb057fd7e2d039e62c61f5063fc125ed1ede114dc6"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -3654,7 +3654,7 @@ dependencies = [
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3808,7 +3808,7 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3900,7 +3900,7 @@ checksum = "5eb37859fda6792e95231aef1c5838f4043ec0ee352d8313421e311c606df612"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"strum 0.25.0",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
|
||||
@@ -4067,7 +4067,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
@@ -4560,15 +4560,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.8"
|
||||
version = "0.17.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
|
||||
checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
|
||||
dependencies = [
|
||||
"console",
|
||||
"instant",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"unicode-width 0.2.0",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5319,7 +5319,7 @@ dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"termcolor",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5345,7 +5345,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socket2 0.5.7",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-util",
|
||||
@@ -5386,7 +5386,7 @@ dependencies = [
|
||||
"sha2 0.10.8",
|
||||
"smallvec",
|
||||
"subprocess",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"uuid 1.11.0",
|
||||
"zstd 0.13.2",
|
||||
@@ -5578,7 +5578,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2 0.10.8",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -5700,7 +5700,7 @@ dependencies = [
|
||||
"serde_with",
|
||||
"sha2 0.10.8",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -6352,7 +6352,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.87",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"typify",
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -6391,7 +6391,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"parking_lot",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6428,9 +6428,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.23"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205"
|
||||
checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -6519,9 +6519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.5"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684"
|
||||
checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"pin-project-lite",
|
||||
@@ -6530,26 +6530,29 @@ dependencies = [
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.16",
|
||||
"socket2 0.5.7",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.8"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6"
|
||||
checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.2.15",
|
||||
"rand 0.8.5",
|
||||
"ring 0.17.8",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6762,7 +6765,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6773,7 +6776,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
@@ -6788,9 +6791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -7075,7 +7078,7 @@ dependencies = [
|
||||
"radix_fmt",
|
||||
"rand 0.8.5",
|
||||
"redis",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7184,16 +7187,16 @@ checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"toml 0.8.19",
|
||||
"toolchain_find",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.39"
|
||||
version = "0.38.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
|
||||
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
@@ -7290,6 +7293,9 @@ name = "rustls-pki-types"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-tokio-stream"
|
||||
@@ -7427,7 +7433,7 @@ dependencies = [
|
||||
"quick-xml 0.30.0",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
"uuid 1.11.0",
|
||||
]
|
||||
@@ -7539,9 +7545,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.12.0"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
|
||||
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -7579,9 +7585,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -7620,9 +7626,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7680,7 +7686,7 @@ checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7691,7 +7697,7 @@ checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7736,7 +7742,7 @@ dependencies = [
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"v8",
|
||||
]
|
||||
|
||||
@@ -7900,7 +7906,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
]
|
||||
|
||||
@@ -8043,9 +8049,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sourcemap"
|
||||
version = "9.0.1"
|
||||
version = "9.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86721155737d89818020dc35713595c6e7b21016073c101e6590c47ca58c2d16"
|
||||
checksum = "4d146f02f4bbbabbbe3da0f9cd3ea2ab779defc4ed1f070b5bd83ea48ed78811"
|
||||
dependencies = [
|
||||
"base64-simd 0.7.0",
|
||||
"bitvec",
|
||||
@@ -8114,7 +8120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1008d95d2ec2d062959352527be30e10fec42a1aa5e5a48d990a5ff0fb9bdc0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8197,7 +8203,7 @@ dependencies = [
|
||||
"sha2 0.10.8",
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
@@ -8284,7 +8290,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"uuid 1.11.0",
|
||||
"whoami",
|
||||
@@ -8326,7 +8332,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"uuid 1.11.0",
|
||||
"whoami",
|
||||
@@ -8533,13 +8539,13 @@ dependencies = [
|
||||
"rustc-hash 1.1.0",
|
||||
"serde",
|
||||
"siphasher",
|
||||
"sourcemap 9.0.1",
|
||||
"sourcemap 9.1.0",
|
||||
"swc_allocator",
|
||||
"swc_atoms",
|
||||
"swc_eq_ignore_macros",
|
||||
"swc_visit",
|
||||
"tracing",
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.14",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -8597,7 +8603,7 @@ dependencies = [
|
||||
"num-bigint",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"sourcemap 9.0.1",
|
||||
"sourcemap 9.1.0",
|
||||
"swc_allocator",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
@@ -8902,7 +8908,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"enum-as-inner",
|
||||
"libc",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -8993,7 +8999,7 @@ dependencies = [
|
||||
"tantivy-stacker",
|
||||
"tantivy-tokenizer-api",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"uuid 1.11.0",
|
||||
"winapi",
|
||||
@@ -9139,18 +9145,38 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.68"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.68"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9198,7 +9224,7 @@ dependencies = [
|
||||
"pretty-hex",
|
||||
"rustls-native-certs 0.6.3",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
@@ -9324,7 +9350,7 @@ dependencies = [
|
||||
"rayon",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -9355,7 +9381,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spm_precompiled",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"unicode-normalization-alignments",
|
||||
"unicode-segmentation",
|
||||
"unicode_categories",
|
||||
@@ -9457,7 +9483,7 @@ checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -9703,7 +9729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -9853,7 +9879,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -9876,7 +9902,7 @@ dependencies = [
|
||||
"resolv-conf",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"trust-dns-proto",
|
||||
@@ -9903,7 +9929,7 @@ dependencies = [
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
@@ -9954,7 +9980,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde_json",
|
||||
"syn 2.0.87",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -10116,6 +10142,12 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
@@ -10578,7 +10610,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -10620,7 +10652,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -10705,7 +10737,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -10723,7 +10755,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -10736,7 +10768,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rsmq_async",
|
||||
@@ -10751,7 +10783,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -10783,7 +10815,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"thiserror 1.0.69",
|
||||
"tikv-jemalloc-ctl",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -10797,7 +10829,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"rsmq_async",
|
||||
@@ -10812,7 +10844,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -10834,7 +10866,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
@@ -10846,7 +10878,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -10855,7 +10887,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10867,7 +10899,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -10879,7 +10911,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10891,7 +10923,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -10902,7 +10934,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -10913,7 +10945,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -10931,7 +10963,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -10948,7 +10980,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10960,7 +10992,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -10978,7 +11010,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.15",
|
||||
@@ -10999,7 +11031,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -11009,7 +11041,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -11042,7 +11074,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -11052,7 +11084,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -29,7 +29,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
6534b0f31fb4a75dd706fca2ce91e37e77e4ad02
|
||||
e3df64af864f9d3546a97ff21f719c553e34ff1c
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE alerts DROP COLUMN acknowledged;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Step 1: Add the new column 'acknowledged' to the 'alerts' table
|
||||
ALTER TABLE alerts
|
||||
ADD COLUMN acknowledged BOOLEAN DEFAULT NULL;
|
||||
|
||||
-- Step 2: Update all existing rows to set 'acknowledged' to true
|
||||
-- we don't want to pop up notifications to all users after migrations
|
||||
-- but only show new alerts from the point of the upgrade
|
||||
UPDATE alerts
|
||||
SET acknowledged = TRUE;
|
||||
+8
-2
@@ -36,7 +36,7 @@ use windmill_common::{
|
||||
ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
|
||||
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
@@ -78,7 +78,7 @@ use crate::monitor::{
|
||||
reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_jwt_secret_setting,
|
||||
reload_license_key, reload_npm_config_registry_setting, reload_pip_index_url_setting,
|
||||
reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config,
|
||||
reload_worker_config,
|
||||
reload_worker_config, reload_critical_alert_mute_ui_setting,
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
@@ -817,6 +817,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::error!(error = %e, "Could not reload jwt secret setting");
|
||||
}
|
||||
},
|
||||
CRITICAL_ALERT_MUTE_UI_SETTING => {
|
||||
tracing::info!("Critical alert UI setting changed");
|
||||
if let Err(e) = reload_critical_alert_mute_ui_setting(&db).await {
|
||||
tracing::error!(error = %e, "Could not reload critical alert UI setting");
|
||||
}
|
||||
},
|
||||
a @_ => {
|
||||
tracing::info!("Unrecognized Global Setting Change Payload: {:?}", a);
|
||||
}
|
||||
|
||||
+17
-2
@@ -39,7 +39,7 @@ use windmill_common::{
|
||||
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
|
||||
HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
|
||||
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
@@ -55,7 +55,7 @@ use windmill_common::{
|
||||
WORKER_GROUP,
|
||||
},
|
||||
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
|
||||
METRICS_DEBUG_ENABLED, METRICS_ENABLED,
|
||||
METRICS_DEBUG_ENABLED, METRICS_ENABLED, CRITICAL_ALERT_MUTE_UI_ENABLED
|
||||
};
|
||||
use windmill_queue::cancel_job;
|
||||
use windmill_worker::{
|
||||
@@ -131,6 +131,10 @@ pub async fn initial_load(
|
||||
tracing::error!("Error loading expose debug metrics: {e:#}");
|
||||
}
|
||||
|
||||
if let Err(e) = reload_critical_alert_mute_ui_setting(db).await {
|
||||
tracing::error!("Error loading critical alert mute ui setting: {e:#}");
|
||||
}
|
||||
|
||||
if let Err(e) = load_tag_per_workspace_enabled(db).await {
|
||||
tracing::error!("Error loading default tag per workpsace: {e:#}");
|
||||
}
|
||||
@@ -226,6 +230,17 @@ pub async fn load_tag_per_workspace_workspaces(db: &DB) -> error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reload_critical_alert_mute_ui_setting(db: &DB) -> error::Result<()> {
|
||||
let mute = load_value_from_global_settings(db, CRITICAL_ALERT_MUTE_UI_SETTING).await;
|
||||
match mute {
|
||||
Ok(Some(serde_json::Value::Bool(t))) => {
|
||||
CRITICAL_ALERT_MUTE_UI_ENABLED.store(t, Ordering::Relaxed);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_metrics_debug_enabled(db: &DB) -> error::Result<()> {
|
||||
let metrics_enabled = load_value_from_global_settings(db, EXPOSE_DEBUG_METRICS_SETTING).await;
|
||||
match metrics_enabled {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1374
-657
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.421.2
|
||||
version: 1.423.2
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -190,7 +190,7 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/users/{username}:
|
||||
/w/{workspace}/users/get/{username}:
|
||||
get:
|
||||
summary: get user (require admin privilege)
|
||||
operationId: getUser
|
||||
@@ -773,6 +773,78 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/settings/critical_alerts:
|
||||
get:
|
||||
summary: Get all critical alerts
|
||||
operationId: getCriticalAlerts
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
- in: query
|
||||
name: page
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
description: The page number to retrieve (minimum value is 1)
|
||||
- in: query
|
||||
name: page_size
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
maximum: 100
|
||||
description: Number of alerts per page (maximum is 100)
|
||||
- in: query
|
||||
name: acknowledged
|
||||
schema:
|
||||
type: boolean
|
||||
nullable: true
|
||||
description: Filter by acknowledgment status; true for acknowledged, false for unacknowledged, and omit for all alerts
|
||||
responses:
|
||||
"200":
|
||||
description: Successfully retrieved all critical alerts
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CriticalAlert'
|
||||
|
||||
/settings/critical_alerts/{id}/acknowledge:
|
||||
post:
|
||||
summary: Acknowledge a critical alert
|
||||
operationId: acknowledgeCriticalAlert
|
||||
tags:
|
||||
- setting
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
description: The ID of the critical alert to acknowledge
|
||||
responses:
|
||||
"200":
|
||||
description: Successfully acknowledged the critical alert
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
example: "Critical alert acknowledged"
|
||||
|
||||
/settings/critical_alerts/acknowledge_all:
|
||||
post:
|
||||
summary: Acknowledge all unacknowledged critical alerts
|
||||
operationId: acknowledgeAllCriticalAlerts
|
||||
tags:
|
||||
- setting
|
||||
responses:
|
||||
"200":
|
||||
description: Successfully acknowledged all unacknowledged critical alerts.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
example: "All unacknowledged critical alerts acknowledged"
|
||||
|
||||
/settings/test_license_key:
|
||||
post:
|
||||
@@ -12114,6 +12186,10 @@ components:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
s3_inputs:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
execution_mode:
|
||||
type: string
|
||||
enum: [viewer, publisher, anonymous]
|
||||
@@ -12721,3 +12797,23 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
CriticalAlert:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: Unique identifier for the alert
|
||||
alert_type:
|
||||
type: string
|
||||
description: Type of alert (e.g., critical_error)
|
||||
message:
|
||||
type: string
|
||||
description: The message content of the alert
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Time when the alert was created
|
||||
acknowledged:
|
||||
type: boolean
|
||||
nullable: true
|
||||
description: Acknowledgment status of the alert, can be true, false, or null if not set
|
||||
|
||||
@@ -7,6 +7,12 @@ use std::collections::HashMap;
|
||||
* Please see the included NOTICE for copyright information and
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::{job_helpers_ee::{
|
||||
get_random_file_name, get_s3_resource, get_workspace_s3_resource, upload_file_internal,
|
||||
UploadFileResponse,
|
||||
}, users::fetch_api_authed_from_permissioned_as};
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
resources::get_resource_value_interpolated_internal,
|
||||
@@ -23,7 +29,13 @@ use axum::{
|
||||
Router,
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
#[cfg(feature = "parquet")]
|
||||
use itertools::Itertools;
|
||||
use magic_crypt::MagicCryptTrait;
|
||||
#[cfg(feature = "parquet")]
|
||||
use object_store::{Attribute, Attributes};
|
||||
#[cfg(feature = "parquet")]
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, value::RawValue};
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -32,6 +44,8 @@ use sqlx::{types::Uuid, FromRow};
|
||||
use std::str;
|
||||
use windmill_audit::audit_ee::audit_log;
|
||||
use windmill_audit::ActionKind;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::build_object_store_client;
|
||||
use windmill_common::{
|
||||
apps::ListAppQuery,
|
||||
db::UserDB,
|
||||
@@ -69,6 +83,7 @@ pub fn workspaced_service() -> Router {
|
||||
pub fn unauthed_service() -> Router {
|
||||
Router::new()
|
||||
.route("/execute_component/*path", post(execute_component))
|
||||
.route("/upload_s3_file/*path", post(upload_s3_file_from_app))
|
||||
.route("/public_app/:secret", get(get_public_app_by_secret))
|
||||
.route("/public_resource/*path", get(get_public_resource))
|
||||
}
|
||||
@@ -179,6 +194,14 @@ pub struct PolicyTriggerableInputs {
|
||||
allow_user_resources: AllowUserResources,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct S3Input {
|
||||
allowed_resources: Vec<String>,
|
||||
allow_user_resources: bool,
|
||||
allow_workspace_resource: bool,
|
||||
file_key_regex: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Policy {
|
||||
pub on_behalf_of: Option<String>,
|
||||
@@ -192,6 +215,7 @@ pub struct Policy {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub triggerables_v2: Option<HashMap<String, PolicyTriggerableInputs>>,
|
||||
pub execution_mode: ExecutionMode,
|
||||
pub s3_inputs: Option<Vec<S3Input>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -432,9 +456,7 @@ async fn get_latest_version(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
|
||||
) -> JsonResult<Option<AppHistory>> {
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let row = sqlx::query!(
|
||||
"SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg
|
||||
@@ -457,7 +479,6 @@ async fn get_latest_version(
|
||||
} else {
|
||||
return Ok(Json(None));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async fn update_app_history(
|
||||
@@ -1067,6 +1088,49 @@ fn digest(code: &str) -> String {
|
||||
format!("rawscript/{:x}", result)
|
||||
}
|
||||
|
||||
async fn get_on_behalf_details_from_policy_and_authed(
|
||||
policy: &Policy,
|
||||
opt_authed: &Option<ApiAuthed>,
|
||||
) -> Result<(String, String, String)> {
|
||||
let (username, permissioned_as, email) = match policy.execution_mode {
|
||||
ExecutionMode::Anonymous => {
|
||||
let username = opt_authed
|
||||
.as_ref()
|
||||
.map(|a| a.username.clone())
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
let (permissioned_as, email) = get_on_behalf_of(&policy)?;
|
||||
(username, permissioned_as, email)
|
||||
}
|
||||
ExecutionMode::Publisher => {
|
||||
let username = opt_authed
|
||||
.as_ref()
|
||||
.map(|a| a.username.clone())
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"publisher execution mode requires authentication".to_string(),
|
||||
)
|
||||
})?;
|
||||
let (permissioned_as, email) = get_on_behalf_of(&policy)?;
|
||||
(username, permissioned_as, email)
|
||||
}
|
||||
ExecutionMode::Viewer => {
|
||||
let (username, email) = opt_authed
|
||||
.as_ref()
|
||||
.map(|a| (a.username.clone(), a.email.clone()))
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest("Required to be authed in viewer mode".to_string())
|
||||
})?;
|
||||
(
|
||||
username.clone(),
|
||||
username_to_permissioned_as(&username),
|
||||
email,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok((username, permissioned_as, email))
|
||||
}
|
||||
|
||||
async fn execute_component(
|
||||
OptAuthed(opt_authed): OptAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -1129,6 +1193,7 @@ async fn execute_component(
|
||||
triggerables_v2: Some(hm),
|
||||
on_behalf_of: None,
|
||||
on_behalf_of_email: None,
|
||||
s3_inputs: None,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -1146,41 +1211,8 @@ async fn execute_component(
|
||||
}
|
||||
};
|
||||
|
||||
let (username, permissioned_as, email) = match policy.execution_mode {
|
||||
ExecutionMode::Anonymous => {
|
||||
let username = opt_authed
|
||||
.as_ref()
|
||||
.map(|a| a.username.clone())
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
let (permissioned_as, email) = get_on_behalf_of(&policy)?;
|
||||
(username, permissioned_as, email)
|
||||
}
|
||||
ExecutionMode::Publisher => {
|
||||
let username = opt_authed
|
||||
.as_ref()
|
||||
.map(|a| a.username.clone())
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"publisher execution mode requires authentication".to_string(),
|
||||
)
|
||||
})?;
|
||||
let (permissioned_as, email) = get_on_behalf_of(&policy)?;
|
||||
(username, permissioned_as, email)
|
||||
}
|
||||
ExecutionMode::Viewer => {
|
||||
let (username, email) = opt_authed
|
||||
.as_ref()
|
||||
.map(|a| (a.username.clone(), a.email.clone()))
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest("Required to be authed in viewer mode".to_string())
|
||||
})?;
|
||||
(
|
||||
username.clone(),
|
||||
username_to_permissioned_as(&username),
|
||||
email,
|
||||
)
|
||||
}
|
||||
};
|
||||
let (username, permissioned_as, email) =
|
||||
get_on_behalf_details_from_policy_and_authed(&policy, &opt_authed).await?;
|
||||
|
||||
let (job_payload, (args, job_id), tag) = match payload {
|
||||
ExecuteApp { args, component, raw_code: Some(raw_code), path: None, .. } => {
|
||||
@@ -1249,6 +1281,248 @@ async fn execute_component(
|
||||
Ok(uuid.to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "parquet"))]
|
||||
async fn upload_s3_file_from_app() -> Result<()> {
|
||||
return Err(Error::BadRequest(
|
||||
"This endpoint requires the parquet feature to be enabled".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct UploadFileToS3Query {
|
||||
file_key: Option<String>,
|
||||
file_extension: Option<String>,
|
||||
s3_resource_path: Option<String>,
|
||||
content_type: Option<String>,
|
||||
content_disposition: Option<String>,
|
||||
force_viewer_file_key_regex: Option<String>,
|
||||
force_viewer_allow_user_resources: Option<bool>,
|
||||
force_viewer_allow_workspace_resource: Option<bool>,
|
||||
force_viewer_allowed_resources: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
async fn upload_s3_file_from_app(
|
||||
OptAuthed(opt_authed): OptAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Query(query): Query<UploadFileToS3Query>,
|
||||
request: axum::extract::Request,
|
||||
) -> JsonResult<UploadFileResponse> {
|
||||
let policy = if let Some(file_key_regex) = query.force_viewer_file_key_regex {
|
||||
Some(Policy {
|
||||
execution_mode: ExecutionMode::Viewer,
|
||||
triggerables: None,
|
||||
triggerables_v2: None,
|
||||
on_behalf_of: None,
|
||||
on_behalf_of_email: None,
|
||||
s3_inputs: Some(vec![S3Input {
|
||||
file_key_regex: file_key_regex,
|
||||
allow_user_resources: query.force_viewer_allow_user_resources.unwrap_or(false),
|
||||
allow_workspace_resource: query
|
||||
.force_viewer_allow_workspace_resource
|
||||
.unwrap_or(false),
|
||||
allowed_resources: query
|
||||
.force_viewer_allowed_resources
|
||||
.map(|s| s.split(',').map(|s| s.to_string()).collect())
|
||||
.unwrap_or_default(),
|
||||
}]),
|
||||
})
|
||||
} else {
|
||||
let policy_o = sqlx::query_scalar!(
|
||||
"SELECT policy from app WHERE path = $1 AND workspace_id = $2",
|
||||
&path.0,
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
policy_o
|
||||
.map(|p| serde_json::from_value::<Policy>(p).map_err(to_anyhow))
|
||||
.transpose()?
|
||||
};
|
||||
|
||||
let user_db = UserDB::new(db.clone());
|
||||
|
||||
let (s3_resource_opt, file_key) = if policy.as_ref().is_some_and(|p| p.s3_inputs.is_some()) {
|
||||
let policy = policy.unwrap();
|
||||
let s3_inputs = policy.s3_inputs.as_ref().unwrap();
|
||||
|
||||
let (username, permissioned_as, email) =
|
||||
get_on_behalf_details_from_policy_and_authed(&policy, &opt_authed).await?;
|
||||
|
||||
let on_behalf_authed =
|
||||
fetch_api_authed_from_permissioned_as(permissioned_as, email, &w_id, &db, username)
|
||||
.await?;
|
||||
|
||||
if let Some(file_key) = query.file_key {
|
||||
// file key is provided => requires workspace, user or list policy and must match the regex
|
||||
let matching_s3_inputs = if let Some(ref s3_resource_path) = query.s3_resource_path {
|
||||
s3_inputs
|
||||
.iter()
|
||||
.filter(|s3_input| {
|
||||
s3_input.allowed_resources.contains(s3_resource_path)
|
||||
|| s3_input.allow_user_resources
|
||||
})
|
||||
.sorted_by_key(|i| i.allow_user_resources) // consider user resources last
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
s3_inputs
|
||||
.iter()
|
||||
.filter(|s3_input| s3_input.allow_workspace_resource)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let matched_input = matching_s3_inputs.iter().find(|s3_input| {
|
||||
match Regex::new(&s3_input.file_key_regex) {
|
||||
Ok(re) => re.is_match(&file_key),
|
||||
Err(e) => {
|
||||
tracing::error!("Error compiling regex: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(matched_input) = matched_input {
|
||||
if let Some(ref s3_resource_path) = query.s3_resource_path {
|
||||
if matched_input.allow_user_resources {
|
||||
if let Some(authed) = opt_authed {
|
||||
(
|
||||
Some(
|
||||
get_s3_resource(
|
||||
&authed,
|
||||
&db,
|
||||
Some(user_db),
|
||||
"",
|
||||
&w_id,
|
||||
s3_resource_path,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
file_key,
|
||||
)
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"User resources are not allowed without being logged in"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
(
|
||||
Some(
|
||||
get_s3_resource(
|
||||
&on_behalf_authed,
|
||||
&db,
|
||||
Some(user_db),
|
||||
"",
|
||||
&w_id,
|
||||
s3_resource_path,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
file_key,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let (_, s3_resource_opt) =
|
||||
get_workspace_s3_resource(&on_behalf_authed, &db, None, "", &w_id, None)
|
||||
.await?;
|
||||
(s3_resource_opt, file_key)
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
"No matching s3 resource found for the given file key".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// no file key => requires unnamed upload policy => allow workspace resource and file_key_regex is empty
|
||||
let has_unnamed_policy = s3_inputs.iter().any(|s3_input| {
|
||||
s3_input.allow_workspace_resource && s3_input.file_key_regex.is_empty()
|
||||
});
|
||||
|
||||
if !has_unnamed_policy {
|
||||
return Err(Error::BadRequest(
|
||||
"no policy found for unnamed s3 file uplooad".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// for now, we place all files into `windmill_uploads` folder with a random name
|
||||
// TODO: make the folder configurable via the workspace settings
|
||||
let file_key = get_random_file_name(query.file_extension);
|
||||
|
||||
let (_, s3_resource_opt) =
|
||||
get_workspace_s3_resource(&on_behalf_authed, &db, None, "", &w_id, None).await?;
|
||||
|
||||
(s3_resource_opt, file_key)
|
||||
}
|
||||
} else {
|
||||
// backward compatibility (no policy)
|
||||
// if no policy but logged in, use the user's auth to get the s3 resource
|
||||
if let Some(authed) = opt_authed {
|
||||
let file_key = query
|
||||
.file_key
|
||||
.unwrap_or_else(|| get_random_file_name(query.file_extension));
|
||||
|
||||
if let Some(ref s3_resource_path) = query.s3_resource_path {
|
||||
(
|
||||
Some(
|
||||
get_s3_resource(
|
||||
&authed,
|
||||
&db,
|
||||
Some(user_db),
|
||||
"",
|
||||
&w_id,
|
||||
s3_resource_path,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
file_key,
|
||||
)
|
||||
} else {
|
||||
let (_, s3_resource) =
|
||||
get_workspace_s3_resource(&authed, &db, None, "", &w_id, None).await?;
|
||||
|
||||
(s3_resource, file_key)
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BadRequest("Missing s3 policy".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let s3_resource = s3_resource_opt.ok_or(Error::InternalErr(
|
||||
"No files storage resource defined at the workspace level".to_string(),
|
||||
))?;
|
||||
let s3_client = build_object_store_client(&s3_resource).await?;
|
||||
|
||||
let options = Attributes::from_iter(vec![
|
||||
(
|
||||
Attribute::ContentType,
|
||||
query.content_type.unwrap_or_else(|| {
|
||||
mime_guess::from_path(&file_key)
|
||||
.first_or_octet_stream()
|
||||
.to_string()
|
||||
}),
|
||||
),
|
||||
(
|
||||
Attribute::ContentDisposition,
|
||||
query.content_disposition.unwrap_or("inline".to_string()),
|
||||
),
|
||||
])
|
||||
.into();
|
||||
|
||||
upload_file_internal(s3_client, &file_key, request, options).await?;
|
||||
|
||||
return Ok(Json(UploadFileResponse { file_key }));
|
||||
}
|
||||
|
||||
fn get_on_behalf_of(policy: &Policy) -> Result<(String, String)> {
|
||||
let permissioned_as = policy
|
||||
.on_behalf_of
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
use axum::Router;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::s3_helpers::StorageResourceType;
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use crate::db::{ApiAuthed, DB};
|
||||
#[cfg(feature = "parquet")]
|
||||
use object_store::{ObjectStore, PutMultipartOpts};
|
||||
#[cfg(feature = "parquet")]
|
||||
use std::sync::Arc;
|
||||
use windmill_common::error;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::{db::UserDB, s3_helpers::ObjectStoreResource};
|
||||
#[derive(Serialize)]
|
||||
pub struct UploadFileResponse {
|
||||
pub file_key: String,
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
@@ -21,3 +33,29 @@ pub async fn get_workspace_s3_resource<'c>(
|
||||
// implementation is not open source
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
pub fn get_random_file_name(_file_extension: Option<String>) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn get_s3_resource<'c>(
|
||||
_authed: &ApiAuthed,
|
||||
_db: &DB,
|
||||
_user_db: Option<UserDB>,
|
||||
_token: &str,
|
||||
_w_id: &str,
|
||||
_resource_path: &str,
|
||||
_resource_type: Option<StorageResourceType>,
|
||||
_job_id: Option<Uuid>,
|
||||
) -> error::Result<ObjectStoreResource> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn upload_file_internal(
|
||||
_s3_client: Arc<dyn ObjectStore>,
|
||||
_file_key: &str,
|
||||
_request: axum::extract::Request,
|
||||
_options: PutMultipartOpts,
|
||||
) -> error::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{self, to_anyhow, Error},
|
||||
flow_status::{Approval, FlowStatus, FlowStatusModule},
|
||||
flows::FlowValue,
|
||||
flows::{add_virtual_items_if_necessary, FlowValue},
|
||||
jobs::{script_path_to_payload, CompletedJob, JobKind, JobPayload, QueuedJob, RawCode},
|
||||
oauth2::HmacSha256,
|
||||
scripts::{ScriptHash, ScriptLang},
|
||||
@@ -4275,18 +4275,26 @@ async fn run_flow_dependencies_job(
|
||||
wait_result
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct BatchRawScript {
|
||||
content: String,
|
||||
language: Option<ScriptLang>,
|
||||
lock: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct BatchInfo {
|
||||
kind: String,
|
||||
flow_value: Option<FlowValue>,
|
||||
path: Option<String>,
|
||||
rawscript: Option<BatchRawScript>,
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn add_batch_jobs(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Extension(_rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Path((w_id, n)): Path<(String, i32)>,
|
||||
Json(batch_info): Json<BatchInfo>,
|
||||
) -> error::JsonResult<Vec<Uuid>> {
|
||||
@@ -4302,6 +4310,10 @@ async fn add_batch_jobs(
|
||||
concurrent_limit,
|
||||
concurrent_time_window_s,
|
||||
timeout,
|
||||
raw_code,
|
||||
raw_lock,
|
||||
raw_flow,
|
||||
flow_status
|
||||
) = match batch_info.kind.as_str() {
|
||||
"script" => {
|
||||
if let Some(path) = batch_info.path {
|
||||
@@ -4329,6 +4341,10 @@ async fn add_batch_jobs(
|
||||
concurrent_limit,
|
||||
concurrency_time_window_s,
|
||||
timeout,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
@@ -4336,61 +4352,72 @@ async fn add_batch_jobs(
|
||||
))?
|
||||
}
|
||||
}
|
||||
"flow" => {
|
||||
let mut uuids: Vec<Uuid> = Vec::new();
|
||||
let payload = if let Some(ref fv) = batch_info.flow_value {
|
||||
JobPayload::RawFlow { value: fv.clone(), path: None, restarted_from: None }
|
||||
} else {
|
||||
if let Some(path) = batch_info.path.as_ref() {
|
||||
JobPayload::Flow {
|
||||
path: path.to_string(),
|
||||
dedicated_worker: None,
|
||||
apply_preprocessor: false,
|
||||
}
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Path is required if no value is not provided"
|
||||
))?
|
||||
}
|
||||
};
|
||||
let mut tx = PushIsolationLevel::IsolatedRoot(db.clone(), rsmq);
|
||||
for _ in 0..n {
|
||||
let ehm = HashMap::new();
|
||||
let (uuid, ntx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
payload.clone(),
|
||||
PushArgs::from(&ehm),
|
||||
authed.display_username(),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
"rawscript" => {
|
||||
if let Some(rawscript) = batch_info.rawscript {
|
||||
(
|
||||
None,
|
||||
None,
|
||||
JobKind::Preview,
|
||||
rawscript.language,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
true,
|
||||
Some(rawscript.content),
|
||||
rawscript.lock,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(&authed.clone().into()),
|
||||
)
|
||||
.await?;
|
||||
tx = PushIsolationLevel::Transaction(ntx);
|
||||
uuids.push(uuid);
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"rawscript is required for `rawscript` kind"
|
||||
))?
|
||||
}
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => {
|
||||
tx.commit().await?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
return Ok(Json(uuids));
|
||||
}
|
||||
"flow" => {
|
||||
let (mut value, job_kind, path) = if let Some(value) = batch_info.flow_value {
|
||||
(value, JobKind::FlowPreview, None)
|
||||
} else if let Some(path) = batch_info.path {
|
||||
let value_json = sqlx::query!(
|
||||
"SELECT flow_version.value AS \"value: sqlx::types::Json<Box<RawValue>>\" FROM flow
|
||||
LEFT JOIN flow_version
|
||||
ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]
|
||||
WHERE flow.path = $1 AND flow.workspace_id = $2",
|
||||
&path, &w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("not found flow at path {:?}", path)))?;
|
||||
let value =
|
||||
serde_json::from_str::<FlowValue>(value_json.value.get()).map_err(|err| {
|
||||
Error::InternalErr(format!(
|
||||
"could not convert json to flow for {path}: {err:?}"
|
||||
))
|
||||
})?;
|
||||
(value, JobKind::Flow, Some(path))
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Path is required if no value is not provided"
|
||||
))?
|
||||
};
|
||||
add_virtual_items_if_necessary(&mut value.modules);
|
||||
let flow_status = FlowStatus::new(&value);
|
||||
(
|
||||
None, // script_hash
|
||||
path, // script_path
|
||||
job_kind, // job_kind
|
||||
None, // language
|
||||
None, // dedicated_worker
|
||||
value.concurrency_key.clone(), // custom_concurrency_key
|
||||
value.concurrent_limit.clone(), // concurrent_limit
|
||||
value.concurrency_time_window_s, // concurrency_time_window_s
|
||||
None, // timeout
|
||||
None, // raw_code
|
||||
None, // raw_lock
|
||||
Some(value), // raw_flow
|
||||
Some(flow_status), // flow_status
|
||||
)
|
||||
}
|
||||
"noop" => (
|
||||
None,
|
||||
@@ -4402,6 +4429,10 @@ async fn add_batch_jobs(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
_ => {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
@@ -4428,8 +4459,8 @@ async fn add_batch_jobs(
|
||||
select gen_random_uuid() as uuid from generate_series(1, $11)
|
||||
)
|
||||
INSERT INTO queue
|
||||
(id, script_hash, script_path, job_kind, language, args, tag, created_by, permissioned_as, email, scheduled_for, workspace_id, concurrent_limit, concurrency_time_window_s, timeout)
|
||||
(SELECT uuid, $1, $2, $3, $4, ('{ "uuid": "' || uuid || '" }')::jsonb, $5, $6, $7, $8, $9, $10, $12, $13, $14 FROM uuid_table)
|
||||
(id, script_hash, script_path, job_kind, language, args, tag, created_by, permissioned_as, email, scheduled_for, workspace_id, concurrent_limit, concurrency_time_window_s, timeout, raw_code, raw_lock, raw_flow, flow_status)
|
||||
(SELECT uuid, $1, $2, $3, $4, ('{ "uuid": "' || uuid || '" }')::jsonb, $5, $6, $7, $8, $9, $10, $12, $13, $14, $15, $16, $17, $18 FROM uuid_table)
|
||||
RETURNING id"#,
|
||||
hash.map(|h| h.0),
|
||||
path,
|
||||
@@ -4444,7 +4475,11 @@ async fn add_batch_jobs(
|
||||
n,
|
||||
concurrent_limit,
|
||||
concurrent_time_window_s,
|
||||
timeout
|
||||
timeout,
|
||||
raw_code,
|
||||
raw_lock,
|
||||
raw_flow.map(sqlx::types::Json) as Option<sqlx::types::Json<FlowValue>>,
|
||||
flow_status.map(sqlx::types::Json) as Option<sqlx::types::Json<FlowStatus>>
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
|
||||
@@ -58,7 +58,10 @@ pub fn global_service() -> Router {
|
||||
)
|
||||
.route("/renew_license_key", post(renew_license_key))
|
||||
.route("/customer_portal", post(create_customer_portal_session))
|
||||
.route("/test_critical_channels", post(test_critical_channels));
|
||||
.route("/test_critical_channels", post(test_critical_channels))
|
||||
.route("/critical_alerts", get(get_critical_alerts))
|
||||
.route("/critical_alerts/:id/acknowledge", post(acknowledge_critical_alert))
|
||||
.route("/critical_alerts/acknowledge_all", post(acknowledge_all_critical_alerts));
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
{
|
||||
@@ -430,3 +433,117 @@ pub async fn test_critical_channels(
|
||||
pub async fn test_critical_channels() -> Result<String> {
|
||||
Ok("Critical channels require EE".to_string())
|
||||
}
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CriticalAlert {
|
||||
id: i32,
|
||||
alert_type: String,
|
||||
message: String,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
acknowledged: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct AlertQueryParams {
|
||||
pub page: Option<i32>,
|
||||
pub page_size: Option<i32>,
|
||||
pub acknowledged: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn get_critical_alerts(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
Query(params): Query<AlertQueryParams>,
|
||||
) -> JsonResult<Vec<CriticalAlert>> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
// Default pagination values if not provided
|
||||
let page = params.page.unwrap_or(1).max(1);
|
||||
let page_size = params.page_size.unwrap_or(10).min(100) as i64;
|
||||
let offset = ((page - 1) * page_size as i32) as i64;
|
||||
|
||||
let alerts = if let Some(acknowledged) = params.acknowledged {
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, acknowledged
|
||||
FROM alerts
|
||||
WHERE acknowledged = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3",
|
||||
acknowledged,
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, acknowledged
|
||||
FROM alerts
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2",
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
};
|
||||
|
||||
Ok(Json(alerts))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
pub async fn get_critical_alerts() -> error::Error {
|
||||
error::Error::NotFound("Critical Alerts require EE".to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn acknowledge_critical_alert(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
Path(id): Path<i32>,
|
||||
) -> error::Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE alerts SET acknowledged = true WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Acknowledged critical alert with id: {}", id);
|
||||
Ok("Critical alert acknowledged".to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
pub async fn acknowledge_critical_alert() -> error::Error {
|
||||
error::Error::NotFound("Critical Alerts require EE".to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn acknowledge_all_critical_alerts(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
) -> error::Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE alerts SET acknowledged = true WHERE acknowledged = false"
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Acknowledged all unacknowledged critical alerts");
|
||||
Ok("All unacknowledged critical alerts acknowledged".to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
pub async fn acknowledge_all_critical_alerts() -> error::Error {
|
||||
error::Error::NotFound("Critical Alerts require EE".to_string())
|
||||
}
|
||||
|
||||
@@ -746,10 +746,20 @@ pub async fn fetch_api_authed(
|
||||
username_override: String,
|
||||
) -> error::Result<ApiAuthed> {
|
||||
let permissioned_as = username_to_permissioned_as(username.as_str());
|
||||
fetch_api_authed_from_permissioned_as(permissioned_as, email, w_id, db, username_override).await
|
||||
}
|
||||
|
||||
pub async fn fetch_api_authed_from_permissioned_as(
|
||||
permissioned_as: String,
|
||||
email: String,
|
||||
w_id: &str,
|
||||
db: &DB,
|
||||
username_override: String,
|
||||
) -> error::Result<ApiAuthed> {
|
||||
let authed =
|
||||
fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?;
|
||||
Ok(ApiAuthed {
|
||||
username: username,
|
||||
username: authed.username,
|
||||
email: email,
|
||||
is_admin: authed.is_admin,
|
||||
is_operator: authed.is_operator,
|
||||
|
||||
@@ -16,6 +16,7 @@ use rand::Rng;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
more_serde::{default_empty_string, default_id, default_null, default_true, is_default},
|
||||
scripts::{Schema, ScriptHash, ScriptLang},
|
||||
};
|
||||
@@ -651,3 +652,29 @@ pub fn add_virtual_items_if_necessary(modules: &mut Vec<FlowModule>) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn has_failure_module<'c>(flow: sqlx::types::Uuid, db: &sqlx::Pool<sqlx::Postgres>, completed: bool) -> Result<bool, Error> {
|
||||
if completed {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT raw_flow->'failure_module' != 'null'::jsonb
|
||||
FROM completed_job
|
||||
WHERE id = $1",
|
||||
flow
|
||||
)
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT raw_flow->'failure_module' != 'null'::jsonb
|
||||
FROM queue
|
||||
WHERE id = $1",
|
||||
flow
|
||||
)
|
||||
}
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"error during retrieval of has_failure_module: {e:#}"
|
||||
))
|
||||
})
|
||||
.map(|v| v.unwrap_or(false))
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation
|
||||
pub const HUB_BASE_URL_SETTING: &str = "hub_base_url";
|
||||
pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url";
|
||||
pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels";
|
||||
pub const CRITICAL_ALERT_MUTE_UI_SETTING: &str = "critical_alert_mute_ui";
|
||||
pub const DEV_INSTANCE_SETTING: &str = "dev_instance";
|
||||
pub const JWT_SECRET_SETTING: &str = "jwt_secret";
|
||||
pub const EMAIL_DOMAIN_SETTING: &str = "email_domain";
|
||||
|
||||
@@ -86,6 +86,8 @@ lazy_static::lazy_static! {
|
||||
pub static ref METRICS_ENABLED: AtomicBool = AtomicBool::new(std::env::var("METRICS_PORT").is_ok() || std::env::var("METRICS_ADDR").is_ok());
|
||||
pub static ref METRICS_DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub static ref CRITICAL_ALERT_MUTE_UI_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub static ref BASE_URL: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
|
||||
pub static ref IS_READY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ pub const DEFAULT_PER_PAGE: usize = 1000;
|
||||
pub const GIT_VERSION: &str =
|
||||
git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
use crate::CRITICAL_ALERT_MUTE_UI_ENABLED;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
@@ -244,9 +247,12 @@ pub fn generate_lock_id(database_name: &str) -> i64 {
|
||||
pub async fn report_critical_error(error_message: String, _db: DB) -> () {
|
||||
tracing::error!("CRITICAL ERROR: {error_message}");
|
||||
|
||||
let mute = CRITICAL_ALERT_MUTE_UI_ENABLED.load(Ordering::Relaxed);
|
||||
|
||||
if let Err(err) = sqlx::query!(
|
||||
"INSERT INTO alerts (alert_type, message) VALUES ('critical_error', $1)",
|
||||
error_message
|
||||
"INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('critical_error', $1, $2)",
|
||||
error_message,
|
||||
mute
|
||||
)
|
||||
.execute(&_db)
|
||||
.await
|
||||
@@ -261,9 +267,12 @@ pub async fn report_critical_error(error_message: String, _db: DB) -> () {
|
||||
pub async fn report_recovered_critical_error(message: String, _db: DB) -> () {
|
||||
tracing::info!("RECOVERED CRITICAL ERROR: {message}");
|
||||
|
||||
let mute = CRITICAL_ALERT_MUTE_UI_ENABLED.load(Ordering::Relaxed);
|
||||
|
||||
if let Err(err) = sqlx::query!(
|
||||
"INSERT INTO alerts (alert_type, message) VALUES ('recovered_critical_error', $1)",
|
||||
message
|
||||
"INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('recovered_critical_error', $1, $2)",
|
||||
message,
|
||||
mute
|
||||
)
|
||||
.execute(&_db)
|
||||
.await
|
||||
|
||||
@@ -71,6 +71,9 @@ use windmill_common::BASE_URL;
|
||||
#[cfg(feature = "cloud")]
|
||||
use windmill_common::users::SUPERADMIN_SYNC_EMAIL;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::flows::has_failure_module;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
|
||||
@@ -482,13 +485,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Deserialize)]
|
||||
struct RawFlowFailureModule {
|
||||
#[cfg(feature = "enterprise")]
|
||||
failure_module: Option<Box<RawValue>>,
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub async fn add_completed_job_error<R: rsmq_async::RsmqConnection + Clone + Send>(
|
||||
db: &Pool<Postgres>,
|
||||
@@ -952,15 +948,7 @@ pub async fn add_completed_job<
|
||||
} else if !skip_downstream_error_handlers
|
||||
&& (matches!(queued_job.job_kind, JobKind::Script)
|
||||
|| matches!(queued_job.job_kind, JobKind::Flow)
|
||||
&& queued_job
|
||||
.raw_flow
|
||||
.as_ref()
|
||||
.and_then(|v| {
|
||||
serde_json::from_str::<RawFlowFailureModule>((**v).get())
|
||||
.ok()
|
||||
.and_then(|v| v.failure_module)
|
||||
})
|
||||
.is_none())
|
||||
&& !has_failure_module(job_id, db, true).await.unwrap_or(false))
|
||||
&& queued_job.parent_job.is_none()
|
||||
{
|
||||
let result = serde_json::from_str(
|
||||
|
||||
@@ -48,6 +48,7 @@ use futures::{
|
||||
|
||||
use crate::common::{resolve_job_timeout, OccupancyMetrics};
|
||||
use crate::job_logger::{append_job_logs, append_with_limit, LARGE_LOG_THRESHOLD_SIZE};
|
||||
use crate::job_logger_ee::process_streaming_log_lines;
|
||||
use crate::{MAX_RESULT_SIZE, MAX_WAIT_FOR_SIGINT, MAX_WAIT_FOR_SIGTERM};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -434,6 +435,8 @@ pub async fn handle_child(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async fn get_mem_peak(pid: Option<u32>, nsjail: bool) -> i32 {
|
||||
if pid.is_none() {
|
||||
return -1;
|
||||
@@ -689,19 +692,24 @@ fn child_joined_output_stream(
|
||||
|
||||
let stdout = BufReader::new(stdout).lines();
|
||||
let stderr = BufReader::new(stderr).lines();
|
||||
stream::select(lines_to_stream(stderr), lines_to_stream(stdout))
|
||||
stream::select(lines_to_stream(stderr, true), lines_to_stream(stdout, false))
|
||||
}
|
||||
|
||||
pub fn lines_to_stream<R: tokio::io::AsyncBufRead + Unpin>(
|
||||
mut lines: tokio::io::Lines<R>,
|
||||
stderr: bool,
|
||||
) -> impl futures::Stream<Item = io::Result<String>> {
|
||||
stream::poll_fn(move |cx| {
|
||||
std::pin::Pin::new(&mut lines)
|
||||
.poll_next_line(cx)
|
||||
.map(|result| result.transpose())
|
||||
.map(|result| {
|
||||
process_streaming_log_lines(result, stderr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn process_status(status: ExitStatus) -> error::Result<()> {
|
||||
if status.success() {
|
||||
Ok(())
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
use itertools::Itertools;
|
||||
use swc_ecma_parser::lexer::util::CharExt;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use object_store::path::Path;
|
||||
use regex::Regex;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use windmill_common::s3_helpers::OBJECT_STORE_CACHE_SETTINGS;
|
||||
|
||||
use windmill_common::error::{self};
|
||||
use windmill_common::worker::{CLOUD_HOSTED, TMP_DIR};
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
|
||||
use windmill_queue::append_logs;
|
||||
|
||||
@@ -19,6 +10,12 @@ use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::DB;
|
||||
|
||||
use crate::job_logger_ee::default_disk_log_storage;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use crate::job_logger_ee::s3_storage;
|
||||
|
||||
|
||||
pub enum CompactLogs {
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
NotEE,
|
||||
@@ -28,150 +25,7 @@ pub enum CompactLogs {
|
||||
S3,
|
||||
}
|
||||
|
||||
async fn compact_logs(
|
||||
job_id: Uuid,
|
||||
w_id: &str,
|
||||
db: &DB,
|
||||
nlogs: String,
|
||||
total_size: Arc<AtomicU32>,
|
||||
compact_kind: CompactLogs,
|
||||
_worker_name: &str,
|
||||
) -> error::Result<(String, String)> {
|
||||
let mut prev_logs = sqlx::query_scalar!(
|
||||
"SELECT logs FROM job_logs WHERE job_id = $1 AND workspace_id = $2",
|
||||
job_id,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
let size = prev_logs.char_indices().count() as i32;
|
||||
let nlogs_len = nlogs.char_indices().count();
|
||||
let to_keep_in_db = usize::max(
|
||||
usize::min(nlogs_len, 3000),
|
||||
nlogs_len % LARGE_LOG_THRESHOLD_SIZE,
|
||||
);
|
||||
let extra_split = to_keep_in_db < nlogs_len;
|
||||
let stored_in_storage_len = if extra_split {
|
||||
nlogs_len - to_keep_in_db
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let extra_to_newline = nlogs
|
||||
.chars()
|
||||
.skip(stored_in_storage_len)
|
||||
.find_position(|x| x.is_line_break())
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(to_keep_in_db);
|
||||
let stored_in_storage_to_newline = stored_in_storage_len + extra_to_newline;
|
||||
|
||||
let (append_to_storage, stored_in_db) = if extra_split {
|
||||
if stored_in_storage_to_newline == nlogs.len() {
|
||||
(nlogs.as_ref(), "".to_string())
|
||||
} else {
|
||||
let split_idx = nlogs
|
||||
.char_indices()
|
||||
.nth(stored_in_storage_to_newline)
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(0);
|
||||
let (append_to_storage, stored_in_db) = nlogs.split_at(split_idx);
|
||||
// tracing::error!("{append_to_storage} ||||| {stored_in_db}");
|
||||
// tracing::error!(
|
||||
// "{:?} {:?} {} {}",
|
||||
// excess_prev_logs.lines().last(),
|
||||
// current_logs.lines().next(),
|
||||
// split_idx,
|
||||
// excess_size_modulo
|
||||
// );
|
||||
(append_to_storage, stored_in_db.to_string())
|
||||
}
|
||||
} else {
|
||||
// tracing::error!("{:?}", nlogs.lines().last());
|
||||
("", nlogs.to_string())
|
||||
};
|
||||
|
||||
let new_size_with_excess = size + stored_in_storage_to_newline as i32;
|
||||
|
||||
let new_size = total_size.fetch_add(
|
||||
new_size_with_excess as u32,
|
||||
std::sync::atomic::Ordering::SeqCst,
|
||||
) + new_size_with_excess as u32;
|
||||
|
||||
let path = format!(
|
||||
"logs/{job_id}/{}_{new_size}.txt",
|
||||
chrono::Utc::now().timestamp_millis()
|
||||
);
|
||||
|
||||
let mut new_current_logs = match compact_kind {
|
||||
CompactLogs::NoS3 => format!("\n[windmill] No object storage set in instance settings. Previous logs have been saved to disk at {path}"),
|
||||
CompactLogs::S3 => format!("\n[windmill] Previous logs have been saved to object storage at {path}"),
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
CompactLogs::NotEE => format!("\n[windmill] Previous logs have been saved to disk at {path}"),
|
||||
};
|
||||
new_current_logs.push_str(&stored_in_db);
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE job_logs SET logs = $1, log_offset = $2,
|
||||
log_file_index = array_append(coalesce(log_file_index, array[]::text[]), $3)
|
||||
WHERE workspace_id = $4 AND job_id = $5",
|
||||
new_current_logs,
|
||||
new_size as i32,
|
||||
path,
|
||||
w_id,
|
||||
job_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
prev_logs.push_str(&append_to_storage);
|
||||
|
||||
return Ok((prev_logs, path));
|
||||
}
|
||||
|
||||
async fn default_disk_log_storage(
|
||||
job_id: Uuid,
|
||||
w_id: &str,
|
||||
db: &DB,
|
||||
nlogs: String,
|
||||
total_size: Arc<AtomicU32>,
|
||||
compact_kind: CompactLogs,
|
||||
worker_name: &str,
|
||||
) {
|
||||
match compact_logs(
|
||||
job_id,
|
||||
&w_id,
|
||||
&db,
|
||||
nlogs,
|
||||
total_size,
|
||||
compact_kind,
|
||||
worker_name,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => tracing::error!("Could not compact logs for job {job_id}: {e:?}",),
|
||||
Ok((prev_logs, path)) => {
|
||||
let path = format!("{}/{}", TMP_DIR, path);
|
||||
let splitted = &path.split("/").collect_vec();
|
||||
tokio::fs::create_dir_all(splitted.into_iter().take(splitted.len() - 1).join("/"))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Could not create logs directory: {e:?}",);
|
||||
e
|
||||
})
|
||||
.ok();
|
||||
let created = tokio::fs::File::create(&path).await;
|
||||
if let Err(e) = created {
|
||||
tracing::error!("Could not create logs file {path}: {e:?}",);
|
||||
return;
|
||||
}
|
||||
if let Err(e) = tokio::fs::write(&path, prev_logs).await {
|
||||
tracing::error!("Could not write to logs file {path}: {e:?}");
|
||||
} else {
|
||||
tracing::info!("Logs length of {job_id} has exceeded a threshold. Previous logs have been saved to disk at {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn append_job_logs(
|
||||
job_id: Uuid,
|
||||
@@ -184,43 +38,7 @@ pub(crate) async fn append_job_logs(
|
||||
) -> () {
|
||||
if must_compact_logs {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = OBJECT_STORE_CACHE_SETTINGS.read().await.clone() {
|
||||
match compact_logs(
|
||||
job_id,
|
||||
&w_id,
|
||||
&db,
|
||||
logs,
|
||||
total_size,
|
||||
CompactLogs::S3,
|
||||
&worker_name,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => tracing::error!("Could not compact logs for job {job_id}: {e:?}",),
|
||||
Ok((prev_logs, path)) => {
|
||||
tracing::info!("Logs length of {job_id} has exceeded a threshold. Previous logs have been saved to object storage at {path}");
|
||||
let path2 = path.clone();
|
||||
if let Err(e) = os
|
||||
.put(&Path::from(path), prev_logs.to_string().into_bytes().into())
|
||||
.await
|
||||
{
|
||||
tracing::error!("Could not save logs to s3: {e:?}");
|
||||
}
|
||||
tracing::info!("Logs of {job_id} saved to object storage at {path2}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
default_disk_log_storage(
|
||||
job_id,
|
||||
&w_id,
|
||||
&db,
|
||||
logs,
|
||||
total_size,
|
||||
CompactLogs::NoS3,
|
||||
&worker_name,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
s3_storage(job_id, &w_id, &db, logs, total_size, &worker_name).await;
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
{
|
||||
@@ -252,6 +70,7 @@ pub fn append_with_limit(dst: &mut String, src: &str, limit: &mut usize) {
|
||||
if *NO_LOGS_AT_ALL {
|
||||
return;
|
||||
}
|
||||
|
||||
let src_str;
|
||||
let src = {
|
||||
src_str = RE_00.replace_all(src, "");
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
use std::io;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::Arc;
|
||||
|
||||
use uuid::Uuid;
|
||||
use windmill_common::DB;
|
||||
|
||||
use crate::job_logger::CompactLogs;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
pub(crate) async fn s3_storage(_job_id: Uuid, _w_id: &String, _db: &sqlx::Pool<sqlx::Postgres>, _logs: &String, _total_size: &Arc<AtomicU32>, _worker_name: &String) {
|
||||
tracing::info!("Logs length of {job_id} has exceeded a threshold. Implementation to store excess on s3 in not OSS");
|
||||
}
|
||||
|
||||
pub(crate) async fn default_disk_log_storage(
|
||||
job_id: Uuid,
|
||||
_w_id: &str,
|
||||
_db: &DB,
|
||||
_nlogs: String,
|
||||
_total_size: Arc<AtomicU32>,
|
||||
_compact_kind: CompactLogs,
|
||||
_worker_name: &str,
|
||||
) {
|
||||
tracing::info!("Logs length of {job_id} has exceeded a threshold. Implementation to store excess on disk in not OSS");
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn process_streaming_log_lines(r: Result<Option<String>, io::Error>, _stderr: bool) -> Option<Result<String, io::Error>> {
|
||||
r.transpose()
|
||||
}
|
||||
@@ -29,7 +29,7 @@ mod rust_executor;
|
||||
mod worker;
|
||||
mod worker_flow;
|
||||
mod worker_lockfiles;
|
||||
|
||||
mod job_logger_ee;
|
||||
pub use worker::*;
|
||||
|
||||
pub use result_processor::handle_job_error;
|
||||
|
||||
@@ -199,8 +199,11 @@ pub async fn uv_pip_compile(
|
||||
.clone()
|
||||
.map(handle_ephemeral_token);
|
||||
if let Some(url) = pip_extra_index_url.as_ref() {
|
||||
args.extend(["--extra-index-url", url, "--no-emit-index-url"]);
|
||||
pip_args.push(format!("--extra-index-url {}", url));
|
||||
url.split(",").for_each(|url| {
|
||||
args.extend(["--extra-index-url", url]);
|
||||
pip_args.push(format!("--extra-index-url {}", url));
|
||||
});
|
||||
args.push("--no-emit-index-url");
|
||||
}
|
||||
let pip_index_url = PIP_INDEX_URL
|
||||
.read()
|
||||
@@ -279,7 +282,9 @@ pub async fn uv_pip_compile(
|
||||
.clone()
|
||||
.map(handle_ephemeral_token);
|
||||
if let Some(url) = pip_extra_index_url.as_ref() {
|
||||
args.extend(["--extra-index-url", url]);
|
||||
url.split(",").for_each(|url| {
|
||||
args.extend(["--extra-index-url", url]);
|
||||
});
|
||||
}
|
||||
let pip_index_url = PIP_INDEX_URL
|
||||
.read()
|
||||
@@ -1167,7 +1172,9 @@ pub async fn handle_python_reqs(
|
||||
.map(handle_ephemeral_token);
|
||||
|
||||
if let Some(url) = pip_extra_index_url.as_ref() {
|
||||
command_args.extend(["--extra-index-url", url]);
|
||||
url.split(",").for_each(|url| {
|
||||
command_args.extend(["--extra-index-url", url]);
|
||||
});
|
||||
}
|
||||
let pip_index_url = PIP_INDEX_URL
|
||||
.read()
|
||||
|
||||
@@ -49,7 +49,7 @@ use windmill_common::{
|
||||
Approval, BranchAllStatus, BranchChosen, FlowStatus, FlowStatusModule, RetryStatus,
|
||||
MAX_RETRY_ATTEMPTS, MAX_RETRY_INTERVAL,
|
||||
},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform, Retry, Suspend},
|
||||
flows::{has_failure_module, FlowModule, FlowModuleValue, FlowValue, InputTransform, Retry, Suspend},
|
||||
};
|
||||
use windmill_queue::schedule::get_schedule_opt;
|
||||
use windmill_queue::{
|
||||
@@ -963,7 +963,7 @@ pub async fn update_flow_status_after_job_completion_internal<
|
||||
false
|
||||
if !is_failure_step
|
||||
&& !skip_error_handler
|
||||
&& has_failure_module(flow, db).await? =>
|
||||
&& has_failure_module(flow, db, false).await? =>
|
||||
{
|
||||
true
|
||||
}
|
||||
@@ -1290,23 +1290,6 @@ async fn compute_skip_branchall_failure<'c>(
|
||||
}))
|
||||
}
|
||||
|
||||
async fn has_failure_module<'c>(flow: Uuid, db: &DB) -> Result<bool, Error> {
|
||||
sqlx::query_scalar::<_, Option<bool>>(
|
||||
"SELECT raw_flow->'failure_module' != 'null'::jsonb
|
||||
FROM queue
|
||||
WHERE id = $1",
|
||||
)
|
||||
.bind(flow)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"error during retrieval of has_failure_module: {e:#}"
|
||||
))
|
||||
})
|
||||
.map(|v| v.unwrap_or(false))
|
||||
}
|
||||
|
||||
// async fn retrieve_cleanup_module<'c>(flow_uuid: Uuid, db: &DB) -> Result<FlowCleanupModule, Error> {
|
||||
// tracing::warn!("Retrieving cleanup module of flow {}", flow_uuid);
|
||||
// let raw_value = sqlx::query_scalar!(
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DenoLandProvider } from "https://deno.land/x/cliffy@v0.25.7/command/upg
|
||||
import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
|
||||
|
||||
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
|
||||
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
|
||||
|
||||
import { VERSION, createBenchScript, getFlowPayload, login } from "./lib.ts";
|
||||
|
||||
@@ -172,8 +173,8 @@ export async function main({
|
||||
kind: "script",
|
||||
path: "f/benchmarks/" + kind,
|
||||
});
|
||||
} else if (["2steps"].includes(kind)) {
|
||||
nStepsFlow = 2;
|
||||
} else if (["2steps", "bigscriptinflow"].includes(kind)) {
|
||||
nStepsFlow = kind == "2steps" ? 2 : 1;
|
||||
const payload = getFlowPayload(kind);
|
||||
body = JSON.stringify({
|
||||
kind: "flow",
|
||||
@@ -194,6 +195,15 @@ export async function main({
|
||||
kind: "script",
|
||||
path: kind.substr(7),
|
||||
});
|
||||
} else if (kind == "bigrawscript") {
|
||||
noVerify = true;
|
||||
body = JSON.stringify({
|
||||
kind: "rawscript",
|
||||
rawscript: {
|
||||
language: api.RawScript.language.BASH,
|
||||
content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(25000) + "echo \"$WM_FLOW_JOB_ID\"\n",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
throw new Error("Unknown script pattern " + kind);
|
||||
}
|
||||
@@ -246,7 +256,7 @@ export async function main({
|
||||
} else {
|
||||
const elapsed = start ? Date.now() - start : 0;
|
||||
completedJobs = await getCompletedJobsCount();
|
||||
if (kind === "2steps" || kind.startsWith("flow:")) {
|
||||
if (nStepsFlow > 0) {
|
||||
completedJobs = Math.floor(completedJobs / (nStepsFlow + 1));
|
||||
}
|
||||
const avgThr = ((completedJobs / elapsed) * 1000).toFixed(2);
|
||||
|
||||
+18
-1
@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
|
||||
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
|
||||
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
|
||||
|
||||
export const VERSION = "v1.421.2";
|
||||
export const VERSION = "v1.423.2";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
@@ -235,6 +235,23 @@ export const getFlowPayload = (flowPattern: string): api.FlowPreview => {
|
||||
],
|
||||
},
|
||||
};
|
||||
} else if (flowPattern == "bigscriptinflow") {
|
||||
return {
|
||||
path: "bigscriptinflow",
|
||||
args: {},
|
||||
value: {
|
||||
modules: [
|
||||
{
|
||||
value: {
|
||||
input_transforms: {},
|
||||
language: api.RawScript.language.BASH,
|
||||
type: "rawscript",
|
||||
content: "# let's bloat that bash script, 3.. 2.. 1.. BOOM\n".repeat(25000) + "echo \"$WM_FLOW_JOB_ID\"\n",
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
path: "2steps",
|
||||
|
||||
@@ -30,5 +30,13 @@
|
||||
{
|
||||
"kind": "nativets",
|
||||
"jobs": 2000
|
||||
},
|
||||
{
|
||||
"kind": "bigrawscript",
|
||||
"jobs": 10000
|
||||
},
|
||||
{
|
||||
"kind": "bigscriptinflow",
|
||||
"jobs": 10000
|
||||
}
|
||||
]
|
||||
+1
-1
@@ -60,7 +60,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.421.2";
|
||||
export const VERSION = "1.423.2";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
]);
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = buildInputs ++ (with pkgs; [ git go xcaddy sqlx-cli ]);
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
git xcaddy sqlx-cli flock rust-analyzer
|
||||
deno python3 python3Packages.pip go bun uv
|
||||
]);
|
||||
packages = [
|
||||
(pkgs.writeScriptBin "wm-caddy" ''
|
||||
cd ./frontend
|
||||
@@ -56,6 +59,9 @@
|
||||
sqlx database create
|
||||
wm-migrate
|
||||
'')
|
||||
(pkgs.writeScriptBin "wm-bench" ''
|
||||
deno run -A benchmarks/main.ts -e admin@windmill.dev -p changeme $*
|
||||
'')
|
||||
(pkgs.writeScriptBin "wm" ''
|
||||
cd ./frontend
|
||||
npm run dev $*
|
||||
@@ -68,6 +74,12 @@
|
||||
DATABASE_URL = "postgres://postgres:changeme@127.0.0.1:5432/";
|
||||
REMOTE = "http://127.0.0.1:8000";
|
||||
REMOTE_LSP = "http://127.0.0.1:3001";
|
||||
DENO_PATH = "${pkgs.deno}/bin/deno";
|
||||
PYTHON_PATH = "${pkgs.python3}/bin/python3";
|
||||
GO_PATH = "${pkgs.go}/bin/go";
|
||||
BUN_PATH = "${pkgs.bun}/bin/bun";
|
||||
UV_PATH = "${pkgs.uv}/bin/uv";
|
||||
FLOCK_PATH = "${pkgs.flock}/bin/flock";
|
||||
};
|
||||
packages.default = self.packages.${system}.windmill;
|
||||
packages.windmill-client = pkgs.stdenv.mkDerivation {
|
||||
@@ -82,7 +94,7 @@
|
||||
npm config set strict-ssl false
|
||||
cd frontend
|
||||
npm install --verbose
|
||||
npm run generate-backend-client-mac
|
||||
npm run ${if pkgs.stdenv.isDarwin then "generate-backend-client-mac" else "generate-backend-client"}
|
||||
NODE_OPTIONS="--max-old-space-size=8192" npm run build
|
||||
'';
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.421.2",
|
||||
"version": "1.423.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.421.2",
|
||||
"version": "1.423.2",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^4.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.421.2",
|
||||
"version": "1.423.2",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -418,6 +418,9 @@
|
||||
bind:selected={value}
|
||||
options={itemsType?.multiselect ?? []}
|
||||
selectedOptionsDraggable={true}
|
||||
on:open={() => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if itemsType?.enum != undefined && Array.isArray(itemsType?.enum) && Array.isArray(value)}
|
||||
@@ -428,6 +431,9 @@
|
||||
bind:selected={value}
|
||||
options={itemsType?.enum ?? []}
|
||||
selectedOptionsDraggable={true}
|
||||
on:open={() => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -816,6 +822,12 @@
|
||||
bind:value
|
||||
{enum_}
|
||||
{autofocus}
|
||||
on:focus={() => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
enumLabels={extra['enumLabels']}
|
||||
/>
|
||||
</div>
|
||||
@@ -884,14 +896,16 @@
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex flex-row w-full items-center justify-between relative">
|
||||
{#if password || extra?.['password'] == true}
|
||||
{#if value && typeof value == 'string' && value?.startsWith('$var:')}
|
||||
<input type="text" bind:value />
|
||||
{:else if onlyMaskPassword}
|
||||
<Password
|
||||
{disabled}
|
||||
bind:password={value}
|
||||
placeholder={placeholder ?? defaultValue ?? ''}
|
||||
/>
|
||||
{#if onlyMaskPassword}
|
||||
{#if value && typeof value == 'string' && value?.startsWith('$var:')}
|
||||
<input type="text" bind:value />
|
||||
{:else}
|
||||
<Password
|
||||
{disabled}
|
||||
bind:password={value}
|
||||
placeholder={placeholder ?? defaultValue ?? ''}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<PasswordArgInput {disabled} bind:value />
|
||||
{/if}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
|
||||
import FlowGraphV2 from './graph/FlowGraphV2.svelte'
|
||||
import { dfs } from './flows/dfs'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
export let flow: {
|
||||
summary: string
|
||||
@@ -22,6 +23,7 @@
|
||||
export let noGraph = false
|
||||
export let triggerNode = false
|
||||
export let stepDetail: FlowModule | string | undefined = undefined
|
||||
export let workspace: string | undefined = $workspaceStore
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
@@ -37,6 +39,7 @@
|
||||
path={flow?.path}
|
||||
{download}
|
||||
minHeight={400}
|
||||
{workspace}
|
||||
modules={flow?.value?.modules}
|
||||
failureModule={flow?.value?.failure_module}
|
||||
preprocessorModule={flow?.value?.preprocessor_module}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import FlowGraphViewerStep from './FlowGraphViewerStep.svelte'
|
||||
import FlowGraphV2 from './graph/FlowGraphV2.svelte'
|
||||
import { buildPrefix } from './graph/graphBuilder'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -74,6 +75,10 @@
|
||||
|
||||
export let wideResults = false
|
||||
export let hideFlowResult = false
|
||||
export let workspace: string | undefined = $workspaceStore
|
||||
export let prefix: string | undefined = undefined
|
||||
export let subflowParentsGlobalModuleStates: Writable<Record<string, GraphModuleState>>[] = []
|
||||
export let subflowParentsDurationStatuses: Writable<Record<string, DurationStatus>>[] = []
|
||||
|
||||
let jobResults: any[] =
|
||||
flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? []
|
||||
@@ -83,6 +88,7 @@
|
||||
|
||||
let localModuleStates: Writable<Record<string, GraphModuleState>> = writable({})
|
||||
let localDurationStatuses: Writable<Record<string, DurationStatus>> = writable({})
|
||||
let expandedSubflows: Record<string, FlowModule[]> = {}
|
||||
|
||||
export let job: Job | undefined = undefined
|
||||
|
||||
@@ -94,6 +100,24 @@
|
||||
// }
|
||||
// }
|
||||
|
||||
function updateModuleStates(
|
||||
moduleState: Writable<Record<string, GraphModuleState>>,
|
||||
key: string,
|
||||
newValue: GraphModuleState,
|
||||
keepType: boolean | undefined
|
||||
) {
|
||||
moduleState.update((x) => {
|
||||
if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) {
|
||||
newValue.type = x[key].type
|
||||
}
|
||||
x[key] = newValue
|
||||
return x
|
||||
})
|
||||
}
|
||||
|
||||
function buildSubflowKey(key: string, prefix: string | undefined) {
|
||||
return prefix ? 'subflow:' + prefix + key : key
|
||||
}
|
||||
function setModuleState(
|
||||
key: string,
|
||||
value: Partial<GraphModuleState>,
|
||||
@@ -102,15 +126,14 @@
|
||||
) {
|
||||
let newValue = { ...($localModuleStates[key] ?? {}), ...value }
|
||||
if (!deepEqual($localModuleStates[key], value) || force) {
|
||||
;[localModuleStates, ...globalModuleStates].forEach((s) => {
|
||||
s.update((x) => {
|
||||
if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) {
|
||||
newValue.type = x[key].type
|
||||
}
|
||||
x[key] = newValue
|
||||
return x
|
||||
})
|
||||
})
|
||||
;[localModuleStates, ...globalModuleStates].forEach((s) =>
|
||||
updateModuleStates(s, key, newValue, keepType)
|
||||
)
|
||||
if (prefix) {
|
||||
subflowParentsGlobalModuleStates.forEach((s) =>
|
||||
updateModuleStates(s, buildSubflowKey(key, prefix), newValue, keepType)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +147,14 @@
|
||||
return x
|
||||
})
|
||||
})
|
||||
if (prefix) {
|
||||
subflowParentsDurationStatuses.forEach((s) => {
|
||||
s.update((x) => {
|
||||
x[buildSubflowKey(key, prefix)].byJob[id] = value
|
||||
return x
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,15 +162,25 @@
|
||||
if ($localDurationStatuses[modId] == undefined) {
|
||||
$localDurationStatuses[modId] = { byJob: {} }
|
||||
}
|
||||
let prefixed = modId
|
||||
globalDurationStatuses.forEach((x) =>
|
||||
x.update((x) => {
|
||||
if (x[prefixed] == undefined) {
|
||||
x[prefixed] = { byJob: {} }
|
||||
if (x[modId] == undefined) {
|
||||
x[modId] = { byJob: {} }
|
||||
}
|
||||
return x
|
||||
})
|
||||
)
|
||||
if (prefix) {
|
||||
subflowParentsDurationStatuses.forEach((x) =>
|
||||
x.update((x) => {
|
||||
let key = buildSubflowKey(modId, prefix)
|
||||
if (x[key] == undefined) {
|
||||
x[key] = { byJob: {} }
|
||||
}
|
||||
return x
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let innerModules: FlowStatusModule[] = []
|
||||
@@ -368,7 +409,7 @@
|
||||
if (globalRefreshes) {
|
||||
let modId = flowJobIds?.moduleId
|
||||
if (modId) {
|
||||
globalRefreshes[modId] = async (loopJob) => {
|
||||
globalRefreshes[buildSubflowKey(modId, prefix)] = async (loopJob) => {
|
||||
setIteration(loopJob.index, loopJob.job, false, modId ?? '')
|
||||
refresh(true, loopJob)
|
||||
}
|
||||
@@ -631,6 +672,36 @@
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function allModulesForTimeline(
|
||||
modules: FlowModule[],
|
||||
expandedSubflows: Record<string, FlowModule[]>
|
||||
): string[] {
|
||||
const ids = dfs(modules, (x) => x.id)
|
||||
|
||||
function rec(ids: string[], prefix: string | undefined): string[] {
|
||||
return ids.concat(
|
||||
ids.flatMap((id) => {
|
||||
let fms = expandedSubflows[id]
|
||||
let oid = id.split(':').pop()
|
||||
if (!oid) {
|
||||
return []
|
||||
}
|
||||
let nprefix = buildPrefix(prefix, oid)
|
||||
return fms
|
||||
? rec(
|
||||
dfs(fms, (x) =>
|
||||
x.id.startsWith('subflow:') ? x.id : buildSubflowKey(x.id, nprefix)
|
||||
),
|
||||
nprefix
|
||||
)
|
||||
: []
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return rec(ids, undefined)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if notAnonynmous}
|
||||
@@ -803,6 +874,9 @@
|
||||
job={storedListJobs[j]}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
{prefix}
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
render={forloop_selected == loopJobId && selected == 'sequence' && render}
|
||||
reducedPolling={flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20}
|
||||
{workspaceId}
|
||||
@@ -880,6 +954,9 @@
|
||||
{childFlow}
|
||||
globalModuleStates={[localModuleStates, ...globalModuleStates]}
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
{prefix}
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
render={failedRetry == retry_selected && render}
|
||||
reducedPolling={false}
|
||||
{workspaceId}
|
||||
@@ -895,6 +972,17 @@
|
||||
bind:refresh={recursiveRefresh[mod.job ?? '']}
|
||||
globalModuleStates={[]}
|
||||
globalDurationStatuses={[]}
|
||||
prefix={buildPrefix(prefix, mod.id ?? '')}
|
||||
subflowParentsGlobalModuleStates={[
|
||||
localModuleStates,
|
||||
...globalModuleStates,
|
||||
...subflowParentsGlobalModuleStates
|
||||
]}
|
||||
subflowParentsDurationStatuses={[
|
||||
localDurationStatuses,
|
||||
...globalDurationStatuses,
|
||||
...subflowParentsDurationStatuses
|
||||
]}
|
||||
render={selected == 'sequence' && render}
|
||||
{workspaceId}
|
||||
jobId={mod.job}
|
||||
@@ -915,6 +1003,9 @@
|
||||
globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]}
|
||||
render={selected == 'sequence' && render}
|
||||
{workspaceId}
|
||||
{prefix}
|
||||
{subflowParentsGlobalModuleStates}
|
||||
{subflowParentsDurationStatuses}
|
||||
jobId={mod.job}
|
||||
innerModule={mod.flow_jobs ? job.raw_flow?.modules[i]?.value : undefined}
|
||||
flowJobIds={mod.flow_jobs
|
||||
@@ -973,6 +1064,7 @@
|
||||
minHeight={wrapperHeight}
|
||||
success={jobId != undefined && isSuccess(job?.['success'])}
|
||||
flowModuleStates={$localModuleStates}
|
||||
bind:expandedSubflows
|
||||
on:select={(e) => {
|
||||
if (rightColumnSelect != 'node_definition') {
|
||||
rightColumnSelect = 'node_status'
|
||||
@@ -1004,11 +1096,13 @@
|
||||
selectedForloopIndex: detail.index
|
||||
})
|
||||
globalRefreshes[detail.moduleId]?.({ job: detail.id, index: detail.index })
|
||||
// console.log('selectedIteration', prefix, detail.moduleId, globalRefreshes)
|
||||
}}
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
preprocessorModule={job.raw_flow?.preprocessor_module}
|
||||
allowSimplifiedPoll={false}
|
||||
{workspace}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -1033,7 +1127,10 @@
|
||||
aggregateWaitTime={job?.aggregate_wait_time_ms}
|
||||
flowDone={job?.['success'] != undefined}
|
||||
bind:this={flowTimeline}
|
||||
flowModules={dfs(job.raw_flow?.modules ?? [], (x) => x.id)}
|
||||
flowModules={allModulesForTimeline(
|
||||
job?.raw_flow?.modules ?? [],
|
||||
expandedSubflows ?? {}
|
||||
)}
|
||||
durationStatuses={localDurationStatuses}
|
||||
/>
|
||||
{:else if rightColumnSelect == 'node_status'}
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="px-2 py-2 grid grid-cols-6 w-full">
|
||||
<div class="truncate">{k}</div>
|
||||
<div class="truncate">{k.startsWith('subflow:') ? k.substring(8) : k}</div>
|
||||
<div class="col-span-5 flex min-h-6">
|
||||
{#if min && total}
|
||||
<div class="flex flex-col gap-2 w-full p-2 ml-4">
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
setPropertyType(arg?.value)
|
||||
codeInjectionDetected = checkCodeInjection(arg?.value) != undefined
|
||||
} else if (propertyType == 'javascript' && focused) {
|
||||
setPropertyType(arg?.expr)
|
||||
// setPropertyType(arg?.expr)
|
||||
$inputMatches = checkCodeInjection(arg?.expr)
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@
|
||||
class={twMerge(
|
||||
'pl-2 pt-2 pb-2 ml-2 relative hover:bg-surface hover:shadow-md transition-all duration-200',
|
||||
$propPickerConfig?.propName == argName
|
||||
? 'bg-surface border-l-4 border-blue-500 shadow-md rounded-l-md z-2000'
|
||||
? 'bg-surface border-l-4 border-blue-500 shadow-md rounded-l-md z-50 '
|
||||
: 'hover:rounded-md',
|
||||
$$props.class
|
||||
)}
|
||||
@@ -565,10 +565,13 @@
|
||||
</div>
|
||||
|
||||
{#if $propPickerConfig?.propName == argName}
|
||||
<div class="text-blue-500 mt-2" in:fade={{ duration: 200 }}>
|
||||
<div
|
||||
class="text-blue-500 absolute top-2 lg:-right-2.5 -right-1"
|
||||
in:fade={{ duration: 200 }}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
width="16"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
{#if keys.length > 0}
|
||||
{#each keys as argName (argName)}
|
||||
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
|
||||
<div class="z-10 pt-2 relative">
|
||||
<div class="pt-2 relative">
|
||||
<InputTransformForm
|
||||
{previousModuleId}
|
||||
bind:arg={args[argName]}
|
||||
|
||||
@@ -61,6 +61,18 @@
|
||||
export let render = true
|
||||
export let title: string | undefined = undefined
|
||||
export let placeholder: string | undefined = undefined
|
||||
export let appPath: string | undefined = undefined
|
||||
export let computeS3ForceViewerPolicies:
|
||||
| (() =>
|
||||
| {
|
||||
allowed_resources: string[]
|
||||
allow_user_resources: boolean
|
||||
allow_workspace_resource: boolean
|
||||
file_key_regex: string
|
||||
}
|
||||
| undefined)
|
||||
| undefined = undefined
|
||||
export let workspace: string | undefined = undefined
|
||||
|
||||
let oneOfSelected: string | undefined = undefined
|
||||
async function updateOneOfSelected(oneOf: SchemaProperty[] | undefined) {
|
||||
@@ -428,6 +440,9 @@
|
||||
.toLowerCase() == 's3object'}
|
||||
<div class="flex flex-col w-full gap-1">
|
||||
<FileUpload
|
||||
{appPath}
|
||||
computeForceViewerPolicies={computeS3ForceViewerPolicies}
|
||||
{workspace}
|
||||
allowMultiple={false}
|
||||
randomFileKey={true}
|
||||
on:addition={(evt) => {
|
||||
|
||||
@@ -16,6 +16,18 @@
|
||||
export let defaultValues: Record<string, any> = {}
|
||||
export let dynamicEnums: Record<string, any> = {}
|
||||
export let disabled: boolean = false
|
||||
export let appPath: string | undefined = undefined
|
||||
export let computeS3ForceViewerPolicies:
|
||||
| (() =>
|
||||
| {
|
||||
allowed_resources: string[]
|
||||
allow_user_resources: boolean
|
||||
allow_workspace_resource: boolean
|
||||
file_key_regex: string
|
||||
}
|
||||
| undefined)
|
||||
| undefined = undefined
|
||||
export let workspace: string | undefined = undefined
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
let errors: { [id: string]: string } = {}
|
||||
@@ -104,6 +116,9 @@
|
||||
{displayType}
|
||||
{css}
|
||||
disabled={disabled || schema.properties[argName].disabled}
|
||||
{appPath}
|
||||
{computeS3ForceViewerPolicies}
|
||||
{workspace}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -10,41 +10,53 @@
|
||||
let path = ''
|
||||
let password = value && !value.startsWith('$var:') ? value : ''
|
||||
|
||||
let isGenerating = false
|
||||
async function generateValue() {
|
||||
let npath =
|
||||
'u/' +
|
||||
($userStore?.username ?? $userStore?.email)?.split('@')[0] +
|
||||
'/secret_arg/' +
|
||||
generateRandomString(12)
|
||||
let nvalue = '$var:' + npath
|
||||
await VariableService.createVariable({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
value: password,
|
||||
is_secret: true,
|
||||
path: npath,
|
||||
description: '',
|
||||
expires_at: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7).toISOString()
|
||||
}
|
||||
})
|
||||
path = npath
|
||||
value = nvalue
|
||||
if (isGenerating) return
|
||||
isGenerating = true
|
||||
try {
|
||||
let npath =
|
||||
'u/' +
|
||||
($userStore?.username ?? $userStore?.email)?.split('@')[0] +
|
||||
'/secret_arg/' +
|
||||
generateRandomString(12)
|
||||
let nvalue = '$var:' + npath
|
||||
await VariableService.createVariable({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
value: password,
|
||||
is_secret: true,
|
||||
path: npath,
|
||||
description: 'Ephemeral secret variable',
|
||||
expires_at: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7).toISOString()
|
||||
}
|
||||
})
|
||||
path = npath
|
||||
value = nvalue
|
||||
debouncedUpdate()
|
||||
} finally {
|
||||
isGenerating = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateValue() {
|
||||
await VariableService.updateVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path: path,
|
||||
requestBody: {
|
||||
value: password
|
||||
}
|
||||
})
|
||||
try {
|
||||
await VariableService.updateVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path: path,
|
||||
requestBody: {
|
||||
value: password
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
generateValue()
|
||||
}
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
function debouncedUpdate() {
|
||||
timeout && clearTimeout(timeout)
|
||||
setTimeout(updateValue, 500)
|
||||
timeout = setTimeout(updateValue, 500)
|
||||
}
|
||||
|
||||
$: password && debouncedUpdate()
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import InstanceNameEditor from './InstanceNameEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { instanceSettingsSelectedTab } from '$lib/stores';
|
||||
let drawer: Drawer
|
||||
let filter = ''
|
||||
|
||||
@@ -56,6 +57,9 @@
|
||||
|
||||
let tab: 'users' | string = 'users'
|
||||
|
||||
$: $instanceSettingsSelectedTab, tab = $instanceSettingsSelectedTab
|
||||
$: tab, instanceSettingsSelectedTab.set(tab)
|
||||
|
||||
let nbDisplayed = 50
|
||||
|
||||
let instanceSettings
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { computeWorkspaceS3FileInputPolicy } from '../../editor/appUtilsS3'
|
||||
import { defaultIfEmptyString } from '$lib/utils'
|
||||
import { userStore } from '$lib/stores'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -26,8 +29,16 @@
|
||||
export let configuration: RichConfigurations
|
||||
export let customCss: ComponentCustomCSS<'schemaformcomponent'> | undefined = undefined
|
||||
|
||||
const { worldStore, connectingInput, app, selectedComponent, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const {
|
||||
worldStore,
|
||||
connectingInput,
|
||||
app,
|
||||
selectedComponent,
|
||||
componentControl,
|
||||
appPath,
|
||||
isEditor,
|
||||
workspace
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
const iterContext = getContext<ListContext>('ListWrapperContext')
|
||||
const listInputs: ListInputs | undefined = getContext<ListInputs>('ListInputs')
|
||||
|
||||
@@ -100,6 +111,14 @@
|
||||
previousDefault = structuredClone(resolvedConfig.defaultValues)
|
||||
args = previousDefault ?? {}
|
||||
}
|
||||
|
||||
function computeS3ForceViewerPolicies() {
|
||||
if (!isEditor) {
|
||||
return undefined
|
||||
}
|
||||
const policy = computeWorkspaceS3FileInputPolicy()
|
||||
return policy
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['schemaformcomponent'].initialData.configuration) as key (key)}
|
||||
@@ -140,6 +159,9 @@
|
||||
bind:this={schemaForm}
|
||||
displayType={Boolean(resolvedConfig.displayType)}
|
||||
largeGap={Boolean(resolvedConfig.largeGap)}
|
||||
appPath={defaultIfEmptyString(appPath, `u/${$userStore?.username ?? 'unknown'}/newapp`)}
|
||||
{computeS3ForceViewerPolicies}
|
||||
{workspace}
|
||||
{css}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import { get } from 'svelte/store'
|
||||
import RefreshButton from '$lib/components/apps/components/helpers/RefreshButton.svelte'
|
||||
import { ctxRegex } from '../../utils'
|
||||
import { computeWorkspaceS3FileInputPolicy } from '../../editor/appUtilsS3'
|
||||
|
||||
// Component props
|
||||
export let id: string
|
||||
@@ -671,6 +672,14 @@
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function computeS3ForceViewerPolicies() {
|
||||
if (!isEditor) {
|
||||
return undefined
|
||||
}
|
||||
const policy = computeWorkspaceS3FileInputPolicy()
|
||||
return policy
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each Object.entries(fields ?? {}) as [key, v] (key)}
|
||||
@@ -754,6 +763,9 @@
|
||||
<div class="px-2 h-fit min-h-0">
|
||||
<LightweightSchemaForm
|
||||
schema={schemaStripped}
|
||||
appPath={defaultIfEmptyString(appPath, `u/${$userStore?.username ?? 'unknown'}/newapp`)}
|
||||
{computeS3ForceViewerPolicies}
|
||||
{workspace}
|
||||
bind:this={schemaForm}
|
||||
bind:args
|
||||
on:inputClicked={handleInputClick}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import FileUpload from '$lib/components/common/fileUpload/FileUpload.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { computeS3FileInputPolicy } from '../../editor/appUtilsS3'
|
||||
import { defaultIfEmptyString } from '$lib/utils'
|
||||
import { userStore } from '$lib/stores'
|
||||
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
@@ -77,6 +80,15 @@
|
||||
{/if}
|
||||
*/
|
||||
let forceDisplayUploads: boolean = false
|
||||
|
||||
const { appPath, isEditor } = getContext<AppViewerContext>('AppViewerContext')
|
||||
function computeForceViewerPolicies() {
|
||||
if (!isEditor) {
|
||||
return undefined
|
||||
}
|
||||
const policy = computeS3FileInputPolicy((configuration as any)?.type?.configuration?.s3, $app)
|
||||
return policy
|
||||
}
|
||||
</script>
|
||||
|
||||
<InitializeComponent {id} />
|
||||
@@ -139,5 +151,7 @@
|
||||
outputs.result.set(value)
|
||||
}}
|
||||
{forceDisplayUploads}
|
||||
appPath={defaultIfEmptyString(appPath, `u/${$userStore?.username ?? 'unknown'}/newapp`)}
|
||||
{computeForceViewerPolicies}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
import ToggleEnable from '$lib/components/common/toggleButton-v2/ToggleEnable.svelte'
|
||||
import HideButton from './settingsPanel/HideButton.svelte'
|
||||
import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
|
||||
import { computeS3FileInputPolicy, computeWorkspaceS3FileInputPolicy } from './appUtilsS3'
|
||||
|
||||
async function hash(message) {
|
||||
try {
|
||||
@@ -195,8 +196,12 @@
|
||||
}
|
||||
|
||||
async function computeTriggerables() {
|
||||
const items = allItems($app.grid, $app.subgrids)
|
||||
|
||||
console.log('items', items)
|
||||
|
||||
const allTriggers: ([string, TriggerableV2] | undefined)[] = (await Promise.all(
|
||||
allItems($app.grid, $app.subgrids)
|
||||
items
|
||||
.flatMap((x) => {
|
||||
let c = x.data as AppComponent
|
||||
let r: { input: AppInput | undefined; id: string }[] = [
|
||||
@@ -296,6 +301,44 @@
|
||||
allTriggers.filter(Boolean) as [string, TriggerableV2][]
|
||||
)
|
||||
policy.triggerables_v2 = ntriggerables
|
||||
|
||||
const s3_inputs = items
|
||||
.filter((x) => (x.data as AppComponent).type === 's3fileinputcomponent')
|
||||
.map((x) => {
|
||||
const c = x.data as AppComponent
|
||||
const config = c.configuration as any
|
||||
return computeS3FileInputPolicy(config?.type?.configuration?.s3, $app)
|
||||
})
|
||||
.filter(Boolean) as {
|
||||
allowed_resources: string[]
|
||||
allow_user_resources: boolean
|
||||
file_key_regex: string
|
||||
}[]
|
||||
|
||||
if (
|
||||
items.findIndex((x) => {
|
||||
const c = x.data as AppComponent
|
||||
if (c.type === 'schemaformcomponent') {
|
||||
return (
|
||||
Object.values((c.componentInput as any)?.value?.properties ?? {}).findIndex(
|
||||
(p: any) => p?.type === 'object' && p?.format === 'resource-s3_object'
|
||||
) !== -1
|
||||
)
|
||||
} else if (c.type === 'formbuttoncomponent' || c.type === 'formcomponent') {
|
||||
return (
|
||||
Object.values((c.componentInput as any)?.fields ?? {}).findIndex(
|
||||
(p: any) => p?.fieldType === 'object' && p?.format === 'resource-s3_object'
|
||||
) !== -1
|
||||
)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) !== -1
|
||||
) {
|
||||
s3_inputs.push(computeWorkspaceS3FileInputPolicy())
|
||||
}
|
||||
|
||||
policy.s3_inputs = s3_inputs
|
||||
}
|
||||
|
||||
async function processRunnable(
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { AppInput, EvalInputV2 } from '../inputType'
|
||||
import type { App } from '../types'
|
||||
import { collectOneOfFields } from './appUtils'
|
||||
|
||||
function filenameExprToRegex(template: string) {
|
||||
const filenameEscaped = template.replaceAll('${file.name}', '<file_name>') // replace filename with placeholder
|
||||
const escapedTemplate = filenameEscaped
|
||||
.slice(1, -1) // remove quotes
|
||||
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // escape regex special characters
|
||||
const regexPattern = escapedTemplate.replaceAll('<file_name>', '[^/]+') // replace filename placeholder with regex pattern
|
||||
return `^${regexPattern}$`
|
||||
}
|
||||
|
||||
function staticToRegex(str: string) {
|
||||
return `^${str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`
|
||||
}
|
||||
|
||||
function checkIfExprIsString(input: string) {
|
||||
return /^(['"`])[^'"`]*\1$/g.test(input)
|
||||
}
|
||||
|
||||
function checkIfEvalIsStringWithFilename(input: EvalInputV2) {
|
||||
if (input.connections.length > 0) {
|
||||
return false
|
||||
} else {
|
||||
return checkIfExprIsString(input.expr.replaceAll('${file.name}', ''))
|
||||
}
|
||||
}
|
||||
|
||||
function removeResourcePrefix(resource: string) {
|
||||
return resource.replace(/^\$res:/, '')
|
||||
}
|
||||
|
||||
export function computeWorkspaceS3FileInputPolicy() {
|
||||
return {
|
||||
allow_workspace_resource: true,
|
||||
allowed_resources: [],
|
||||
allow_user_resources: false,
|
||||
file_key_regex: ''
|
||||
}
|
||||
}
|
||||
|
||||
export function computeS3FileInputPolicy(s3Config: any, app: App) {
|
||||
const resourceInput = s3Config?.resource as AppInput | undefined
|
||||
const pathTemplateInput = s3Config?.pathTemplate as AppInput | undefined
|
||||
|
||||
const allow_workspace_resource =
|
||||
!resourceInput || (resourceInput.type === 'static' && !resourceInput.value)
|
||||
const allowed_resources: string[] = resourceInput
|
||||
? resourceInput.type === 'static'
|
||||
? resourceInput.value
|
||||
? [removeResourcePrefix(resourceInput.value)]
|
||||
: []
|
||||
: collectOneOfFields(
|
||||
{
|
||||
s3_resource: resourceInput
|
||||
},
|
||||
app
|
||||
).s3_resource?.map((s) => removeResourcePrefix(s)) ?? []
|
||||
: []
|
||||
|
||||
const allow_user_resources =
|
||||
(resourceInput?.type === 'evalv2' && resourceInput?.allowUserResources) ?? false
|
||||
let file_key_regex = '^.*$'
|
||||
if (pathTemplateInput) {
|
||||
if (pathTemplateInput.type === 'static') {
|
||||
file_key_regex = staticToRegex(pathTemplateInput.value)
|
||||
} else if (
|
||||
pathTemplateInput.type === 'evalv2' &&
|
||||
checkIfEvalIsStringWithFilename(pathTemplateInput)
|
||||
) {
|
||||
file_key_regex = filenameExprToRegex(pathTemplateInput.expr)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
allow_workspace_resource,
|
||||
allowed_resources,
|
||||
allow_user_resources,
|
||||
file_key_regex
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@
|
||||
{:else if componentInput?.type === 'user'}
|
||||
<span class="text-2xs italic text-tertiary">Field's value is set by the user</span>
|
||||
{/if}
|
||||
{#if (componentInput?.type === 'evalv2' || componentInput?.type === 'connected' || componentInput?.type === 'user') && fieldType == 'object' && format?.startsWith('resource-')}
|
||||
{#if (componentInput?.type === 'evalv2' || componentInput?.type === 'connected' || componentInput?.type === 'user') && ((fieldType == 'object' && format?.startsWith('resource-') && format !== 'resource-s3_object') || fieldType == 'resource')}
|
||||
<div class="flex flex-row items-center">
|
||||
<Toggle
|
||||
size="xs"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { HelpersService } from '$lib/gen'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import { Ban, CheckCheck, FileWarning, Files, RefreshCcw, Trash } from 'lucide-svelte'
|
||||
@@ -27,6 +27,17 @@
|
||||
export let defaultValue: string | undefined = undefined
|
||||
export let workspace: string | undefined = undefined
|
||||
export let fileUploads: Writable<FileUploadData[]> = writable([])
|
||||
export let appPath: string | undefined = undefined
|
||||
export let computeForceViewerPolicies:
|
||||
| (() =>
|
||||
| {
|
||||
allowed_resources: string[]
|
||||
allow_user_resources: boolean
|
||||
allow_workspace_resource: boolean
|
||||
file_key_regex: string
|
||||
}
|
||||
| undefined)
|
||||
| undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -115,6 +126,26 @@
|
||||
params.append('content_type', fileToUpload.type)
|
||||
}
|
||||
|
||||
if (computeForceViewerPolicies !== undefined) {
|
||||
const forceViewerPolicies = computeForceViewerPolicies()
|
||||
|
||||
if (forceViewerPolicies) {
|
||||
params.append(
|
||||
'force_viewer_allowed_resources',
|
||||
forceViewerPolicies.allowed_resources.join(',')
|
||||
)
|
||||
params.append(
|
||||
'force_viewer_allow_user_resources',
|
||||
JSON.stringify(forceViewerPolicies.allow_user_resources)
|
||||
)
|
||||
params.append(
|
||||
'force_viewer_allow_workspace_resource',
|
||||
JSON.stringify(forceViewerPolicies.allow_workspace_resource)
|
||||
)
|
||||
params.append('force_viewer_file_key_regex', forceViewerPolicies.file_key_regex)
|
||||
}
|
||||
}
|
||||
|
||||
// let response = await fetch(
|
||||
// `/api/w/${$workspaceStore}/job_helpers/multipart_upload_s3_file?${params.toString()}`,
|
||||
// {
|
||||
@@ -158,9 +189,16 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
xhr?.open(
|
||||
'POST',
|
||||
`/api/w/${workspace ?? $workspaceStore}/job_helpers/upload_s3_file?${params.toString()}`,
|
||||
appPath
|
||||
? `/api/w/${
|
||||
workspace ?? $workspaceStore
|
||||
}/apps_u/upload_s3_file/${appPath}?${params.toString()}`
|
||||
: `/api/w/${
|
||||
workspace ?? $workspaceStore
|
||||
}/job_helpers/upload_s3_file?${params.toString()}`,
|
||||
true
|
||||
)
|
||||
xhr?.setRequestHeader('Content-Type', 'application/octet-stream')
|
||||
@@ -309,7 +347,7 @@
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
{#if fileUpload.progress === 100 && !fileUpload.cancelled}
|
||||
{#if fileUpload.progress === 100 && !fileUpload.cancelled && $userStore}
|
||||
<Button
|
||||
size="xs2"
|
||||
color="red"
|
||||
|
||||
@@ -78,6 +78,10 @@
|
||||
newItem={newFlow}
|
||||
isFlow={true}
|
||||
/>
|
||||
{:else if $selectedId.startsWith('subflow:')}
|
||||
<div class="p-4"
|
||||
>Selected step is witin an expanded subflow and is not directly editable in the flow editor</div
|
||||
>
|
||||
{:else}
|
||||
{@const dup = checkDup($flowStore.value.modules)}
|
||||
{#if dup}
|
||||
|
||||
@@ -307,7 +307,7 @@
|
||||
{/if}
|
||||
|
||||
{#if inlineScripts?.length > 0}
|
||||
<div class="pb-0 flex flex-row items-center gap-2">
|
||||
<div class="pb-0 flex flex-row items-center gap-2 -mt-[3px]">
|
||||
<div class=" text-2xs font-light text-secondary ml-2"
|
||||
>New {selectedKind != 'script' ? selectedKind + ' ' : ''}script</div
|
||||
>
|
||||
|
||||
@@ -253,6 +253,10 @@
|
||||
forceReload++
|
||||
await reload(flowModule)
|
||||
}
|
||||
if (flowModule.value.type == 'flow') {
|
||||
forceReload++
|
||||
await reload(flowModule)
|
||||
}
|
||||
}}
|
||||
on:createScriptFromInlineScript={async () => {
|
||||
const [module, state] = await createScriptFromInlineScript(
|
||||
@@ -361,7 +365,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else if flowModule.value.type === 'flow'}
|
||||
<FlowPathViewer path={flowModule.value.path} />
|
||||
{#key forceReload}
|
||||
<FlowPathViewer path={flowModule.value.path} />
|
||||
{/key}
|
||||
{/if}
|
||||
</Pane>
|
||||
<Pane bind:size={editorSettingsPanelSize} minSize={20}>
|
||||
@@ -378,13 +384,14 @@
|
||||
class={advancedSelected === 'runtime' ? 'h-[calc(100%-68px)]' : 'h-[calc(100%-34px)]'}
|
||||
>
|
||||
{#if selected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow')}
|
||||
<div class="h-full overflow-auto px-2 bg-surface" id="flow-editor-step-input">
|
||||
<div class="h-full overflow-auto bg-surface" id="flow-editor-step-input">
|
||||
<PropPickerWrapper
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
error={failureModule}
|
||||
noPadding
|
||||
>
|
||||
<InputTransformSchemaForm
|
||||
class="px-1 xl:px-2"
|
||||
bind:this={inputTransformSchemaForm}
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
schema={$flowStateStore[$selectedId]?.schema ?? {}}
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
import {
|
||||
Bed,
|
||||
Database,
|
||||
ExternalLink,
|
||||
Gauge,
|
||||
GitFork,
|
||||
Pen,
|
||||
PhoneIncoming,
|
||||
RefreshCcw,
|
||||
Repeat,
|
||||
Save,
|
||||
Square,
|
||||
@@ -139,6 +141,32 @@
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if module.value.type === 'flow'}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
on:click={async () => {
|
||||
if (module.value.type == 'flow') {
|
||||
window.open(`/flows/edit/${module.value.path}`, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
}}
|
||||
startIcon={{ icon: Pen }}
|
||||
iconOnly={false}
|
||||
>
|
||||
Edit <ExternalLink size={12} />
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
on:click={async () => {
|
||||
dispatch('reload')
|
||||
}}
|
||||
startIcon={{
|
||||
icon: RefreshCcw
|
||||
}}
|
||||
iconOnly={true}
|
||||
/>
|
||||
{/if}
|
||||
<div class="px-0.5" />
|
||||
{#if module.value.type === 'rawscript'}
|
||||
<FlowModuleWorkerTagSelect bind:tag={module.value.tag} />
|
||||
|
||||
@@ -276,8 +276,13 @@
|
||||
</Popover>
|
||||
|
||||
<div class="flex items-center space-x-2 relative max-w-[25%]" bind:clientWidth={idBadgeWidth}>
|
||||
{#if id && id !== 'preprocessor' && !id.startsWith('failure')}
|
||||
<Badge color="indigo" wrapperClass="max-w-full" baseClass="max-w-full truncate" title={id}>
|
||||
{#if id && id !== 'preprocessor' && !id.startsWith('failure') && !id.startsWith('subflow:')}
|
||||
<Badge
|
||||
color="indigo"
|
||||
wrapperClass="max-w-full"
|
||||
baseClass="max-w-full truncate !px-1"
|
||||
title={id}
|
||||
>
|
||||
<span class="max-w-full text-2xs truncate">{id}</span></Badge
|
||||
>
|
||||
{#if deletable}
|
||||
@@ -290,6 +295,10 @@ hover:border-blue-700 hover:!visible {hover ? '' : '!hidden'}"
|
||||
title="Edit Id"><Pencil size={14} /></button
|
||||
>
|
||||
{/if}
|
||||
{:else if id?.startsWith('subflow:')}
|
||||
<Badge color="blue" wrapperClass="max-w-full" baseClass="!px-1" title={id}>
|
||||
<span class="max-w-full text-2xs truncate">{id.substring('subflow:'.length)}</span></Badge
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
import { getDependentComponents } from '../flowExplorer'
|
||||
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { copilotInfo, tutorialsToDo } from '$lib/stores'
|
||||
import { copilotInfo, tutorialsToDo, workspaceStore } from '$lib/stores'
|
||||
|
||||
import FlowTutorials from '$lib/components/FlowTutorials.svelte'
|
||||
import { ignoredTutorials } from '$lib/components/tutorials/ignoredTutorials'
|
||||
@@ -44,6 +44,7 @@
|
||||
export let disableSettings = false
|
||||
export let newFlow: boolean = false
|
||||
export let smallErrorHandler = false
|
||||
export let workspace: string | undefined = $workspaceStore
|
||||
|
||||
let flowTutorials: FlowTutorials | undefined = undefined
|
||||
|
||||
@@ -345,6 +346,7 @@
|
||||
preprocessorModule={$flowStore.value?.preprocessor_module}
|
||||
{selectedId}
|
||||
{flowInputsStore}
|
||||
{workspace}
|
||||
on:delete={({ detail }) => {
|
||||
let e = detail.detail
|
||||
dependents = getDependentComponents(e.id, $flowStore)
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
export let modules: FlowModule[]
|
||||
export let moving: string | undefined = undefined
|
||||
export let duration_ms: number | undefined = undefined
|
||||
export let isTrigger: boolean = false
|
||||
|
||||
export let retries: number | undefined = undefined
|
||||
export let flowJobs:
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<div class="truncate text-2xs text-center"><pre>{preLabel}</pre></div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if id && !hideId}
|
||||
{#if id && !hideId && !id?.startsWith('subflow:')}
|
||||
<div class="flex items-center shrink min-w-0">
|
||||
<Badge color="indigo" wrapperClass="w-full" baseClass="max-w-full" title={id}>
|
||||
<span class="max-w-full text-2xs truncate">{id}</span>
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
{alwaysOn}
|
||||
{displayContext}
|
||||
{error}
|
||||
previousId={pickableProperties?.previousId}
|
||||
{pickableProperties}
|
||||
allowCopy={!notSelectable && !$propPickerConfig}
|
||||
on:select={({ detail }) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { type FlowModule } from '../../gen'
|
||||
import { FlowService, type FlowModule } from '../../gen'
|
||||
import { NODE, type GraphModuleState } from '.'
|
||||
import { createEventDispatcher, getContext, onDestroy, onMount, setContext } from 'svelte'
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
import FlowYamlEditor from '../flows/header/FlowYamlEditor.svelte'
|
||||
import BranchOneEndNode from './renderers/nodes/branchOneEndNode.svelte'
|
||||
import type { TriggerContext } from '../triggers'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import SubflowBound from './renderers/nodes/SubflowBound.svelte'
|
||||
|
||||
export let success: boolean | undefined = undefined
|
||||
export let modules: FlowModule[] | undefined = []
|
||||
@@ -65,6 +67,7 @@
|
||||
undefined
|
||||
)
|
||||
export let triggerNode = false
|
||||
export let workspace: string = $workspaceStore ?? 'NO_WORKSPACE'
|
||||
|
||||
let useDataflow: Writable<boolean | undefined> = writable<boolean | undefined>(false)
|
||||
|
||||
@@ -85,6 +88,8 @@
|
||||
|
||||
let simplifiableFlow: SimplifiableFlow | undefined = undefined
|
||||
|
||||
export let expandedSubflows: Record<string, FlowModule[]> = {}
|
||||
|
||||
if (triggerContext && allowSimplifiedPoll) {
|
||||
if (isSimplifiable(modules)) {
|
||||
triggerContext?.simplifiedPoll?.set(true)
|
||||
@@ -201,6 +206,15 @@
|
||||
},
|
||||
simplifyFlow: (detail) => {
|
||||
triggerContext?.simplifiedPoll.set(detail)
|
||||
},
|
||||
expandSubflow: async (id: string, path: string) => {
|
||||
const flow = await FlowService.getFlowByPath({ workspace: workspace, path })
|
||||
expandedSubflows[id] = flow.value.modules
|
||||
expandedSubflows = expandedSubflows
|
||||
},
|
||||
minimizeSubflow: (id: string) => {
|
||||
delete expandedSubflows[id]
|
||||
expandedSubflows = expandedSubflows
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +236,8 @@
|
||||
$selectedId,
|
||||
moving,
|
||||
simplifiableFlow,
|
||||
triggerNode ? path : undefined
|
||||
triggerNode ? path : undefined,
|
||||
expandedSubflows
|
||||
)
|
||||
|
||||
const nodes = writable<Node[]>([])
|
||||
@@ -230,69 +245,6 @@
|
||||
|
||||
let height = 0
|
||||
|
||||
// function removeInputNode(nodes, edges, id) {
|
||||
// const inputNode = nodes.find((node) => node.id === id)
|
||||
// if (!inputNode) return { nodes, edges }
|
||||
|
||||
// // Find edges connected to the input node
|
||||
// const connectedEdges = edges.filter((edge) => edge.source === id || edge.target === id)
|
||||
|
||||
// // Remove the input node
|
||||
// let updatedNodes = nodes.filter((node) => node.id !== id)
|
||||
|
||||
// // Remove edges connected to the input node
|
||||
// let updatedEdges = edges.filter((edge) => edge.source !== id && edge.target !== id)
|
||||
|
||||
// // Create new edges from the input node's parent to its children
|
||||
// const inputEdges = connectedEdges.filter((edge) => edge.target === id)
|
||||
// const outputEdges = connectedEdges.filter((edge) => edge.source === id)
|
||||
|
||||
// inputEdges.forEach((inputEdge) => {
|
||||
// outputEdges.forEach((outputEdge) => {
|
||||
// const newEdge = {
|
||||
// id: `edge:${inputEdge.source}->${outputEdge.target}`,
|
||||
// source: inputEdge.source,
|
||||
// target: outputEdge.target,
|
||||
// type: 'empty',
|
||||
// data: {
|
||||
// ...outputEdge.data,
|
||||
// sourceId: inputEdge.source,
|
||||
// targetId: outputEdge.target
|
||||
// }
|
||||
// }
|
||||
// updatedEdges.push(newEdge)
|
||||
// })
|
||||
// })
|
||||
|
||||
// // Update parent ids of the nodes
|
||||
// updatedNodes = updatedNodes.map((node) => {
|
||||
// if (node.data && node.data.parentIds && node.data.parentIds.includes(id)) {
|
||||
// const updatedParentIds = node.data.parentIds.filter((parentId) => parentId !== id)
|
||||
// if (inputNode.data && inputNode.data.parentIds) {
|
||||
// updatedParentIds.push(...inputNode.data.parentIds)
|
||||
// }
|
||||
// return {
|
||||
// ...node,
|
||||
// data: {
|
||||
// ...node.data,
|
||||
// parentIds: [...new Set(updatedParentIds)] // Remove duplicates
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return node
|
||||
// })
|
||||
|
||||
// return { nodes: updatedNodes, edges: updatedEdges }
|
||||
// }
|
||||
|
||||
// function processGraph(graph, simplifiable) {
|
||||
// let newGraph = { nodes: graph.nodes, edges: graph.edges }
|
||||
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, 'Input')
|
||||
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, simplifiable.forLoopNode.id)
|
||||
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, simplifiable.triggerNode.id)
|
||||
// return newGraph
|
||||
// }
|
||||
|
||||
function isSimplifiable(modules: FlowModule[] | undefined): boolean {
|
||||
if (!modules || modules?.length !== 2) {
|
||||
return false
|
||||
@@ -313,7 +265,7 @@
|
||||
|
||||
$nodes = layoutNodes(newGraph.nodes)
|
||||
$edges = newGraph.edges
|
||||
height = Math.max(...$nodes.map((n) => n.position.y + NODE.height + 40), minHeight)
|
||||
height = Math.max(...$nodes.map((n) => n.position.y + NODE.height + 100), minHeight)
|
||||
}
|
||||
|
||||
$: (graph || allowSimplifiedPoll) && updateStores()
|
||||
@@ -330,6 +282,7 @@
|
||||
whileLoopEnd: ForLoopEndNode,
|
||||
branchOneStart: BranchOneStart,
|
||||
branchOneEnd: BranchOneEndNode,
|
||||
subflowBound: SubflowBound,
|
||||
noBranch: NoBranchNode,
|
||||
trigger: TriggersNode
|
||||
} as any
|
||||
|
||||
@@ -15,6 +15,8 @@ export type GraphEventHandlers = {
|
||||
selectedIteration: (detail, moduleId: string) => void
|
||||
changeId: (newId: string) => void
|
||||
simplifyFlow: (detail: boolean) => void
|
||||
expandSubflow: (id: string, path: string) => void
|
||||
minimizeSubflow: (id: string) => void
|
||||
}
|
||||
|
||||
export type SimplifiableFlow = { simplifiedFlow: boolean }
|
||||
@@ -26,6 +28,10 @@ export function isTriggerStep(module: FlowModule | undefined): boolean {
|
||||
module.value.is_trigger === true
|
||||
)
|
||||
}
|
||||
|
||||
export function buildPrefix(prefix: string | undefined, id: string): string {
|
||||
return (prefix ?? '') + id + ':'
|
||||
}
|
||||
export function graphBuilder(
|
||||
modules: FlowModule[] | undefined,
|
||||
extra: Record<string, any>,
|
||||
@@ -37,7 +43,8 @@ export function graphBuilder(
|
||||
selectedId: string | undefined,
|
||||
moving: string | undefined,
|
||||
simplifiableFlow: SimplifiableFlow | undefined,
|
||||
flowPathForTriggerNode: string | undefined
|
||||
flowPathForTriggerNode: string | undefined,
|
||||
expandedSubflows: Record<string, FlowModule[]>
|
||||
// triggerProps?: {
|
||||
// path?: string
|
||||
// flowIsSimplifiable?: boolean
|
||||
@@ -61,6 +68,10 @@ export function graphBuilder(
|
||||
throw new Error(`Duplicated node detected: ${module.id}`)
|
||||
}
|
||||
|
||||
if (module.id.startsWith('subflow:')) {
|
||||
extra.insertable = false
|
||||
}
|
||||
|
||||
nodes.push({
|
||||
id: module.id,
|
||||
data: {
|
||||
@@ -114,6 +125,7 @@ export function graphBuilder(
|
||||
function addEdge(
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
prefix: string | undefined,
|
||||
options?: {
|
||||
disableInsert?: boolean
|
||||
customId?: string
|
||||
@@ -154,7 +166,7 @@ export function graphBuilder(
|
||||
// If the index is -1, it means that the target module is not in the modules array, so we set it to the length of the array
|
||||
index: index >= 0 ? index : mods?.length ?? 0,
|
||||
...extra,
|
||||
insertable: extra.insertable && !options?.disableInsert
|
||||
insertable: extra.insertable && !options?.disableInsert && prefix == undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -188,11 +200,11 @@ export function graphBuilder(
|
||||
}
|
||||
nodes.push(triggerNode)
|
||||
if (preprocessorModule != null && preprocessorModule != undefined) {
|
||||
addEdge('Trigger', preprocessorModule.id, {
|
||||
addEdge('Trigger', preprocessorModule.id, undefined, {
|
||||
type: 'empty'
|
||||
})
|
||||
} else {
|
||||
addEdge('Trigger', 'Input', {
|
||||
addEdge('Trigger', 'Input', undefined, {
|
||||
type: 'empty'
|
||||
})
|
||||
}
|
||||
@@ -220,16 +232,25 @@ export function graphBuilder(
|
||||
beforeNode: Node,
|
||||
nextNode: Node | undefined,
|
||||
simplifiedTriggerView: boolean,
|
||||
prefix: string | undefined,
|
||||
currentOffset = 0,
|
||||
disableMoveIds: string[] = [],
|
||||
parentIndex?: string,
|
||||
branchChosen?: boolean
|
||||
) {
|
||||
if (prefix != undefined) {
|
||||
modules.forEach((m) => {
|
||||
if (!m['oid']) {
|
||||
m['oid'] = m.id
|
||||
}
|
||||
m.id = 'subflow:' + prefix + m['oid']
|
||||
})
|
||||
}
|
||||
let previousId: string | undefined = undefined
|
||||
|
||||
if (modules.length === 0) {
|
||||
if (nextNode) {
|
||||
addEdge(beforeNode.id, nextNode.id, {
|
||||
addEdge(beforeNode.id, nextNode.id, prefix, {
|
||||
subModules: modules,
|
||||
disableMoveIds
|
||||
})
|
||||
@@ -239,8 +260,8 @@ export function graphBuilder(
|
||||
const localDisableMoveIds = [...disableMoveIds, module.id]
|
||||
|
||||
// Add the edge between the previous node and the current one
|
||||
if (index > 0 && previousId) {
|
||||
addEdge(previousId, module.id, {
|
||||
if (index > 0 && previousId && expandedSubflows[module.id] == undefined) {
|
||||
addEdge(previousId, module.id, prefix, {
|
||||
subModules: modules,
|
||||
disableMoveIds
|
||||
})
|
||||
@@ -284,10 +305,10 @@ export function graphBuilder(
|
||||
|
||||
nodes.push(startNode)
|
||||
|
||||
addEdge(module.id, startNode.id, {
|
||||
addEdge(module.id, startNode.id, prefix, {
|
||||
type: 'empty'
|
||||
})
|
||||
addEdge(startNode.id, endNode.id, {
|
||||
addEdge(startNode.id, endNode.id, prefix, {
|
||||
type: 'empty'
|
||||
})
|
||||
} else {
|
||||
@@ -311,7 +332,7 @@ export function graphBuilder(
|
||||
|
||||
nodes.push(startNode)
|
||||
|
||||
addEdge(module.id, startNode.id, {
|
||||
addEdge(module.id, startNode.id, prefix, {
|
||||
type: 'empty'
|
||||
})
|
||||
|
||||
@@ -320,6 +341,7 @@ export function graphBuilder(
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
prefix,
|
||||
currentOffset,
|
||||
localDisableMoveIds,
|
||||
parentIndex ? `${parentIndex}-${index}-${branchIndex}` : `${index}-${branchIndex}`
|
||||
@@ -349,11 +371,11 @@ export function graphBuilder(
|
||||
}
|
||||
|
||||
if (!simplifiedTriggerView) {
|
||||
addEdge(module.id, startNode.id, {
|
||||
addEdge(module.id, startNode.id, prefix, {
|
||||
type: 'empty'
|
||||
})
|
||||
} else if (previousId) {
|
||||
addEdge(previousId, startNode.id, {
|
||||
addEdge(previousId, startNode.id, prefix, {
|
||||
type: 'empty'
|
||||
})
|
||||
}
|
||||
@@ -383,6 +405,7 @@ export function graphBuilder(
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
prefix,
|
||||
currentOffset + 25,
|
||||
localDisableMoveIds,
|
||||
parentIndex
|
||||
@@ -406,7 +429,7 @@ export function graphBuilder(
|
||||
position: { x: -1, y: -1 },
|
||||
type: 'whileLoopStart'
|
||||
}
|
||||
addEdge(module.id, startNode.id, {
|
||||
addEdge(module.id, startNode.id, prefix, {
|
||||
type: 'empty'
|
||||
})
|
||||
|
||||
@@ -427,6 +450,7 @@ export function graphBuilder(
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
prefix,
|
||||
currentOffset + 25,
|
||||
localDisableMoveIds,
|
||||
parentIndex
|
||||
@@ -465,7 +489,7 @@ export function graphBuilder(
|
||||
|
||||
nodes.push(defaultBranch)
|
||||
|
||||
addEdge(module.id, defaultBranch.id, { type: 'empty' })
|
||||
addEdge(module.id, defaultBranch.id, prefix, { type: 'empty' })
|
||||
|
||||
const branchChosen = extra.flowModuleStates?.[module.id]?.branchChosen
|
||||
processModules(
|
||||
@@ -473,6 +497,7 @@ export function graphBuilder(
|
||||
defaultBranch,
|
||||
endNode,
|
||||
false,
|
||||
prefix,
|
||||
currentOffset,
|
||||
localDisableMoveIds,
|
||||
parentIndex ? `${parentIndex}-${index}` : index.toString(),
|
||||
@@ -500,13 +525,14 @@ export function graphBuilder(
|
||||
|
||||
nodes.push(startNode)
|
||||
|
||||
addEdge(module.id, startNode.id, { type: 'empty' })
|
||||
addEdge(module.id, startNode.id, prefix, { type: 'empty' })
|
||||
|
||||
processModules(
|
||||
branch.modules,
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
prefix,
|
||||
currentOffset,
|
||||
localDisableMoveIds,
|
||||
parentIndex ? `${parentIndex}-${index}` : index.toString(),
|
||||
@@ -516,13 +542,72 @@ export function graphBuilder(
|
||||
|
||||
previousId = endNode.id
|
||||
} else {
|
||||
addNode(module, currentOffset, 'module', modules)
|
||||
let expanded = expandedSubflows[module.id]
|
||||
if (expanded) {
|
||||
const startId = `${module.id}-subflow-start`
|
||||
const idWithoutPrefix = module.id.startsWith('subflow:')
|
||||
? module.id.substring(8)
|
||||
: module.id
|
||||
const startNode = {
|
||||
id: startId,
|
||||
data: {
|
||||
offset: currentOffset,
|
||||
label: `Start of subflow ${idWithoutPrefix}`,
|
||||
id: startId,
|
||||
subflowId: module.id,
|
||||
modules: modules,
|
||||
eventHandlers: eventHandlers,
|
||||
...extra
|
||||
},
|
||||
position: { x: -1, y: -1 },
|
||||
type: 'subflowBound'
|
||||
}
|
||||
|
||||
previousId = module.id
|
||||
nodes.push(startNode)
|
||||
|
||||
if (previousId) {
|
||||
addEdge(previousId!, startNode.id, prefix, { type: 'empty' })
|
||||
} else {
|
||||
addEdge(beforeNode.id, startNode.id, prefix, { type: 'empty' })
|
||||
}
|
||||
|
||||
const endId = `${module.id}-subflow-end`
|
||||
const endNode = {
|
||||
id: endId,
|
||||
data: {
|
||||
offset: currentOffset,
|
||||
label: `End of subflow ${idWithoutPrefix}`,
|
||||
id: endId,
|
||||
subflowId: module.id,
|
||||
modules: modules,
|
||||
eventHandlers: eventHandlers,
|
||||
...extra
|
||||
},
|
||||
position: { x: -1, y: -1 },
|
||||
type: 'subflowBound'
|
||||
}
|
||||
|
||||
nodes.push(endNode)
|
||||
|
||||
processModules(
|
||||
expanded,
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
buildPrefix(prefix, module['oid'] ?? module.id),
|
||||
currentOffset,
|
||||
localDisableMoveIds
|
||||
)
|
||||
|
||||
previousId = endNode.id
|
||||
} else {
|
||||
addNode(module, currentOffset, 'module', modules)
|
||||
previousId = module.id
|
||||
}
|
||||
}
|
||||
|
||||
if (index === 0) {
|
||||
addEdge(beforeNode.id, module.id, {
|
||||
if (index === 0 && expandedSubflows[module.id] == undefined) {
|
||||
addEdge(beforeNode.id, module.id, prefix, {
|
||||
subModules: modules,
|
||||
disableMoveIds,
|
||||
disableInsert: simplifiedTriggerView
|
||||
@@ -530,7 +615,7 @@ export function graphBuilder(
|
||||
}
|
||||
|
||||
if (index === modules.length - 1 && previousId && nextNode) {
|
||||
addEdge(previousId, nextNode.id, {
|
||||
addEdge(previousId, nextNode.id, prefix, {
|
||||
subModules: modules,
|
||||
disableMoveIds
|
||||
})
|
||||
@@ -540,9 +625,9 @@ export function graphBuilder(
|
||||
}
|
||||
|
||||
if (simplifiableFlow?.simplifiedFlow === true && triggerNode) {
|
||||
processModules(modules, triggerNode, undefined, true)
|
||||
processModules(modules, triggerNode, undefined, true, undefined)
|
||||
} else {
|
||||
processModules(modules, inputNode, resultNode, false)
|
||||
processModules(modules, inputNode, resultNode, false, undefined)
|
||||
}
|
||||
|
||||
if (failureModule) {
|
||||
@@ -558,14 +643,14 @@ export function graphBuilder(
|
||||
|
||||
Object.entries(toAdd).forEach((x) => {
|
||||
addNode({ ...failureModule, id: x[1] }, 0, 'module')
|
||||
addEdge(x[0], x[1], { type: 'empty' })
|
||||
addEdge(x[0], x[1], undefined, { type: 'empty' })
|
||||
})
|
||||
}
|
||||
|
||||
if (preprocessorModule) {
|
||||
addNode(preprocessorModule, 0, 'module')
|
||||
const id = JSON.parse(JSON.stringify(preprocessorModule.id))
|
||||
addEdge(id, 'Input', { type: 'empty' })
|
||||
addEdge(id, 'Input', undefined, { type: 'empty' })
|
||||
}
|
||||
|
||||
if (failureModule && !extra.flowModuleStates) {
|
||||
@@ -598,7 +683,7 @@ export function graphBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
addEdge(pid, selectedId!, {
|
||||
addEdge(pid, selectedId!, undefined, {
|
||||
customId: `dep-${pid}-${selectedId}-${input}-${index}`,
|
||||
type: 'dataflowedge'
|
||||
})
|
||||
@@ -608,7 +693,7 @@ export function graphBuilder(
|
||||
Object.entries(deps.dependents).forEach((x, i) => {
|
||||
let pid = x[0]
|
||||
|
||||
addEdge(selectedId!, pid, {
|
||||
addEdge(selectedId!, pid, undefined, {
|
||||
customId: `dep-${selectedId}-${pid}-${i}`,
|
||||
type: 'dataflowedge'
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import MapItem from '$lib/components/flows/map/MapItem.svelte'
|
||||
import type { FlowModule, FlowModuleValue } from '$lib/gen'
|
||||
import { GitBranchPlus } from 'lucide-svelte'
|
||||
import { GitBranchPlus, Maximize2 } from 'lucide-svelte'
|
||||
import NodeWrapper from './NodeWrapper.svelte'
|
||||
import type { GraphEventHandlers } from '../../graphBuilder'
|
||||
import type { GraphModuleState } from '../../model'
|
||||
@@ -46,6 +46,19 @@
|
||||
</script>
|
||||
|
||||
<NodeWrapper offset={data.offset} let:darkMode>
|
||||
{#if data.module.value.type == 'flow'}
|
||||
<button
|
||||
title="Unexpand subflow"
|
||||
class="z-50 absolute -top-[10px] right-[25px] rounded-full h-[20px] w-[20px] center-center text-primary bg-surface duration-150 hover:bg-surface-hover"
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
if (data.module.value.type == 'flow') {
|
||||
data.eventHandlers.expandSubflow(data.module.id, data.module.value.path)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
{/if}
|
||||
<MapItem
|
||||
mod={data.module}
|
||||
insertable={data.insertable}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import VirtualItem from '$lib/components/flows/map/VirtualItem.svelte'
|
||||
import NodeWrapper from './NodeWrapper.svelte'
|
||||
import { Minimize2 } from 'lucide-svelte'
|
||||
import type { GraphModuleState } from '../../model'
|
||||
import { getStateColor } from '../../util'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import type { GraphEventHandlers } from '../../graphBuilder'
|
||||
|
||||
export let data: {
|
||||
label: string
|
||||
preLabel: string | undefined
|
||||
insertable: boolean
|
||||
flowModuleStates: Record<string, GraphModuleState> | undefined
|
||||
subflowId: string
|
||||
id: string
|
||||
modules: FlowModule[]
|
||||
selected: boolean
|
||||
eventHandlers: GraphEventHandlers
|
||||
offset: number
|
||||
}
|
||||
</script>
|
||||
|
||||
<NodeWrapper let:darkMode offset={data.offset}>
|
||||
<VirtualItem
|
||||
label={data.label}
|
||||
preLabel={data.preLabel}
|
||||
selectable
|
||||
selected={data.selected}
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
borderColor={undefined}
|
||||
on:select={() => {
|
||||
data.eventHandlers.select(data.id)
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
title="Unexpand subflow"
|
||||
class="z-50 absolute -top-[10px] right-[25px] rounded-full h-[20px] w-[20px] center-center text-primary bg-surface duration-150 hover:bg-surface-hover"
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
data.eventHandlers.minimizeSubflow(data.subflowId)
|
||||
}}
|
||||
>
|
||||
<Minimize2 size={12} />
|
||||
</button>
|
||||
</NodeWrapper>
|
||||
@@ -146,6 +146,15 @@ export const settings: Record<string, Setting[]> = {
|
||||
storage: 'setting',
|
||||
ee_only: 'Channels other than tracing are only available in the EE version'
|
||||
},
|
||||
{
|
||||
label: 'Mute critical alerts in UI',
|
||||
description: 'Enable to mute critical alerts in the UI',
|
||||
key: 'critical_alert_mute_ui',
|
||||
fieldType: 'boolean',
|
||||
storage: 'setting',
|
||||
requiresReloadOnChange: true,
|
||||
ee_only: 'Critical alerts in UI are only available in the EE version'
|
||||
},
|
||||
{
|
||||
label: 'Azure OpenAI base path',
|
||||
description:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
export let allowCopy = true
|
||||
export let collapseLevel: number | undefined = undefined
|
||||
export let prefix = ''
|
||||
export let expandedEvenOnLevel0: string | undefined = undefined
|
||||
|
||||
let s3FileViewer: S3FilePicker
|
||||
|
||||
@@ -124,7 +125,9 @@
|
||||
{allowCopy}
|
||||
on:select
|
||||
{collapseLevel}
|
||||
collapsed={collapseLevel !== undefined ? level + 1 >= collapseLevel : undefined}
|
||||
collapsed={collapseLevel !== undefined
|
||||
? level + 1 >= collapseLevel && key != expandedEvenOnLevel0
|
||||
: undefined}
|
||||
/>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
import ClearableInput from '../common/clearableInput/ClearableInput.svelte'
|
||||
import { filterNestedObject } from '../flows/previousResults'
|
||||
import type { PropPickerContext } from '../prop_picker'
|
||||
import Scrollable from '../Scrollable.svelte'
|
||||
|
||||
export let pickableProperties: PickableProperties
|
||||
export let displayContext = true
|
||||
export let error: boolean = false
|
||||
export let allowCopy = false
|
||||
export let alwaysOn = false
|
||||
export let previousId: string | undefined = undefined
|
||||
|
||||
let variables: Record<string, string> = {}
|
||||
let resources: Record<string, any> = {}
|
||||
@@ -190,14 +192,22 @@
|
||||
$: search, $inputMatches, $propPickerConfig, updateState()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full rounded overflow-hidden">
|
||||
<div class="px-2 py-2">
|
||||
<div class="flex flex-col h-full rounded">
|
||||
<div class="px-1 py-1">
|
||||
<ClearableInput bind:value={search} placeholder="Search prop..." />
|
||||
</div>
|
||||
<div
|
||||
class="overflow-y-auto px-2 pt-2 grow relative"
|
||||
class:bg-surface-secondary={!$propPickerConfig && !alwaysOn}
|
||||
<!-- <div
|
||||
class="px-2 pt-2 grow relative"
|
||||
class:bg-surface-secondary={!$propPickerConfig && !alwaysOn} -->
|
||||
<Scrollable
|
||||
scrollableClass="grow relative min-h-0 px-1 xl:px-2 py-1 shrink {!$propPickerConfig && !alwaysOn
|
||||
? 'bg-surface-secondary'
|
||||
: ''}"
|
||||
>
|
||||
<!-- <div
|
||||
class="px-2 pt-2 grow relative"
|
||||
class:bg-surface-secondary={!$propPickerConfig && !alwaysOn}
|
||||
> -->
|
||||
<!-- <pre class="text-2xs w-full"
|
||||
>{JSON.stringify({ pickableProperties, resultByIdFiltered }, null, 2)}</pre
|
||||
> -->
|
||||
@@ -257,6 +267,7 @@
|
||||
<span class="font-normal text-sm text-tertiary font-mono">results</span>
|
||||
<div class="overflow-y-auto pb-2">
|
||||
<ObjectViewer
|
||||
expandedEvenOnLevel0={previousId}
|
||||
{allowCopy}
|
||||
pureViewer={!$propPickerConfig}
|
||||
collapseLevel={allResultsCollapsed ? 1 : undefined}
|
||||
@@ -305,6 +316,7 @@
|
||||
pureViewer={!$propPickerConfig}
|
||||
collapseLevel={allResultsCollapsed ? 1 : undefined}
|
||||
json={resultByIdFiltered}
|
||||
expandedEvenOnLevel0={previousId}
|
||||
prefix="results"
|
||||
on:select
|
||||
/>
|
||||
@@ -396,5 +408,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</Scrollable>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import Popover from '../Popover.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
export let numUnacknowledgedCriticalAlerts: number
|
||||
export let isCollapsed: boolean
|
||||
export let disabled: boolean = false
|
||||
export let lightMode: boolean = false
|
||||
export let stopPropagationOnClick: boolean = false
|
||||
export let shortcut: string = ''
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if !disabled}
|
||||
<Popover appearTimeout={0} disappearTimeout={0} class="w-full" disablePopup={!isCollapsed}>
|
||||
<div class="py-1.5 px-1 border-t border-gray-700">
|
||||
<button
|
||||
on:click={(e) => {
|
||||
if (stopPropagationOnClick) e.preventDefault()
|
||||
dispatch('click')
|
||||
}}
|
||||
class={twMerge(
|
||||
'group flex items-center px-2 py-2 font-light rounded-md h-8 gap-3 w-full',
|
||||
lightMode
|
||||
? 'text-primary hover:bg-surface-hover '
|
||||
: ' hover:bg-[#2A3648] text-primary-inverse dark:text-primary',
|
||||
'transition-all',
|
||||
$$props.class
|
||||
)}
|
||||
title={label}
|
||||
>
|
||||
<span
|
||||
class="flex items-center justify-center h-6 w-6 rounded-lg bg-red-600 text-white font-bold text-xs"
|
||||
>
|
||||
{numUnacknowledgedCriticalAlerts > 9 ? '9+' : numUnacknowledgedCriticalAlerts}
|
||||
</span>
|
||||
|
||||
{#if !isCollapsed && label}
|
||||
<span
|
||||
class={twMerge(
|
||||
'whitespace-pre truncate',
|
||||
lightMode ? 'text-primary' : 'text-primary-inverse dark:text-primary',
|
||||
'transition-all',
|
||||
$$props.class
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
<span class="pl-2 text-xs dark:text-secondary light:text-secondary-inverse font-semibold">
|
||||
{shortcut}
|
||||
</span>
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<svelte:fragment slot="text">
|
||||
{#if label}
|
||||
{label}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import CriticalAlertModalInner from './CriticalAlertModalInner.svelte'
|
||||
import { SettingService } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Modal from '../common/modal/Modal.svelte'
|
||||
|
||||
export let open: boolean = false
|
||||
export let numUnacknowledgedCriticalAlerts: number = 0
|
||||
|
||||
let checkForNewAlertsInterval: ReturnType<typeof setInterval>
|
||||
let checkingForNewAlerts = false
|
||||
|
||||
onMount(() => {
|
||||
updateHasUnacknowledgedCriticalAlerts(true)
|
||||
checkForNewAlertsInterval = setInterval(() => {
|
||||
updateHasUnacknowledgedCriticalAlerts(true)
|
||||
}, 15000)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(checkForNewAlertsInterval)
|
||||
})
|
||||
|
||||
async function updateHasUnacknowledgedCriticalAlerts(sendToast: boolean = false) {
|
||||
if (checkingForNewAlerts) return
|
||||
checkingForNewAlerts = true
|
||||
|
||||
try {
|
||||
const unacknowledged = await SettingService.getCriticalAlerts({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
acknowledged: false
|
||||
})
|
||||
|
||||
if (numUnacknowledgedCriticalAlerts === 0 && unacknowledged.length > 0 && sendToast) {
|
||||
sendUserToast(
|
||||
'Critical Alert:',
|
||||
true,
|
||||
[
|
||||
{
|
||||
label: 'View',
|
||||
callback: () => {
|
||||
open = true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Acknowledge',
|
||||
callback: () => {
|
||||
if (unacknowledged[0].id) acknowledgeAlert(unacknowledged[0].id)
|
||||
}
|
||||
}
|
||||
],
|
||||
unacknowledged[0].message,
|
||||
10000
|
||||
)
|
||||
}
|
||||
numUnacknowledgedCriticalAlerts = unacknowledged.length
|
||||
} finally {
|
||||
checkingForNewAlerts = false
|
||||
}
|
||||
}
|
||||
|
||||
async function acknowledgeAlert(id: number) {
|
||||
await SettingService.acknowledgeCriticalAlert({ id })
|
||||
updateHasUnacknowledgedCriticalAlerts()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:open title="Critical Alerts" cancelText="Close" style="max-width: 66%;">
|
||||
<CriticalAlertModalInner {updateHasUnacknowledgedCriticalAlerts} />
|
||||
</Modal>
|
||||
@@ -0,0 +1,203 @@
|
||||
<script lang="ts">
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { SettingService } from '$lib/gen'
|
||||
import { CheckCircle2, AlertCircle, RefreshCw, CheckSquare2, AlertTriangle } from 'lucide-svelte'
|
||||
import type { CriticalAlert } from '$lib/gen'
|
||||
import { onMount } from 'svelte'
|
||||
import { instanceSettingsSelectedTab } from '$lib/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
export let updateHasUnacknowledgedCriticalAlerts: () => void = () => {}
|
||||
|
||||
let alerts: CriticalAlert[] = []
|
||||
|
||||
let isRefreshing = false
|
||||
let hasCriticalAlertChannels = false
|
||||
|
||||
$: loading = isRefreshing
|
||||
|
||||
onMount(() => {
|
||||
refreshAlerts()
|
||||
})
|
||||
|
||||
// Pagination
|
||||
let page = 1
|
||||
let pageSize = 10
|
||||
let hasMore = true
|
||||
|
||||
let hideAcknowledged = false
|
||||
|
||||
async function acknowledgeAll() {
|
||||
await SettingService.acknowledgeAllCriticalAlerts()
|
||||
getAlerts(false)
|
||||
}
|
||||
|
||||
async function fetchAlerts(pageNumber: number) {
|
||||
isRefreshing = true
|
||||
try {
|
||||
const newAlerts = await SettingService.getCriticalAlerts({
|
||||
page: pageNumber,
|
||||
pageSize: pageSize,
|
||||
acknowledged: hideAcknowledged ? false : undefined
|
||||
})
|
||||
|
||||
alerts = newAlerts
|
||||
hasMore = newAlerts.length === pageSize
|
||||
page = pageNumber
|
||||
updateHasUnacknowledgedCriticalAlerts()
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isRefreshing = false
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
async function getAlerts(reset?: boolean) {
|
||||
if (reset) page = 1
|
||||
await fetchAlerts(page)
|
||||
}
|
||||
|
||||
async function checkCriticalAlertChannels() {
|
||||
const channels = (await SettingService.getGlobal({ key: 'critical_error_channels' })) as any[]
|
||||
hasCriticalAlertChannels = channels && channels.length > 0
|
||||
}
|
||||
|
||||
async function acknowledgeAlert(id: number) {
|
||||
await SettingService.acknowledgeCriticalAlert({ id })
|
||||
getAlerts(false)
|
||||
}
|
||||
|
||||
function formatDate(dateString: string | undefined): string {
|
||||
if (!dateString) return ''
|
||||
const date = new Date(dateString)
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
async function refreshAlerts() {
|
||||
checkCriticalAlertChannels()
|
||||
await getAlerts(true)
|
||||
}
|
||||
|
||||
function goToPreviousPage() {
|
||||
if (page > 1) {
|
||||
fetchAlerts(page - 1)
|
||||
}
|
||||
}
|
||||
|
||||
function goToNextPage() {
|
||||
if (hasMore) {
|
||||
fetchAlerts(page + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function goToCoreTab() {
|
||||
goto('/#superadmin-settings')
|
||||
instanceSettingsSelectedTab.set('Core')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if !hasCriticalAlertChannels}
|
||||
<div class="flex flex-row pb-4">
|
||||
<AlertTriangle color="orange" class="w-6 h-6 mr-2" />
|
||||
<p>
|
||||
No critical alert channels are set up. Go to the
|
||||
<a href="/#superadmin-settings" on:click|preventDefault={goToCoreTab}>Instance Settings</a>
|
||||
page to configure critical alert channels.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Row of action buttons above the table -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex space-x-2">
|
||||
<Button color="green" startIcon={{ icon: CheckSquare2 }} size="sm" on:click={acknowledgeAll}
|
||||
>Acknowledge All</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
<button class="p-2 rounded-full hover:bg-gray-200" on:click={refreshAlerts} disabled={loading}>
|
||||
<RefreshCw class={loading ? 'animate-spin ' : ''} size="20" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Pagination controls above the table -->
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<div class="flex items-center space-x-4">
|
||||
<Button size="xs2" on:click={goToPreviousPage} disabled={page <= 1}>Previous</Button>
|
||||
<span>Page {page}</span>
|
||||
<Button size="xs2" on:click={goToNextPage} disabled={!hasMore}>Next</Button>
|
||||
</div>
|
||||
<div class="pr-3">
|
||||
<Toggle
|
||||
bind:checked={hideAcknowledged}
|
||||
on:change={refreshAlerts}
|
||||
options={{ right: 'Hide Acknowledged' }}
|
||||
size="xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table of alerts with scrollable body -->
|
||||
<div class="overflow-y-auto max-h-1/2">
|
||||
<table class="min-w-full w-full">
|
||||
<thead class="bg-gray-600 text-white sticky top-0 z-10">
|
||||
<tr>
|
||||
<th class="w-[60px] px-4 py-2 text-center">Type</th>
|
||||
<th class="px-4 py-2 text-center">Message</th>
|
||||
<th class="w-[150px] px-4 py-2 text-center">Created At</th>
|
||||
<th class="w-[180px] px-4 py-2 text-center">Acknowledge</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each alerts as { id, alert_type, message, created_at, acknowledged }}
|
||||
{#if !hideAcknowledged || !acknowledged}
|
||||
<tr class="bg-gray-100 dark:bg-gray-700 dark:text-white text-center">
|
||||
<td class="border px-4 py-2 w-[100px]">
|
||||
{#if alert_type === 'recovered_critical_error'}
|
||||
<span title="Recovered Critical Alert">
|
||||
<CheckCircle2 size="20" color="green" />
|
||||
</span>
|
||||
{:else}
|
||||
<span title="Critical Alert">
|
||||
<AlertCircle size="20" color="red" />
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="border px-4 py-2">{message}</td>
|
||||
<!-- Flexible width -->
|
||||
<td class="border px-4 py-2 w-[150px]">{formatDate(created_at)}</td>
|
||||
<td class="border px-4 py-2 w-[180px]">
|
||||
<div class="flex justify-center items-center">
|
||||
{#if !acknowledged}
|
||||
<Button
|
||||
color="green"
|
||||
startIcon={{ icon: CheckSquare2 }}
|
||||
size="xs2"
|
||||
on:click={() => {
|
||||
if (id) acknowledgeAlert(id)
|
||||
}}>Acknowledge</Button
|
||||
>
|
||||
{:else}
|
||||
<CheckCircle2 size="20" color="green" />
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{#if alerts.length === 0}
|
||||
<p class="text-center text-gray-500 mt-4">No critical alerts available.</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,8 +1,8 @@
|
||||
//regex that match path starting with u/ or f/ and with at least 2 /
|
||||
const pathRegex = /\b(u|f)\/[^\/\s]+\/[^\/\s]+\b/g
|
||||
|
||||
export function processMessage(message: string): string {
|
||||
return message.replaceAll(pathRegex, (path) => {
|
||||
export function processMessage(message: string | undefined): string {
|
||||
return (message ?? 'Error without message').replaceAll(pathRegex, (path) => {
|
||||
return `<span class="bg-surface-secondary p-1 text-xs font-mono whitespace-nowrap rounded-md">${path}</span>`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -283,7 +283,9 @@
|
||||
<ToggleButtonGroup
|
||||
class="w-auto"
|
||||
bind:selected={http_method}
|
||||
disabled={!($userStore?.is_admin || $userStore?.is_super_admin) || !can_write}
|
||||
disabled={!($userStore?.is_admin || $userStore?.is_super_admin) ||
|
||||
!can_write ||
|
||||
!!static_asset_config}
|
||||
>
|
||||
<ToggleButton label="GET" value="get" />
|
||||
<ToggleButton label="POST" value="post" />
|
||||
@@ -328,6 +330,7 @@
|
||||
script_path = ''
|
||||
initialScriptPath = ''
|
||||
is_flow = false
|
||||
http_method = 'get'
|
||||
} else {
|
||||
static_asset_config = undefined
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ class Websocket(TypedDict):
|
||||
class WmTrigger(TypedDict):
|
||||
kind: Literal["http", "email", "webhook", "websocket"]
|
||||
http: Http | None
|
||||
websocket: Websocket | None
|
||||
websocket: Websocket | None
|
||||
|
||||
def preprocessor(
|
||||
wm_trigger: WmTrigger,
|
||||
|
||||
@@ -121,3 +121,5 @@ export type DBSchema = SQLSchema | GraphqlSchema
|
||||
export type DBSchemas = Partial<Record<string, DBSchema>>
|
||||
|
||||
export const dbSchemas = writable<DBSchemas>({})
|
||||
|
||||
export const instanceSettingsSelectedTab = writable('Core')
|
||||
@@ -14,7 +14,9 @@
|
||||
import { classNames, getModifierKey } from '$lib/utils'
|
||||
import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte'
|
||||
import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte'
|
||||
import CriticalAlertModal from '$lib/components/sidebar/CriticalAlertModal.svelte'
|
||||
import {
|
||||
enterpriseLicense,
|
||||
copilotInfo,
|
||||
isPremiumStore,
|
||||
starStore,
|
||||
@@ -48,6 +50,7 @@
|
||||
import MenuButton from '$lib/components/sidebar/MenuButton.svelte'
|
||||
import { setContext } from 'svelte'
|
||||
import { base } from '$app/paths'
|
||||
import CriticalAlertButton from '$lib/components/sidebar/CriticalAlertButton.svelte'
|
||||
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
let menuOpen = false
|
||||
@@ -275,6 +278,26 @@
|
||||
}
|
||||
|
||||
setContext('openSearchWithPrefilledText', openSearchModal)
|
||||
|
||||
$: {
|
||||
if ($enterpriseLicense && $superadmin) {
|
||||
loadCriticalAlertsMuted()
|
||||
}
|
||||
}
|
||||
|
||||
let numUnacknowledgedCriticalAlerts = 0
|
||||
let isCriticalAlertsModalOpen = false
|
||||
let isCriticalAlertsUiMuted = false
|
||||
|
||||
async function loadCriticalAlertsMuted() {
|
||||
isCriticalAlertsUiMuted = (await SettingService.getGlobal({
|
||||
key: 'critical_alert_mute_ui'
|
||||
})) as boolean
|
||||
}
|
||||
|
||||
function openCriticalAlertsModal(text?: string): void {
|
||||
isCriticalAlertsModalOpen = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth />
|
||||
@@ -291,6 +314,12 @@
|
||||
{:else if $userStore}
|
||||
<GlobalSearchModal bind:this={globalSearchModal} />
|
||||
{#if $superadmin}
|
||||
{#if !isCriticalAlertsUiMuted && $enterpriseLicense}
|
||||
<CriticalAlertModal
|
||||
bind:open={isCriticalAlertsModalOpen}
|
||||
bind:numUnacknowledgedCriticalAlerts
|
||||
/>
|
||||
{/if}
|
||||
<SuperadminSettings bind:this={superadminSettings} />
|
||||
{/if}
|
||||
<div>
|
||||
@@ -356,7 +385,6 @@
|
||||
<WindmillIcon white={true} height="20px" width="20px" />
|
||||
Windmill
|
||||
</div>
|
||||
|
||||
<div class="px-2 py-4 space-y-2 border-y border-gray-500">
|
||||
<WorkspaceMenu />
|
||||
<FavoriteMenu {favoriteLinks} />
|
||||
@@ -405,6 +433,17 @@
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{#if $superadmin && $enterpriseLicense}
|
||||
<CriticalAlertButton
|
||||
stopPropagationOnClick={true}
|
||||
on:click={() => openCriticalAlertsModal()}
|
||||
{numUnacknowledgedCriticalAlerts}
|
||||
{isCollapsed}
|
||||
label="Critical Alerts"
|
||||
class="!text-xs"
|
||||
disabled={numUnacknowledgedCriticalAlerts === 0}
|
||||
/>
|
||||
{/if}
|
||||
<div class="px-2 py-4 space-y-2 border-y border-gray-700">
|
||||
<WorkspaceMenu {isCollapsed} />
|
||||
<FavoriteMenu {favoriteLinks} {isCollapsed} />
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
<div class="text-center text-sm text-tertiary mt-2"> No routes </div>
|
||||
{:else if items?.length}
|
||||
<div class="border rounded-md divide-y">
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, route_path, is_flow, extra_perms, canWrite, marked, http_method } (path)}
|
||||
{#each items.slice(0, nbDisplayed) as { path, edited_by, edited_at, script_path, route_path, is_flow, extra_perms, canWrite, marked, http_method, static_asset_config } (path)}
|
||||
{@const href = `${is_flow ? '/flows/get' : '/scripts/get'}/${script_path}`}
|
||||
|
||||
<div
|
||||
@@ -228,7 +228,11 @@
|
||||
{path}
|
||||
</div>
|
||||
<div class="text-secondary text-xs truncate text-left font-light">
|
||||
runnable: {script_path}
|
||||
{#if static_asset_config}
|
||||
file: {static_asset_config.s3}
|
||||
{:else}
|
||||
runnable: {script_path}
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -284,6 +284,7 @@
|
||||
<h2 class="mt-10">Flow details</h2>
|
||||
<div class="border border-gray-700">
|
||||
<FlowGraphV2
|
||||
workspace={job.workspace_id}
|
||||
triggerNode={false}
|
||||
modules={job.raw_flow?.modules}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { decodeState } from '$lib/utils'
|
||||
|
||||
let content = localStorage.getItem('svelvet')
|
||||
@@ -8,7 +9,13 @@
|
||||
: { modules: [], failureModule: undefined, preprocessorModule: undefined }
|
||||
</script>
|
||||
|
||||
<FlowGraphV2 triggerNode={false} {modules} {failureModule} {preprocessorModule} />
|
||||
<FlowGraphV2
|
||||
workspace={$workspaceStore}
|
||||
triggerNode={false}
|
||||
{modules}
|
||||
{failureModule}
|
||||
{preprocessorModule}
|
||||
/>
|
||||
<a
|
||||
download="flow.json"
|
||||
href={'data:text/json;charset=utf-8,' +
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.421.2"
|
||||
wmill_pg = ">=1.421.2"
|
||||
wmill = ">=1.423.2"
|
||||
wmill_pg = ">=1.423.2"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.421.2
|
||||
version: 1.423.2
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.421.2'
|
||||
ModuleVersion = '1.423.2'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill-pg"
|
||||
version = "1.421.2"
|
||||
version = "1.423.2"
|
||||
description = "An extension client for the wmill client library focused on pg"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -706,7 +706,9 @@ export async function loadS3FileStream(
|
||||
export async function writeS3File(
|
||||
s3object: S3Object | undefined,
|
||||
fileContent: string | Blob,
|
||||
s3ResourcePath: string | undefined = undefined
|
||||
s3ResourcePath: string | undefined = undefined,
|
||||
contentType: string | undefined = undefined,
|
||||
contentDisposition: string | undefined = undefined
|
||||
): Promise<S3Object> {
|
||||
let fileContentBlob: Blob;
|
||||
if (typeof fileContent === "string") {
|
||||
@@ -724,6 +726,8 @@ export async function writeS3File(
|
||||
s3ResourcePath: s3ResourcePath,
|
||||
requestBody: fileContentBlob,
|
||||
storage: s3object?.storage,
|
||||
contentType,
|
||||
contentDisposition,
|
||||
});
|
||||
return {
|
||||
s3: response.file_key,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.421.2",
|
||||
"version": "1.423.2",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user