mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-20 16:02:28 +00:00
Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06898b9837 | ||
|
|
8306e6e5d2 | ||
|
|
6c1e7eed51 | ||
|
|
f24894b1a5 | ||
|
|
50d78a979b | ||
|
|
c75fd751b0 | ||
|
|
f0f720f490 | ||
|
|
855571f359 | ||
|
|
41219e1f84 | ||
|
|
499fe7e7fe | ||
|
|
72613f3580 | ||
|
|
7d4ee825fa | ||
|
|
1cd253405d | ||
|
|
4dab80e24d | ||
|
|
38bb5e25cb | ||
|
|
3678bf976e | ||
|
|
6623972ee9 | ||
|
|
6a4391f616 | ||
|
|
f7a83c03c1 | ||
|
|
82f541565f | ||
|
|
44e1ed6c9c | ||
|
|
58ca3a6866 | ||
|
|
0b3a084c81 | ||
|
|
17c8c8a561 | ||
|
|
1a6283b42a | ||
|
|
126b610561 | ||
|
|
0b89260540 | ||
|
|
6c4818259c | ||
|
|
283167264e | ||
|
|
b3e7a9cb79 | ||
|
|
c3a54ddd7c | ||
|
|
ae684d8608 | ||
|
|
f077849b8f | ||
|
|
cf2d09e7a8 | ||
|
|
bb354cfeb8 | ||
|
|
bebfa76fc2 | ||
|
|
b0ac12052a | ||
|
|
f412ede6ed | ||
|
|
f94651115a | ||
|
|
6f907c79b4 | ||
|
|
6e7ea4111c | ||
|
|
ff9acb4717 | ||
|
|
a393c93085 | ||
|
|
23920aee84 | ||
|
|
d47c078bb5 | ||
|
|
b4a6a7e724 | ||
|
|
332f66e348 | ||
|
|
d2dfd27b88 | ||
|
|
b56a5c3e7a | ||
|
|
ae81b4f456 | ||
|
|
cd508951d7 | ||
|
|
94b56d3ffe | ||
|
|
cb8c3560af | ||
|
|
a47939d13c | ||
|
|
e8836a393a | ||
|
|
86adebde39 | ||
|
|
b44b9c1b82 | ||
|
|
d384bb8cb4 | ||
|
|
ec873c2c09 | ||
|
|
d37f13d023 | ||
|
|
a2eac4ae37 | ||
|
|
22b2f4988d | ||
|
|
13b318200b | ||
|
|
ec552d5ef6 | ||
|
|
3c3fdbdf26 |
@@ -51,7 +51,7 @@ jobs:
|
||||
$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,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages,mcp,private
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,nats,sqs_trigger,postgres_trigger,gcp_trigger,mqtt_trigger,websocket,smtp,static_frontend,all_languages_windows,mcp,private
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
|
||||
|
||||
@@ -12,12 +12,12 @@ jobs:
|
||||
- name: Check organization membership
|
||||
id: check-membership
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }}
|
||||
COMMENTER: ${{ github.event.comment.user.login }}
|
||||
run: |
|
||||
ORG="windmill-labs"
|
||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-H "Authorization: token $GH_TOKEN" \
|
||||
-H "Authorization: token $ORG_ACCESS_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"https://api.github.com/orgs/$ORG/members/$COMMENTER")
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: windmill-labs/windmill-helm-charts
|
||||
token: ${{ secrets.DOCS_TOKEN }}
|
||||
token: ${{ secrets.HELM_CHART_TOKEN }}
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
|
||||
- name: Create PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.DOCS_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.HELM_CHART_TOKEN }}
|
||||
run: |
|
||||
gh pr create \
|
||||
--title "helm: bump version to ${{ env.VERSION }}" \
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
$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,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages,mcp,private
|
||||
cargo build --release --features=enterprise,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy,license,http_trigger,zip,oauth2,kafka,sqs_trigger,nats,postgres_trigger,mqtt_trigger,gcp_trigger,websocket,smtp,static_frontend,all_languages_windows,mcp,private
|
||||
- name: Rename binary with corresponding architecture
|
||||
run: |
|
||||
Rename-Item -Path ".\backend\target\release\windmill.exe" -NewName "windmill-ee.exe"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Publish rust-client to crates.io on release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_rust_and_publish_to_crates_io:
|
||||
runs-on: ubicloud-standard-8
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v20
|
||||
with:
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
- run: cd rust-client && nix develop ../ --command ./dev.nu --check --publish
|
||||
env:
|
||||
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
@@ -62,9 +62,10 @@ jobs:
|
||||
# Create thread if it doesn't exist or if check failed
|
||||
if [ "$thread_exists" = false ]; then
|
||||
echo "Creating new thread"
|
||||
THREAD_TITLE="#${PR_NUMBER}: ${PR_TITLE} by \`${PR_AUTHOR}\`"
|
||||
payload=$(jq -n \
|
||||
--arg content "${PR_URL}" \
|
||||
--arg thread "#${PR_NUMBER}: $PR_TITLE by \`${PR_AUTHOR}\`" \
|
||||
--arg thread "${THREAD_TITLE:0:99}" \
|
||||
'{
|
||||
content: $content,
|
||||
thread_name: $thread,
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [1.496.3](https://github.com/windmill-labs/windmill/compare/v1.496.2...v1.496.3) (2025-06-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve concurrent job parallelism performance ([e8836a3](https://github.com/windmill-labs/windmill/commit/e8836a393a872bb91e68ba0037681caf24149470))
|
||||
* Prioritize diff contexts in script mode for ai chat ([#5888](https://github.com/windmill-labs/windmill/issues/5888)) ([a47939d](https://github.com/windmill-labs/windmill/commit/a47939d13c30e2d4b41efd539f845959174d4fb1))
|
||||
|
||||
## [1.496.2](https://github.com/windmill-labs/windmill/compare/v1.496.1...v1.496.2) (2025-06-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add clearable by default for select ([#5900](https://github.com/windmill-labs/windmill/issues/5900)) ([b44b9c1](https://github.com/windmill-labs/windmill/commit/b44b9c1b82116ad5487af95d1f78226d56c75179))
|
||||
|
||||
## [1.496.1](https://github.com/windmill-labs/windmill/compare/v1.496.0...v1.496.1) (2025-06-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* never consider minor version for global site packages ([#5893](https://github.com/windmill-labs/windmill/issues/5893)) ([22b2f49](https://github.com/windmill-labs/windmill/commit/22b2f4988db9314f2403508933d0aa932187c668))
|
||||
|
||||
## [1.496.0](https://github.com/windmill-labs/windmill/compare/v1.495.1...v1.496.0) (2025-06-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* generate http route triggers from openapi spec ([#5857](https://github.com/windmill-labs/windmill/issues/5857)) ([5713483](https://github.com/windmill-labs/windmill/commit/571348377b73d54b4d2a1c5775ab00b247b01910))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow fileupload drag and drop in edit mode on full component without triggering file picker ([#5889](https://github.com/windmill-labs/windmill/issues/5889)) ([9ae3212](https://github.com/windmill-labs/windmill/commit/9ae3212a1e0f88a8297bf41ab53e3c1be4bcc56c))
|
||||
* **python:** account instance version when cli deploy and local lockfile ([#5894](https://github.com/windmill-labs/windmill/issues/5894)) ([ec552d5](https://github.com/windmill-labs/windmill/commit/ec552d5ef6fdb5e824e453f196f9cf16629ee2ea))
|
||||
* use full client side js library for route gen from openapi ([#5891](https://github.com/windmill-labs/windmill/issues/5891)) ([3c3fdbd](https://github.com/windmill-labs/windmill/commit/3c3fdbdf26a9581b815210839b91ebdedb924093))
|
||||
|
||||
## [1.495.0](https://github.com/windmill-labs/windmill/compare/v1.494.0...v1.495.0) (2025-06-05)
|
||||
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "CREATE INDEX CONCURRENTLY IF NOT EXISTS v2_job_queue_suspend ON v2_job_queue (workspace_id, suspend) WHERE suspend > 0;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "19f0ccadd3ee44719a781ea0d73ea4e45f5b2c3d5c0aa5dbecf9ea9838881b74"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT path FROM script WHERE workspace_id = $1 AND archived = false",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3e244a5057d4f1b4a18c0edac52cdf695c7e7aa0468d2686255de3d83719e6d0"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC, devops DESC\n LIMIT $1 OFFSET $2",
|
||||
"query": "WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n ORDER BY super_admin DESC, devops DESC\n LIMIT $1 OFFSET $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -67,5 +67,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3895cee539a24b4c6ea89fa7a835fc62bc93b0530efba09fc3c32a8f93eaabb1"
|
||||
"hash": "5430f7728c1e9b539cc8aad29ca9e6733943278998d3df62a9486607827e59ec"
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE parent_job = $1\n AND f.id = j.id AND q.id = j.id\n AND suspend = $2 AND (f.flow_status->'step')::int = 0",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "553108ba3c0b8d579800bc8b5a4f887d79fb4c13b60b19c4913a8db18521958c"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT distinct(path) FROM script WHERE workspace_id = $1 AND archived = true",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8373b2649ab46310860adbdd7b717261771ac61d46d82d42d085ffebeb18be06"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "authors",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "operators",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "author_count",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "operator_count",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "cb3862634f18160207ee2621ddfca43f00456a27fda32583846497116f92f96c"
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH active_users as (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),\n active_authors as (SELECT distinct email FROM usr WHERE usr.operator IS false AND email IN (SELECT email FROM active_users)),\n active_authors_agg as (SELECT array_agg(email) as authors FROM active_authors),\n active_ops_agg as (SELECT array_agg(email) as operators from active_users WHERE email NOT IN (SELECT email FROM active_authors))\n SELECT active_authors_agg.authors, active_ops_agg.operators, array_length(active_authors_agg.authors, 1) as author_count, array_length(active_ops_agg.operators, 1) as operator_count FROM active_authors_agg, active_ops_agg",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "authors",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "operators",
|
||||
"type_info": "VarcharArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "author_count",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "operator_count",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "cce991f582bc9d2ba28a5b2b41c679366bb07bc6a100727721a787160ac6910c"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE v2_job_queue q SET suspend = 0\n FROM v2_job j, v2_job_status f\n WHERE q.workspace_id = $1 AND q.suspend = $3 AND j.parent_job = $2\n AND f.id = j.id AND q.id = j.id\n AND (f.flow_status->'step')::int = 0",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f1dbcb6e6d82d17c19eb88c0e67dc1cb8baf5bd40b75a2a9cd3ebac440fda632"
|
||||
}
|
||||
Vendored
+3
-1
@@ -11,5 +11,7 @@
|
||||
"remote.autoForwardPorts": true,
|
||||
"conventionalCommits.scopes": [
|
||||
"restructring triggers, decoding trigger message on work"
|
||||
]
|
||||
],
|
||||
"files.exclude": { "**/*ee.rs": false },
|
||||
"search.exclude": { "**/*ee.rs": false }
|
||||
}
|
||||
|
||||
Generated
+114
-94
@@ -334,7 +334,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"half",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
"num",
|
||||
]
|
||||
|
||||
@@ -836,9 +836,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sqs"
|
||||
version = "1.71.0"
|
||||
version = "1.72.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0519921fc7065a54b8dec1d10de2893d99a61d030b9f8eb00ee83eb62d2a2676"
|
||||
checksum = "e7c66032c543428209106e3a104402289d9f18f77c7eb3de8f6e165256c42c38"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -993,7 +993,7 @@ dependencies = [
|
||||
"hyper 0.14.32",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper-rustls 0.27.6",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
@@ -1639,9 +1639,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
version = "3.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
@@ -1681,9 +1681,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.0"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
|
||||
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
@@ -1859,9 +1859,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.25"
|
||||
version = "1.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
|
||||
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -1979,9 +1979,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.39"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f"
|
||||
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -1989,9 +1989,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.39"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51"
|
||||
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -2001,9 +2001,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.32"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -2013,9 +2013,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
@@ -3516,7 +3516,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls 0.27.6",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"percent-encoding",
|
||||
@@ -4122,7 +4122,7 @@ dependencies = [
|
||||
"deno_tls",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls 0.27.6",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
@@ -5115,9 +5115,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
@@ -5690,11 +5690,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.14",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5950,7 +5950,7 @@ checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"gpu-descriptor-types",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6065,9 +6065,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.3"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
@@ -6100,7 +6100,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6422,9 +6422,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.6"
|
||||
version = "0.27.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
dependencies = [
|
||||
"http 1.3.1",
|
||||
"hyper 1.6.0",
|
||||
@@ -6728,7 +6728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -7299,9 +7299,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
|
||||
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
@@ -7393,7 +7393,7 @@ version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7402,7 +7402,7 @@ version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7929,7 +7929,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-util",
|
||||
"twox-hash 2.1.0",
|
||||
"twox-hash 2.1.1",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -9023,7 +9023,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"futures",
|
||||
"half",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
"lz4_flex",
|
||||
"num",
|
||||
"num-bigint",
|
||||
@@ -9034,7 +9034,7 @@ dependencies = [
|
||||
"snap",
|
||||
"thrift",
|
||||
"tokio",
|
||||
"twox-hash 2.1.0",
|
||||
"twox-hash 2.1.1",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
@@ -9318,9 +9318,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "postgres-native-tls"
|
||||
@@ -9462,7 +9462,7 @@ version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit 0.22.26",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9765,7 +9765,7 @@ checksum = "6b450dad8382b1b95061d5ca1eb792081fb082adf48c678791fe917509596d5f"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"equivalent",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
"parking_lot 0.12.4",
|
||||
]
|
||||
|
||||
@@ -10236,7 +10236,7 @@ dependencies = [
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls 0.27.6",
|
||||
"hyper-rustls 0.27.7",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
@@ -10402,13 +10402,15 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rmcp"
|
||||
version = "0.1.5"
|
||||
source = "git+https://github.com/windmill-labs/rust-sdk#9142b40202e49ca0b6530fa49a3abc8bd1b2fcf0"
|
||||
source = "git+https://github.com/modelcontextprotocol/rust-sdk#db03f63e76b5b32f65d34a1bd08ae56dab595f60"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"axum",
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"futures",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"paste",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.0",
|
||||
@@ -10416,20 +10418,24 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sse-stream",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmcp-macros"
|
||||
version = "0.1.5"
|
||||
source = "git+https://github.com/windmill-labs/rust-sdk#9142b40202e49ca0b6530fa49a3abc8bd1b2fcf0"
|
||||
source = "git+https://github.com/modelcontextprotocol/rust-sdk#db03f63e76b5b32f65d34a1bd08ae56dab595f60"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
@@ -10967,6 +10973,7 @@ version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
@@ -11230,9 +11237,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -11517,9 +11524,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -11709,7 +11716,7 @@ dependencies = [
|
||||
"futures-intrusive",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
"hashlink 0.10.0",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
@@ -11880,6 +11887,19 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sse-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f649a9f9e91db2ed32f3724516eac2bc09fab77fc33be8f670f5619b9dc6c3f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
@@ -13247,7 +13267,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hashbrown 0.15.3",
|
||||
"hashbrown 0.15.4",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"tokio",
|
||||
@@ -13288,9 +13308,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.9"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -13310,9 +13330,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.26"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap 2.9.0",
|
||||
"toml_datetime",
|
||||
@@ -13469,9 +13489,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -13480,9 +13500,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -13676,9 +13696,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908"
|
||||
checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56"
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
@@ -14512,7 +14532,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -14563,7 +14583,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -14672,7 +14692,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
@@ -14687,7 +14707,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -14700,7 +14720,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -14714,7 +14734,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -14790,7 +14810,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -14804,7 +14824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -14827,7 +14847,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -14839,7 +14859,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -14848,7 +14868,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -14860,7 +14880,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -14872,7 +14892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -14884,7 +14904,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -14896,7 +14916,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -14908,7 +14928,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -14919,7 +14939,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -14930,7 +14950,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -14941,7 +14961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -14964,7 +14984,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -14981,7 +15001,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -14993,7 +15013,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -15011,7 +15031,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.16",
|
||||
@@ -15035,7 +15055,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -15045,7 +15065,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15078,7 +15098,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -15088,7 +15108,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -15918,9 +15938,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"
|
||||
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -32,7 +32,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.495.1"
|
||||
version = "1.496.3"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -96,7 +96,9 @@ csharp = ["windmill-worker/csharp"]
|
||||
nu = ["windmill-worker/nu"]
|
||||
java = ["windmill-worker/java"]
|
||||
all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
|
||||
|
||||
# For windows we have another set of languages enabled
|
||||
# NOTE: DuckDB is ignored because of compilation problems
|
||||
all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"]
|
||||
|
||||
[patch.crates-io]
|
||||
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
|
||||
|
||||
@@ -1 +1 @@
|
||||
ab4f856708a056973faea19f10eda5efff546176
|
||||
2c3e21f4573486628e0b8969ff478c237bd0283f
|
||||
|
||||
+8
-8
@@ -80,8 +80,7 @@ use windmill_worker::{
|
||||
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR,
|
||||
DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR,
|
||||
JAVA_CACHE_DIR, NU_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR,
|
||||
PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, TAR_PY310_CACHE_DIR,
|
||||
TAR_PY311_CACHE_DIR, TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR,
|
||||
PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, UV_CACHE_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
@@ -160,6 +159,7 @@ lazy_static::lazy_static! {
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok())
|
||||
.unwrap_or(3600 * 12);
|
||||
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
@@ -1066,7 +1066,9 @@ Windmill Community Edition {GIT_VERSION}
|
||||
}
|
||||
|
||||
if server_mode {
|
||||
tracing::info!("monitor task started");
|
||||
if !*windmill_common::QUIET_LOGS {
|
||||
tracing::info!("monitor task started");
|
||||
}
|
||||
}
|
||||
monitor_db(
|
||||
&conn,
|
||||
@@ -1078,7 +1080,9 @@ Windmill Community Edition {GIT_VERSION}
|
||||
)
|
||||
.await;
|
||||
if server_mode {
|
||||
tracing::info!("monitor task finished");
|
||||
if !*windmill_common::QUIET_LOGS {
|
||||
tracing::info!("monitor task finished");
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1281,10 +1285,6 @@ pub async fn run_workers(
|
||||
PY311_CACHE_DIR,
|
||||
PY312_CACHE_DIR,
|
||||
PY313_CACHE_DIR,
|
||||
TAR_PY310_CACHE_DIR,
|
||||
TAR_PY311_CACHE_DIR,
|
||||
TAR_PY312_CACHE_DIR,
|
||||
TAR_PY313_CACHE_DIR,
|
||||
BUN_BUNDLE_CACHE_DIR,
|
||||
GO_CACHE_DIR,
|
||||
GO_BIN_CACHE_DIR,
|
||||
|
||||
@@ -1324,7 +1324,6 @@ pub async fn monitor_db(
|
||||
initial_load: bool,
|
||||
_killpill_tx: KillpillSender,
|
||||
) {
|
||||
tracing::info!("Starting periodic monitor task");
|
||||
let zombie_jobs_f = async {
|
||||
if server_mode && !initial_load && !*DISABLE_ZOMBIE_JOBS_MONITORING {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
@@ -1422,7 +1421,6 @@ pub async fn monitor_db(
|
||||
apply_autoscaling_f,
|
||||
update_min_worker_version_f,
|
||||
);
|
||||
tracing::info!("Periodic monitor task completed");
|
||||
}
|
||||
|
||||
pub async fn expose_queue_metrics(db: &Pool<Postgres>) {
|
||||
|
||||
@@ -69,7 +69,7 @@ if [ "$REVERT" == "YES" ]; then
|
||||
for ee_file in $(find ${EE_CODE_DIR} -name "*ee.rs"); do
|
||||
ce_file="${ee_file/${EE_CODE_DIR}/}"
|
||||
ce_file="${root_dirpath}/backend/${ce_file}"
|
||||
rm ${ce_file}
|
||||
rm ${ce_file} || true
|
||||
done
|
||||
elif [ "$MOVE_NEW_FILES" == "NO" ]; then
|
||||
# This replaces all files in current repo with alternative EE files in windmill-ee-private
|
||||
@@ -80,7 +80,7 @@ elif [ "$MOVE_NEW_FILES" == "NO" ]; then
|
||||
cp "${ee_file}" "${ce_file}"
|
||||
echo "File copied '${ee_file}' -->> '${ce_file}'"
|
||||
else
|
||||
ln -s "${ee_file}" "${ce_file}"
|
||||
ln -s "${ee_file}" "${ce_file}" || true
|
||||
echo "Symlink created '${ee_file}' -->> '${ce_file}'"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -40,7 +40,7 @@ mcp = ["dep:rmcp"]
|
||||
python = []
|
||||
|
||||
[dependencies]
|
||||
rmcp = { git = "https://github.com/windmill-labs/rust-sdk", features = ["transport-sse-server"], optional = true }
|
||||
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", features=["transport-streamable-http-server", "transport-streamable-http-server-session", "transport-worker"], optional = true }
|
||||
windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-audit.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.495.1
|
||||
version: 1.496.3
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -24,7 +24,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref AI_REQUEST_CACHE: Cache<(String, AIProvider), ExpiringAIRequestConfig> = Cache::new(500);
|
||||
}
|
||||
|
||||
const AZURE_API_VERSION: &str = "2024-10-21";
|
||||
const AZURE_API_VERSION: &str = "2025-04-01-preview";
|
||||
const OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -143,17 +143,33 @@ impl AIRequestConfig {
|
||||
path: &str,
|
||||
body: Bytes,
|
||||
) -> Result<RequestBuilder> {
|
||||
let url = format!("{}/{}", self.base_url, path);
|
||||
|
||||
let body = if let Some(user) = self.user {
|
||||
Self::add_user_to_body(body, user)?
|
||||
} else {
|
||||
body
|
||||
};
|
||||
|
||||
let is_azure = matches!(provider, AIProvider::OpenAI) && self.base_url != OPENAI_BASE_URL
|
||||
let base_url = self.base_url.trim_end_matches('/');
|
||||
|
||||
let is_azure = matches!(provider, AIProvider::OpenAI) && base_url != OPENAI_BASE_URL
|
||||
|| matches!(provider, AIProvider::AzureOpenAI);
|
||||
|
||||
let url = if is_azure {
|
||||
if base_url.ends_with("/deployments") {
|
||||
let model = Self::get_azure_model(&body)?;
|
||||
format!("{}/{}/{}", base_url, model, path)
|
||||
} else if base_url.ends_with("/openai") {
|
||||
let model = Self::get_azure_model(&body)?;
|
||||
format!("{}/deployments/{}/{}", base_url, model, path)
|
||||
} else {
|
||||
format!("{}/{}", base_url, path)
|
||||
}
|
||||
} else {
|
||||
format!("{}/{}", base_url, path)
|
||||
};
|
||||
|
||||
tracing::debug!("AI request URL: {}", url);
|
||||
|
||||
let mut request = HTTP_CLIENT
|
||||
.post(url)
|
||||
.header("content-type", "application/json")
|
||||
@@ -199,6 +215,18 @@ impl AIRequestConfig {
|
||||
.map_err(|e| Error::internal_err(format!("Failed to reserialize request body: {}", e)))?
|
||||
.into())
|
||||
}
|
||||
|
||||
fn get_azure_model(body: &Bytes) -> Result<String> {
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct AzureModel {
|
||||
model: String,
|
||||
}
|
||||
|
||||
let azure_model: AzureModel = serde_json::from_slice(body)
|
||||
.map_err(|e| Error::internal_err(format!("Failed to parse request body: {}", e)))?;
|
||||
|
||||
Ok(azure_model.model)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -804,6 +804,14 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
|
||||
.execute(db)
|
||||
.await?;
|
||||
});
|
||||
|
||||
run_windmill_migration!("v2_job_queue_suspend", db, |tx| {
|
||||
sqlx::query!(
|
||||
"CREATE INDEX CONCURRENTLY IF NOT EXISTS v2_job_queue_suspend ON v2_job_queue (workspace_id, suspend) WHERE suspend > 0;"
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,16 @@ use crate::oauth2_oss::SlackVerifier;
|
||||
use crate::smtp_server_oss::SmtpServer;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
use crate::mcp::{setup_mcp_server, Runner as McpRunner};
|
||||
use crate::mcp::{extract_and_store_workspace_id, setup_mcp_server, shutdown_mcp_server};
|
||||
#[cfg(feature = "mcp")]
|
||||
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
|
||||
|
||||
use crate::tracing_init::MyOnFailure;
|
||||
use crate::{
|
||||
tracing_init::{MyMakeSpan, MyOnResponse},
|
||||
users::OptAuthed,
|
||||
webhook_util::WebhookShared,
|
||||
};
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
use agent_workers_oss::AgentCache;
|
||||
|
||||
@@ -520,21 +522,18 @@ pub async fn run_server(
|
||||
|
||||
// Setup MCP server
|
||||
#[allow(unused_variables)]
|
||||
let (mcp_router, mcp_main_ct, mcp_service_ct) = {
|
||||
let (mcp_router, mcp_session_manager) = {
|
||||
#[cfg(feature = "mcp")]
|
||||
if server_mode || mcp_mode {
|
||||
let (mcp_sse_server, mcp_router) = setup_mcp_server(addr, "/api/mcp/w/:workspace_id")?;
|
||||
#[cfg(feature = "mcp")]
|
||||
let mcp_main_ct = mcp_sse_server.config.ct.clone(); // Token to signal shutdown *to* MCP
|
||||
#[cfg(feature = "mcp")]
|
||||
let mcp_service_ct = mcp_sse_server.with_service(McpRunner::new); // Token to wait for MCP *service* shutdown
|
||||
(mcp_router, Some(mcp_main_ct), Some(mcp_service_ct))
|
||||
let (mcp_router, mcp_session_manager) = setup_mcp_server().await?;
|
||||
let mcp_middleware = axum::middleware::from_fn(extract_and_store_workspace_id);
|
||||
(mcp_router.layer(mcp_middleware), Some(mcp_session_manager))
|
||||
} else {
|
||||
(Router::new(), None, None)
|
||||
(Router::new(), Option::<Arc<LocalSessionManager>>::None)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "mcp"))]
|
||||
(Router::new(), None::<()>, None::<()>)
|
||||
(Router::new(), Option::<()>::None)
|
||||
};
|
||||
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
@@ -660,7 +659,7 @@ pub async fn run_server(
|
||||
.layer(from_extractor::<OptAuthed>())
|
||||
.layer(cors.clone()),
|
||||
)
|
||||
.nest("/mcp/w/:workspace_id", mcp_router)
|
||||
.nest("/mcp/w/:workspace_id/sse", mcp_router)
|
||||
.layer(from_extractor::<OptAuthed>())
|
||||
.nest("/agent_workers", {
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
@@ -819,16 +818,9 @@ pub async fn run_server(
|
||||
tracing::info!("Graceful shutdown of server");
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
{
|
||||
if let Some(mcp_main_ct) = mcp_main_ct {
|
||||
tracing::info!("Received shutdown signal, cancelling MCP server...");
|
||||
mcp_main_ct.cancel();
|
||||
}
|
||||
if let Some(mcp_service_ct) = mcp_service_ct {
|
||||
tracing::info!("Waiting for MCP service cancellation...");
|
||||
mcp_service_ct.cancelled().await;
|
||||
tracing::info!("MCP service cancelled.");
|
||||
}
|
||||
if let Some(mcp_session_manager) = mcp_session_manager {
|
||||
shutdown_mcp_server(mcp_session_manager).await;
|
||||
tracing::info!("MCP server shutdown");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+112
-43
@@ -1,11 +1,10 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::body::to_bytes;
|
||||
use axum::Router;
|
||||
use rmcp::transport::sse_server::{SseServer, SseServerConfig};
|
||||
use axum::{extract::Path, http::Request, middleware::Next, response::Response};
|
||||
use rmcp::{
|
||||
handler::server::ServerHandler,
|
||||
model::*,
|
||||
@@ -17,7 +16,6 @@ use serde_json::Value;
|
||||
use sql_builder::prelude::*;
|
||||
use sqlx::FromRow;
|
||||
use tokio::try_join;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::worker::to_raw_value;
|
||||
use windmill_common::{DB, HUB_BASE_URL};
|
||||
@@ -29,6 +27,9 @@ use crate::jobs::{
|
||||
run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery,
|
||||
};
|
||||
use crate::HTTP_CLIENT;
|
||||
use rmcp::transport::streamable_http_server::{
|
||||
session::local::LocalSessionManager, SessionManager, StreamableHttpService,
|
||||
};
|
||||
use windmill_common::utils::{query_elems_from_hub, StripPath};
|
||||
|
||||
/// Transforms the path for workspace scripts/flows.
|
||||
@@ -856,28 +857,44 @@ impl ServerHandler for Runner {
|
||||
})
|
||||
};
|
||||
|
||||
let authed = context
|
||||
.req_extensions
|
||||
.get::<ApiAuthed>()
|
||||
.ok_or_else(|| Error::internal_error("ApiAuthed not found", None))?;
|
||||
let db = context
|
||||
.req_extensions
|
||||
.get::<DB>()
|
||||
.ok_or_else(|| Error::internal_error("DB not found", None))?;
|
||||
let user_db = context
|
||||
.req_extensions
|
||||
.get::<UserDB>()
|
||||
.ok_or_else(|| Error::internal_error("UserDB not found", None))?;
|
||||
let http_parts = context
|
||||
.extensions
|
||||
.get::<axum::http::request::Parts>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("http::request::Parts not found");
|
||||
Error::internal_error("http::request::Parts not found", None)
|
||||
})?;
|
||||
|
||||
let authed = http_parts.extensions.get::<ApiAuthed>().ok_or_else(|| {
|
||||
tracing::error!("ApiAuthed Axum extension not found");
|
||||
Error::internal_error("ApiAuthed Axum extension not found", None)
|
||||
})?;
|
||||
let db = http_parts.extensions.get::<DB>().ok_or_else(|| {
|
||||
tracing::error!("DB Axum extension not found");
|
||||
Error::internal_error("DB Axum extension not found", None)
|
||||
})?;
|
||||
let user_db = http_parts.extensions.get::<UserDB>().ok_or_else(|| {
|
||||
tracing::error!("UserDB Axum extension not found");
|
||||
Error::internal_error("UserDB Axum extension not found", None)
|
||||
})?;
|
||||
let args = parse_args(request.arguments)?;
|
||||
|
||||
let workspace_id = http_parts
|
||||
.extensions
|
||||
.get::<WorkspaceId>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("WorkspaceId not found");
|
||||
Error::internal_error("WorkspaceId not found", None)
|
||||
})
|
||||
.map(|w_id| w_id.0.clone())?;
|
||||
|
||||
let (tool_type, path, is_hub) =
|
||||
Runner::reverse_transform(&request.name).unwrap_or_default();
|
||||
|
||||
let item_schema = if is_hub {
|
||||
Runner::get_hub_script_schema(&format!("hub/{}", path), db).await?
|
||||
} else {
|
||||
Runner::get_item_schema(&path, user_db, authed, &context.workspace_id, &tool_type)
|
||||
.await?
|
||||
Runner::get_item_schema(&path, user_db, authed, &workspace_id, &tool_type).await?
|
||||
};
|
||||
|
||||
let schema_obj = if let Some(ref s) = item_schema {
|
||||
@@ -906,8 +923,6 @@ impl ServerHandler for Runner {
|
||||
} else {
|
||||
windmill_queue::PushArgsOwned::default()
|
||||
};
|
||||
|
||||
let w_id = context.workspace_id.clone();
|
||||
let script_or_flow_path = if is_hub {
|
||||
StripPath(format!("hub/{}", path))
|
||||
} else {
|
||||
@@ -922,7 +937,7 @@ impl ServerHandler for Runner {
|
||||
script_or_flow_path,
|
||||
authed.clone(),
|
||||
user_db.clone(),
|
||||
w_id.clone(),
|
||||
workspace_id.clone(),
|
||||
push_args,
|
||||
)
|
||||
.await
|
||||
@@ -934,7 +949,7 @@ impl ServerHandler for Runner {
|
||||
authed.clone(),
|
||||
user_db.clone(),
|
||||
push_args,
|
||||
w_id.clone(),
|
||||
workspace_id.clone(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
@@ -978,19 +993,38 @@ impl ServerHandler for Runner {
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
mut _context: RequestContext<RoleServer>,
|
||||
) -> Result<ListToolsResult, Error> {
|
||||
let workspace_id = _context.workspace_id.clone();
|
||||
let db = _context
|
||||
.req_extensions
|
||||
.get::<DB>()
|
||||
.ok_or_else(|| Error::internal_error("DB not found", None))?;
|
||||
let user_db = _context
|
||||
.req_extensions
|
||||
.get::<UserDB>()
|
||||
.ok_or_else(|| Error::internal_error("UserDB not found", None))?;
|
||||
let authed = _context
|
||||
.req_extensions
|
||||
.get::<ApiAuthed>()
|
||||
.ok_or_else(|| Error::internal_error("ApiAuthed not found", None))?;
|
||||
let http_parts = _context
|
||||
.extensions
|
||||
.get::<axum::http::request::Parts>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("http::request::Parts not found");
|
||||
Error::internal_error("http::request::Parts not found", None)
|
||||
})?;
|
||||
|
||||
let db = http_parts.extensions.get::<DB>().ok_or_else(|| {
|
||||
tracing::error!("DB Axum extension not found");
|
||||
Error::internal_error("DB Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
let user_db = http_parts.extensions.get::<UserDB>().ok_or_else(|| {
|
||||
tracing::error!("UserDB Axum extension not found");
|
||||
Error::internal_error("UserDB Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
let authed = http_parts.extensions.get::<ApiAuthed>().ok_or_else(|| {
|
||||
tracing::error!("ApiAuthed Axum extension not found");
|
||||
Error::internal_error("ApiAuthed Axum extension not found", None)
|
||||
})?;
|
||||
|
||||
let workspace_id = http_parts
|
||||
.extensions
|
||||
.get::<WorkspaceId>()
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("WorkspaceId not found");
|
||||
Error::internal_error("WorkspaceId not found", None)
|
||||
})
|
||||
.map(|w_id| w_id.0.clone())?;
|
||||
|
||||
let owned_scope = authed.scopes.as_ref().and_then(|scopes| {
|
||||
scopes
|
||||
.iter()
|
||||
@@ -1127,15 +1161,50 @@ impl ServerHandler for Runner {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_mcp_server(addr: SocketAddr, path: &str) -> anyhow::Result<(SseServer, Router)> {
|
||||
let config = SseServerConfig {
|
||||
bind: addr,
|
||||
sse_path: "/sse".to_string(),
|
||||
post_path: "/message".to_string(),
|
||||
full_message_path: path.to_string(),
|
||||
ct: CancellationToken::new(),
|
||||
sse_keep_alive: None,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorkspaceId(pub String);
|
||||
|
||||
pub async fn extract_and_store_workspace_id(
|
||||
Path(params): Path<String>,
|
||||
mut request: Request<axum::body::Body>,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
let workspace_id = params;
|
||||
request.extensions_mut().insert(WorkspaceId(workspace_id));
|
||||
next.run(request).await
|
||||
}
|
||||
|
||||
pub async fn setup_mcp_server() -> anyhow::Result<(Router, Arc<LocalSessionManager>)> {
|
||||
let session_manager = Arc::new(LocalSessionManager::default());
|
||||
let service_config = Default::default();
|
||||
let service = StreamableHttpService::new(Runner::new, session_manager.clone(), service_config);
|
||||
|
||||
let router = axum::Router::new().nest_service("/", service);
|
||||
Ok((router, session_manager))
|
||||
}
|
||||
|
||||
pub async fn shutdown_mcp_server(session_manager: Arc<LocalSessionManager>) {
|
||||
let session_ids_to_close = {
|
||||
let sessions_map = session_manager.sessions.read().await;
|
||||
sessions_map.keys().cloned().collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
Ok(SseServer::new(config))
|
||||
if !session_ids_to_close.is_empty() {
|
||||
tracing::info!(
|
||||
"Closing {} active MCP session(s)...",
|
||||
session_ids_to_close.len()
|
||||
);
|
||||
let close_futures = session_ids_to_close
|
||||
.iter()
|
||||
.map(|session_id| {
|
||||
let manager_clone = session_manager.clone();
|
||||
async move {
|
||||
if let Err(_) = manager_clone.close_session(session_id).await {
|
||||
tracing::warn!("Error closing MCP session");
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
futures::future::join_all(close_futures).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1289,6 +1289,11 @@ async fn raw_script_by_path_unpinned(
|
||||
raw_script_by_path_internal(path, user_db, db, authed, w_id, true).await
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref DEBUG_RAW_SCRIPT_ENDPOINTS: bool =
|
||||
std::env::var("DEBUG_RAW_SCRIPT_ENDPOINTS").is_ok();
|
||||
}
|
||||
|
||||
async fn raw_script_by_path_internal(
|
||||
path: StripPath,
|
||||
user_db: UserDB,
|
||||
@@ -1341,6 +1346,26 @@ async fn raw_script_by_path_internal(
|
||||
"Script {path} exists but {} does not have permissions to access it",
|
||||
authed.username
|
||||
)));
|
||||
} else {
|
||||
if *DEBUG_RAW_SCRIPT_ENDPOINTS {
|
||||
let other_script_o = sqlx::query_scalar!(
|
||||
"SELECT path FROM script WHERE workspace_id = $1 AND archived = false",
|
||||
w_id
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
let other_script_archived = sqlx::query_scalar!(
|
||||
"SELECT distinct(path) FROM script WHERE workspace_id = $1 AND archived = true",
|
||||
w_id
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
tracing::warn!(
|
||||
"Script {path} does not exist in workspace {w_id} but these paths do, non-archived: {:?} | archived: {:?}",
|
||||
other_script_o.join(", "),
|
||||
other_script_archived.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -542,7 +542,7 @@ async fn list_users_as_super_admin(
|
||||
let rows = if active_only.is_some_and(|x| x) {
|
||||
sqlx::query_as!(
|
||||
GlobalUserInfo,
|
||||
"WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login')),
|
||||
"WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),
|
||||
authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)
|
||||
SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username
|
||||
FROM password
|
||||
@@ -1750,7 +1750,22 @@ async fn refresh_token(
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
let _ = create_session_token(&authed.email, super_admin, &mut tx, cookies).await?;
|
||||
let new_token = create_session_token(&authed.email, super_admin, &mut tx, cookies).await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&AuditAuthor {
|
||||
email: authed.email.to_string(),
|
||||
username: authed.email.to_string(),
|
||||
username_override: None,
|
||||
},
|
||||
"users.token.refresh",
|
||||
ActionKind::Create,
|
||||
&"global",
|
||||
Some(&truncate_token(&new_token)),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok("token refreshed".to_string())
|
||||
|
||||
@@ -149,6 +149,8 @@ lazy_static::lazy_static! {
|
||||
pub static ref DEPLOYED_SCRIPT_INFO_CACHE: Cache<(String, i64), ScriptHashInfo> = Cache::new(1000);
|
||||
pub static ref FLOW_INFO_CACHE: Cache<(String, i64), FlowVersionInfo> = Cache::new(1000);
|
||||
|
||||
pub static ref QUIET_LOGS: bool = std::env::var("QUIET_LOGS").map(|s| s.parse::<bool>().unwrap_or(false)).unwrap_or(false);
|
||||
|
||||
}
|
||||
|
||||
const LATEST_VERSION_ID_CACHE_TTL: std::time::Duration = std::time::Duration::from_secs(60);
|
||||
|
||||
@@ -81,7 +81,7 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
Mode::Worker
|
||||
} else if &x == "agent" {
|
||||
println!("Binary is in 'agent' mode");
|
||||
println!("Binary is in 'agent' mode with BASE_INTERNAL_URL={}", std::env::var("BASE_INTERNAL_URL").unwrap_or_default());
|
||||
if std::env::var("BASE_INTERNAL_URL").is_err() {
|
||||
panic!("BASE_INTERNAL_URL is required in agent mode")
|
||||
}
|
||||
|
||||
@@ -2716,7 +2716,7 @@ pub async fn get_result_by_id(
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(_) => {
|
||||
Err(e) => {
|
||||
let root = sqlx::query!(
|
||||
"SELECT
|
||||
id As \"id!\",
|
||||
@@ -2733,7 +2733,7 @@ pub async fn get_result_by_id(
|
||||
let restarted_from_id = not_found_if_none(
|
||||
root.restarted_from,
|
||||
"Id not found in the result's mapping of the root job and root job had no restarted from information",
|
||||
format!("parent: {}, root: {}, id: {}", flow_id, root.id, node_id),
|
||||
format!("parent: {}, root: {}, id: {}, error: {e:#}", flow_id, root.id, node_id),
|
||||
)?;
|
||||
|
||||
get_result_by_id_from_original_flow(
|
||||
|
||||
@@ -3,7 +3,11 @@ use std::{collections::HashMap, fs::DirBuilder, process::Stdio};
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde_json::value::RawValue;
|
||||
use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use tokio::{
|
||||
fs::{self, File},
|
||||
io::AsyncReadExt,
|
||||
process::Command,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
@@ -91,6 +95,7 @@ pub async fn handle_go_job(
|
||||
true,
|
||||
skip_go_mod,
|
||||
skip_tidy,
|
||||
false,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
occupation_metrics,
|
||||
@@ -356,11 +361,21 @@ pub async fn install_go_dependencies(
|
||||
non_dep_job: bool,
|
||||
skip_go_mod: bool,
|
||||
has_sum: bool,
|
||||
raw_deps: bool,
|
||||
worker_name: &str,
|
||||
w_id: &str,
|
||||
occupation_metrics: &mut OccupancyMetrics,
|
||||
) -> error::Result<String> {
|
||||
if !skip_go_mod {
|
||||
if raw_deps {
|
||||
let go_mod =
|
||||
if let Some(module) = code.lines().find(|l| l.trim_start().starts_with("module ")) {
|
||||
code.replace(module, "module mymod")
|
||||
} else {
|
||||
format!("module mymod\n{code}")
|
||||
};
|
||||
fs::write(format!("{job_dir}/go.mod"), go_mod).await?;
|
||||
}
|
||||
if !raw_deps && !skip_go_mod {
|
||||
gen_go_mymod(code, job_dir).await?;
|
||||
let mut child_cmd = Command::new(GO_PATH.as_str());
|
||||
child_cmd
|
||||
@@ -400,7 +415,9 @@ pub async fn install_go_dependencies(
|
||||
|
||||
let mut new_lockfile = false;
|
||||
|
||||
let hash = if !has_sum {
|
||||
let hash = if raw_deps {
|
||||
calculate_hash(code)
|
||||
} else if !has_sum {
|
||||
calculate_hash(parse_go_imports(&code)?.iter().join("\n").as_str())
|
||||
} else {
|
||||
"".to_string()
|
||||
@@ -429,7 +446,15 @@ pub async fn install_go_dependencies(
|
||||
}
|
||||
}
|
||||
|
||||
let mod_command = if skip_tidy { "download" } else { "tidy" };
|
||||
let mod_command = if skip_tidy ||
|
||||
// If there is go.mod provided we want to use `download` only.
|
||||
// Unlike `tidy` it does not modify local go.mod
|
||||
raw_deps
|
||||
{
|
||||
"download"
|
||||
} else {
|
||||
"tidy"
|
||||
};
|
||||
let mut child_cmd = Command::new(GO_PATH.as_str());
|
||||
child_cmd
|
||||
.current_dir(job_dir)
|
||||
|
||||
@@ -658,7 +658,7 @@ except BaseException as e:
|
||||
|
||||
// Add /tmp/windmill/cache/python_x_y_z/global-site-packages to PYTHONPATH.
|
||||
// Usefull if certain wheels needs to be preinstalled before execution.
|
||||
let global_site_packages_path = py_version.to_cache_dir() + "/global-site-packages";
|
||||
let global_site_packages_path = py_version.to_cache_dir(true) + "/global-site-packages";
|
||||
let additional_python_paths_folders = {
|
||||
let mut paths = additional_python_paths.clone();
|
||||
if std::fs::metadata(&global_site_packages_path).is_ok() {
|
||||
@@ -1489,7 +1489,7 @@ pub async fn handle_python_reqs(
|
||||
if req.starts_with('#') || req.starts_with('-') || req.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let py_prefix = &py_version.to_cache_dir();
|
||||
let py_prefix = &py_version.to_cache_dir(false);
|
||||
|
||||
let venv_p = format!(
|
||||
"{py_prefix}/{}",
|
||||
@@ -1740,7 +1740,7 @@ pub async fn handle_python_reqs(
|
||||
tokio::select! {
|
||||
// Cancel was called on the job
|
||||
_ = kill_rx.recv() => return Err(anyhow::anyhow!("S3 pull was canceled")),
|
||||
pull = pull_from_tar(os, venv_p.clone(), py_version.to_cache_dir_top_level(), None, false) => {
|
||||
pull = pull_from_tar(os, venv_p.clone(), py_version.to_cache_dir_top_level(false), None, false) => {
|
||||
if let Err(e) = pull {
|
||||
tracing::info!(
|
||||
workspace_id = %w_id,
|
||||
@@ -1891,7 +1891,7 @@ pub async fn handle_python_reqs(
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet", unix))]
|
||||
if s3_push {
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
tokio::spawn(build_tar_and_push(os, venv_p.clone(), py_version.to_cache_dir_top_level(), None, false));
|
||||
tokio::spawn(build_tar_and_push(os, venv_p.clone(), py_version.to_cache_dir_top_level(false), None, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -278,14 +278,24 @@ impl PyV {
|
||||
.into())
|
||||
}
|
||||
}
|
||||
/// e.g.: `/tmp/windmill/cache/python_3xy`
|
||||
pub(crate) fn to_cache_dir(&self) -> String {
|
||||
/// e.g.: `/tmp/windmill/cache/python_3_x_y`
|
||||
pub(crate) fn to_cache_dir(&self, ignore_patch: bool) -> String {
|
||||
use windmill_common::worker::ROOT_CACHE_DIR;
|
||||
format!("{ROOT_CACHE_DIR}{}", self.to_cache_dir_top_level())
|
||||
format!(
|
||||
"{ROOT_CACHE_DIR}{}",
|
||||
self.to_cache_dir_top_level(ignore_patch)
|
||||
)
|
||||
}
|
||||
|
||||
/// e.g.: `python_3_x_y`
|
||||
pub fn to_cache_dir_top_level(&self) -> String {
|
||||
pub fn to_cache_dir_top_level(&self, ignore_patch: bool) -> String {
|
||||
if ignore_patch {
|
||||
if let [major, minor, ..] = self.release() {
|
||||
return format!("python_{major}_{minor}");
|
||||
}
|
||||
|
||||
tracing::warn!("failed to parse python's ({}) top level directory with no patch digit, fallback to full version.", self.to_string());
|
||||
}
|
||||
format!("python_{}", self.to_string().replace(".", "_"))
|
||||
}
|
||||
|
||||
@@ -589,7 +599,7 @@ impl PyV {
|
||||
// For the default version directory created during startup (main.rs)
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(self.to_cache_dir())
|
||||
.create(self.to_cache_dir(false))
|
||||
.await
|
||||
.expect("could not create initial worker dir");
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ mount {
|
||||
#[cfg(not(debug_assertions))]
|
||||
const DEV_CONF_NSJAIL: &'static str = "";
|
||||
|
||||
#[cfg(not(windows))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref CARGO_HOME_DEFAULT: String = format!("{}/.cargo", *HOME_DIR);
|
||||
static ref RUSTUP_HOME_DEFAULT: String = format!("{}/.rustup", *HOME_DIR);
|
||||
|
||||
@@ -168,15 +168,10 @@ use windmill_common::bench::{benchmark_init, BenchmarkInfo, BenchmarkIter};
|
||||
|
||||
use windmill_common::add_time;
|
||||
|
||||
pub const PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_310");
|
||||
pub const PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_311");
|
||||
pub const PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_312");
|
||||
pub const PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_313");
|
||||
|
||||
pub const TAR_PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_310");
|
||||
pub const TAR_PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_311");
|
||||
pub const TAR_PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_312");
|
||||
pub const TAR_PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/python_313");
|
||||
pub const PY310_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_10");
|
||||
pub const PY311_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_11");
|
||||
pub const PY312_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_12");
|
||||
pub const PY313_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "python_3_13");
|
||||
|
||||
pub const TAR_JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "tar/java");
|
||||
|
||||
|
||||
@@ -627,9 +627,10 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
sqlx::query!(
|
||||
"UPDATE v2_job_queue q SET suspend = 0
|
||||
FROM v2_job j, v2_job_status f
|
||||
WHERE parent_job = $1
|
||||
WHERE q.workspace_id = $1 AND q.suspend = $3 AND j.parent_job = $2
|
||||
AND f.id = j.id AND q.id = j.id
|
||||
AND suspend = $2 AND (f.flow_status->'step')::int = 0",
|
||||
AND (f.flow_status->'step')::int = 0",
|
||||
w_id,
|
||||
flow,
|
||||
nindex
|
||||
)
|
||||
|
||||
@@ -1965,12 +1965,11 @@ async fn ansible_dep(
|
||||
use windmill_parser_yaml::add_versions_to_requirements_yaml;
|
||||
|
||||
use crate::ansible_executor::{
|
||||
create_ansible_cfg, get_collection_locks, get_git_ssh_cmd, get_role_locks,
|
||||
install_galaxy_collections,
|
||||
};
|
||||
create_ansible_cfg, get_collection_locks, get_git_ssh_cmd, get_role_locks,
|
||||
install_galaxy_collections,
|
||||
};
|
||||
use windmill_common::client::AuthedClient;
|
||||
|
||||
|
||||
let python_lockfile = python_dep(
|
||||
reqs.python_reqs.join("\n").to_string(),
|
||||
job_id,
|
||||
@@ -2102,7 +2101,12 @@ async fn capture_dependency_job(
|
||||
|
||||
(
|
||||
job_raw_code.to_owned(),
|
||||
crate::PyV::parse_from_requirements(&split_requirements(job_raw_code)),
|
||||
match crate::PyV::try_parse_from_requirements(&split_requirements(
|
||||
job_raw_code,
|
||||
)) {
|
||||
Some(pyv) => pyv,
|
||||
None => crate::PyV::gravitational_version(job_id, w_id, None).await,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
let mut version_specifiers = vec![];
|
||||
@@ -2189,11 +2193,6 @@ async fn capture_dependency_job(
|
||||
}
|
||||
}
|
||||
ScriptLang::Go => {
|
||||
if raw_deps {
|
||||
return Err(Error::ExecutionErr(
|
||||
"Raw dependencies not supported for go".to_string(),
|
||||
));
|
||||
}
|
||||
install_go_dependencies(
|
||||
job_id,
|
||||
job_raw_code,
|
||||
@@ -2204,6 +2203,7 @@ async fn capture_dependency_job(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
raw_deps,
|
||||
worker_name,
|
||||
w_id,
|
||||
occupancy_metrics,
|
||||
|
||||
+1
-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.495.1";
|
||||
export const VERSION = "v1.496.3";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ export {
|
||||
// }
|
||||
// });
|
||||
|
||||
export const VERSION = "1.495.1";
|
||||
export const VERSION = "1.496.3";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
+12
-3
@@ -40,7 +40,7 @@ export class LockfileGenerationError extends Error {
|
||||
export async function generateAllMetadata() {}
|
||||
|
||||
function findClosestRawReqs(
|
||||
lang: "bun" | "python3" | "php" | undefined,
|
||||
lang: "bun" | "python3" | "php" | "go" | undefined,
|
||||
remotePath: string,
|
||||
globalDeps: GlobalDeps
|
||||
): string | undefined {
|
||||
@@ -72,6 +72,15 @@ function findClosestRawReqs(
|
||||
bestCandidate = { k, v };
|
||||
}
|
||||
});
|
||||
} else if (lang == "go") {
|
||||
Object.entries(globalDeps.goMods).forEach(([k, v]) => {
|
||||
if (
|
||||
remotePath.startsWith(k) &&
|
||||
k.length >= (bestCandidate?.k ?? "").length
|
||||
) {
|
||||
bestCandidate = { k, v };
|
||||
}
|
||||
});
|
||||
}
|
||||
// @ts-ignore
|
||||
return bestCandidate?.v;
|
||||
@@ -193,14 +202,14 @@ export async function generateScriptMetadataInternal(
|
||||
const language = inferContentTypeFromFilePath(scriptPath, opts.defaultTs);
|
||||
|
||||
const rawReqs = findClosestRawReqs(
|
||||
language as "bun" | "python3" | "php" | undefined,
|
||||
language as "bun" | "python3" | "php" | "go" | undefined,
|
||||
scriptPath,
|
||||
globalDeps
|
||||
);
|
||||
if (rawReqs) {
|
||||
log.info(
|
||||
(await blueColor())(
|
||||
`Found raw requirements (package.json/requirements.txt/composer.json) for ${scriptPath}, using it`
|
||||
`Found raw requirements (package.json/requirements.txt/composer.json/go.mod) for ${scriptPath}, using it`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
+10
-5
@@ -869,11 +869,13 @@ export type GlobalDeps = {
|
||||
pkgs: Record<string, string>;
|
||||
reqs: Record<string, string>;
|
||||
composers: Record<string, string>;
|
||||
goMods: Record<string, string>;
|
||||
};
|
||||
export async function findGlobalDeps(): Promise<GlobalDeps> {
|
||||
const pkgs: { [key: string]: string } = {};
|
||||
const reqs: { [key: string]: string } = {};
|
||||
const composers: { [key: string]: string } = {};
|
||||
const goMods: { [key: string]: string } = {};
|
||||
const els = await FSFSElement(Deno.cwd(), [], false);
|
||||
for await (const entry of readDirRecursiveWithIgnore((p, isDir) => {
|
||||
p = SEP + p;
|
||||
@@ -882,21 +884,24 @@ export async function findGlobalDeps(): Promise<GlobalDeps> {
|
||||
!(
|
||||
p.endsWith(SEP + "package.json") ||
|
||||
p.endsWith(SEP + "requirements.txt") ||
|
||||
p.endsWith(SEP + "composer.json")
|
||||
p.endsWith(SEP + "composer.json") ||
|
||||
p.endsWith(SEP + "go.mod")
|
||||
)
|
||||
);
|
||||
}, els)) {
|
||||
if (entry.isDirectory || entry.ignored) continue;
|
||||
const content = await entry.getContentText();
|
||||
if (entry.path.endsWith("package.json")) {
|
||||
pkgs[entry.path.substring(0, entry.path.length - 12)] = content;
|
||||
pkgs[entry.path.substring(0, entry.path.length - "package.json".length)] = content;
|
||||
} else if (entry.path.endsWith("requirements.txt")) {
|
||||
reqs[entry.path.substring(0, entry.path.length - 16)] = content;
|
||||
reqs[entry.path.substring(0, entry.path.length - "requirements.txt".length)] = content;
|
||||
} else if (entry.path.endsWith("composer.json")) {
|
||||
composers[entry.path.substring(0, entry.path.length - 13)] = content;
|
||||
composers[entry.path.substring(0, entry.path.length - "composer.json".length)] = content;
|
||||
} else if (entry.path.endsWith("go.mod")) {
|
||||
goMods[entry.path.substring(0, entry.path.length - "go.mod".length)] = content;
|
||||
}
|
||||
}
|
||||
return { pkgs, reqs, composers };
|
||||
return { pkgs, reqs, composers, goMods };
|
||||
}
|
||||
async function generateMetadata(
|
||||
opts: GlobalOptions & {
|
||||
|
||||
@@ -22,6 +22,15 @@
|
||||
"rustfmt"
|
||||
];
|
||||
};
|
||||
patchedClang = pkgs.llvmPackages_18.clang.overrideAttrs (oldAttrs: {
|
||||
postFixup = ''
|
||||
# Copy the original postFixup logic but skip add-hardening.sh
|
||||
${oldAttrs.postFixup or ""}
|
||||
|
||||
# Remove the line that substitutes add-hardening.sh
|
||||
sed -i 's/.*source.*add-hardening\.sh.*//' $out/bin/clang
|
||||
'';
|
||||
});
|
||||
buildInputs = with pkgs; [
|
||||
openssl
|
||||
openssl.dev
|
||||
@@ -64,16 +73,24 @@
|
||||
in {
|
||||
# Enter by `nix develop .#wasm`
|
||||
devShells."wasm" = pkgs.mkShell {
|
||||
# Explicitly set paths for headers and linker
|
||||
shellHook = ''
|
||||
export CC=${patchedClang}/bin/clang
|
||||
'';
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
(rust-bin.nightly.latest.default.override {
|
||||
extensions = [
|
||||
"rust-src" # for rust-analyzer
|
||||
"rust-analyzer"
|
||||
];
|
||||
targets = [ "wasm32-unknown-unknown" ];
|
||||
targets =
|
||||
[ "wasm32-unknown-unknown" "wasm32-unknown-emscripten" ];
|
||||
})
|
||||
wasm-pack
|
||||
deno
|
||||
emscripten
|
||||
# Needed for extra dependencies
|
||||
glibc_multi
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -81,6 +98,7 @@
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
# Essentials
|
||||
rust
|
||||
cargo-watch
|
||||
cargo-sweep
|
||||
git
|
||||
xcaddy
|
||||
@@ -197,7 +215,7 @@
|
||||
ORACLE_LIB_DIR = "${pkgs.oracle-instantclient.lib}/lib";
|
||||
ANSIBLE_PLAYBOOK_PATH = "${pkgs.ansible}/bin/ansible-playbook";
|
||||
ANSIBLE_GALAXY_PATH = "${pkgs.ansible}/bin/ansible-galaxy";
|
||||
RUST_LOG = "debug";
|
||||
# RUST_LOG = "debug";
|
||||
SQLX_OFFLINE = "true";
|
||||
|
||||
# See this issue: https://github.com/NixOS/nixpkgs/issues/370494
|
||||
|
||||
Generated
+157
-1300
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.495.1",
|
||||
"version": "1.496.3",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -50,7 +50,6 @@
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"style-to-object": "^0.4.1",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"svelte": "^5.0.0",
|
||||
@@ -70,7 +69,6 @@
|
||||
"typescript": "^5.5.0",
|
||||
"vite": "^6.3.2",
|
||||
"vite-plugin-mkcert": "^1.17.5",
|
||||
"vite-plugin-node-polyfills": "^0.23.0",
|
||||
"yootils": "^0.3.1"
|
||||
},
|
||||
"overrides": {
|
||||
@@ -80,7 +78,6 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.1.1",
|
||||
"@aws-crypto/sha256-js": "^4.0.0",
|
||||
"@codingame/monaco-vscode-configuration-service-override": "~16.1.1",
|
||||
"@codingame/monaco-vscode-editor-api": "~16.1.1",
|
||||
@@ -93,10 +90,11 @@
|
||||
"@leeoniya/ufuzzy": "^1.0.8",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@scalar/openapi-parser": "^0.15.0",
|
||||
"@tanstack/svelte-table": "npm:tanstack-table-8-svelte-5@^0.1",
|
||||
"@windmill-labs/svelte-dnd-action": "^0.9.48",
|
||||
"@xyflow/svelte": "^1.0.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xyflow/svelte": "^0.1.15",
|
||||
"ag-charts-community": "^9.0.1",
|
||||
"ag-charts-enterprise": "^9.0.1",
|
||||
"ag-grid-community": "^31.3.4",
|
||||
@@ -136,6 +134,7 @@
|
||||
"svelte-exmarkdown": "^5.0.0",
|
||||
"svelte-infinite-loading": "^1.4.0",
|
||||
"svelte-tiny-virtual-list": "^2.0.5",
|
||||
"@tutorlatin/svelte-tiny-virtual-list": "^3.0.2",
|
||||
"tailwind-merge": "^1.13.2",
|
||||
"vscode": "npm:@codingame/monaco-vscode-extension-api@~16.1.1",
|
||||
"vscode-languageclient": "~9.0.1",
|
||||
|
||||
@@ -1,85 +1,90 @@
|
||||
export const action = (node) => {
|
||||
/* Constants */
|
||||
const update = new Event('update')
|
||||
import { tick } from 'svelte'
|
||||
|
||||
/* Functions */
|
||||
const init = () => {
|
||||
addStyles()
|
||||
observeElement()
|
||||
addEventListeners()
|
||||
setInitialHeight()
|
||||
}
|
||||
type TextArea = HTMLTextAreaElement
|
||||
|
||||
const dispatchUpdateEvent = () => {
|
||||
node.dispatchEvent(update)
|
||||
}
|
||||
export const autosize = (node: TextArea) => {
|
||||
/* ------------------------------------------------------------------
|
||||
* Constants
|
||||
* ---------------------------------------------------------------- */
|
||||
const UPDATE_EVENT = new Event('update')
|
||||
const MIN_HEIGHT = 30 // px
|
||||
const EXTRA = 2 // px added to scrollHeight
|
||||
|
||||
const setInitialHeight = () => {
|
||||
let height = 0
|
||||
if (node.value) {
|
||||
height = Math.max(node.scrollHeight ?? 0, 30) + 2
|
||||
} else {
|
||||
if (node.placeholder) {
|
||||
node.value = node.placeholder
|
||||
height = Math.max(node.scrollHeight ?? 0, 30) + 2
|
||||
node.value = ''
|
||||
} else {
|
||||
node.value = '|'
|
||||
node.style.height = '0px'
|
||||
height = Math.max(node.scrollHeight ?? 0, 30) + 2
|
||||
node.value = ''
|
||||
}
|
||||
}
|
||||
let width = 0
|
||||
|
||||
node.style.height = height + 'px'
|
||||
}
|
||||
|
||||
const setHeight = () => {
|
||||
/* ------------------------------------------------------------------
|
||||
* Core resize routine
|
||||
* ---------------------------------------------------------------- */
|
||||
const resize = () => {
|
||||
node.style.height = 'auto'
|
||||
node.style.height = Math.max(node.scrollHeight ?? 0, 30) + 2 + 'px'
|
||||
node.style.height = `${Math.max(node.scrollHeight, MIN_HEIGHT) + EXTRA}px`
|
||||
}
|
||||
|
||||
const addStyles = () => {
|
||||
node.style.boxSizing = 'border-box'
|
||||
}
|
||||
/* ------------------------------------------------------------------
|
||||
* Patch `value` so programmatic changes trigger resize
|
||||
* ---------------------------------------------------------------- */
|
||||
const proto = Object.getPrototypeOf(node)
|
||||
const desc = Object.getOwnPropertyDescriptor(proto, 'value')
|
||||
|
||||
const observeElement = () => {
|
||||
let elementPrototype = Object.getPrototypeOf(node)
|
||||
let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, 'value')
|
||||
if (desc) {
|
||||
Object.defineProperty(node, 'value', {
|
||||
get: function () {
|
||||
return descriptor?.get?.apply(this, arguments as any)
|
||||
get() {
|
||||
return desc.get?.call(this)
|
||||
},
|
||||
set: function () {
|
||||
descriptor?.set?.apply(this, arguments as any)
|
||||
dispatchUpdateEvent()
|
||||
set(v: unknown) {
|
||||
desc.set?.call(this, v)
|
||||
node.dispatchEvent(UPDATE_EVENT)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const addEventListeners = () => {
|
||||
node.addEventListener('input', (e) => {
|
||||
dispatchUpdateEvent()
|
||||
})
|
||||
node.addEventListener('update', setHeight)
|
||||
}
|
||||
/* ------------------------------------------------------------------
|
||||
* Event listeners
|
||||
* ---------------------------------------------------------------- */
|
||||
const onInput = () => node.dispatchEvent(UPDATE_EVENT)
|
||||
|
||||
const removeEventListeners = () => {
|
||||
node.removeEventListener('input', dispatchUpdateEvent)
|
||||
node.removeEventListener('update', setHeight)
|
||||
}
|
||||
node.addEventListener('input', onInput)
|
||||
node.addEventListener('update', resize)
|
||||
|
||||
if (node.tagName.toLowerCase() !== 'textarea') {
|
||||
throw new Error('svelte-textarea-auto-height can only be used on textarea elements.')
|
||||
} else {
|
||||
init()
|
||||
/* ------------------------------------------------------------------
|
||||
* Inline styling
|
||||
* ---------------------------------------------------------------- */
|
||||
node.style.boxSizing = 'border-box'
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
removeEventListeners()
|
||||
}
|
||||
/* ------------------------------------------------------------------
|
||||
* Wait for DOM mount, then do an initial measure.
|
||||
* If the <textarea> is already visible (offsetWidth > 0) this covers it;
|
||||
* otherwise the first ResizeObserver callback will.
|
||||
* ---------------------------------------------------------------- */
|
||||
;(async () => {
|
||||
await tick()
|
||||
resize()
|
||||
})()
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* ResizeObserver – handles:
|
||||
* • first time the element gets a real width
|
||||
* • container/window resizes afterwards
|
||||
* ---------------------------------------------------------------- */
|
||||
const ro = new ResizeObserver(([entry]) => {
|
||||
const newWidth = entry.contentRect.width
|
||||
if (newWidth !== width) {
|
||||
width = newWidth
|
||||
resize()
|
||||
}
|
||||
})
|
||||
ro.observe(node)
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Action lifecycle
|
||||
* ---------------------------------------------------------------- */
|
||||
return {
|
||||
destroy() {
|
||||
ro.disconnect()
|
||||
node.removeEventListener('input', onInput)
|
||||
node.removeEventListener('update', resize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default action
|
||||
export default autosize
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let email: string
|
||||
let username: string
|
||||
let email: string | undefined = $state()
|
||||
let username: string | undefined = $state()
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
const key = event.key
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let automateUsernameCreation = false
|
||||
let automateUsernameCreation = $state(false)
|
||||
async function getAutomateUsernameCreationSetting() {
|
||||
automateUsernameCreation =
|
||||
((await SettingService.getGlobal({ key: 'automate_username_creation' })) as any) ?? false
|
||||
@@ -35,17 +35,17 @@
|
||||
await WorkspaceService.addUser({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
email,
|
||||
email: email!,
|
||||
username: automateUsernameCreation ? undefined : username,
|
||||
is_admin: selected == 'admin',
|
||||
operator: selected == 'operator'
|
||||
}
|
||||
})
|
||||
sendUserToast(`Added ${email}`)
|
||||
if (!(await UserService.existsEmail({ email }))) {
|
||||
if (!(await UserService.existsEmail({ email: email! }))) {
|
||||
let isSuperadmin = $superadmin
|
||||
if (!isCloudHosted()) {
|
||||
const emailCopy = email
|
||||
const emailCopy = email!
|
||||
sendUserToast(
|
||||
`User ${email} is not registered yet on the instance. ${
|
||||
!isSuperadmin
|
||||
@@ -62,7 +62,7 @@
|
||||
goto('#superadmin-settings')
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
: []
|
||||
)
|
||||
}
|
||||
@@ -70,47 +70,49 @@
|
||||
dispatch('new')
|
||||
}
|
||||
|
||||
let selected: 'operator' | 'developer' | 'admin' = 'developer'
|
||||
let selected: 'operator' | 'developer' | 'admin' = $state('developer')
|
||||
</script>
|
||||
|
||||
<Popover placement="bottom-end">
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: UserPlus }}>
|
||||
Add new user
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
<div class="flex flex-col w-72 p-4">
|
||||
<span class="text-sm mb-2 leading-6 font-semibold">Add a new user</span>
|
||||
|
||||
<span class="text-xs mb-1 leading-6">Email</span>
|
||||
<input type="email mb-1" on:keyup={handleKeyUp} placeholder="email" bind:value={email} />
|
||||
<input type="email mb-1" onkeyup={handleKeyUp} placeholder="email" bind:value={email} />
|
||||
|
||||
{#if !automateUsernameCreation}
|
||||
<span class="text-xs mb-1 pt-2 leading-6">Username</span>
|
||||
<input type="text" on:keyup={handleKeyUp} placeholder="username" bind:value={username} />
|
||||
<input type="text" onkeyup={handleKeyUp} placeholder="username" bind:value={username} />
|
||||
{/if}
|
||||
|
||||
<span class="text-xs mb-1 pt-2 leading-6">Role</span>
|
||||
<ToggleButtonGroup bind:selected class="mb-4" let:item>
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="admin"
|
||||
label="Admin"
|
||||
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButtonGroup bind:selected class="mb-4">
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="admin"
|
||||
label="Admin"
|
||||
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
|
||||
{item}
|
||||
/>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -129,5 +131,5 @@
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
import type { DurationStatus } from './graph'
|
||||
import type { Writable } from 'svelte/store'
|
||||
|
||||
export let states: Writable<Record<string, DurationStatus>> | undefined
|
||||
interface Props {
|
||||
states: Writable<Record<string, DurationStatus>> | undefined
|
||||
}
|
||||
|
||||
let { states }: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
|
||||
@@ -23,31 +23,43 @@
|
||||
import Popover from './meltComponents/Popover.svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { Loader2, Github, RotateCw, Plus, Minus, Download } from 'lucide-svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { onDestroy, untrack } from 'svelte'
|
||||
|
||||
export let resourceType: string
|
||||
export let resourceTypeInfo: ResourceType | undefined
|
||||
export let args: Record<string, any> | any = {}
|
||||
export let linkedSecret: string | undefined = undefined
|
||||
export let isValid = true
|
||||
export let linkedSecretCandidates: string[] | undefined = undefined
|
||||
export let description: string | undefined = undefined
|
||||
interface Props {
|
||||
resourceType: string
|
||||
resourceTypeInfo: ResourceType | undefined
|
||||
args?: Record<string, any> | any
|
||||
linkedSecret?: string | undefined
|
||||
isValid?: boolean
|
||||
linkedSecretCandidates?: string[] | undefined
|
||||
description?: string | undefined
|
||||
}
|
||||
|
||||
let schema = emptySchema()
|
||||
let notFound = false
|
||||
let {
|
||||
resourceType,
|
||||
resourceTypeInfo,
|
||||
args = $bindable({}),
|
||||
linkedSecret = $bindable(undefined),
|
||||
isValid = $bindable(true),
|
||||
linkedSecretCandidates = undefined,
|
||||
description = $bindable(undefined)
|
||||
}: Props = $props()
|
||||
|
||||
let supabaseWizard = false
|
||||
let schema = $state(emptySchema())
|
||||
let notFound = $state(false)
|
||||
|
||||
let loadingGithubInstallations = false
|
||||
let githubInstallations: GetGlobalConnectedRepositoriesResponse = []
|
||||
let workspaceGithubInstallations: GetGlobalConnectedRepositoriesResponse = []
|
||||
let selectedGHAppAccountId: string | undefined = undefined
|
||||
let selectedGHAppRepository: string | undefined = undefined
|
||||
let githubInstallationUrl: string | undefined = undefined
|
||||
let supabaseWizard = $state(false)
|
||||
|
||||
let loadingGithubInstallations = $state(false)
|
||||
let githubInstallations: GetGlobalConnectedRepositoriesResponse = $state([])
|
||||
let workspaceGithubInstallations: GetGlobalConnectedRepositoriesResponse = $state([])
|
||||
let selectedGHAppAccountId: string | undefined = $state(undefined)
|
||||
let selectedGHAppRepository: string | undefined = $state(undefined)
|
||||
let githubInstallationUrl: string | undefined = $state(undefined)
|
||||
let installationCheckInterval: number | undefined = undefined
|
||||
let isCheckingInstallation = false
|
||||
let importJwt = ''
|
||||
let githubAppPopover: { open: () => void; close: () => void } | null = null
|
||||
let isCheckingInstallation = $state(false)
|
||||
let importJwt = $state('')
|
||||
let githubAppPopover: { open: () => void; close: () => void } | null = $state(null)
|
||||
|
||||
async function loadGithubInstallations() {
|
||||
if (!$enterpriseLicense) return
|
||||
@@ -159,9 +171,6 @@
|
||||
notFound = true
|
||||
}
|
||||
}
|
||||
$: $workspaceStore && loadSchema()
|
||||
|
||||
$: notFound && rawCode && parseJson()
|
||||
|
||||
function parseJson() {
|
||||
try {
|
||||
@@ -173,13 +182,9 @@
|
||||
error = e.message
|
||||
}
|
||||
}
|
||||
let error = ''
|
||||
let rawCode = ''
|
||||
let viewJsonSchema = false
|
||||
|
||||
$: rawCode && parseJson()
|
||||
|
||||
$: textFileContent && parseTextFileContent()
|
||||
let error = $state('')
|
||||
let rawCode = $state('')
|
||||
let viewJsonSchema = $state(false)
|
||||
|
||||
function switchTab(asJson: boolean) {
|
||||
viewJsonSchema = asJson
|
||||
@@ -193,11 +198,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: resourceType == 'postgresql' && isSupabaseAvailable()
|
||||
$: resourceType == 'git_repository' && $userStore?.is_admin && loadGithubInstallations()
|
||||
|
||||
let connectionString = ''
|
||||
let validConnectionString = true
|
||||
let connectionString = $state('')
|
||||
let validConnectionString = $state(true)
|
||||
function parseConnectionString(close: (_: any) => void) {
|
||||
const regex =
|
||||
/postgres(?:ql)?:\/\/(?<user>[^:@]+)(?::(?<password>[^@]+))?@(?<host>[^:\/?]+)(?::(?<port>\d+))?\/(?<dbname>[^\?]+)?(?:\?.*sslmode=(?<sslmode>[^&]+))?/
|
||||
@@ -241,8 +243,8 @@
|
||||
close(null)
|
||||
}
|
||||
|
||||
let rawCodeEditor: SimpleEditor | undefined = undefined
|
||||
let textFileContent: string
|
||||
let rawCodeEditor: SimpleEditor | undefined = $state(undefined)
|
||||
let textFileContent: string | undefined = $state(undefined)
|
||||
|
||||
function parseTextFileContent() {
|
||||
args = {
|
||||
@@ -250,13 +252,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: githubInstallationsNotInWorkspace = githubInstallations.filter((installation) => {
|
||||
return !workspaceGithubInstallations.some(
|
||||
(workspaceInstallation) =>
|
||||
workspaceInstallation.installation_id === installation.installation_id
|
||||
)
|
||||
})
|
||||
|
||||
async function deleteInstallation(installation_id: number) {
|
||||
if (!$workspaceStore) {
|
||||
sendUserToast('Failed to delete installation', true)
|
||||
@@ -329,6 +324,34 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
$effect(() => {
|
||||
$workspaceStore && untrack(() => loadSchema())
|
||||
})
|
||||
$effect(() => {
|
||||
notFound && rawCode && untrack(() => parseJson())
|
||||
})
|
||||
$effect(() => {
|
||||
rawCode && untrack(() => parseJson())
|
||||
})
|
||||
$effect(() => {
|
||||
textFileContent && untrack(() => parseTextFileContent())
|
||||
})
|
||||
$effect(() => {
|
||||
resourceType == 'postgresql' && untrack(() => isSupabaseAvailable())
|
||||
})
|
||||
$effect(() => {
|
||||
resourceType == 'git_repository' &&
|
||||
$userStore?.is_admin &&
|
||||
untrack(() => loadGithubInstallations())
|
||||
})
|
||||
let githubInstallationsNotInWorkspace = $derived(
|
||||
githubInstallations.filter((installation) => {
|
||||
return !workspaceGithubInstallations.some(
|
||||
(workspaceInstallation) =>
|
||||
workspaceInstallation.installation_id === installation.installation_id
|
||||
)
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if !notFound}
|
||||
@@ -346,7 +369,7 @@
|
||||
placement: 'bottom'
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
<Button
|
||||
spacingSize="sm"
|
||||
size="xs"
|
||||
@@ -357,8 +380,8 @@
|
||||
>
|
||||
From connection string
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content" let:close>
|
||||
{/snippet}
|
||||
{#snippet content({ close })}
|
||||
<div class="block text-primary p-4">
|
||||
<div class="w-[550px] flex flex-col items-start gap-1">
|
||||
<div class="flex flex-row gap-1 w-full">
|
||||
@@ -384,7 +407,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if resourceType == 'postgresql' && supabaseWizard}
|
||||
@@ -419,7 +442,7 @@
|
||||
}}
|
||||
disabled={!$enterpriseLicense || loadingGithubInstallations}
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
<Button
|
||||
color="none"
|
||||
variant="border"
|
||||
@@ -433,8 +456,8 @@
|
||||
>
|
||||
{$enterpriseLicense ? 'GitHub App' : 'GitHub App (ee only)'}
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content" let:close>
|
||||
{/snippet}
|
||||
{#snippet content({ close })}
|
||||
<div class="block text-primary p-4">
|
||||
<div class="flex flex-col gap-4 w-[600px]">
|
||||
{#if workspaceGithubInstallations.length > 0}
|
||||
@@ -654,7 +677,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
{:else}
|
||||
<Button
|
||||
|
||||
@@ -61,29 +61,31 @@
|
||||
on:refresh
|
||||
express={expressOAuthSetup}
|
||||
/>
|
||||
<div slot="actions" class="flex gap-1">
|
||||
{#if step > 1}
|
||||
<Button variant="border" on:click={appConnectInner?.back}>Back</Button>
|
||||
{/if}
|
||||
{#if isGoogleSignin}
|
||||
<button {disabled} on:click={appConnectInner?.next}>
|
||||
<img
|
||||
class="h-10 w-auto object-contain"
|
||||
src={darkMode ? '/google_signin_dark.png' : '/google_signin_light.png'}
|
||||
alt="Google sign-in"
|
||||
/>
|
||||
</button>
|
||||
{:else}
|
||||
<Button {disabled} on:click={appConnectInner?.next}>
|
||||
{#if step == 2 && !manual}
|
||||
Connect
|
||||
{:else if step == 1}
|
||||
Next
|
||||
{:else}
|
||||
Save
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{#snippet actions()}
|
||||
<div class="flex gap-1">
|
||||
{#if step > 1}
|
||||
<Button variant="border" on:click={appConnectInner?.back ?? (() => {})}>Back</Button>
|
||||
{/if}
|
||||
{#if isGoogleSignin}
|
||||
<button {disabled} on:click={appConnectInner?.next}>
|
||||
<img
|
||||
class="h-10 w-auto object-contain"
|
||||
src={darkMode ? '/google_signin_dark.png' : '/google_signin_light.png'}
|
||||
alt="Google sign-in"
|
||||
/>
|
||||
</button>
|
||||
{:else}
|
||||
<Button {disabled} on:click={appConnectInner?.next ?? (() => {})}>
|
||||
{#if step == 2 && !manual}
|
||||
Connect
|
||||
{:else if step == 1}
|
||||
Next
|
||||
{:else}
|
||||
Save
|
||||
{/if}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
import {
|
||||
@@ -27,14 +29,25 @@
|
||||
import { apiTokenApps, forceSecretValue, linkedSecretValue } from './app_connect'
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
|
||||
export let step = 1
|
||||
export let resourceType = ''
|
||||
export let isGoogleSignin = false
|
||||
export let disabled = false
|
||||
export let manual = true
|
||||
export let express = false
|
||||
interface Props {
|
||||
step?: number
|
||||
resourceType?: string
|
||||
isGoogleSignin?: boolean
|
||||
disabled?: boolean
|
||||
manual?: boolean
|
||||
express?: boolean
|
||||
}
|
||||
|
||||
let isValid = true
|
||||
let {
|
||||
step = $bindable(1),
|
||||
resourceType = $bindable(''),
|
||||
isGoogleSignin = $bindable(false),
|
||||
disabled = $bindable(false),
|
||||
manual = $bindable(true),
|
||||
express = false
|
||||
}: Props = $props()
|
||||
|
||||
let isValid = $state(true)
|
||||
|
||||
const nativeLanguagesCategory = [
|
||||
'postgresql',
|
||||
@@ -46,15 +59,15 @@
|
||||
'oracledb'
|
||||
]
|
||||
|
||||
let filter = ''
|
||||
let value: string = ''
|
||||
let filter = $state('')
|
||||
let value: string = $state('')
|
||||
let valueToken: TokenResponse | undefined = undefined
|
||||
let connects: string[] | undefined = undefined
|
||||
let connects: string[] | undefined = $state(undefined)
|
||||
let connectsManual:
|
||||
| [string, { img?: string; instructions: string[]; key?: string }][]
|
||||
| undefined = undefined
|
||||
let args: any = {}
|
||||
let renderDescription = true
|
||||
| undefined = $state(undefined)
|
||||
let args: any = $state({})
|
||||
let renderDescription = $state(true)
|
||||
|
||||
function computeCandidates(resourceType: string, argsKeys: string[], passwords: string[]) {
|
||||
return apiTokenApps[resourceType]?.linkedSecret
|
||||
@@ -66,8 +79,8 @@
|
||||
)
|
||||
}
|
||||
|
||||
let linkedSecret: string | undefined = undefined
|
||||
let linkedSecretCandidates: string[] | undefined = undefined
|
||||
let linkedSecret: string | undefined = $state(undefined)
|
||||
let linkedSecretCandidates: string[] | undefined = $state(undefined)
|
||||
function computeLinkedSecret(resourceType: string, argsKeys: string[], passwords: string[]) {
|
||||
linkedSecretCandidates = computeCandidates(resourceType, argsKeys, passwords)
|
||||
return (
|
||||
@@ -76,14 +89,14 @@
|
||||
)
|
||||
}
|
||||
|
||||
let scopes: string[] = []
|
||||
let scopes: string[] = $state([])
|
||||
let extra_params: [string, string][] = []
|
||||
let path: string
|
||||
let description = ''
|
||||
let path: string = $state('')
|
||||
let description = $state('')
|
||||
|
||||
let resourceTypeInfo: ResourceType | undefined = undefined
|
||||
let resourceTypeInfo: ResourceType | undefined = $state(undefined)
|
||||
|
||||
let pathError = ''
|
||||
let pathError = $state('')
|
||||
|
||||
export async function open(rt?: string) {
|
||||
if (!rt) {
|
||||
@@ -112,7 +125,9 @@
|
||||
async function loadConnects() {
|
||||
if (!connects) {
|
||||
try {
|
||||
connects = (await OauthService.listOauthConnects()).filter((x) => x != 'supabase_wizard')
|
||||
connects = (await OauthService.listOauthConnects())
|
||||
.filter((x) => x != 'supabase_wizard')
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
} catch (e) {
|
||||
connects = []
|
||||
console.error('Error loading OAuth connects', e)
|
||||
@@ -122,27 +137,31 @@
|
||||
|
||||
const connectAndManual = ['gitlab']
|
||||
|
||||
$: isGoogleSignin =
|
||||
step == 1 &&
|
||||
(resourceType == 'google' ||
|
||||
resourceType == 'gmail' ||
|
||||
resourceType == 'gcal' ||
|
||||
resourceType == 'gdrive' ||
|
||||
resourceType == 'gsheets')
|
||||
run(() => {
|
||||
isGoogleSignin =
|
||||
step == 1 &&
|
||||
(resourceType == 'google' ||
|
||||
resourceType == 'gmail' ||
|
||||
resourceType == 'gcal' ||
|
||||
resourceType == 'gdrive' ||
|
||||
resourceType == 'gsheets')
|
||||
})
|
||||
|
||||
$: disabled =
|
||||
(step == 1 && resourceType == '') ||
|
||||
(step == 2 &&
|
||||
value == '' &&
|
||||
args &&
|
||||
args['token'] == '' &&
|
||||
args['password'] == '' &&
|
||||
args['api_key'] == '' &&
|
||||
args['key'] == '' &&
|
||||
linkedSecret != undefined) ||
|
||||
step == 3 ||
|
||||
(step == 4 && pathError != '') ||
|
||||
!isValid
|
||||
run(() => {
|
||||
disabled =
|
||||
(step == 1 && resourceType == '') ||
|
||||
(step == 2 &&
|
||||
value == '' &&
|
||||
args &&
|
||||
args['token'] == '' &&
|
||||
args['password'] == '' &&
|
||||
args['api_key'] == '' &&
|
||||
args['key'] == '' &&
|
||||
linkedSecret != undefined) ||
|
||||
step == 3 ||
|
||||
(step == 4 && pathError != '') ||
|
||||
!isValid
|
||||
})
|
||||
|
||||
export async function loadResourceTypes() {
|
||||
if (connectsManual) {
|
||||
@@ -154,14 +173,18 @@
|
||||
|
||||
connectsManual = availableRts
|
||||
.filter((x) => connectAndManual.includes(x) || !Object.keys(connects ?? {}).includes(x))
|
||||
.map((x) => [
|
||||
x,
|
||||
apiTokenApps[x] ?? {
|
||||
instructions: '',
|
||||
img: undefined,
|
||||
linkedSecret: undefined
|
||||
}
|
||||
])
|
||||
.map(
|
||||
(x) =>
|
||||
[
|
||||
x,
|
||||
apiTokenApps[x] ?? {
|
||||
instructions: '',
|
||||
img: undefined,
|
||||
linkedSecret: undefined
|
||||
}
|
||||
] as [string, { img?: string; instructions: string[]; key?: string }]
|
||||
)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
const filteredNativeLanguages = filteredConnectsManual?.filter(
|
||||
(o) => nativeLanguagesCategory?.includes(o[0]) ?? false
|
||||
)
|
||||
@@ -177,7 +200,7 @@
|
||||
}
|
||||
|
||||
function popupListener(event) {
|
||||
console.log('Received oauth popup message')
|
||||
console.log('Received oauth popup message', event)
|
||||
let data = event.data
|
||||
if (event.origin == null || event.origin !== window.location.origin) {
|
||||
console.log(
|
||||
@@ -280,6 +303,9 @@
|
||||
// dispatch('close')
|
||||
// }
|
||||
} else {
|
||||
if (!path) {
|
||||
throw Error('Path is not set')
|
||||
}
|
||||
let exists = await VariableService.existsVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path
|
||||
@@ -370,31 +396,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatch = createEventDispatcher<{ error: string; refresh: string; close: void }>()
|
||||
|
||||
let filteredConnects: { key: string }[] = []
|
||||
let filteredConnects: { key: string }[] = $state([])
|
||||
let filteredConnectsManual: [string, { img?: string; instructions: string[]; key?: string }][] =
|
||||
[]
|
||||
$state([])
|
||||
|
||||
let editScopes = false
|
||||
let editScopes = $state(false)
|
||||
</script>
|
||||
|
||||
{#if !express}
|
||||
<SearchItems
|
||||
{filter}
|
||||
items={connects
|
||||
? connects
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((key) => ({
|
||||
key
|
||||
}))
|
||||
? connects.map((key) => ({
|
||||
key
|
||||
}))
|
||||
: undefined}
|
||||
bind:filteredItems={filteredConnects}
|
||||
f={(x) => x.key}
|
||||
/>
|
||||
<SearchItems
|
||||
{filter}
|
||||
items={connectsManual?.sort((a, b) => a[0].localeCompare(b[0]))}
|
||||
items={connectsManual}
|
||||
bind:filteredItems={filteredConnectsManual}
|
||||
f={(x) => x[0]}
|
||||
/>
|
||||
@@ -590,7 +614,7 @@
|
||||
</div>
|
||||
<h3 class="mb-4 flex gap-4"
|
||||
>Scopes <button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
editScopes = !editScopes
|
||||
}}><Pen size={14} /></button
|
||||
></h3
|
||||
|
||||
@@ -5,17 +5,21 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let resourceType: string | undefined = undefined
|
||||
export let workspace: string
|
||||
export let express = false
|
||||
interface Props {
|
||||
resourceType?: string | undefined
|
||||
workspace: string
|
||||
express?: boolean
|
||||
}
|
||||
|
||||
let step = 1
|
||||
let disabled = false
|
||||
let manual = true
|
||||
let { resourceType = $bindable(undefined), workspace, express = false }: Props = $props()
|
||||
|
||||
let appConnect: AppConnectInner | undefined = undefined
|
||||
let step = $state(1)
|
||||
let disabled = $state(false)
|
||||
let manual = $state(true)
|
||||
|
||||
let darkMode: boolean = false
|
||||
let appConnect: AppConnectInner | undefined = $state(undefined)
|
||||
|
||||
let darkMode: boolean = $state(false)
|
||||
|
||||
if (workspace) {
|
||||
$workspaceStore = workspace
|
||||
|
||||
@@ -1,60 +1,67 @@
|
||||
<script lang="ts">
|
||||
import type { EnumType } from '$lib/common'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import Select from './Select.svelte'
|
||||
|
||||
export let disabled: boolean
|
||||
export let value: any
|
||||
export let enum_: EnumType
|
||||
export let autofocus: boolean | null
|
||||
export let defaultValue: string | undefined
|
||||
export let valid: boolean
|
||||
export let create: boolean
|
||||
export let required: boolean
|
||||
export let enumLabels: Record<string, string> | undefined = undefined
|
||||
interface Props {
|
||||
disabled: boolean
|
||||
value: string | undefined
|
||||
enum_: EnumType
|
||||
autofocus: boolean | null
|
||||
defaultValue: string | undefined
|
||||
valid: boolean
|
||||
create: boolean
|
||||
enumLabels?: Record<string, string> | undefined
|
||||
}
|
||||
|
||||
let {
|
||||
disabled,
|
||||
value = $bindable(),
|
||||
enum_,
|
||||
autofocus,
|
||||
defaultValue,
|
||||
valid,
|
||||
create,
|
||||
enumLabels = undefined
|
||||
}: Props = $props()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let customItems: string[] = []
|
||||
let customItems: string[] = $state([])
|
||||
|
||||
function onCreate(newItem: string) {
|
||||
console.log(newItem)
|
||||
customItems = [...customItems, newItem]
|
||||
let items = $derived.by(() => {
|
||||
const l = [...(enum_ ? enum_ : []), ...customItems].map((item) => ({
|
||||
value: item,
|
||||
label: enumLabels?.[item] ?? item
|
||||
}))
|
||||
if (create && filterText && l.every((i) => i.value !== filterText)) {
|
||||
l.push({ value: filterText, label: `Add new: ${filterText}` })
|
||||
}
|
||||
return l
|
||||
})
|
||||
|
||||
return newItem
|
||||
}
|
||||
let filterText = $state('')
|
||||
</script>
|
||||
|
||||
<div class="w-full flex-col">
|
||||
<div class="w-full">
|
||||
<AutoComplete
|
||||
items={[...(required ? [] : ['']), ...(enum_ ? enum_ : []), ...customItems]}
|
||||
labelFunction={(val) => (enumLabels ? enumLabels[val] ?? val : val)}
|
||||
bind:selectedItem={value}
|
||||
inputClassName={twMerge(
|
||||
'bg-surface-secondary flex',
|
||||
valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'
|
||||
)}
|
||||
value={value ?? defaultValue}
|
||||
hideArrow={true}
|
||||
dropdownClassName="!text-sm !py-2 !rounded-sm !border-gray-200 !border !shadow-md !bg-surface-primary"
|
||||
className="w-full"
|
||||
noInputStyles
|
||||
onFocus={() => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
onBlur={() => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
{create}
|
||||
{required}
|
||||
{onCreate}
|
||||
<Select
|
||||
inputClass={valid ? '' : '!border-red-500/60'}
|
||||
clearable
|
||||
{disabled}
|
||||
{autofocus}
|
||||
createText="Press enter to use this non-predefined value"
|
||||
autofocus={autofocus ?? undefined}
|
||||
bind:filterText
|
||||
{items}
|
||||
bind:value={
|
||||
() => value ?? defaultValue,
|
||||
(newValue) => {
|
||||
if (newValue && items.findIndex((i) => i.value === newValue) === -1)
|
||||
customItems.push(newValue)
|
||||
value = newValue
|
||||
}
|
||||
}
|
||||
onFocus={() => dispatch('focus')}
|
||||
onBlur={() => dispatch('blur')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { Button, DrawerContent } from './common'
|
||||
|
||||
export let value: any
|
||||
let jsonViewer: Drawer
|
||||
let jsonViewerContent: any | undefined
|
||||
interface Props {
|
||||
value: any
|
||||
}
|
||||
|
||||
let { value }: Props = $props()
|
||||
let jsonViewer: Drawer | undefined = $state()
|
||||
let jsonViewerContent: any | undefined = $state()
|
||||
|
||||
function isString(value: any) {
|
||||
return typeof value === 'string' || value instanceof String
|
||||
@@ -33,7 +37,7 @@
|
||||
|
||||
<Drawer bind:this={jsonViewer} size="800px">
|
||||
<DrawerContent title="Argument Details" on:close={jsonViewer.closeDrawer}>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
on:click={() => copyToClipboard(JSON.stringify(jsonViewerContent, null, 4))}
|
||||
color="light"
|
||||
@@ -42,7 +46,7 @@
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
{#if isString(jsonViewerContent)}
|
||||
<pre>{jsonViewerContent}</pre>
|
||||
{:else}
|
||||
@@ -61,17 +65,17 @@
|
||||
{:else if isString(value) && value.startsWith('$res:')}
|
||||
<button
|
||||
class="text-xs text-blue-500"
|
||||
on:click={async () => {
|
||||
onclick={async () => {
|
||||
await getResource(value.substring('$res:'.length))
|
||||
jsonViewer.toggleDrawer()
|
||||
jsonViewer?.toggleDrawer()
|
||||
}}>{value}</button
|
||||
>
|
||||
{:else if isString(value) && value.startsWith('$var:')}
|
||||
<button
|
||||
class="text-xs text-blue-500"
|
||||
on:click={async () => {
|
||||
onclick={async () => {
|
||||
await getVariable(value.substring('$res:'.length))
|
||||
jsonViewer.toggleDrawer()
|
||||
jsonViewer?.toggleDrawer()
|
||||
}}>{value}</button
|
||||
>
|
||||
{:else if typeof value !== 'object'}
|
||||
@@ -80,9 +84,9 @@
|
||||
{#if JSON.stringify(value).length > 80}
|
||||
<button
|
||||
class="text-xs text-blue-500"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
jsonViewerContent = value
|
||||
jsonViewer.toggleDrawer()
|
||||
jsonViewer?.toggleDrawer()
|
||||
}}>See expanded</button
|
||||
>
|
||||
{/if}
|
||||
@@ -92,9 +96,9 @@
|
||||
{#if JSON.stringify(value).length > 120}
|
||||
<button
|
||||
class="text-xs absolute top-0 right-4 text-tertiary"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
jsonViewerContent = value
|
||||
jsonViewer.toggleDrawer()
|
||||
jsonViewer?.toggleDrawer()
|
||||
}}><Expand size={18} /></button
|
||||
>
|
||||
{/if}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault, stopPropagation, createBubbler } from 'svelte/legacy'
|
||||
|
||||
const bubble = createBubbler()
|
||||
import type { EnumType, SchemaProperty } from '$lib/common'
|
||||
import {
|
||||
setInputCat as computeInputCat,
|
||||
@@ -7,7 +10,7 @@
|
||||
getSchemaFromProperties
|
||||
} from '$lib/utils'
|
||||
import { DollarSign, Pipette, Plus, X, Check, Loader2 } from 'lucide-svelte'
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte'
|
||||
import { createEventDispatcher, onMount, tick, untrack } from 'svelte'
|
||||
import Multiselect from 'svelte-multiselect'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { Button, SecondsInput } from './common'
|
||||
@@ -36,99 +39,166 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import DynSelect from './DynSelect.svelte'
|
||||
import type { Script } from '$lib/gen'
|
||||
import type { SchemaDiff } from '$lib/components/schema/schemaUtils'
|
||||
import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte'
|
||||
import type { ComponentCustomCSS } from './apps/types'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
export let defaultValue: any = undefined
|
||||
interface Props {
|
||||
label?: string
|
||||
value: any
|
||||
defaultValue?: any
|
||||
description?: string
|
||||
format?: string
|
||||
contentEncoding?: 'base64' | 'binary' | undefined
|
||||
type?: string | undefined
|
||||
oneOf?: SchemaProperty[] | undefined
|
||||
required?: boolean
|
||||
pattern?: undefined | string
|
||||
valid?: boolean
|
||||
enum_?: EnumType
|
||||
disabled?: boolean
|
||||
itemsType?:
|
||||
| {
|
||||
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
multiselect?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined
|
||||
displayHeader?: boolean
|
||||
properties?: { [name: string]: SchemaProperty } | undefined
|
||||
nestedRequired?: string[] | undefined
|
||||
autofocus?: boolean | null
|
||||
compact?: boolean
|
||||
password?: boolean
|
||||
pickForField?: string | undefined
|
||||
variableEditor?: VariableEditor | undefined
|
||||
itemPicker?: ItemPicker | undefined
|
||||
noMargin?: boolean
|
||||
extra?: Record<string, any>
|
||||
minW?: boolean
|
||||
prettifyHeader?: boolean
|
||||
resourceTypes: string[] | undefined
|
||||
disablePortal?: boolean
|
||||
showSchemaExplorer?: boolean
|
||||
simpleTooltip?: string | undefined
|
||||
customErrorMessage?: string | undefined
|
||||
onlyMaskPassword?: boolean
|
||||
nullable?: boolean
|
||||
title?: string | undefined
|
||||
placeholder?: string | undefined
|
||||
order?: string[] | undefined
|
||||
editor?: SimpleEditor | undefined
|
||||
orderEditable?: boolean
|
||||
shouldDispatchChanges?: boolean
|
||||
noDefaultOnSelectFirst?: boolean
|
||||
helperScript?:
|
||||
| { type: 'inline'; path?: string; lang: Script['language']; code: string }
|
||||
| { type: 'hash'; hash: string }
|
||||
| undefined
|
||||
otherArgs?: Record<string, any>
|
||||
lightHeader?: boolean
|
||||
diffStatus?: SchemaDiff | undefined
|
||||
hideNested?: boolean
|
||||
nestedParent?: { label: string; nestedParent: any | undefined } | undefined
|
||||
nestedClasses?: string
|
||||
displayType?: boolean
|
||||
css?: ComponentCustomCSS<'schemaformcomponent'> | undefined
|
||||
appPath?: string | undefined
|
||||
computeS3ForceViewerPolicies?:
|
||||
| (() =>
|
||||
| {
|
||||
allowed_resources: string[]
|
||||
allow_user_resources: boolean
|
||||
allow_workspace_resource: boolean
|
||||
file_key_regex: string
|
||||
}
|
||||
| undefined)
|
||||
| undefined
|
||||
workspace?: string | undefined
|
||||
actions?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
export let description: string = ''
|
||||
export let format: string = ''
|
||||
export let contentEncoding: 'base64' | 'binary' | undefined = undefined
|
||||
export let type: string | undefined = undefined
|
||||
export let oneOf: SchemaProperty[] | undefined = undefined
|
||||
export let required = false
|
||||
export let pattern: undefined | string = undefined
|
||||
export let valid = true
|
||||
export let enum_: EnumType = undefined
|
||||
export let disabled = false
|
||||
export let itemsType:
|
||||
| {
|
||||
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
multiselect?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined = undefined
|
||||
let {
|
||||
label = '',
|
||||
value = $bindable(),
|
||||
defaultValue = $bindable(undefined),
|
||||
description = $bindable(undefined),
|
||||
format = $bindable(undefined),
|
||||
contentEncoding = undefined,
|
||||
type = undefined,
|
||||
oneOf = $bindable(undefined),
|
||||
required = false,
|
||||
pattern = $bindable(undefined),
|
||||
valid = $bindable(undefined),
|
||||
enum_ = $bindable(undefined),
|
||||
disabled = false,
|
||||
itemsType = $bindable(undefined),
|
||||
displayHeader = true,
|
||||
properties = $bindable(undefined),
|
||||
nestedRequired = undefined,
|
||||
autofocus = null,
|
||||
compact = false,
|
||||
password = false,
|
||||
pickForField = $bindable(undefined),
|
||||
variableEditor = undefined,
|
||||
itemPicker = undefined,
|
||||
noMargin = false,
|
||||
extra = {},
|
||||
minW = true,
|
||||
prettifyHeader = false,
|
||||
resourceTypes,
|
||||
disablePortal = false,
|
||||
showSchemaExplorer = false,
|
||||
simpleTooltip = undefined,
|
||||
customErrorMessage = undefined,
|
||||
onlyMaskPassword = false,
|
||||
nullable = false,
|
||||
title = $bindable(undefined),
|
||||
placeholder = $bindable(undefined),
|
||||
order = $bindable(undefined),
|
||||
editor = $bindable(undefined),
|
||||
orderEditable = false,
|
||||
shouldDispatchChanges = false,
|
||||
noDefaultOnSelectFirst = false,
|
||||
helperScript = undefined,
|
||||
otherArgs = {},
|
||||
lightHeader = false,
|
||||
diffStatus = undefined,
|
||||
hideNested = false,
|
||||
nestedParent = undefined,
|
||||
nestedClasses = '',
|
||||
displayType = true,
|
||||
css = undefined,
|
||||
appPath = undefined,
|
||||
computeS3ForceViewerPolicies = undefined,
|
||||
workspace = undefined,
|
||||
actions
|
||||
}: Props = $props()
|
||||
|
||||
export let displayHeader = true
|
||||
export let properties: { [name: string]: SchemaProperty } | undefined = undefined
|
||||
export let nestedRequired: string[] | undefined = undefined
|
||||
export let autofocus: boolean | null = null
|
||||
export let compact = false
|
||||
export let password = false
|
||||
export let pickForField: string | undefined = undefined
|
||||
export let variableEditor: VariableEditor | undefined = undefined
|
||||
export let itemPicker: ItemPicker | undefined = undefined
|
||||
export let noMargin = false
|
||||
export let extra: Record<string, any> = {}
|
||||
export let minW = true
|
||||
export let prettifyHeader = false
|
||||
export let resourceTypes: string[] | undefined
|
||||
export let disablePortal = false
|
||||
export let showSchemaExplorer = false
|
||||
export let simpleTooltip: string | undefined = undefined
|
||||
export let customErrorMessage: string | undefined = undefined
|
||||
export let onlyMaskPassword = false
|
||||
export let nullable: boolean = false
|
||||
export let title: string | undefined = undefined
|
||||
export let placeholder: string | undefined = undefined
|
||||
export let order: string[] | undefined = undefined
|
||||
export let editor: SimpleEditor | undefined = undefined
|
||||
export let orderEditable = false
|
||||
export let shouldDispatchChanges: boolean = false
|
||||
export let noDefaultOnSelectFirst: boolean = false
|
||||
export let helperScript:
|
||||
| { type: 'inline'; path?: string; lang: Script['language']; code: string }
|
||||
| { type: 'hash'; hash: string }
|
||||
| undefined = undefined
|
||||
export let otherArgs: Record<string, any> = {}
|
||||
export let lightHeader = false
|
||||
export let diffStatus: SchemaDiff | undefined = undefined
|
||||
export let hideNested = false
|
||||
export let nestedParent: { label: string; nestedParent: any | undefined } | undefined = undefined
|
||||
export let nestedClasses = ''
|
||||
export let displayType = true
|
||||
export let css: ComponentCustomCSS<'schemaformcomponent'> | 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
|
||||
$effect(() => {
|
||||
if (description == undefined) {
|
||||
description = ''
|
||||
}
|
||||
if (format == undefined) {
|
||||
format = ''
|
||||
}
|
||||
if (valid == undefined) {
|
||||
valid = true
|
||||
}
|
||||
})
|
||||
|
||||
$: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding)
|
||||
|
||||
let oneOfSelected: string | undefined = undefined
|
||||
let oneOfSelected: string | undefined = $state(undefined)
|
||||
let tagKey = $derived(
|
||||
oneOf?.find((o) => Object.keys(o.properties ?? {})?.includes('kind')) ? 'kind' : 'label'
|
||||
)
|
||||
async function updateOneOfSelected(oneOf: SchemaProperty[] | undefined) {
|
||||
if (
|
||||
oneOf &&
|
||||
oneOf.length >= 2 &&
|
||||
(!oneOfSelected || !oneOf.some((o) => o.title === oneOfSelected) || !value)
|
||||
) {
|
||||
const tagKey = oneOf.find((o) => Object.keys(o.properties ?? {}).includes('kind'))
|
||||
? 'kind'
|
||||
: 'label'
|
||||
if (value && value[tagKey] && oneOf.some((o) => o.title === value[tagKey])) {
|
||||
const existingValue = JSON.parse(JSON.stringify(value))
|
||||
oneOfSelected = value[tagKey]
|
||||
@@ -142,10 +212,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: updateOneOfSelected(oneOf)
|
||||
|
||||
$: oneOf && value && onOneOfChange()
|
||||
|
||||
function onOneOfChange() {
|
||||
const label = value?.['label']
|
||||
const kind = value?.['kind']
|
||||
@@ -157,54 +223,50 @@
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
|
||||
|
||||
let ignoreValueUndefined = false
|
||||
let error: string = ''
|
||||
let s3FilePicker: S3FilePicker
|
||||
let s3FileUploadRawMode: false
|
||||
let isListJson = false
|
||||
let hasIsListJsonChanged = false
|
||||
let ignoreValueUndefined = $state(false)
|
||||
let error: string = $state('')
|
||||
let s3FilePicker: S3FilePicker | undefined = $state()
|
||||
let s3FileUploadRawMode: boolean = $state(false)
|
||||
let isListJson = $state(false)
|
||||
let hasIsListJsonChanged = $state(false)
|
||||
|
||||
let el: HTMLTextAreaElement | undefined = undefined
|
||||
let el: HTMLTextAreaElement | undefined = $state(undefined)
|
||||
|
||||
let rawValue: string | undefined = undefined
|
||||
let rawValue: string | undefined = $state(undefined)
|
||||
|
||||
function computeDefaultValue(
|
||||
nvalue?: any,
|
||||
inputCat?: string,
|
||||
defaultValue?: any,
|
||||
nnullable?: boolean
|
||||
) {
|
||||
let lastValue: any = null
|
||||
|
||||
function computeDefaultValue(inputCat?: string, defaultValue?: any, nnullable?: boolean) {
|
||||
let nvalue: any = null
|
||||
if (label == 'toString' && typeof value == 'function') {
|
||||
value = undefined
|
||||
nvalue = undefined
|
||||
}
|
||||
if ((value == undefined || value == null) && !ignoreValueUndefined) {
|
||||
value = structuredClone(defaultValue)
|
||||
nvalue = structuredClone($state.snapshot(defaultValue))
|
||||
if (defaultValue === undefined || defaultValue === null) {
|
||||
if (inputCat === 'string') {
|
||||
value = nullable ? null : ''
|
||||
nvalue = nullable ? null : ''
|
||||
} else if (inputCat == 'enum' && required) {
|
||||
value = enum_?.[0]
|
||||
nvalue = enum_?.[0]
|
||||
} else if (inputCat == 'boolean') {
|
||||
value = false
|
||||
nvalue = false
|
||||
} else if (inputCat == 'list') {
|
||||
value = []
|
||||
nvalue = []
|
||||
}
|
||||
} else if (inputCat === 'object') {
|
||||
evalValueToRaw()
|
||||
}
|
||||
}
|
||||
|
||||
if (nnullable && type === 'string' && value === '') {
|
||||
if (nnullable && type === 'string' && value === '' && nvalue == null) {
|
||||
value = null
|
||||
} else if (nvalue != null && !deepEqual(nvalue, value)) {
|
||||
value = nvalue
|
||||
lastValue = value
|
||||
}
|
||||
}
|
||||
|
||||
$: computeDefaultValue(value, inputCat, defaultValue, nullable)
|
||||
|
||||
let lastValue: any = undefined
|
||||
|
||||
// By setting isListJson to true, we can render inputs even if the value is not an array of the correct type
|
||||
// This avoids the issue of the input being rendered as a string with value: [object Object], or as a number with value: NaN
|
||||
function checkArrayValueType() {
|
||||
@@ -233,16 +295,7 @@
|
||||
lastValue = value
|
||||
}
|
||||
|
||||
$: !isListJson &&
|
||||
inputCat === 'list' &&
|
||||
value != lastValue &&
|
||||
itemsType?.type &&
|
||||
!hasIsListJsonChanged &&
|
||||
checkArrayValueType()
|
||||
|
||||
$: defaultValue != undefined && handleDefaultValueChange()
|
||||
|
||||
let oldDefaultValue = structuredClone(defaultValue)
|
||||
let oldDefaultValue = structuredClone($state.snapshot(defaultValue))
|
||||
function handleDefaultValueChange() {
|
||||
if (
|
||||
deepEqual(value, oldDefaultValue) &&
|
||||
@@ -251,7 +304,7 @@
|
||||
) {
|
||||
value = defaultValue
|
||||
}
|
||||
oldDefaultValue = structuredClone(defaultValue)
|
||||
oldDefaultValue = structuredClone($state.snapshot(defaultValue))
|
||||
}
|
||||
|
||||
function isObjectCat(inputCat?: string) {
|
||||
@@ -286,11 +339,6 @@
|
||||
}
|
||||
|
||||
let setCodeDisabled = false
|
||||
$: (inputCat &&
|
||||
(isObjectCat(inputCat) || isRawStringEditor(inputCat)) &&
|
||||
!oneOf &&
|
||||
evalValueToRaw()) ||
|
||||
value
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
function setNewValueFromCode(nvalue: any) {
|
||||
@@ -305,7 +353,6 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
computeDefaultValue()
|
||||
evalValueToRaw()
|
||||
})
|
||||
|
||||
@@ -389,22 +436,62 @@
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
let redraw = 0
|
||||
let itemsLimit = 50
|
||||
|
||||
$: validateInput(pattern, value, required)
|
||||
let redraw = $state(0)
|
||||
let itemsLimit = $state(50)
|
||||
|
||||
let oldValue = value
|
||||
|
||||
function compareValues(value) {
|
||||
if (!deepEqual(oldValue, value)) {
|
||||
oldValue = value
|
||||
dispatchIfMounted('change')
|
||||
dispatch('change')
|
||||
}
|
||||
}
|
||||
|
||||
let debounced = debounce(() => compareValues(value), 50)
|
||||
$: shouldDispatchChanges && debounced(value)
|
||||
let inputCat = $derived(computeInputCat(type, format, itemsType?.type, enum_, contentEncoding))
|
||||
$effect(() => {
|
||||
oneOf && untrack(() => updateOneOfSelected(oneOf))
|
||||
})
|
||||
$effect(() => {
|
||||
oneOf && value && untrack(() => onOneOfChange())
|
||||
})
|
||||
|
||||
$effect.pre(() => {
|
||||
value
|
||||
let args = [inputCat, defaultValue, nullable]
|
||||
|
||||
untrack(() => {
|
||||
if (deepEqual(value, lastValue)) return
|
||||
computeDefaultValue(...args)
|
||||
})
|
||||
})
|
||||
$effect.pre(() => {
|
||||
!isListJson &&
|
||||
inputCat === 'list' &&
|
||||
value != lastValue &&
|
||||
itemsType?.type &&
|
||||
!hasIsListJsonChanged &&
|
||||
untrack(() => checkArrayValueType())
|
||||
})
|
||||
$effect.pre(() => {
|
||||
defaultValue != undefined && untrack(() => handleDefaultValueChange())
|
||||
})
|
||||
$effect.pre(() => {
|
||||
;(inputCat &&
|
||||
(isObjectCat(inputCat) || isRawStringEditor(inputCat)) &&
|
||||
!oneOf &&
|
||||
untrack(() => evalValueToRaw())) ||
|
||||
value
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
let args = [pattern, value, required] as const
|
||||
untrack(() => validateInput(...args))
|
||||
})
|
||||
$effect(() => {
|
||||
shouldDispatchChanges && debounced(value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<S3FilePicker
|
||||
@@ -417,7 +504,7 @@
|
||||
readOnlyMode={false}
|
||||
/>
|
||||
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<div
|
||||
class={twMerge(
|
||||
'flex flex-col w-full rounded-md relative',
|
||||
@@ -441,17 +528,21 @@
|
||||
>
|
||||
<button
|
||||
class="p-1 bg-green-500 text-white hover:bg-green-600"
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
dispatch('acceptChange', { label, nestedParent })
|
||||
}}
|
||||
onclick={stopPropagation(
|
||||
preventDefault(() => {
|
||||
dispatch('acceptChange', { label, nestedParent })
|
||||
})
|
||||
)}
|
||||
>
|
||||
<Check size={14} />
|
||||
</button>
|
||||
<button
|
||||
class="p-1 hover:bg-red-500 hover:text-white"
|
||||
on:click|preventDefault|stopPropagation={() => {
|
||||
dispatch('rejectChange', { label, nestedParent })
|
||||
}}
|
||||
onclick={stopPropagation(
|
||||
preventDefault(() => {
|
||||
dispatch('rejectChange', { label, nestedParent })
|
||||
})
|
||||
)}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
@@ -502,11 +593,11 @@
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
{autofocus}
|
||||
on:focus
|
||||
on:blur
|
||||
onfocus={bubble('focus')}
|
||||
onblur={bubble('blur')}
|
||||
{disabled}
|
||||
type="number"
|
||||
on:keydown={() => {
|
||||
onkeydown={() => {
|
||||
ignoreValueUndefined = true
|
||||
}}
|
||||
class={valid
|
||||
@@ -545,6 +636,9 @@
|
||||
outerDivClass={'dark:!border-gray-500 !border-gray-300'}
|
||||
{disabled}
|
||||
bind:selected={value}
|
||||
onremove={(e) => {
|
||||
if (Array.isArray(value)) value = value.filter((v) => v !== e.option)
|
||||
}}
|
||||
options={itemsType?.multiselect ?? []}
|
||||
selectedOptionsDraggable={true}
|
||||
onopen={() => {
|
||||
@@ -552,14 +646,17 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if itemsType?.enum != undefined && Array.isArray(itemsType?.enum) && Array.isArray(value)}
|
||||
{:else if itemsType?.enum != undefined && Array.isArray(itemsType?.enum) && (Array.isArray(value) || value == undefined)}
|
||||
<div class="items-start">
|
||||
<Multiselect
|
||||
ulOptionsClass={'p-2 !bg-surface-secondary'}
|
||||
outerDivClass={'dark:!border-gray-500 !border-gray-300'}
|
||||
{disabled}
|
||||
onremove={(e) => {
|
||||
if (Array.isArray(value)) value = value.filter((v) => v !== e.option)
|
||||
}}
|
||||
bind:selected={
|
||||
() => [...value],
|
||||
() => [...(value ?? [])],
|
||||
(v) => {
|
||||
if (!deepEqual(v, value)) {
|
||||
value = v
|
||||
@@ -610,7 +707,7 @@
|
||||
<input
|
||||
type="file"
|
||||
class="my-6"
|
||||
on:change={(x) => fileChanged(x, (val) => (value[i] = val))}
|
||||
onchange={(x) => fileChanged(x, (val) => (value[i] = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined}
|
||||
@@ -624,7 +721,6 @@
|
||||
{/await}
|
||||
{:else if Array.isArray(itemsType?.enum)}
|
||||
<ArgEnum
|
||||
required
|
||||
create={extra['disableCreate'] != true}
|
||||
on:focus={() => {
|
||||
dispatch('focus')
|
||||
@@ -633,7 +729,7 @@
|
||||
dispatch('blur')
|
||||
}}
|
||||
{defaultValue}
|
||||
{valid}
|
||||
valid={valid ?? true}
|
||||
{disabled}
|
||||
{autofocus}
|
||||
bind:value={value[i]}
|
||||
@@ -680,7 +776,7 @@
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
value = value.filter((_, index) => index !== i)
|
||||
redraw += 1
|
||||
}}
|
||||
@@ -691,7 +787,7 @@
|
||||
{/if}
|
||||
{/each}
|
||||
{#if value.length > itemsLimit}
|
||||
<button on:click={() => (itemsLimit += 50)} class="text-xs py-2 text-blue-600"
|
||||
<button onclick={() => (itemsLimit += 50)} class="text-xs py-2 text-blue-600"
|
||||
>{itemsLimit}/{value.length}: Load 50 more...</button
|
||||
>
|
||||
{/if}
|
||||
@@ -754,16 +850,16 @@
|
||||
args={otherArgs}
|
||||
{helperScript}
|
||||
bind:value
|
||||
entrypoint={format.substring('dynselect_'.length)}
|
||||
entrypoint={format?.substring('dynselect_'.length) ?? ''}
|
||||
/>
|
||||
{:else if inputCat == 'resource-object' && resourceTypes == undefined}
|
||||
<span class="text-2xs text-tertiary">Loading resource types...</span>
|
||||
{:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format.split('-').length > 1 && resourceTypes.includes(format.substring('resource-'.length))))}
|
||||
{:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format && format?.split('-').length > 1 && resourceTypes.includes(format?.substring('resource-'.length))))}
|
||||
<ObjectResourceInput
|
||||
{defaultValue}
|
||||
selectFirst={!noDefaultOnSelectFirst}
|
||||
{disablePortal}
|
||||
{format}
|
||||
format={format ?? ''}
|
||||
bind:value
|
||||
bind:editor
|
||||
on:clear={() => {
|
||||
@@ -771,7 +867,7 @@
|
||||
}}
|
||||
{showSchemaExplorer}
|
||||
/>
|
||||
{:else if inputCat == 'resource-object' && format.split('-').length > 1 && format
|
||||
{:else if inputCat == 'resource-object' && format && format.split('-').length > 1 && format
|
||||
.replace('resource-', '')
|
||||
.replace('_', '')
|
||||
.toLowerCase() == 's3object'}
|
||||
@@ -855,11 +951,12 @@
|
||||
: 'label'
|
||||
value = { ...toKeep, [tagKey]: detail }
|
||||
}}
|
||||
let:item
|
||||
>
|
||||
{#each oneOf as obj}
|
||||
<ToggleButton value={obj.title ?? ''} label={obj.title} {item} />
|
||||
{/each}
|
||||
{#snippet children({ item })}
|
||||
{#each oneOf as obj}
|
||||
<ToggleButton value={obj.title ?? ''} label={obj.title} {item} />
|
||||
{/each}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
{#if oneOfSelected}
|
||||
{@const objIdx = oneOf.findIndex((o) => o.title === oneOfSelected)}
|
||||
@@ -873,13 +970,18 @@
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
schema={{
|
||||
properties: obj.properties,
|
||||
order: obj.order,
|
||||
$schema: '',
|
||||
required: obj.required ?? [],
|
||||
type: 'object'
|
||||
}}
|
||||
bind:schema={
|
||||
() => ({
|
||||
properties: obj.properties ?? {},
|
||||
order: obj.order,
|
||||
$schema: '',
|
||||
required: obj.required ?? [],
|
||||
type: 'object'
|
||||
}),
|
||||
() => {
|
||||
dispatch('nestedChange')
|
||||
}
|
||||
}
|
||||
bind:args={value}
|
||||
dndType={`nested-${title}`}
|
||||
hiddenArgs={['label', 'kind']}
|
||||
@@ -889,9 +991,6 @@
|
||||
oneOf[objIdx].order = keys
|
||||
}
|
||||
}}
|
||||
on:change={() => {
|
||||
dispatch('nestedChange')
|
||||
}}
|
||||
on:nestedChange
|
||||
{shouldDispatchChanges}
|
||||
/>
|
||||
@@ -909,7 +1008,12 @@
|
||||
required: obj.required ?? [],
|
||||
type: 'object'
|
||||
}}
|
||||
bind:args={value}
|
||||
bind:args={
|
||||
() => value,
|
||||
(v) => {
|
||||
value = { ...v, [tagKey]: oneOfSelected }
|
||||
}
|
||||
}
|
||||
{shouldDispatchChanges}
|
||||
on:change={() => {
|
||||
dispatch('nestedChange')
|
||||
@@ -971,22 +1075,24 @@
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
schema={{
|
||||
properties,
|
||||
$schema: '',
|
||||
required: nestedRequired ?? [],
|
||||
type: 'object',
|
||||
order
|
||||
}}
|
||||
bind:schema={
|
||||
() => ({
|
||||
properties,
|
||||
$schema: '',
|
||||
required: nestedRequired ?? [],
|
||||
type: 'object',
|
||||
order
|
||||
}),
|
||||
(newSchema) => {
|
||||
dispatch('nestedChange')
|
||||
}
|
||||
}
|
||||
bind:args={value}
|
||||
dndType={`nested-${title}`}
|
||||
on:reorder={(e) => {
|
||||
const keys = e.detail
|
||||
order = keys
|
||||
}}
|
||||
on:change={() => {
|
||||
dispatch('nestedChange')
|
||||
}}
|
||||
diff={diffStatus && typeof diffStatus.diff === 'object' ? diffStatus.diff : {}}
|
||||
on:acceptChange={(e) => {
|
||||
dispatch('acceptChange', e.detail)
|
||||
@@ -1065,10 +1171,9 @@
|
||||
{:else if inputCat == 'enum'}
|
||||
<div class="flex flex-row w-full gap-1">
|
||||
<ArgEnum
|
||||
{required}
|
||||
create={extra['disableCreate'] != true}
|
||||
{defaultValue}
|
||||
{valid}
|
||||
valid={valid ?? true}
|
||||
{disabled}
|
||||
bind:value
|
||||
{enum_}
|
||||
@@ -1119,7 +1224,7 @@
|
||||
<input
|
||||
{autofocus}
|
||||
type="file"
|
||||
on:change={(x) => fileChanged(x, (val) => (value = val))}
|
||||
onchange={(x) => fileChanged(x, (val) => (value = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{#if value?.length}
|
||||
@@ -1144,8 +1249,8 @@
|
||||
{:else if inputCat == 'email'}
|
||||
<input
|
||||
{autofocus}
|
||||
on:focus
|
||||
on:blur
|
||||
onfocus={bubble('focus')}
|
||||
onblur={bubble('blur')}
|
||||
{disabled}
|
||||
type="email"
|
||||
class={valid
|
||||
@@ -1177,14 +1282,14 @@
|
||||
{autofocus}
|
||||
rows={extra?.['minRows'] ? extra['minRows']?.toString() : '1'}
|
||||
bind:this={el}
|
||||
on:focus={(e) => {
|
||||
onfocus={(e) => {
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={(e) => {
|
||||
onblur={(e) => {
|
||||
dispatch('blur')
|
||||
}}
|
||||
use:autosize
|
||||
on:keydown={onKeyDown}
|
||||
onkeydown={onKeyDown}
|
||||
{disabled}
|
||||
class={twMerge(
|
||||
'w-full',
|
||||
@@ -1197,10 +1302,10 @@
|
||||
></textarea>
|
||||
{/key}
|
||||
{#if !disabled && itemPicker && extra?.['disableVariablePicker'] != true}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<button
|
||||
class="absolute right-1 top-1 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface-secondary border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
pickForField = label
|
||||
itemPicker?.openDrawer?.()
|
||||
}}
|
||||
@@ -1216,7 +1321,7 @@
|
||||
{#if value && typeof value == 'string' && value?.startsWith('$var:')}
|
||||
Linked to variable <button
|
||||
class="text-blue-500 underline"
|
||||
on:click={() => variableEditor?.editVariable?.(value.slice(5))}
|
||||
onclick={() => variableEditor?.editVariable?.(value.slice(5))}
|
||||
>{value.slice(5)}</button
|
||||
>
|
||||
{/if}
|
||||
@@ -1224,7 +1329,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<slot name="actions" />
|
||||
{@render actions?.()}
|
||||
</div>
|
||||
|
||||
{#if !compact || (error && error != '')}
|
||||
|
||||
@@ -9,17 +9,25 @@
|
||||
import EditableSchemaDrawer from './schema/EditableSchemaDrawer.svelte'
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
|
||||
export let canEditResourceType: boolean = false
|
||||
export let originalType: string | undefined = undefined
|
||||
export let itemsType:
|
||||
| {
|
||||
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined
|
||||
interface Props {
|
||||
canEditResourceType?: boolean
|
||||
originalType?: string | undefined
|
||||
itemsType:
|
||||
| {
|
||||
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
|
||||
let {
|
||||
canEditResourceType = false,
|
||||
originalType = undefined,
|
||||
itemsType = $bindable()
|
||||
}: Props = $props()
|
||||
|
||||
let selected:
|
||||
| 'string'
|
||||
@@ -29,7 +37,7 @@
|
||||
| 'enum'
|
||||
| 'resource'
|
||||
| 's3object'
|
||||
| undefined =
|
||||
| undefined = $state(
|
||||
itemsType?.type != 'string'
|
||||
? itemsType?.type == 'object' && itemsType?.resourceType == 's3object'
|
||||
? 's3object'
|
||||
@@ -39,12 +47,13 @@
|
||||
: itemsType?.contentEncoding == 'base64'
|
||||
? 'bytes'
|
||||
: 'string'
|
||||
)
|
||||
|
||||
let schema = {
|
||||
let schema = $state({
|
||||
properties: itemsType?.properties || {},
|
||||
order: Object.keys(itemsType?.properties || {}),
|
||||
required: Object.values(itemsType?.properties || {}).map((p) => p.required)
|
||||
}
|
||||
})
|
||||
|
||||
function updateItemsType() {
|
||||
itemsType = {
|
||||
@@ -59,7 +68,7 @@
|
||||
<Label label="Items type">
|
||||
<select
|
||||
bind:value={selected}
|
||||
on:change={() => {
|
||||
onchange={() => {
|
||||
if (selected == 'enum') {
|
||||
itemsType = { type: 'string', enum: [] }
|
||||
} else if (selected == 'string') {
|
||||
@@ -127,7 +136,7 @@
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (itemsType && itemsType.enum) {
|
||||
const enumValue = itemsType.enum[index]
|
||||
itemsType.enum = itemsType.enum.filter((el) => el !== enumValue)
|
||||
|
||||
@@ -6,10 +6,19 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import AssignableTagsInner from './AssignableTagsInner.svelte'
|
||||
|
||||
export let placement: 'bottom-end' | 'top-end' = 'bottom-end'
|
||||
export let color: 'nord' | 'dark' = 'dark'
|
||||
export let disabled = false
|
||||
export let showWorkspaceRestriction = false
|
||||
interface Props {
|
||||
placement?: 'bottom-end' | 'top-end'
|
||||
color?: 'nord' | 'dark'
|
||||
disabled?: boolean
|
||||
showWorkspaceRestriction?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
placement = 'bottom-end',
|
||||
color = 'dark',
|
||||
disabled = false,
|
||||
showWorkspaceRestriction = false
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
<Popover
|
||||
@@ -18,7 +27,7 @@
|
||||
closeButton
|
||||
usePointerDownOutside
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
<Button {color} size="xs" nonCaptureEvent={true} {disabled}>
|
||||
<div class="flex flex-row gap-1 items-center"
|
||||
><Pen size={14} /> Custom tags <Tooltip light
|
||||
@@ -29,8 +38,8 @@
|
||||
></div
|
||||
>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
<AssignableTagsInner {showWorkspaceRestriction} on:refresh />
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault, stopPropagation } from 'svelte/legacy'
|
||||
|
||||
import { Button } from './common'
|
||||
import { ExternalLink, Loader2, X } from 'lucide-svelte'
|
||||
import { SettingService, WorkerService } from '$lib/gen'
|
||||
@@ -9,9 +11,13 @@
|
||||
import { base } from '$lib/base'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let showWorkspaceRestriction = false
|
||||
let newTag: string = ''
|
||||
let customTags: string[] | undefined = undefined
|
||||
interface Props {
|
||||
showWorkspaceRestriction?: boolean
|
||||
}
|
||||
|
||||
let { showWorkspaceRestriction = false }: Props = $props()
|
||||
let newTag: string = $state('')
|
||||
let customTags: string[] | undefined = $state(undefined)
|
||||
|
||||
async function loadCustomTags() {
|
||||
try {
|
||||
@@ -40,15 +46,17 @@
|
||||
<button
|
||||
class="z-10 rounded-full p-1 duration-200 hover:bg-gray-200"
|
||||
aria-label="Remove item"
|
||||
on:click|preventDefault|stopPropagation={async () => {
|
||||
await SettingService.setGlobal({
|
||||
key: CUSTOM_TAGS_SETTING,
|
||||
requestBody: { value: customTags?.filter((x) => x != customTag) }
|
||||
onclick={stopPropagation(
|
||||
preventDefault(async () => {
|
||||
await SettingService.setGlobal({
|
||||
key: CUSTOM_TAGS_SETTING,
|
||||
requestBody: { value: customTags?.filter((x) => x != customTag) }
|
||||
})
|
||||
dispatch('refresh')
|
||||
loadCustomTags()
|
||||
sendUserToast('Tag removed')
|
||||
})
|
||||
dispatch('refresh')
|
||||
loadCustomTags()
|
||||
sendUserToast('Tag removed')
|
||||
}}
|
||||
)}
|
||||
>
|
||||
<X size={12} />
|
||||
</button><NoWorkerWithTagWarning tag={customTag} />
|
||||
@@ -74,7 +82,9 @@
|
||||
}}
|
||||
disabled={newTag.trim() == '' || !$superadmin}
|
||||
>
|
||||
Add {#if !$superadmin} <span class="text-2xs text-tertiary">superadmin only</span> {/if}
|
||||
Add {#if !$superadmin}
|
||||
<span class="text-2xs text-tertiary">superadmin only</span>
|
||||
{/if}
|
||||
</Button>
|
||||
<span class="text-sm text-primary"
|
||||
>Configure <a href="{base}/workers" target="_blank" class="inline-flex gap-1 items-baseline"
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
|
||||
export let value: any
|
||||
interface Props {
|
||||
value: any
|
||||
}
|
||||
|
||||
$: enabled = value != undefined
|
||||
|
||||
$: changeDomain(value?.['domain'], value?.['custom'])
|
||||
let { value = $bindable() }: Props = $props()
|
||||
|
||||
function changeDomain(domain, custom) {
|
||||
if (value) {
|
||||
@@ -31,10 +31,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
let enabled = $derived(value != undefined)
|
||||
$effect(() => {
|
||||
changeDomain(value?.['domain'], value?.['custom'])
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="text-sm font-medium text-primary flex gap-4 items-center"
|
||||
><div class="w-[120px]"><IconedResourceType name="auth0" after={true} /></div><Toggle
|
||||
checked={enabled}
|
||||
@@ -57,10 +61,11 @@
|
||||
on:selected={({ detail }) => {
|
||||
value['custom'] = detail === 'custom'
|
||||
}}
|
||||
let:item
|
||||
>
|
||||
<ToggleButton value="org" label={'Org'} {item} />
|
||||
<ToggleButton value="custom" label="Custom" {item} />
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="org" label={'Org'} {item} />
|
||||
<ToggleButton value="custom" label="Custom" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div class="grow">
|
||||
|
||||
@@ -18,9 +18,31 @@
|
||||
import { ExternalLink, Plus } from 'lucide-svelte'
|
||||
import AzureOauthSettings from './AzureOauthSettings.svelte'
|
||||
|
||||
export let snowflakeAccountIdentifier = ''
|
||||
export let oauths: Record<string, any> = {}
|
||||
export let requirePreexistingUserForOauth: boolean = false
|
||||
interface Props {
|
||||
snowflakeAccountIdentifier?: string
|
||||
oauths?: Record<string, any>
|
||||
requirePreexistingUserForOauth?: boolean
|
||||
scim?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
snowflakeAccountIdentifier = $bindable(),
|
||||
oauths = $bindable(),
|
||||
requirePreexistingUserForOauth = $bindable(),
|
||||
scim
|
||||
}: Props = $props()
|
||||
|
||||
$effect(() => {
|
||||
if (snowflakeAccountIdentifier == undefined) {
|
||||
snowflakeAccountIdentifier = ''
|
||||
}
|
||||
if (oauths == undefined) {
|
||||
oauths = {}
|
||||
}
|
||||
if (requirePreexistingUserForOauth == undefined) {
|
||||
requirePreexistingUserForOauth = false
|
||||
}
|
||||
})
|
||||
|
||||
const windmillBuiltins = [
|
||||
'azure_oauth',
|
||||
@@ -45,12 +67,12 @@
|
||||
'xero'
|
||||
]
|
||||
|
||||
let oauth_name = undefined
|
||||
let oauth_name = $state(undefined)
|
||||
|
||||
let clientName = ''
|
||||
let resourceName = ''
|
||||
let clientName = $state('')
|
||||
let resourceName = $state('')
|
||||
|
||||
let tab: 'sso' | 'oauth' | 'scim' = 'sso'
|
||||
let tab: 'sso' | 'oauth' | 'scim' = $state('sso')
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@@ -62,60 +84,147 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
{#if tab === 'sso'}
|
||||
{#if !$enterpriseLicense || $enterpriseLicense.endsWith('_pro')}
|
||||
<Alert type="warning" title="Limited to 10 SSO users">
|
||||
Without EE, the number of SSO users is limited to 10. SCIM/SAML is available on EE
|
||||
</Alert>
|
||||
{/if}
|
||||
{#if oauths}
|
||||
{#if tab === 'sso'}
|
||||
{#if !$enterpriseLicense || $enterpriseLicense.endsWith('_pro')}
|
||||
<Alert type="warning" title="Limited to 10 SSO users">
|
||||
Without EE, the number of SSO users is limited to 10. SCIM/SAML is available on EE
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<div class="py-1"></div>
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When at least one of the below options is set, users will be able to login to Windmill
|
||||
via their third-party account.
|
||||
<br /> To test SSO, the recommended workflow is to to save the settings and try to login
|
||||
in an incognito window.
|
||||
<a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#sso">Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 py-4">
|
||||
<OAuthSetting name="google" bind:value={oauths['google']} />
|
||||
<OAuthSetting name="microsoft" bind:value={oauths['microsoft']} />
|
||||
<OktaSetting bind:value={oauths['okta']} />
|
||||
<Auth0Setting bind:value={oauths['auth0']} />
|
||||
<OAuthSetting name="github" bind:value={oauths['github']} />
|
||||
<OAuthSetting name="gitlab" bind:value={oauths['gitlab']} />
|
||||
<OAuthSetting name="jumpcloud" bind:value={oauths['jumpcloud']} />
|
||||
<KeycloakSetting bind:value={oauths['keycloak']} />
|
||||
<AuthentikSetting bind:value={oauths['authentik']} />
|
||||
<AutheliaSetting bind:value={oauths['authelia']} />
|
||||
<KanidmSetting bind:value={oauths['kanidm']} />
|
||||
<ZitadelSetting bind:value={oauths['zitadel']} />
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if !['authelia', 'authentik', 'google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'auth0', 'keycloak', 'slack', 'kanidm', 'zitadel'].includes(k) && 'login_config' in oauths[k]}
|
||||
{#if oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="text-md font-medium text-primary">{k}</label>
|
||||
<CloseButton
|
||||
on:close={() => {
|
||||
if (oauths) {
|
||||
delete oauths[k]
|
||||
oauths = { ...oauths }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="p-2 border rounded">
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Custom Name</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Custom Name"
|
||||
bind:value={oauths[k]['display_name']}
|
||||
/>
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Id</span>
|
||||
<input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Secret</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Client Secret"
|
||||
bind:value={oauths[k]['secret']}
|
||||
/>
|
||||
</label>
|
||||
{#if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomSso bind:login_config={oauths[k]['login_config']} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<input type="text" placeholder="client_id" bind:value={clientName} />
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: Plus }}
|
||||
disabled={clientName == ''}
|
||||
on:click={() => {
|
||||
if (oauths) {
|
||||
oauths[clientName] = { id: '', secret: '', login_config: {} }
|
||||
}
|
||||
clientName = ''
|
||||
}}
|
||||
>
|
||||
Add custom SSO client {!$enterpriseLicense ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<Toggle
|
||||
options={{
|
||||
right: 'Require users to have been added manually to Windmill to sign in through OAuth'
|
||||
}}
|
||||
bind:checked={requirePreexistingUserForOauth}
|
||||
/>
|
||||
</div>
|
||||
{:else if tab === 'oauth'}
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When one of the below options is set, you will be able to create a specific resource
|
||||
containing a token automatically generated by the third-party provider.
|
||||
<br />
|
||||
To test it after setting an oauth client, go to the Resources menu and create a new one of
|
||||
the type of your oauth client (i.e. a 'github' resource if you set Github OAuth).
|
||||
<br /><a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#oauth"
|
||||
>Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="slack" bind:value={oauths['slack']} />
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="teams" eeOnly={true} bind:value={oauths['teams']} />
|
||||
<div class="py-1"></div>
|
||||
|
||||
<div class="py-1"></div>
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When at least one of the below options is set, users will be able to login to Windmill via
|
||||
their third-party account.
|
||||
<br /> To test SSO, the recommended workflow is to to save the settings and try to login in
|
||||
an incognito window.
|
||||
<a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#sso">Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 py-4">
|
||||
<OAuthSetting name="google" bind:value={oauths['google']} />
|
||||
<OAuthSetting name="microsoft" bind:value={oauths['microsoft']} />
|
||||
<OktaSetting bind:value={oauths['okta']} />
|
||||
<Auth0Setting bind:value={oauths['auth0']} />
|
||||
<OAuthSetting name="github" bind:value={oauths['github']} />
|
||||
<OAuthSetting name="gitlab" bind:value={oauths['gitlab']} />
|
||||
<OAuthSetting name="jumpcloud" bind:value={oauths['jumpcloud']} />
|
||||
<KeycloakSetting bind:value={oauths['keycloak']} />
|
||||
<AuthentikSetting bind:value={oauths['authentik']} />
|
||||
<AutheliaSetting bind:value={oauths['authelia']} />
|
||||
<KanidmSetting bind:value={oauths['kanidm']} />
|
||||
<ZitadelSetting bind:value={oauths['zitadel']} />
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if !['authelia', 'authentik', 'google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'auth0', 'keycloak', 'slack', 'kanidm', 'zitadel'].includes(k) && 'login_config' in oauths[k]}
|
||||
{#if oauths[k]}
|
||||
{#if oauths[k] && !('login_config' in oauths[k])}
|
||||
{#if !['slack', 'teams'].includes(k) && oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="text-md font-medium text-primary">{k}</label>
|
||||
<CloseButton
|
||||
on:close={() => {
|
||||
delete oauths[k]
|
||||
oauths = { ...oauths }
|
||||
if (oauths) {
|
||||
delete oauths[k]
|
||||
oauths = { ...oauths }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="p-2 border rounded">
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Custom Name</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Custom Name"
|
||||
bind:value={oauths[k]['display_name']}
|
||||
/>
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Id</span>
|
||||
<input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
|
||||
@@ -124,141 +233,68 @@
|
||||
<span class="text-primary font-semibold text-sm">Client Secret</span>
|
||||
<input type="text" placeholder="Client Secret" bind:value={oauths[k]['secret']} />
|
||||
</label>
|
||||
{#if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomSso bind:login_config={oauths[k]['login_config']} />
|
||||
{#if k === 'azure_oauth'}
|
||||
<AzureOauthSettings bind:connect_config={oauths[k]['connect_config']} />
|
||||
{:else if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomOauth bind:connect_config={oauths[k]['connect_config']} />
|
||||
{/if}
|
||||
{#if k == 'snowflake_oauth'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm flex gap-2 items-center"
|
||||
><a
|
||||
href="https://docs.snowflake.com/en/user-guide/admin-account-identifier#using-an-account-name-as-an-identifier"
|
||||
target="_blank">Snowflake Account Identifier</a
|
||||
><ExternalLink size={12} /></span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="<orgname>-<account_name>"
|
||||
required={true}
|
||||
bind:value={snowflakeAccountIdentifier}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<input type="text" placeholder="client_id" bind:value={clientName} />
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: Plus }}
|
||||
disabled={clientName == ''}
|
||||
on:click={() => {
|
||||
oauths[clientName] = { id: '', secret: '', login_config: {} }
|
||||
clientName = ''
|
||||
}}
|
||||
>
|
||||
Add custom SSO client {!$enterpriseLicense ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<Toggle
|
||||
options={{
|
||||
right: 'Require users to have been added manually to Windmill to sign in through OAuth'
|
||||
}}
|
||||
bind:checked={requirePreexistingUserForOauth}
|
||||
/>
|
||||
</div>
|
||||
{:else if tab === 'oauth'}
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When one of the below options is set, you will be able to create a specific resource
|
||||
containing a token automatically generated by the third-party provider.
|
||||
<br />
|
||||
To test it after setting an oauth client, go to the Resources menu and create a new one of the
|
||||
type of your oauth client (i.e. a 'github' resource if you set Github OAuth).
|
||||
<br /><a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#oauth"
|
||||
>Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="slack" bind:value={oauths['slack']} />
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="teams" eeOnly={true} bind:value={oauths['teams']} />
|
||||
<div class="py-1"></div>
|
||||
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if oauths[k] && !('login_config' in oauths[k])}
|
||||
{#if !['slack', 'teams'].includes(k) && oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="text-md font-medium text-primary">{k}</label>
|
||||
<CloseButton
|
||||
on:close={() => {
|
||||
delete oauths[k]
|
||||
oauths = { ...oauths }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="p-2 border rounded">
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Id</span>
|
||||
<input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Secret</span>
|
||||
<input type="text" placeholder="Client Secret" bind:value={oauths[k]['secret']} />
|
||||
</label>
|
||||
{#if k === 'azure_oauth'}
|
||||
<AzureOauthSettings bind:connect_config={oauths[k]['connect_config']} />
|
||||
{:else if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomOauth bind:connect_config={oauths[k]['connect_config']} />
|
||||
{/if}
|
||||
{#if k == 'snowflake_oauth'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm flex gap-2 items-center"
|
||||
><a
|
||||
href="https://docs.snowflake.com/en/user-guide/admin-account-identifier#using-an-account-name-as-an-identifier"
|
||||
target="_blank">Snowflake Account Identifier</a
|
||||
><ExternalLink size={12} /></span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="<orgname>-<account_name>"
|
||||
required={true}
|
||||
bind:value={snowflakeAccountIdentifier}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<select name="oauth_name" id="oauth_name" bind:value={oauth_name}>
|
||||
<option value={undefined}>Select an OAuth client</option>
|
||||
<option value="custom">Fully Custom (requires ee)</option>
|
||||
{#each windmillBuiltins as name}
|
||||
<option value={name}>{capitalize(name)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if oauth_name == 'custom'}
|
||||
<input type="text" placeholder="client_id" bind:value={resourceName} />
|
||||
{:else}
|
||||
<input type="text" value={oauth_name ?? ''} disabled />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<div class="flex gap-2">
|
||||
<select name="oauth_name" id="oauth_name" bind:value={oauth_name}>
|
||||
<option value={undefined}>Select an OAuth client</option>
|
||||
<option value="custom">Fully Custom (requires ee)</option>
|
||||
{#each windmillBuiltins as name}
|
||||
<option value={name}>{capitalize(name)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if oauth_name == 'custom'}
|
||||
<input type="text" placeholder="client_id" bind:value={resourceName} />
|
||||
{:else}
|
||||
<input type="text" value={oauth_name ?? ''} disabled />
|
||||
{/if}
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: Plus }}
|
||||
disabled={!oauth_name ||
|
||||
(oauth_name == 'custom' && resourceName == '') ||
|
||||
(oauth_name == 'custom' && !$enterpriseLicense)}
|
||||
on:click={() => {
|
||||
let name = oauth_name == 'custom' ? resourceName : oauth_name
|
||||
oauths[name ?? ''] = { id: '', secret: '' }
|
||||
resourceName = ''
|
||||
}}
|
||||
>
|
||||
Add OAuth client {oauth_name == 'custom' && !$enterpriseLicense ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
{:else if tab == 'scim'}
|
||||
<slot name="scim" />
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: Plus }}
|
||||
disabled={!oauth_name ||
|
||||
(oauth_name == 'custom' && resourceName == '') ||
|
||||
(oauth_name == 'custom' && !$enterpriseLicense)}
|
||||
on:click={() => {
|
||||
if (oauths) {
|
||||
let name = oauth_name == 'custom' ? resourceName : oauth_name
|
||||
oauths[name ?? ''] = { id: '', secret: '' }
|
||||
}
|
||||
resourceName = ''
|
||||
}}
|
||||
>
|
||||
Add OAuth client {oauth_name == 'custom' && !$enterpriseLicense ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
{:else if tab == 'scim'}
|
||||
{@render scim?.()}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
import Label from './Label.svelte'
|
||||
import MultiSelect from 'svelte-multiselect'
|
||||
|
||||
export let config: AutoscalingConfig | undefined
|
||||
export let worker_tags: string[] | undefined
|
||||
interface Props {
|
||||
config: AutoscalingConfig | undefined
|
||||
worker_tags: string[] | undefined
|
||||
}
|
||||
|
||||
let { config = $bindable(), worker_tags }: Props = $props()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let test_input: number = 3
|
||||
let test_input: number = $state(3)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-16 pt-2">
|
||||
@@ -49,11 +53,11 @@
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Min # of Workers
|
||||
{#if config !== undefined}
|
||||
<input on:input={() => dispatch('dirty')} type="number" bind:value={config.min_workers} />
|
||||
<input oninput={() => dispatch('dirty')} type="number" bind:value={config.min_workers} />
|
||||
{#if config.min_workers !== undefined && config.min_workers != undefined && config.min_workers > config.max_workers}
|
||||
<div class="text-red-600 text-xs whitespace-nowrap"
|
||||
>Minimum cannot be {'>'} to Maximum</div
|
||||
@@ -63,11 +67,11 @@
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Max # of Workers
|
||||
{#if config !== undefined}
|
||||
<input on:input={() => dispatch('dirty')} type="number" bind:value={config.max_workers} />
|
||||
<input oninput={() => dispatch('dirty')} type="number" bind:value={config.max_workers} />
|
||||
{:else}
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
@@ -75,12 +79,12 @@
|
||||
<div class="p-2">
|
||||
<Section label="Advanced" small collapsable={true}>
|
||||
<div class="flex flex-col gap-2 text-2xs">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Cooldown seconds after an incremental scale-in/out
|
||||
{#if config !== undefined}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="number"
|
||||
step="1"
|
||||
min="30"
|
||||
@@ -91,12 +95,12 @@
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Cooldown seconds after a full scale out
|
||||
{#if config !== undefined}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="number"
|
||||
step="1"
|
||||
min="30"
|
||||
@@ -107,12 +111,12 @@
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Num jobs waiting to trigger an incremental scale-out
|
||||
{#if config !== undefined}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="number"
|
||||
bind:value={config.inc_scale_num_jobs_waiting}
|
||||
placeholder="1"
|
||||
@@ -122,14 +126,14 @@
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Num jobs waiting to trigger a full scale out <Tooltip
|
||||
>Default: max_workers, full scale out = scale out to max workers</Tooltip
|
||||
>
|
||||
{#if config !== undefined}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="number"
|
||||
placeholder="max workers"
|
||||
bind:value={config.full_scale_jobs_waiting}
|
||||
@@ -138,14 +142,14 @@
|
||||
<input type="number" disabled />
|
||||
{/if}
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Occupancy rate % threshold to go below to trigger a scale-in (decrease) <Tooltip
|
||||
>Default: 25%, need to go below average of all of 15s, 5m and 30m occupancy rates</Tooltip
|
||||
>
|
||||
{#if config !== undefined}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="number"
|
||||
step="1"
|
||||
min="0"
|
||||
@@ -157,14 +161,14 @@
|
||||
<input type="number" step="0.01" disabled />
|
||||
{/if}
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Occupancy rate threshold to exceed to trigger an incremental scale-out (increase) <Tooltip
|
||||
>Default: 75%, need to exceed average of all of 15s, 5m and 30m occupancy rates</Tooltip
|
||||
>
|
||||
{#if config !== undefined}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="number"
|
||||
step="1"
|
||||
min="0"
|
||||
@@ -176,14 +180,14 @@
|
||||
<input type="number" step="0.01" disabled />
|
||||
{/if}
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label>
|
||||
Num workers to scale-in/out by when incremental <Tooltip
|
||||
>Default: (max_workers - min_workers) / 5</Tooltip
|
||||
>
|
||||
{#if config !== undefined}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
@@ -196,12 +200,12 @@
|
||||
</label>
|
||||
|
||||
<Label label="Custom tags to autoscale on">
|
||||
<svelte:fragment slot="header">
|
||||
{#snippet header()}
|
||||
<Tooltip>
|
||||
By default, autoscaling will apply to the tags the worker group is assigned to but
|
||||
you can override this here.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
{#if config}
|
||||
{#if config.custom_tags}
|
||||
<MultiSelect
|
||||
@@ -259,30 +263,31 @@
|
||||
on:selected={(e) => dispatch('dirty')}
|
||||
bind:selected={config.integration.type}
|
||||
class="mb-4 mt-2"
|
||||
let:item
|
||||
>
|
||||
<ToggleButton
|
||||
value="dryrun"
|
||||
label="Dry run"
|
||||
tooltip="See autoscaling events but not actual scaling actions will be performed"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="script"
|
||||
label="Custom script"
|
||||
tooltip="Run a custom script to scale your worker group"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton disabled value="ecs" label="ECS (soon)" {item} />
|
||||
<ToggleButton disabled value="nomad" label="Nomad (soon)" {item} />
|
||||
<ToggleButton disabled value="kubernetes" label="Kubernetes (soon)" {item} />
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="dryrun"
|
||||
label="Dry run"
|
||||
tooltip="See autoscaling events but not actual scaling actions will be performed"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="script"
|
||||
label="Custom script"
|
||||
tooltip="Run a custom script to scale your worker group"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton disabled value="ecs" label="ECS (soon)" {item} />
|
||||
<ToggleButton disabled value="nomad" label="Nomad (soon)" {item} />
|
||||
<ToggleButton disabled value="kubernetes" label="Kubernetes (soon)" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
|
||||
{#if config.integration.type === 'script'}
|
||||
<label>
|
||||
Script path on the 'admins' workspace
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="text"
|
||||
bind:value={config.integration.path}
|
||||
/>
|
||||
@@ -292,7 +297,7 @@
|
||||
Custom tag for executing script (optional)
|
||||
{#if config.integration.tag}
|
||||
<input
|
||||
on:input={() => dispatch('dirty')}
|
||||
oninput={() => dispatch('dirty')}
|
||||
type="text"
|
||||
bind:value={config.integration.tag}
|
||||
/>
|
||||
@@ -337,12 +342,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<ToggleButtonGroup selected={'script'} disabled class="mb-4 mt-2" let:item>
|
||||
<ToggleButton value="dryrun" label="Dry run" {item} />
|
||||
<ToggleButton value="script" label="Custom script" {item} />
|
||||
<ToggleButton value="ecs" label="ECS (soon)" {item} />
|
||||
<ToggleButton value="nomad" label="Nomad (soon)" {item} />
|
||||
<ToggleButton value="kubernetes" label="Kubernetes (soon)" {item} />
|
||||
<ToggleButtonGroup selected={'script'} disabled class="mb-4 mt-2">
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="dryrun" label="Dry run" {item} />
|
||||
<ToggleButton value="script" label="Custom script" {item} />
|
||||
<ToggleButton value="ecs" label="ECS (soon)" {item} />
|
||||
<ToggleButton value="nomad" label="Nomad (soon)" {item} />
|
||||
<ToggleButton value="kubernetes" label="Kubernetes (soon)" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<label>
|
||||
|
||||
@@ -18,10 +18,14 @@
|
||||
import YAML from 'yaml'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import ContentSearchInnerItem from './ContentSearchInnerItem.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
export let search: string = ''
|
||||
interface Props {
|
||||
search?: string
|
||||
}
|
||||
|
||||
let { search = $bindable('') }: Props = $props()
|
||||
|
||||
export async function open(nsearch?: string) {
|
||||
await Promise.all([loadScripts(), loadResources(), loadApps(), loadFlows()])
|
||||
@@ -46,19 +50,19 @@
|
||||
flows = await FlowService.listSearchFlow({ workspace: $workspaceStore ?? '' })
|
||||
}
|
||||
|
||||
let searchKind: 'all' | 'scripts' | 'flows' | 'apps' | 'resources' = 'all'
|
||||
let searchKind: 'all' | 'scripts' | 'flows' | 'apps' | 'resources' = $state('all')
|
||||
|
||||
let scripts: undefined | { path: string; content: string }[] = undefined
|
||||
let filteredScriptItems: { path: string; content: string; marked: any }[] = []
|
||||
let scripts: undefined | { path: string; content: string }[] = $state(undefined)
|
||||
let filteredScriptItems: { path: string; content: string; marked: any }[] = $state([])
|
||||
|
||||
let resources: undefined | { path: string; value: any }[] = undefined
|
||||
let filteredResourceItems: { path: string; value: any; marked: any }[] = []
|
||||
let resources: undefined | { path: string; value: any }[] = $state(undefined)
|
||||
let filteredResourceItems: { path: string; value: any; marked: any }[] = $state([])
|
||||
|
||||
let flows: undefined | { path: string; value: any }[] = undefined
|
||||
let filteredFlowItems: { path: string; value: any; marked: any }[] = []
|
||||
let flows: undefined | { path: string; value: any }[] = $state(undefined)
|
||||
let filteredFlowItems: { path: string; value: any; marked: any }[] = $state([])
|
||||
|
||||
let apps: undefined | { path: string; value: any }[] = undefined
|
||||
let filteredAppItems: { path: string; value: any; marked: any }[] = []
|
||||
let apps: undefined | { path: string; value: any }[] = $state(undefined)
|
||||
let filteredAppItems: { path: string; value: any; marked: any }[] = $state([])
|
||||
|
||||
function getCounts(n: number) {
|
||||
return ` (${n})`
|
||||
@@ -73,23 +77,34 @@
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
$: counts =
|
||||
let showNbScripts = $state(10)
|
||||
let showNbApps = $state(10)
|
||||
let showNbResources = $state(10)
|
||||
let showNbFlows = $state(10)
|
||||
|
||||
function resetShows() {
|
||||
showNbScripts = 10
|
||||
showNbApps = 10
|
||||
showNbResources = 10
|
||||
showNbFlows = 10
|
||||
}
|
||||
let counts = $derived(
|
||||
search == '' ||
|
||||
!scripts ||
|
||||
!resources ||
|
||||
!flows ||
|
||||
!apps ||
|
||||
!filteredAppItems ||
|
||||
!filteredFlowItems ||
|
||||
!filteredResourceItems ||
|
||||
!filteredScriptItems
|
||||
!scripts ||
|
||||
!resources ||
|
||||
!flows ||
|
||||
!apps ||
|
||||
!filteredAppItems ||
|
||||
!filteredFlowItems ||
|
||||
!filteredResourceItems ||
|
||||
!filteredScriptItems
|
||||
? {
|
||||
all: '',
|
||||
apps: '',
|
||||
flows: '',
|
||||
resources: '',
|
||||
scripts: ''
|
||||
}
|
||||
}
|
||||
: {
|
||||
all: getCounts(
|
||||
filteredAppItems.length +
|
||||
@@ -101,21 +116,11 @@
|
||||
resources: getCounts(filteredResourceItems.length),
|
||||
flows: getCounts(filteredFlowItems.length),
|
||||
scripts: getCounts(filteredScriptItems.length)
|
||||
}
|
||||
|
||||
let showNbScripts = 10
|
||||
let showNbApps = 10
|
||||
let showNbResources = 10
|
||||
let showNbFlows = 10
|
||||
|
||||
$: search && resetShows()
|
||||
|
||||
function resetShows() {
|
||||
showNbScripts = 10
|
||||
showNbApps = 10
|
||||
showNbResources = 10
|
||||
showNbFlows = 10
|
||||
}
|
||||
}
|
||||
)
|
||||
$effect(() => {
|
||||
search && untrack(() => resetShows())
|
||||
})
|
||||
</script>
|
||||
|
||||
<SearchItems
|
||||
@@ -157,42 +162,44 @@
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2 flex-wrap sticky top-0 left-0 right-0 bg-surface">
|
||||
<div class="p-2">
|
||||
<ToggleButtonGroup bind:selected={searchKind} class="h-10 " let:item>
|
||||
<ToggleButton small light value="all" label={'All' + counts.all} {item} />
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="scripts"
|
||||
icon={Code2}
|
||||
label={'Scripts' + counts.scripts}
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="resources"
|
||||
icon={Boxes}
|
||||
label={'Resources' + counts.resources}
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="flows"
|
||||
label={'Flows' + counts.flows}
|
||||
icon={FlowIcon}
|
||||
selectedColor="#14b8a6"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="apps"
|
||||
label={'Apps' + counts.apps}
|
||||
icon={LayoutDashboard}
|
||||
selectedColor="#fb923c"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButtonGroup bind:selected={searchKind} class="h-10 ">
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton small light value="all" label={'All' + counts.all} {item} />
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="scripts"
|
||||
icon={Code2}
|
||||
label={'Scripts' + counts.scripts}
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="resources"
|
||||
icon={Boxes}
|
||||
label={'Resources' + counts.resources}
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="flows"
|
||||
label={'Flows' + counts.flows}
|
||||
icon={FlowIcon}
|
||||
selectedColor="#14b8a6"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
value="apps"
|
||||
label={'Apps' + counts.apps}
|
||||
icon={LayoutDashboard}
|
||||
selectedColor="#fb923c"
|
||||
{item}
|
||||
/>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
@@ -245,7 +252,7 @@
|
||||
href={`/scripts/get/${item.path}`}
|
||||
on:close
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
href={`/scripts/get/${item.path}`}
|
||||
color="light"
|
||||
@@ -267,7 +274,7 @@
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
<pre class="text-xs border rounded-md p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code>
|
||||
</pre>
|
||||
@@ -295,7 +302,7 @@
|
||||
href={`/resources#${item.path}`}
|
||||
on:close
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
href={`/resources#${item.path}`}
|
||||
color="light"
|
||||
@@ -305,7 +312,7 @@
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
<pre class="text-xs border rounded-md p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code></pre
|
||||
>
|
||||
@@ -333,7 +340,7 @@
|
||||
href={`/flows/get/${item.path}`}
|
||||
on:close
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
href={`/flows/get/${item.path}`}
|
||||
color="light"
|
||||
@@ -354,7 +361,7 @@
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
|
||||
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code></pre
|
||||
@@ -383,7 +390,7 @@
|
||||
href={`/apps/get/${item.path}`}
|
||||
on:close
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
href={`/apps/get/${item.path}`}
|
||||
color="light"
|
||||
@@ -404,7 +411,7 @@
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
|
||||
<pre class="text-xs border p-2 overflow-auto max-h-40 w-full"
|
||||
><code>{@html item.marked}</code></pre
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let title: string
|
||||
export let href: string
|
||||
interface Props {
|
||||
title: string
|
||||
href: string
|
||||
actions?: import('svelte').Snippet
|
||||
children?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let { title, href, actions, children }: Props = $props()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
@@ -12,7 +18,7 @@
|
||||
<a
|
||||
class="text-sm text-blue-500 truncate hover:underline"
|
||||
{href}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
@@ -20,8 +26,8 @@
|
||||
</a>
|
||||
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<slot name="actions" />
|
||||
{@render actions?.()}
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<textarea rows="4" use:autosize bind:value placeholder="Variable value"></textarea>
|
||||
</Section>
|
||||
</div>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
on:click={() => updateVariable()}
|
||||
disabled={value === '' || name === ''}
|
||||
@@ -79,6 +79,6 @@
|
||||
>
|
||||
{edit ? 'Update' : 'Save'}
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
{/if}
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
loading={refreshing}
|
||||
on:click={() => refresh()}
|
||||
@@ -266,7 +266,7 @@
|
||||
size="xs"
|
||||
color="light"
|
||||
/>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
{/if}
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
class="mb-4"
|
||||
selected={dbSchema.publicOnly ? 'dbo' : 'all'}
|
||||
on:selected={handleSelected}
|
||||
let:item
|
||||
>
|
||||
<ToggleButton value="dbo" label={dbSchema.schema.dbo ? 'Dbo' : 'Public'} {item} />
|
||||
<ToggleButton value="all" label="All" {item} />
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="dbo" label={dbSchema.schema.dbo ? 'Dbo' : 'Public'} {item} />
|
||||
<ToggleButton value="all" label="All" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
{#if dbSchema.lang === 'graphql'}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import 'ag-grid-community/styles/ag-grid.css'
|
||||
import '$lib/components/apps/components/display/table/theme/windmill-theme.css'
|
||||
import { untrack } from 'svelte'
|
||||
|
||||
type Props = {
|
||||
dbTableOps: IDbTableOps
|
||||
@@ -45,7 +46,7 @@
|
||||
let refreshCount = $state(0)
|
||||
const refresh = () => (refreshCount += 1)
|
||||
|
||||
$effect(() => eGui && mountGrid())
|
||||
$effect(() => eGui && untrack(() => mountGrid()))
|
||||
function mountGrid() {
|
||||
if (eGui && !api) {
|
||||
createGrid(eGui, {
|
||||
@@ -95,7 +96,7 @@
|
||||
const key = { quicksearch, colDefs: dbTableOps.colDefs, refreshCount }
|
||||
if (deepEqual(key, prevUpdateKey)) return
|
||||
prevUpdateKey = key
|
||||
updateGrid()
|
||||
untrack(() => updateGrid())
|
||||
})
|
||||
function updateGrid() {
|
||||
dbTableOps.getCount({ quicksearch }).then((result) => (rowCount = result))
|
||||
|
||||
@@ -262,7 +262,6 @@
|
||||
? o.split('.')[1]
|
||||
: o
|
||||
}))}
|
||||
clearable={false}
|
||||
/>
|
||||
</Cell>
|
||||
<Cell>
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createBubbler } from 'svelte/legacy'
|
||||
|
||||
const bubble = createBubbler()
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { Button } from './common'
|
||||
import { Clock, X } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
// import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
// import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
|
||||
export let value: string | undefined = undefined
|
||||
interface Props {
|
||||
// import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
value?: string | undefined
|
||||
clearable?: boolean
|
||||
autofocus?: boolean | null
|
||||
useDropdown?: boolean
|
||||
minDate?: string | undefined
|
||||
maxDate?: string | undefined
|
||||
disabled?: boolean | undefined
|
||||
inputClass?: string | undefined
|
||||
}
|
||||
|
||||
export let clearable: boolean = false
|
||||
export let autofocus: boolean | null = false
|
||||
export let useDropdown: boolean = false
|
||||
export let minDate: string | undefined = undefined
|
||||
export let maxDate: string | undefined = undefined
|
||||
export let disabled: boolean | undefined = undefined
|
||||
export let inputClass: string | undefined = undefined
|
||||
let {
|
||||
value = $bindable(undefined),
|
||||
clearable = false,
|
||||
autofocus = false,
|
||||
useDropdown = false,
|
||||
minDate = undefined,
|
||||
maxDate = undefined,
|
||||
disabled = undefined,
|
||||
inputClass = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let date: string | undefined = undefined
|
||||
let time: string | undefined = undefined
|
||||
let date: string | undefined = $state(undefined)
|
||||
let time: string | undefined = $state(undefined)
|
||||
|
||||
// let format: 'local' | 'utc' = 'local'
|
||||
|
||||
@@ -38,10 +53,15 @@
|
||||
: '12:00'
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
$effect(() => {
|
||||
value
|
||||
untrack(() => {
|
||||
parseValue(value)
|
||||
})
|
||||
})
|
||||
|
||||
let initialDate = date
|
||||
let initialTime = time
|
||||
let initialDate = untrack(() => date)
|
||||
let initialTime = untrack(() => time)
|
||||
|
||||
function parseDateAndTime(date: string | undefined, time: string | undefined) {
|
||||
if (date && time && (initialDate != date || initialTime != time)) {
|
||||
@@ -54,7 +74,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: parseDateAndTime(date, time)
|
||||
$effect(() => {
|
||||
;[date, time]
|
||||
untrack(() => {
|
||||
parseDateAndTime(date, time)
|
||||
})
|
||||
})
|
||||
|
||||
function isValidDate(d: Date | undefined): boolean {
|
||||
return d instanceof Date && !isNaN(d as any)
|
||||
@@ -73,8 +98,13 @@
|
||||
let randomId = 'datetarget-' + Math.random().toString(36).substring(7)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-1 items-center w-full" id={randomId} on:pointerdown on:focus>
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<div
|
||||
class="flex flex-row gap-1 items-center w-full"
|
||||
id={randomId}
|
||||
onpointerdown={bubble('pointerdown')}
|
||||
onfocus={bubble('focus')}
|
||||
>
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
type="date"
|
||||
bind:value={date}
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
<Button
|
||||
disabled={workspaceToDeployTo == undefined}
|
||||
on:click={() => deployWorkspace?.deployAll()}>Deploy All</Button
|
||||
>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
OpenAPI,
|
||||
type Preview,
|
||||
type OpenFlow,
|
||||
type FlowModule,
|
||||
WorkspaceService,
|
||||
type InputTransform,
|
||||
type TriggersCount
|
||||
@@ -20,7 +19,7 @@
|
||||
import { setCopilotInfo, userStore, workspaceStore } from '$lib/stores'
|
||||
import { emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { onDestroy, onMount, setContext } from 'svelte'
|
||||
import { onDestroy, onMount, setContext, untrack } from 'svelte'
|
||||
import DarkModeToggle from '$lib/components/sidebar/DarkModeToggle.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import { getUserExt } from '$lib/user'
|
||||
@@ -49,16 +48,6 @@
|
||||
import type { FlowPropPickerConfig, PropPickerContext } from './prop_picker'
|
||||
import type { PickableProperties } from './flows/previousResults'
|
||||
import { Triggers } from './triggers/triggers.svelte'
|
||||
$: token = $page.url.searchParams.get('wm_token') ?? undefined
|
||||
$: workspace = $page.url.searchParams.get('workspace') ?? undefined
|
||||
$: themeDarkRaw = $page.url.searchParams.get('activeColorTheme')
|
||||
$: themeDark = themeDarkRaw == '2' || themeDarkRaw == '4'
|
||||
|
||||
$: if (token) {
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
OpenAPI.TOKEN = token
|
||||
loadUser()
|
||||
}
|
||||
|
||||
let flowCopilotContext: FlowCopilotContext = {
|
||||
shouldUpdatePropertyType: writable<{
|
||||
@@ -87,14 +76,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
$: if (workspace) {
|
||||
$workspaceStore = workspace
|
||||
setupCopilotInfo()
|
||||
}
|
||||
|
||||
$: if (workspace && token) {
|
||||
loadUser()
|
||||
}
|
||||
|
||||
async function loadUser() {
|
||||
try {
|
||||
@@ -105,31 +86,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
let darkModeToggle: DarkModeToggle
|
||||
let darkMode: boolean | undefined = undefined
|
||||
let modeInitialized = false
|
||||
let darkModeToggle: DarkModeToggle | undefined = $state()
|
||||
let darkMode: boolean | undefined = $state(undefined)
|
||||
let modeInitialized = $state(false)
|
||||
function initializeMode() {
|
||||
modeInitialized = true
|
||||
darkModeToggle.toggle()
|
||||
darkModeToggle?.toggle()
|
||||
}
|
||||
$: darkModeToggle &&
|
||||
themeDark != darkMode &&
|
||||
darkMode != undefined &&
|
||||
!modeInitialized &&
|
||||
initializeMode()
|
||||
|
||||
let testJobLoader: TestJobLoader
|
||||
let testJobLoader: TestJobLoader | undefined = $state()
|
||||
let socket: WebSocket | undefined = undefined
|
||||
|
||||
// Test args input
|
||||
let args: Record<string, any> = {}
|
||||
let isValid: boolean = true
|
||||
let args: Record<string, any> = $state({})
|
||||
let isValid: boolean = $state(true)
|
||||
|
||||
// Test
|
||||
let testIsLoading = false
|
||||
let testJob: Job | undefined
|
||||
let pastPreviews: CompletedJob[] = []
|
||||
let validCode = true
|
||||
let testIsLoading = $state(false)
|
||||
let testJob: Job | undefined = $state()
|
||||
let pastPreviews: CompletedJob[] = $state([])
|
||||
let validCode = $state(true)
|
||||
|
||||
type LastEditScript = {
|
||||
content: string
|
||||
@@ -140,9 +116,9 @@
|
||||
tag?: string
|
||||
}
|
||||
|
||||
let currentScript: LastEditScript | undefined = undefined
|
||||
let currentScript: LastEditScript | undefined = $state(undefined)
|
||||
|
||||
let schema = emptySchema()
|
||||
let schema = $state(emptySchema())
|
||||
const href = window.location.href
|
||||
const indexQ = href.indexOf('?')
|
||||
const searchParams = indexQ > -1 ? new URLSearchParams(href.substring(indexQ)) : undefined
|
||||
@@ -151,12 +127,12 @@
|
||||
connectWs()
|
||||
}
|
||||
|
||||
let useLock = false
|
||||
let useLock = $state(false)
|
||||
|
||||
let lockChanges = false
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
let loadingCodebaseButton = false
|
||||
let loadingCodebaseButton = $state(false)
|
||||
let lastCommandId = ''
|
||||
|
||||
const el = (event) => {
|
||||
@@ -174,7 +150,7 @@
|
||||
}
|
||||
} else if (event.data.type == 'testPreviewBundle') {
|
||||
if (event.data.id == lastCommandId && currentScript) {
|
||||
testJobLoader.runPreview(
|
||||
testJobLoader?.runPreview(
|
||||
currentScript.path,
|
||||
event.data.file,
|
||||
currentScript.language,
|
||||
@@ -333,7 +309,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let typescriptBundlePreviewMode = false
|
||||
let typescriptBundlePreviewMode = $state(false)
|
||||
function runTest() {
|
||||
if (mode == 'script') {
|
||||
if (!currentScript) {
|
||||
@@ -391,7 +367,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let relativePaths: any[] = []
|
||||
let relativePaths: any[] = $state([])
|
||||
let lastPath: string | undefined = undefined
|
||||
async function replaceScript(lastEdit: LastEditScript) {
|
||||
mode = 'script'
|
||||
@@ -416,14 +392,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
let mode: 'script' | 'flow' = 'script'
|
||||
let mode: 'script' | 'flow' = $state('script')
|
||||
|
||||
const flowStore = writable({
|
||||
summary: '',
|
||||
value: { modules: [] },
|
||||
extra_perms: {},
|
||||
schema: emptySchema()
|
||||
} as OpenFlow)
|
||||
const flowStore = $state({
|
||||
val: {
|
||||
summary: '',
|
||||
value: { modules: [] },
|
||||
extra_perms: {},
|
||||
schema: emptySchema()
|
||||
} as OpenFlow
|
||||
})
|
||||
|
||||
type LastEditFlow = {
|
||||
flow: OpenFlow
|
||||
@@ -436,14 +414,14 @@
|
||||
// sendUserToast(JSON.stringify(lastEdit.flow), true)
|
||||
// return
|
||||
try {
|
||||
if (!deepEqual(lastEdit.flow, $flowStore)) {
|
||||
if (!deepEqual(lastEdit.flow, flowStore)) {
|
||||
if (!lastEdit.flow.summary) {
|
||||
lastEdit.flow.summary = 'New flow'
|
||||
}
|
||||
if (!lastEdit.flow.value?.modules) {
|
||||
lastEdit.flow.value = { modules: [] }
|
||||
}
|
||||
$flowStore = lastEdit.flow
|
||||
flowStore.val = lastEdit.flow
|
||||
inferModuleArgs($selectedIdStore)
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -453,10 +431,10 @@
|
||||
|
||||
const flowStateStore = writable({} as FlowState)
|
||||
|
||||
const previewArgsStore = writable<Record<string, any>>({})
|
||||
const previewArgsStore = $state({ val: {} })
|
||||
const scriptEditorDrawer = writable(undefined)
|
||||
const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined)
|
||||
const history = initHistory($flowStore)
|
||||
const moving = writable<{ id: string } | undefined>(undefined)
|
||||
const history = initHistory(flowStore.val)
|
||||
|
||||
const testStepStore = writable<Record<string, any>>({})
|
||||
const selectedIdStore = writable('settings-metadata')
|
||||
@@ -496,7 +474,6 @@
|
||||
flowPropPickerConfig: writable<FlowPropPickerConfig | undefined>(undefined),
|
||||
pickablePropertiesFiltered: writable<PickableProperties | undefined>(undefined)
|
||||
})
|
||||
$: updateFlow($flowStore)
|
||||
|
||||
let lastSent: OpenFlow | undefined = undefined
|
||||
function updateFlow(flow: OpenFlow) {
|
||||
@@ -509,17 +486,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedIdStore && inferModuleArgs($selectedIdStore)
|
||||
|
||||
let flowPreviewButtons: FlowPreviewButtons
|
||||
let reload = 0
|
||||
let flowPreviewButtons: FlowPreviewButtons | undefined = $state()
|
||||
let reload = $state(0)
|
||||
|
||||
async function inferModuleArgs(selectedIdStore: string) {
|
||||
if (selectedIdStore == '') {
|
||||
return
|
||||
}
|
||||
//@ts-ignore
|
||||
dfs($flowStore.value.modules, async (mod) => {
|
||||
dfs(flowStore.value.modules, async (mod) => {
|
||||
if (mod.id == selectedIdStore) {
|
||||
if (
|
||||
mod.value.type == 'rawscript' ||
|
||||
@@ -544,9 +519,44 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
let token = $derived($page.url.searchParams.get('wm_token') ?? undefined)
|
||||
let workspace = $derived($page.url.searchParams.get('workspace') ?? undefined)
|
||||
let themeDarkRaw = $derived($page.url.searchParams.get('activeColorTheme'))
|
||||
let themeDark = $derived(themeDarkRaw == '2' || themeDarkRaw == '4')
|
||||
$effect(() => {
|
||||
if (token) {
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
OpenAPI.TOKEN = token
|
||||
untrack(() => loadUser())
|
||||
}
|
||||
})
|
||||
$effect(() => {
|
||||
if (workspace) {
|
||||
$workspaceStore = workspace
|
||||
untrack(() => setupCopilotInfo())
|
||||
}
|
||||
})
|
||||
$effect(() => {
|
||||
if (workspace && token) {
|
||||
untrack(() => loadUser())
|
||||
}
|
||||
})
|
||||
$effect(() => {
|
||||
darkModeToggle &&
|
||||
themeDark != darkMode &&
|
||||
darkMode != undefined &&
|
||||
!modeInitialized &&
|
||||
untrack(() => initializeMode())
|
||||
})
|
||||
$effect(() => {
|
||||
flowStore.val && untrack(() => updateFlow(flowStore.val))
|
||||
})
|
||||
$effect(() => {
|
||||
$selectedIdStore && untrack(() => inferModuleArgs($selectedIdStore))
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
<svelte:window onkeydown={onKeyDown} />
|
||||
|
||||
<TestJobLoader
|
||||
on:done={loadPastTests}
|
||||
@@ -686,10 +696,9 @@
|
||||
</div>
|
||||
<Splitpanes horizontal class="h-full max-h-screen grow">
|
||||
<Pane size={67}>
|
||||
{#if $flowStore?.value?.modules}
|
||||
{#if flowStore.val?.value?.modules}
|
||||
<div id="flow-editor"></div>
|
||||
<FlowModuleSchemaMap
|
||||
bind:modules={$flowStore.value.modules}
|
||||
disableAi
|
||||
disableTutorials
|
||||
smallErrorHandler={true}
|
||||
@@ -709,7 +718,7 @@
|
||||
$testStepStore['preprocessor'] = ev.detail.args ?? {}
|
||||
$selectedIdStore = 'preprocessor'
|
||||
} else {
|
||||
$previewArgsStore = ev.detail.args ?? {}
|
||||
previewArgsStore.val = ev.detail.args ?? {}
|
||||
flowPreviewButtons?.openPreview()
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
diffViewer.closeDrawer()
|
||||
}
|
||||
|
||||
|
||||
function prepareDiff(data: Value) {
|
||||
const metadata = structuredClone(cleanValueProperties(replaceFalseWithUndefined(data)))
|
||||
const content = metadata['content']
|
||||
@@ -126,8 +125,8 @@
|
||||
data_[dataType]?.content !== data_.current.content
|
||||
? 'content'
|
||||
: data_[dataType]?.metadata !== data_.current.metadata
|
||||
? 'metadata'
|
||||
: undefined
|
||||
? 'metadata'
|
||||
: undefined
|
||||
}
|
||||
|
||||
$: updateContentType(data, diffType)
|
||||
@@ -256,7 +255,7 @@
|
||||
<Loader2 class="animate-spin" />
|
||||
{/if}
|
||||
</div>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
{#if data?.button}
|
||||
<Button
|
||||
color="light"
|
||||
@@ -268,6 +267,6 @@
|
||||
}}>{data.button.text}</Button
|
||||
>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { json } from 'svelte-highlight/languages'
|
||||
import { copyToClipboard, roughSizeOfObject } from '$lib/utils'
|
||||
@@ -36,30 +38,12 @@
|
||||
import type { DisplayResultUi } from './custom_ui'
|
||||
import { getContext, hasContext, createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { toJsonStr } from '$lib/utils'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
|
||||
export let result: any
|
||||
export let requireHtmlApproval = false
|
||||
export let filename: string | undefined = undefined
|
||||
export let disableExpand = false
|
||||
export let jobId: string | undefined = undefined
|
||||
export let workspaceId: string | undefined = undefined
|
||||
export let hideAsJson: boolean = false
|
||||
export let noControls: boolean = false
|
||||
export let drawerOpen = false
|
||||
export let nodeId: string | undefined = undefined
|
||||
export let language: string | undefined = undefined
|
||||
export let appPath: string | undefined = undefined
|
||||
export let customUi: DisplayResultUi | undefined = undefined
|
||||
export let isTest: boolean = true
|
||||
export let externalToolbarAvailable: boolean = false
|
||||
|
||||
const IMG_MAX_SIZE = 10000000
|
||||
const TABLE_MAX_SIZE = 5000000
|
||||
const DISPLAY_MAX_SIZE = 100000
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
|
||||
|
||||
let resultKind:
|
||||
| 'json'
|
||||
@@ -82,14 +66,53 @@
|
||||
| 'map'
|
||||
| 'nondisplayable'
|
||||
| 'pdf'
|
||||
| undefined
|
||||
| undefined = $state()
|
||||
|
||||
let hasBigInt = false
|
||||
$: resultKind = inferResultKind(result)
|
||||
let hasBigInt = $state(false)
|
||||
|
||||
export let forceJson = false
|
||||
let enableHtml = false
|
||||
let s3FileDisplayRawMode = false
|
||||
interface Props {
|
||||
result: any
|
||||
requireHtmlApproval?: boolean
|
||||
filename?: string | undefined
|
||||
disableExpand?: boolean
|
||||
jobId?: string | undefined
|
||||
workspaceId?: string | undefined
|
||||
hideAsJson?: boolean
|
||||
noControls?: boolean
|
||||
drawerOpen?: boolean
|
||||
nodeId?: string | undefined
|
||||
language?: string | undefined
|
||||
appPath?: string | undefined
|
||||
customUi?: DisplayResultUi | undefined
|
||||
isTest?: boolean
|
||||
externalToolbarAvailable?: boolean
|
||||
forceJson?: boolean
|
||||
copilot_fix?: import('svelte').Snippet
|
||||
children?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
result,
|
||||
requireHtmlApproval = false,
|
||||
filename = undefined,
|
||||
disableExpand = false,
|
||||
jobId = undefined,
|
||||
workspaceId = undefined,
|
||||
hideAsJson = false,
|
||||
noControls = false,
|
||||
drawerOpen = $bindable(false),
|
||||
nodeId = undefined,
|
||||
language = undefined,
|
||||
appPath = undefined,
|
||||
customUi = undefined,
|
||||
isTest = true,
|
||||
externalToolbarAvailable = false,
|
||||
forceJson = $bindable(false),
|
||||
copilot_fix,
|
||||
children
|
||||
}: Props = $props()
|
||||
let enableHtml = $state(false)
|
||||
let s3FileDisplayRawMode = $state(false)
|
||||
|
||||
function isTableRow(result: any): boolean {
|
||||
return Array.isArray(result) && result.every((x) => Array.isArray(x))
|
||||
@@ -125,14 +148,14 @@
|
||||
)
|
||||
}
|
||||
|
||||
let largeObject: boolean | undefined = undefined
|
||||
let largeObject: boolean | undefined = $state(undefined)
|
||||
|
||||
function checkIfS3(result: any, keys: string[]) {
|
||||
return keys.includes('s3') && typeof result.s3 === 'string'
|
||||
}
|
||||
|
||||
let is_render_all = false
|
||||
let download_as_csv = false
|
||||
let is_render_all = $state(false)
|
||||
let download_as_csv = $state(false)
|
||||
function inferResultKind(result: any) {
|
||||
try {
|
||||
if (result === 'WINDMILL_TOO_BIG') {
|
||||
@@ -267,8 +290,8 @@
|
||||
return 'json'
|
||||
}
|
||||
|
||||
let jsonViewer: Drawer
|
||||
let s3FileViewer: S3FilePicker
|
||||
let jsonViewer: Drawer | undefined = $state()
|
||||
let s3FileViewer: S3FilePicker | undefined = $state()
|
||||
|
||||
function checkIfHasBigInt(result: any) {
|
||||
if (typeof result === 'number' && Number.isInteger(result) && !Number.isSafeInteger(result)) {
|
||||
@@ -386,20 +409,20 @@
|
||||
}
|
||||
|
||||
export function openDrawer() {
|
||||
jsonViewer.openDrawer()
|
||||
jsonViewer?.openDrawer()
|
||||
}
|
||||
|
||||
let globalForceJson: boolean = false
|
||||
let globalForceJson: boolean = $state(false)
|
||||
|
||||
let seeS3PreviewFileFromList = ''
|
||||
let seeS3PreviewFileFromList = $state('')
|
||||
|
||||
const disableTooltips = hasContext('disableTooltips')
|
||||
? getContext('disableTooltips') === true
|
||||
: false
|
||||
|
||||
let resultHeaderHeight = 0
|
||||
let resultHeaderHeight = $state(0)
|
||||
|
||||
let toolbarLocation: 'self' | 'external' | undefined = undefined
|
||||
let toolbarLocation: 'self' | 'external' | undefined = $state(undefined)
|
||||
function chooseToolbarLocation(shouldShowToolbar: boolean, resultHeaderHeight: number) {
|
||||
if (!shouldShowToolbar) {
|
||||
toolbarLocation = undefined
|
||||
@@ -408,27 +431,32 @@
|
||||
} else {
|
||||
toolbarLocation = 'self'
|
||||
}
|
||||
dispatchIfMounted('toolbar-location-changed', toolbarLocation)
|
||||
dispatch('toolbar-location-changed', toolbarLocation)
|
||||
}
|
||||
|
||||
export function getToolbarLocation() {
|
||||
return toolbarLocation
|
||||
}
|
||||
|
||||
$: chooseToolbarLocation(
|
||||
!is_render_all &&
|
||||
resultKind != 'nondisplayable' &&
|
||||
result != undefined &&
|
||||
length != undefined &&
|
||||
largeObject != undefined &&
|
||||
!disableExpand &&
|
||||
!noControls,
|
||||
resultHeaderHeight
|
||||
)
|
||||
|
||||
onDestroy(() => {
|
||||
dispatch('toolbar-location-changed', undefined)
|
||||
})
|
||||
$effect(() => {
|
||||
;[result]
|
||||
resultKind = inferResultKind(result)
|
||||
})
|
||||
$effect(() => {
|
||||
chooseToolbarLocation(
|
||||
!is_render_all &&
|
||||
resultKind != 'nondisplayable' &&
|
||||
result != undefined &&
|
||||
length != undefined &&
|
||||
largeObject != undefined &&
|
||||
!disableExpand &&
|
||||
!noControls,
|
||||
resultHeaderHeight
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<HighlightTheme />
|
||||
@@ -442,17 +470,18 @@
|
||||
on:selected={(ev) => {
|
||||
globalForceJson = ev.detail === 'json'
|
||||
}}
|
||||
let:item
|
||||
>
|
||||
<ToggleButton class="px-1.5" value="pretty" label="Pretty" icon={Highlighter} {item} />
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton class="px-1.5" value="pretty" label="Pretty" icon={Highlighter} {item} />
|
||||
|
||||
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
|
||||
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col w-full gap-10">
|
||||
{#each result['render_all'] as res}
|
||||
<svelte:self
|
||||
<DisplayResult
|
||||
{noControls}
|
||||
result={res}
|
||||
{requireHtmlApproval}
|
||||
@@ -484,30 +513,31 @@
|
||||
{#if !hideAsJson && !['json', 's3object'].includes(resultKind ?? '') && typeof result === 'object'}<ToggleButtonGroup
|
||||
class="h-6"
|
||||
selected={forceJson ? 'json' : resultKind?.startsWith('table-') ? 'table' : 'pretty'}
|
||||
let:item
|
||||
on:selected={(ev) => {
|
||||
forceJson = ev.detail === 'json'
|
||||
}}
|
||||
>
|
||||
{#if ['table-col', 'table-row', 'table-row-object'].includes(resultKind ?? '')}
|
||||
<ToggleButton class="px-1.5" value="table" label="Table" icon={Table2} {item} />
|
||||
{:else}
|
||||
<ToggleButton
|
||||
class="px-1.5"
|
||||
value="pretty"
|
||||
label="Pretty"
|
||||
icon={Highlighter}
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
|
||||
{#snippet children({ item })}
|
||||
{#if ['table-col', 'table-row', 'table-row-object'].includes(resultKind ?? '')}
|
||||
<ToggleButton class="px-1.5" value="table" label="Table" icon={Table2} {item} />
|
||||
{:else}
|
||||
<ToggleButton
|
||||
class="px-1.5"
|
||||
value="pretty"
|
||||
label="Pretty"
|
||||
icon={Highlighter}
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
<ToggleButton class="px-1.5" value="json" label="JSON" icon={Braces} {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="text-secondary text-xs flex gap-2.5 z-10 items-center">
|
||||
{#if customUi?.disableAiFix !== true}
|
||||
<slot name="copilot-fix" />
|
||||
{@render copilot_fix?.()}
|
||||
{/if}
|
||||
{#if toolbarLocation === 'self'}
|
||||
<!-- TODO : When svelte 5 is released, use a snippet to pass the toolbar to a parent -->
|
||||
@@ -647,7 +677,7 @@
|
||||
>{JSON.stringify(result.error.extra, null, 4)}</pre
|
||||
>
|
||||
{/if}
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{#if !isTest && language === 'bun'}
|
||||
<div class="pt-20"></div>
|
||||
@@ -723,7 +753,7 @@
|
||||
/>
|
||||
<button
|
||||
class="text-secondary underline text-2xs whitespace-nowrap"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
s3FileViewer?.open?.(result)
|
||||
}}
|
||||
><span class="flex items-center gap-1"
|
||||
@@ -738,7 +768,7 @@
|
||||
<FileDownload {workspaceId} s3object={result} {appPath} />
|
||||
<button
|
||||
class="text-secondary underline text-2xs whitespace-nowrap"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
s3FileViewer?.open?.(result)
|
||||
}}
|
||||
><span class="flex items-center gap-1"
|
||||
@@ -813,7 +843,7 @@
|
||||
/>
|
||||
<button
|
||||
class="text-secondary text-2xs whitespace-nowrap"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
s3FileViewer?.open?.(s3object)
|
||||
}}
|
||||
><span class="flex items-center gap-1"
|
||||
@@ -835,7 +865,7 @@
|
||||
/>{:else}
|
||||
<button
|
||||
class="text-secondary whitespace-nowrap flex gap-2 items-center"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
seeS3PreviewFileFromList = s3object?.s3
|
||||
}}
|
||||
>open table preview <ArrowDownFromLine />
|
||||
@@ -855,7 +885,7 @@
|
||||
{:else}
|
||||
<button
|
||||
class="text-secondary whitespace-nowrap flex gap-2 items-center"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
seeS3PreviewFileFromList = s3object?.s3
|
||||
}}
|
||||
>open image preview <ArrowDownFromLine />
|
||||
@@ -956,7 +986,7 @@
|
||||
{#if !disableExpand && !noControls}
|
||||
<Drawer bind:this={jsonViewer} bind:open={drawerOpen} size="900px">
|
||||
<DrawerContent title="Expanded Result" on:close={jsonViewer.closeDrawer}>
|
||||
<svelte:fragment slot="actions">
|
||||
{#snippet actions()}
|
||||
{#if customUi?.disableDownload !== true}
|
||||
<Button
|
||||
download="{filename ?? 'result'}.json"
|
||||
@@ -982,8 +1012,8 @@
|
||||
>
|
||||
Copy to clipboard
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:self
|
||||
{/snippet}
|
||||
<DisplayResult
|
||||
{noControls}
|
||||
{result}
|
||||
{requireHtmlApproval}
|
||||
|
||||
@@ -2,19 +2,27 @@
|
||||
import Popover from './Popover.svelte'
|
||||
import { Badge } from './common'
|
||||
|
||||
export let has_draft: boolean = false
|
||||
export let draft_only: boolean = false
|
||||
interface Props {
|
||||
has_draft?: boolean
|
||||
draft_only?: boolean
|
||||
}
|
||||
|
||||
let { has_draft = false, draft_only = false }: Props = $props()
|
||||
</script>
|
||||
|
||||
{#if has_draft}
|
||||
{#if draft_only}
|
||||
<Popover notClickable>
|
||||
<svelte:fragment slot="text">Never deployed and is only a draft</svelte:fragment>
|
||||
{#snippet text()}
|
||||
Never deployed and is only a draft
|
||||
{/snippet}
|
||||
<Badge small color="indigo" baseClass="border border-indigo-200">Draft only</Badge>
|
||||
</Popover>
|
||||
{:else}
|
||||
<Popover notClickable>
|
||||
<svelte:fragment slot="text">Is deployed and has a draft</svelte:fragment>
|
||||
{#snippet text()}
|
||||
Is deployed and has a draft
|
||||
{/snippet}
|
||||
<Badge small color="indigo" baseClass="border border-indigo-200">+Draft</Badge>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import ResultJobLoader from './ResultJobLoader.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { stateSnapshot } from '$lib/svelte5Utils.svelte'
|
||||
|
||||
export let value: any = undefined
|
||||
export let helperScript:
|
||||
@@ -87,7 +88,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
let lastArgs = structuredClone({ ...args, [name]: undefined })
|
||||
let lastArgs = structuredClone({ ...stateSnapshot(args), [name]: undefined })
|
||||
$: (entrypoint || helperScript) && refreshOptions()
|
||||
|
||||
$: args && changeArgs()
|
||||
@@ -96,7 +97,7 @@
|
||||
function changeArgs() {
|
||||
timeout && clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
let argsWithoutSelf = { ...args, [name]: undefined }
|
||||
let argsWithoutSelf = { ...stateSnapshot(args), [name]: undefined }
|
||||
if (deepEqual(argsWithoutSelf, lastArgs)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createBubbler, preventDefault, stopPropagation } from 'svelte/legacy'
|
||||
|
||||
const bubble = createBubbler()
|
||||
import type { Schema } from '$lib/common'
|
||||
import { VariableService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
@@ -11,7 +14,7 @@
|
||||
import FlowPropertyEditor from './schema/FlowPropertyEditor.svelte'
|
||||
import PropertyEditor from './schema/PropertyEditor.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import { createEventDispatcher, tick, untrack } from 'svelte'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import Label from './Label.svelte'
|
||||
@@ -22,72 +25,110 @@
|
||||
import SchemaFormDnd from './schema/SchemaFormDND.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { tweened } from 'svelte/motion'
|
||||
import type { SchemaDiff } from '$lib/components/schema/schemaUtils'
|
||||
import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte'
|
||||
import type { EditableSchemaFormUi } from '$lib/components/custom_ui'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
|
||||
export let schema: Schema | any
|
||||
export let hiddenArgs: string[] = []
|
||||
export let args: Record<string, any> = {}
|
||||
export let shouldHideNoInputs: boolean = false
|
||||
export let noVariablePicker = false
|
||||
export let flexWrap = false
|
||||
export let uiOnly: boolean = false
|
||||
export let isFlowInput: boolean = false
|
||||
export let noPreview: boolean = false
|
||||
export let jsonEnabled: boolean = true
|
||||
export let isAppInput: boolean = false
|
||||
export let displayWebhookWarning: boolean = false
|
||||
export let onlyMaskPassword: boolean = false
|
||||
export let dndType: string | undefined = undefined
|
||||
export let editTab:
|
||||
| 'inputEditor'
|
||||
| 'history'
|
||||
| 'savedInputs'
|
||||
| 'json'
|
||||
| 'captures'
|
||||
| 'firstStepInputs'
|
||||
| undefined
|
||||
export let previewSchema: Record<string, any> | undefined = undefined
|
||||
export let editPanelInitialSize: number | undefined = undefined
|
||||
export let editPanelSize = 0
|
||||
export let diff: Record<string, SchemaDiff> = {}
|
||||
export let disableDnd: boolean = false
|
||||
export let shouldDispatchChanges: boolean = false
|
||||
export let isValid: boolean = true
|
||||
export let customUi: EditableSchemaFormUi | undefined = undefined
|
||||
|
||||
// export let openEditTab: () => void = () => {}
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
|
||||
|
||||
let clazz: string = ''
|
||||
export { clazz as class }
|
||||
|
||||
$: if (args == undefined || typeof args !== 'object') {
|
||||
args = {}
|
||||
interface Props {
|
||||
schema: Schema | any
|
||||
hiddenArgs?: string[]
|
||||
args?: Record<string, any>
|
||||
shouldHideNoInputs?: boolean
|
||||
noVariablePicker?: boolean
|
||||
flexWrap?: boolean
|
||||
uiOnly?: boolean
|
||||
isFlowInput?: boolean
|
||||
noPreview?: boolean
|
||||
jsonEnabled?: boolean
|
||||
isAppInput?: boolean
|
||||
displayWebhookWarning?: boolean
|
||||
onlyMaskPassword?: boolean
|
||||
dndType?: string | undefined
|
||||
editTab:
|
||||
| 'inputEditor'
|
||||
| 'history'
|
||||
| 'savedInputs'
|
||||
| 'json'
|
||||
| 'captures'
|
||||
| 'firstStepInputs'
|
||||
| undefined
|
||||
previewSchema?: Record<string, any> | undefined
|
||||
editPanelInitialSize?: number | undefined
|
||||
editPanelSize?: number
|
||||
diff?: Record<string, SchemaDiff>
|
||||
disableDnd?: boolean
|
||||
shouldDispatchChanges?: boolean
|
||||
isValid?: boolean
|
||||
customUi?: EditableSchemaFormUi | undefined
|
||||
pannelExtraButtonWidth?: number
|
||||
class?: string
|
||||
openEditTab?: import('svelte').Snippet
|
||||
addProperty?: import('svelte').Snippet
|
||||
runButton?: import('svelte').Snippet
|
||||
extraTab?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
schema = $bindable(),
|
||||
hiddenArgs = [],
|
||||
args = $bindable(undefined),
|
||||
shouldHideNoInputs = false,
|
||||
noVariablePicker = false,
|
||||
flexWrap = false,
|
||||
uiOnly = false,
|
||||
isFlowInput = false,
|
||||
noPreview = false,
|
||||
jsonEnabled = true,
|
||||
isAppInput = false,
|
||||
displayWebhookWarning = false,
|
||||
onlyMaskPassword = false,
|
||||
dndType = undefined,
|
||||
editTab,
|
||||
previewSchema = undefined,
|
||||
editPanelInitialSize = undefined,
|
||||
editPanelSize = $bindable(0),
|
||||
diff = {},
|
||||
disableDnd = false,
|
||||
shouldDispatchChanges = false,
|
||||
isValid = $bindable(true),
|
||||
customUi = undefined,
|
||||
pannelExtraButtonWidth = 0,
|
||||
class: clazz = '',
|
||||
openEditTab,
|
||||
addProperty,
|
||||
runButton,
|
||||
extraTab
|
||||
}: Props = $props()
|
||||
|
||||
$effect.pre(() => {
|
||||
if (args == undefined) {
|
||||
args = {}
|
||||
}
|
||||
})
|
||||
|
||||
export function setDefaults() {
|
||||
const nargs = {}
|
||||
|
||||
Object.keys(schema?.properties ?? {}).forEach((key) => {
|
||||
if (schema?.properties[key].default != undefined && args[key] == undefined) {
|
||||
if (schema?.properties[key].default != undefined && args?.[key] == undefined) {
|
||||
let value = schema?.properties[key].default
|
||||
nargs[key] = value === 'object' ? structuredClone(value) : value
|
||||
nargs[key] = value === 'object' ? structuredClone($state.snapshot(value)) : value
|
||||
}
|
||||
})
|
||||
args = nargs
|
||||
}
|
||||
|
||||
let pickForField: string | undefined
|
||||
let itemPicker: ItemPicker | undefined = undefined
|
||||
let variableEditor: VariableEditor | undefined = undefined
|
||||
let itemPicker: ItemPicker | undefined = $state(undefined)
|
||||
let variableEditor: VariableEditor | undefined = $state(undefined)
|
||||
|
||||
let keys: string[] = Array.isArray(schema?.order)
|
||||
? [...schema.order]
|
||||
: (Object.keys(schema?.properties ?? {}) ?? Object.keys(schema?.properties ?? {}))
|
||||
|
||||
$: schema && onSchemaChange()
|
||||
let keys: string[] = $state(
|
||||
Array.isArray(schema?.order)
|
||||
? [...schema.order]
|
||||
: (Object.keys(schema?.properties ?? {}) ?? Object.keys(schema?.properties ?? {}))
|
||||
)
|
||||
|
||||
function alignOrderWithProperties(schema: {
|
||||
properties: Record<string, any>
|
||||
@@ -134,15 +175,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
let opened: string | undefined = keys[0]
|
||||
let selected = ''
|
||||
let opened: string | undefined = $state(untrack(() => keys[0]))
|
||||
|
||||
function computeSelected(property: any) {
|
||||
if (!opened) return ''
|
||||
return property.type !== 'object'
|
||||
? property.type
|
||||
: property.format === 'resource-s3_object'
|
||||
? 'S3'
|
||||
: property.oneOf && property.oneOf.length >= 2
|
||||
? 'oneOf'
|
||||
: 'object'
|
||||
}
|
||||
|
||||
export function openField(key: string) {
|
||||
opened = key
|
||||
}
|
||||
|
||||
export function deleteField(key: string) {
|
||||
delete args[key]
|
||||
delete args?.[key]
|
||||
delete schema.properties[key]
|
||||
if (schema.required?.includes(key)) {
|
||||
schema.required = schema.required?.filter((x) => x !== key)
|
||||
@@ -152,20 +203,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: opened && updateSelected(schema.properties[opened])
|
||||
function updateSelected(property: any) {
|
||||
if (!property) return
|
||||
selected = opened
|
||||
? property.type !== 'object'
|
||||
? property.type
|
||||
: property.format === 'resource-s3_object'
|
||||
? 'S3'
|
||||
: property.oneOf && property.oneOf.length >= 2
|
||||
? 'oneOf'
|
||||
: 'object'
|
||||
: ''
|
||||
}
|
||||
|
||||
function renameProperty(oldName: string, key: string) {
|
||||
const el = document.getElementById(key) as HTMLInputElement
|
||||
const newName = el.value
|
||||
@@ -179,8 +216,10 @@
|
||||
// clear the input
|
||||
el.value = oldName
|
||||
} else {
|
||||
args[newName] = args[oldName]
|
||||
delete args[oldName]
|
||||
if (args) {
|
||||
args[newName] = args[oldName]
|
||||
delete args[oldName]
|
||||
}
|
||||
|
||||
schema.properties[newName] = schema.properties[oldName]
|
||||
delete schema.properties[oldName]
|
||||
@@ -199,26 +238,34 @@
|
||||
|
||||
opened = newName
|
||||
|
||||
schema = schema
|
||||
schema = $state.snapshot(schema)
|
||||
dispatch('change', schema)
|
||||
sendUserToast('Argument renamed successfully')
|
||||
sendUserToast('Argument renamed')
|
||||
}
|
||||
}
|
||||
|
||||
let jsonView: boolean = customUi?.jsonOnly == true
|
||||
let schemaString: string = JSON.stringify(schema, null, '\t')
|
||||
let error: string | undefined = undefined
|
||||
let editor: SimpleEditor | undefined = undefined
|
||||
let jsonView: boolean = $state(customUi?.jsonOnly == true)
|
||||
let schemaString: string = $state(JSON.stringify(schema, null, '\t'))
|
||||
let error: string | undefined = $state(undefined)
|
||||
let editor: SimpleEditor | undefined = $state(undefined)
|
||||
|
||||
export function updateJson() {
|
||||
schemaString = JSON.stringify(schema, null, '\t')
|
||||
editor?.setCode(schemaString)
|
||||
}
|
||||
|
||||
const editTabDefaultSize = noPreview ? 100 : 50
|
||||
editPanelSize = editTab ? (editPanelInitialSize ?? editTabDefaultSize) : 0
|
||||
let inputPanelSize = 100 - editPanelSize
|
||||
let inputPanelSize = $state(100 - editPanelSize)
|
||||
let editPanelSizeSmooth = tweened(editPanelSize, {
|
||||
duration: 150
|
||||
})
|
||||
let inputPanelSizeSmooth = tweened(inputPanelSize, { duration: 150 })
|
||||
let inputPanelSizeSmooth = tweened(
|
||||
untrack(() => inputPanelSize),
|
||||
{ duration: 150 }
|
||||
)
|
||||
|
||||
function openEditTab() {
|
||||
function openEditTabFn() {
|
||||
if (editPanelSize > 0) return
|
||||
editPanelSizeSmooth.set(editTabDefaultSize)
|
||||
inputPanelSizeSmooth.set(100 - editTabDefaultSize)
|
||||
@@ -232,19 +279,24 @@
|
||||
function updatePanelSizes(editSize: number, inputSize: number) {
|
||||
editPanelSize = editSize
|
||||
inputPanelSize = inputSize
|
||||
dispatchIfMounted('editPanelSizeChanged', editSize)
|
||||
dispatch('editPanelSizeChanged', editSize)
|
||||
}
|
||||
$: updatePanelSizes($editPanelSizeSmooth, $inputPanelSizeSmooth)
|
||||
|
||||
$: !!editTab ? openEditTab() : closeEditTab()
|
||||
|
||||
let panelButtonWidth: number = 0
|
||||
export let pannelExtraButtonWidth: number = 0
|
||||
|
||||
export function updateJson() {
|
||||
schemaString = JSON.stringify(schema, null, '\t')
|
||||
editor?.setCode(schemaString)
|
||||
}
|
||||
let panelButtonWidth: number = $state(0)
|
||||
$effect(() => {
|
||||
if (args == undefined || typeof args !== 'object') {
|
||||
args = {}
|
||||
}
|
||||
})
|
||||
$effect(() => {
|
||||
schema && untrack(() => onSchemaChange())
|
||||
})
|
||||
$effect(() => {
|
||||
updatePanelSizes($editPanelSizeSmooth, $inputPanelSizeSmooth)
|
||||
})
|
||||
$effect(() => {
|
||||
!!editTab ? openEditTabFn() : closeEditTab()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full">
|
||||
@@ -254,37 +306,41 @@
|
||||
style="right: calc({editPanelSize}% - 1px - {pannelExtraButtonWidth}px); top: 0px;"
|
||||
bind:clientWidth={panelButtonWidth}
|
||||
>
|
||||
<slot name="openEditTab" />
|
||||
{@render openEditTab?.()}
|
||||
</div>
|
||||
</div>
|
||||
<Splitpanes class="splitter-hidden w-full">
|
||||
{#if !noPreview}
|
||||
<Pane bind:size={inputPanelSize} minSize={20}>
|
||||
<div
|
||||
class="h-full flex flex-col gap-2 {$$slots.openEditTab && editPanelSize > 0
|
||||
class="h-full flex flex-col gap-2 {openEditTab && editPanelSize > 0
|
||||
? 'pr-[38px]'
|
||||
: 'pr-2'}"
|
||||
>
|
||||
{#if $$slots.addProperty}
|
||||
{#if addProperty}
|
||||
<div class="w-full justify-left pr-2 grow-0">
|
||||
<div
|
||||
style={editPanelSize > 0
|
||||
? `width: 100%;`
|
||||
: `width: calc(100% - ${panelButtonWidth - pannelExtraButtonWidth}px);`}
|
||||
>
|
||||
<slot name="addProperty" />
|
||||
{@render addProperty?.()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="min-h-0 overflow-y-auto grow rounded-md {$$slots.runButton
|
||||
? 'flex flex-col gap-2'
|
||||
: ''}"
|
||||
class="min-h-0 overflow-y-auto grow rounded-md {runButton ? 'flex flex-col gap-2' : ''}"
|
||||
>
|
||||
<SchemaFormDnd
|
||||
nestedClasses={'flex flex-col gap-1'}
|
||||
schema={previewSchema ? previewSchema : schema}
|
||||
bind:schema={
|
||||
() => (previewSchema ? previewSchema : schema),
|
||||
(newSchema) => {
|
||||
schema = newSchema
|
||||
tick().then(() => dispatch('change', schema))
|
||||
}
|
||||
}
|
||||
{dndType}
|
||||
{disableDnd}
|
||||
{onlyMaskPassword}
|
||||
@@ -293,12 +349,10 @@
|
||||
opened = e.detail
|
||||
}}
|
||||
on:reorder={(e) => {
|
||||
schema.order = e.detail
|
||||
schema = schema
|
||||
tick().then(() => dispatch('change', schema))
|
||||
}}
|
||||
on:change={() => {
|
||||
schema = schema
|
||||
schema = {
|
||||
...schema,
|
||||
order: e.detail
|
||||
}
|
||||
tick().then(() => dispatch('change', schema))
|
||||
}}
|
||||
prettifyHeader={isAppInput}
|
||||
@@ -314,7 +368,7 @@
|
||||
noVariablePicker={noVariablePicker || customUi?.disableVariablePicker === true}
|
||||
/>
|
||||
|
||||
<slot name="runButton" />
|
||||
{@render runButton?.()}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
@@ -327,7 +381,7 @@
|
||||
class={twMerge('border rounded-md', panelButtonWidth > 0 ? 'rounded-tl-none' : '')}
|
||||
>
|
||||
{#if editTab !== 'inputEditor'}
|
||||
<slot name="extraTab" />
|
||||
{@render extraTab?.()}
|
||||
{:else}
|
||||
<!-- WIP -->
|
||||
{#if jsonEnabled && customUi?.jsonOnly != true}
|
||||
@@ -357,14 +411,14 @@
|
||||
{#if keys.length > 0}
|
||||
{#each keys as argName, i (argName)}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full flex bg-gray-50 dark:bg-gray-800 px-4 py-1 justify-between items-center hover:bg-gray-100 cursor-pointer',
|
||||
opened === argName ? 'bg-gray-100 hover:bg-gray-200' : ''
|
||||
)}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (opened === argName) {
|
||||
opened = undefined
|
||||
} else {
|
||||
@@ -375,9 +429,9 @@
|
||||
<div class="flex flex-row gap-2">
|
||||
{argName}
|
||||
{#if !uiOnly}
|
||||
<div on:click|stopPropagation|preventDefault>
|
||||
<div onclick={stopPropagation(preventDefault(bubble('click')))}>
|
||||
<Popover placement="bottom-end" containerClasses="p-4" closeButton>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
@@ -385,8 +439,8 @@
|
||||
startIcon={{ icon: Pen }}
|
||||
iconOnly
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content" let:close>
|
||||
{/snippet}
|
||||
{#snippet content({ close })}
|
||||
<Label label="Name" class="p-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<input
|
||||
@@ -394,7 +448,7 @@
|
||||
class="w-full !bg-surface"
|
||||
value={argName}
|
||||
id={argName + i}
|
||||
on:keydown={(event) => {
|
||||
onkeydown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
renameProperty(argName, argName + i)
|
||||
close()
|
||||
@@ -414,7 +468,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
</Label>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -429,7 +483,7 @@
|
||||
class="rounded-full p-1 text-gray-500 bg-white
|
||||
duration-200 hover:bg-gray-600 focus:bg-gray-600 hover:text-white dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
dispatch('delete', argName)
|
||||
}}
|
||||
>
|
||||
@@ -461,103 +515,103 @@
|
||||
{isFlowInput}
|
||||
{isAppInput}
|
||||
on:change={() => {
|
||||
schema = schema
|
||||
schema = $state.snapshot(schema)
|
||||
dispatch('change', schema)
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="typeeditor">
|
||||
{#snippet typeeditor()}
|
||||
{#if isFlowInput || isAppInput}
|
||||
<Label label="Type">
|
||||
<ToggleButtonGroup
|
||||
tabListClass="flex-wrap"
|
||||
class="h-auto"
|
||||
let:item
|
||||
bind:selected
|
||||
bind:selected={
|
||||
() => computeSelected(schema.properties[opened ?? '']),
|
||||
(v) => {
|
||||
const isS3 = v == 'S3'
|
||||
const isOneOf = v == 'oneOf'
|
||||
|
||||
const emptyProperty = {
|
||||
contentEncoding: undefined,
|
||||
enum_: undefined,
|
||||
pattern: undefined,
|
||||
default: undefined,
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
currency: undefined,
|
||||
currencyLocale: undefined,
|
||||
multiselect: undefined,
|
||||
password: undefined,
|
||||
dateFormat: undefined,
|
||||
...(v == 'array' ? { items: { type: 'string' } } : {}),
|
||||
showExpr: undefined,
|
||||
nullable: undefined,
|
||||
required: undefined
|
||||
}
|
||||
|
||||
if (isS3) {
|
||||
schema.properties[argName] = {
|
||||
...emptyProperty,
|
||||
type: 'object',
|
||||
format: 'resource-s3_object'
|
||||
}
|
||||
} else if (isOneOf) {
|
||||
schema.properties[argName] = {
|
||||
...emptyProperty,
|
||||
type: 'object',
|
||||
oneOf: [
|
||||
{
|
||||
title: 'Option 1',
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
enum: ['Option 1']
|
||||
},
|
||||
property_1: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Option 2',
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
enum: ['Option 2']
|
||||
},
|
||||
property_2: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else {
|
||||
schema.properties[argName] = {
|
||||
...emptyProperty,
|
||||
format: undefined,
|
||||
type: v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
on:selected={(e) => {
|
||||
const isS3 = e.detail == 'S3'
|
||||
const isOneOf = e.detail == 'oneOf'
|
||||
|
||||
const emptyProperty = {
|
||||
contentEncoding: undefined,
|
||||
enum_: undefined,
|
||||
pattern: undefined,
|
||||
default: undefined,
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
currency: undefined,
|
||||
currencyLocale: undefined,
|
||||
multiselect: undefined,
|
||||
password: undefined,
|
||||
dateFormat: undefined,
|
||||
...(e.detail == 'array'
|
||||
? { items: { type: 'string' } }
|
||||
: {}),
|
||||
showExpr: undefined,
|
||||
nullable: undefined,
|
||||
required: undefined
|
||||
}
|
||||
|
||||
if (isS3) {
|
||||
schema.properties[argName] = {
|
||||
...emptyProperty,
|
||||
type: 'object',
|
||||
format: 'resource-s3_object'
|
||||
}
|
||||
} else if (isOneOf) {
|
||||
schema.properties[argName] = {
|
||||
...emptyProperty,
|
||||
type: 'object',
|
||||
oneOf: [
|
||||
{
|
||||
title: 'Option 1',
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
enum: ['Option 1']
|
||||
},
|
||||
property_1: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Option 2',
|
||||
type: 'object',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
enum: ['Option 2']
|
||||
},
|
||||
property_2: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else {
|
||||
schema.properties[argName] = {
|
||||
...emptyProperty,
|
||||
format: undefined,
|
||||
type: e.detail
|
||||
}
|
||||
}
|
||||
// No better solution than this, needs future rework
|
||||
setTimeout(() => {
|
||||
schema = schema
|
||||
dispatch('change', schema)
|
||||
}, 100)
|
||||
schema = schema
|
||||
dispatch('change', schema)
|
||||
dispatch('schemaChange')
|
||||
}}
|
||||
>
|
||||
{#each [['String', 'string'], ['Number', 'number'], ['Integer', 'integer'], ['Object', 'object'], ['OneOf', 'oneOf'], ['Array', 'array'], ['Boolean', 'boolean'], ['S3 Object', 'S3']] as x}
|
||||
<ToggleButton value={x[1]} label={x[0]} {item} />
|
||||
{/each}
|
||||
{#snippet children({ item })}
|
||||
{#each [['String', 'string'], ['Number', 'number'], ['Integer', 'integer'], ['Object', 'object'], ['OneOf', 'oneOf'], ['Array', 'array'], ['Boolean', 'boolean'], ['S3 Object', 'S3']] as x}
|
||||
<ToggleButton value={x[1]} label={x[0]} {item} />
|
||||
{/each}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</Label>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
|
||||
{#if isFlowInput || isAppInput}
|
||||
<FlowPropertyEditor
|
||||
@@ -594,7 +648,7 @@
|
||||
dispatch('change', schema)
|
||||
}}
|
||||
on:schemaChange={(e) => {
|
||||
schema = schema
|
||||
schema = $state.snapshot(schema)
|
||||
dispatch('change', schema)
|
||||
dispatch('schemaChange')
|
||||
}}
|
||||
@@ -651,7 +705,9 @@
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, _) => {
|
||||
if (pickForField) {
|
||||
args[pickForField] = '$var:' + path
|
||||
if (args) {
|
||||
args[pickForField] = '$var:' + path
|
||||
}
|
||||
}
|
||||
}}
|
||||
itemName="Variable"
|
||||
@@ -664,17 +720,19 @@
|
||||
...x
|
||||
}))}
|
||||
>
|
||||
<div slot="submission">
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
on:click={() => variableEditor?.initNew?.()}
|
||||
>
|
||||
New variable
|
||||
</Button>
|
||||
</div>
|
||||
{#snippet submission()}
|
||||
<div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
on:click={() => variableEditor?.initNew?.()}
|
||||
>
|
||||
New variable
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ItemPicker>
|
||||
|
||||
<VariableEditor bind:this={variableEditor} />
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
let websocketInterval: NodeJS.Timeout | undefined
|
||||
let lastWsAttempt: Date = new Date()
|
||||
let nbWsAttempt = 0
|
||||
let disposeMethod: () => void | undefined
|
||||
let disposeMethod: (() => void) | undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
let graphqlService: MonacoGraphQLAPI | undefined = undefined
|
||||
|
||||
@@ -1267,15 +1267,20 @@
|
||||
|
||||
onFileChanges()
|
||||
|
||||
editor = meditor.create(divEl as HTMLDivElement, {
|
||||
...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
model,
|
||||
fontSize: !small ? 14 : 12,
|
||||
lineNumbersMinChars,
|
||||
// overflowWidgetsDomNode: widgets,
|
||||
tabSize: lang == 'python' ? 4 : 2,
|
||||
folding
|
||||
})
|
||||
try {
|
||||
editor = meditor.create(divEl as HTMLDivElement, {
|
||||
...editorConfig(code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
model,
|
||||
fontSize: !small ? 14 : 12,
|
||||
lineNumbersMinChars,
|
||||
// overflowWidgetsDomNode: widgets,
|
||||
tabSize: lang == 'python' ? 4 : 2,
|
||||
folding
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error loading monaco:', e)
|
||||
return
|
||||
}
|
||||
|
||||
keepModelAroundToAvoidDisposalOfWorkers()
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script context="module" lang="ts">
|
||||
<script module lang="ts">
|
||||
export const EDITOR_BAR_WIDTH_THRESHOLD = 1044
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import { ResourceService, VariableService, type Script } from '$lib/gen'
|
||||
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
@@ -19,7 +21,7 @@
|
||||
import ToggleHubWorkspace from './ToggleHubWorkspace.svelte'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
import { SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS } from '$lib/consts'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { getScriptByPath, scriptLangToEditorLang } from '$lib/scripts'
|
||||
import Toggle from './Toggle.svelte'
|
||||
@@ -47,110 +49,145 @@
|
||||
import type { EditorBarUi } from './custom_ui'
|
||||
import EditorSettings from './EditorSettings.svelte'
|
||||
|
||||
export let lang: SupportedLanguage | 'bunnative' | undefined
|
||||
export let editor: Editor | undefined
|
||||
export let websocketAlive: {
|
||||
pyright: boolean
|
||||
ruff: boolean
|
||||
deno: boolean
|
||||
go: boolean
|
||||
shellcheck: boolean
|
||||
interface Props {
|
||||
lang: SupportedLanguage | 'bunnative' | undefined
|
||||
editor: Editor | undefined
|
||||
websocketAlive: {
|
||||
pyright: boolean
|
||||
ruff: boolean
|
||||
deno: boolean
|
||||
go: boolean
|
||||
shellcheck: boolean
|
||||
}
|
||||
iconOnly?: boolean
|
||||
validCode?: boolean
|
||||
kind?: 'script' | 'trigger' | 'approval'
|
||||
template?: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative'
|
||||
collabMode?: boolean
|
||||
collabLive?: boolean
|
||||
collabUsers?: { name: string }[]
|
||||
scriptPath?: string | undefined
|
||||
diffEditor?: DiffEditor | undefined
|
||||
args: Record<string, any>
|
||||
noHistory?: boolean
|
||||
saveToWorkspace?: boolean
|
||||
customUi?: EditorBarUi
|
||||
lastDeployedCode?: string | undefined
|
||||
diffMode?: boolean
|
||||
showHistoryDrawer?: boolean
|
||||
right?: import('svelte').Snippet
|
||||
}
|
||||
export let iconOnly: boolean = false
|
||||
export let validCode: boolean = true
|
||||
export let kind: 'script' | 'trigger' | 'approval' = 'script'
|
||||
export let template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' =
|
||||
'script'
|
||||
export let collabMode = false
|
||||
export let collabLive = false
|
||||
export let collabUsers: { name: string }[] = []
|
||||
export let scriptPath: string | undefined = undefined
|
||||
export let diffEditor: DiffEditor | undefined = undefined
|
||||
export let args: Record<string, any>
|
||||
export let noHistory = false
|
||||
export let saveToWorkspace = false
|
||||
export let customUi: EditorBarUi = {}
|
||||
export let lastDeployedCode: string | undefined = undefined
|
||||
export let diffMode: boolean = false
|
||||
export let showHistoryDrawer: boolean = false
|
||||
|
||||
let contextualVariablePicker: ItemPicker
|
||||
let variablePicker: ItemPicker
|
||||
let resourcePicker: ItemPicker
|
||||
let resourceTypePicker: ItemPicker
|
||||
let variableEditor: VariableEditor
|
||||
let resourceEditor: ResourceEditorDrawer
|
||||
let showContextVarPicker = false
|
||||
let showVarPicker = false
|
||||
let showResourcePicker = false
|
||||
let showResourceTypePicker = false
|
||||
let {
|
||||
lang,
|
||||
editor,
|
||||
websocketAlive,
|
||||
iconOnly = false,
|
||||
validCode = true,
|
||||
kind = 'script',
|
||||
template = 'script',
|
||||
collabMode = false,
|
||||
collabLive = false,
|
||||
collabUsers = [],
|
||||
scriptPath = undefined,
|
||||
diffEditor = undefined,
|
||||
args,
|
||||
noHistory = false,
|
||||
saveToWorkspace = false,
|
||||
customUi = {},
|
||||
lastDeployedCode = undefined,
|
||||
diffMode = false,
|
||||
showHistoryDrawer = $bindable(false),
|
||||
right
|
||||
}: Props = $props()
|
||||
|
||||
$: showContextVarPicker = [
|
||||
'python3',
|
||||
'bash',
|
||||
'powershell',
|
||||
'go',
|
||||
'deno',
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust',
|
||||
'csharp',
|
||||
'nu',
|
||||
'java'
|
||||
// for related places search: ADD_NEW_LANG
|
||||
].includes(lang ?? '')
|
||||
$: showVarPicker = [
|
||||
'python3',
|
||||
'bash',
|
||||
'powershell',
|
||||
'go',
|
||||
'deno',
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust',
|
||||
'csharp',
|
||||
'nu',
|
||||
'java'
|
||||
// for related places search: ADD_NEW_LANG
|
||||
].includes(lang ?? '')
|
||||
$: showResourcePicker = [
|
||||
'python3',
|
||||
'bash',
|
||||
'powershell',
|
||||
'go',
|
||||
'deno',
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust',
|
||||
'csharp',
|
||||
'nu',
|
||||
'java'
|
||||
// for related places search: ADD_NEW_LANG
|
||||
].includes(lang ?? '')
|
||||
$: showResourceTypePicker =
|
||||
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
|
||||
lang === 'python3' ||
|
||||
lang === 'php'
|
||||
let contextualVariablePicker: ItemPicker | undefined = $state()
|
||||
let variablePicker: ItemPicker | undefined = $state()
|
||||
let resourcePicker: ItemPicker | undefined = $state()
|
||||
let resourceTypePicker: ItemPicker | undefined = $state()
|
||||
let variableEditor: VariableEditor | undefined = $state()
|
||||
let resourceEditor: ResourceEditorDrawer | undefined = $state()
|
||||
let showContextVarPicker = $state(false)
|
||||
let showVarPicker = $state(false)
|
||||
let showResourcePicker = $state(false)
|
||||
let showResourceTypePicker = $state(false)
|
||||
|
||||
let codeViewer: Drawer
|
||||
let codeObj: { language: SupportedLanguage; content: string } | undefined = undefined
|
||||
run(() => {
|
||||
showContextVarPicker = [
|
||||
'python3',
|
||||
'bash',
|
||||
'powershell',
|
||||
'go',
|
||||
'deno',
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust',
|
||||
'csharp',
|
||||
'nu',
|
||||
'java'
|
||||
// for related places search: ADD_NEW_LANG
|
||||
].includes(lang ?? '')
|
||||
})
|
||||
run(() => {
|
||||
showVarPicker = [
|
||||
'python3',
|
||||
'bash',
|
||||
'powershell',
|
||||
'go',
|
||||
'deno',
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust',
|
||||
'csharp',
|
||||
'nu',
|
||||
'java'
|
||||
// for related places search: ADD_NEW_LANG
|
||||
].includes(lang ?? '')
|
||||
})
|
||||
run(() => {
|
||||
showResourcePicker = [
|
||||
'python3',
|
||||
'bash',
|
||||
'powershell',
|
||||
'go',
|
||||
'deno',
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php',
|
||||
'rust',
|
||||
'csharp',
|
||||
'nu',
|
||||
'java'
|
||||
// for related places search: ADD_NEW_LANG
|
||||
].includes(lang ?? '')
|
||||
})
|
||||
run(() => {
|
||||
showResourceTypePicker =
|
||||
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
|
||||
lang === 'python3' ||
|
||||
lang === 'php'
|
||||
})
|
||||
|
||||
let codeViewer: Drawer | undefined = $state()
|
||||
let codeObj: { language: SupportedLanguage; content: string } | undefined = $state(undefined)
|
||||
|
||||
function addEditorActions() {
|
||||
editor?.addAction('insert-variable', 'Windmill: Insert variable', () => {
|
||||
variablePicker.openDrawer()
|
||||
variablePicker?.openDrawer()
|
||||
})
|
||||
editor?.addAction('insert-resource', 'Windmill: Insert resource', () => {
|
||||
resourcePicker.openDrawer()
|
||||
resourcePicker?.openDrawer()
|
||||
})
|
||||
}
|
||||
|
||||
$: editor && addEditorActions()
|
||||
run(() => {
|
||||
editor && untrack(() => addEditorActions())
|
||||
})
|
||||
|
||||
async function loadVariables() {
|
||||
return await VariableService.listVariable({ workspace: $workspaceStore ?? '' })
|
||||
@@ -162,9 +199,9 @@
|
||||
})
|
||||
}
|
||||
|
||||
let scriptPicker: Drawer
|
||||
let pick_existing: 'hub' | 'workspace' = 'hub'
|
||||
let filter = ''
|
||||
let scriptPicker: Drawer | undefined = $state()
|
||||
let pick_existing: 'hub' | 'workspace' = $state('hub')
|
||||
let filter = $state('')
|
||||
|
||||
async function onScriptPick(e: { detail: { path: string } }) {
|
||||
codeObj = undefined
|
||||
@@ -462,21 +499,23 @@ string ${windmillPathToCamelCaseName(path)} = await client.GetStringAsync(uri);
|
||||
itemName="Variable"
|
||||
extraField="path"
|
||||
loadItems={loadVariables}
|
||||
buttons={{ 'Edit/View': (x) => variableEditor.editVariable(x) }}
|
||||
buttons={{ 'Edit/View': (x) => variableEditor?.editVariable(x) }}
|
||||
>
|
||||
<div slot="submission" class="flex flex-row">
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
on:click={() => {
|
||||
variableEditor.initNew()
|
||||
}}
|
||||
>
|
||||
New variable
|
||||
</Button>
|
||||
</div>
|
||||
{#snippet submission()}
|
||||
<div class="flex flex-row">
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
on:click={() => {
|
||||
variableEditor?.initNew()
|
||||
}}
|
||||
>
|
||||
New variable
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ItemPicker>
|
||||
|
||||
<ItemPicker
|
||||
@@ -548,24 +587,26 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
|
||||
tooltip="Resources represent connections to third party systems. Resources are a good way to define a connection to a frequently used third party system such as a database."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/resources_and_types"
|
||||
itemName="Resource"
|
||||
buttons={{ 'Edit/View': (x) => resourceEditor.initEdit(x) }}
|
||||
buttons={{ 'Edit/View': (x) => resourceEditor?.initEdit(x) }}
|
||||
extraField="description"
|
||||
extraField2="resource_type"
|
||||
loadItems={async () =>
|
||||
await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
|
||||
>
|
||||
<div slot="submission" class="flex flex-row gap-x-1 mr-2">
|
||||
<Button
|
||||
startIcon={{ icon: Plus }}
|
||||
target="_blank"
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
href="{base}/resources?connect_app=undefined"
|
||||
>
|
||||
Add resource
|
||||
</Button>
|
||||
</div>
|
||||
{#snippet submission()}
|
||||
<div class="flex flex-row gap-x-1 mr-2">
|
||||
<Button
|
||||
startIcon={{ icon: Plus }}
|
||||
target="_blank"
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
href="{base}/resources?connect_app=undefined"
|
||||
>
|
||||
Add resource
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ItemPicker>
|
||||
|
||||
{#if showResourceTypePicker}
|
||||
@@ -650,7 +691,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={resourceTypePicker.openDrawer}
|
||||
on:click={() => resourceTypePicker?.openDrawer()}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: Package }}
|
||||
>
|
||||
@@ -724,7 +765,9 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
|
||||
}}
|
||||
/>
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">Toggle diff mode</svelte:fragment>
|
||||
{#snippet text()}
|
||||
Toggle diff mode
|
||||
{/snippet}
|
||||
<DiffIcon class="ml-1 text-tertiary" size={14} />
|
||||
</Popover>
|
||||
</div>
|
||||
@@ -739,14 +782,16 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
|
||||
on:change={() => dispatch('toggleCollabMode')}
|
||||
/>
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">Multiplayer</svelte:fragment>
|
||||
{#snippet text()}
|
||||
Multiplayer
|
||||
{/snippet}
|
||||
<Users class="ml-1 text-tertiary" size={14} />
|
||||
</Popover>
|
||||
{#if collabLive}
|
||||
<button
|
||||
title="Show invite link"
|
||||
class="p-1 rounded hover:bg-gray-400 mx-1 border"
|
||||
on:click={() => dispatch('collabPopup')}><Link size={14} /></button
|
||||
onclick={() => dispatch('collabPopup')}><Link size={14} /></button
|
||||
>
|
||||
<div class="isolate flex -space-x-2 pl-2">
|
||||
{#each collabUsers as user}
|
||||
@@ -773,7 +818,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<slot name="right" />
|
||||
{@render right?.()}
|
||||
{#if scriptPath && !noHistory}
|
||||
<Button
|
||||
btnClasses="!font-medium text-tertiary"
|
||||
|
||||
@@ -329,7 +329,7 @@
|
||||
schema={customHandlerSchema}
|
||||
bind:args={handlerExtraArgs}
|
||||
shouldHideNoInputs
|
||||
class="text-xs"
|
||||
className="text-xs"
|
||||
/>
|
||||
{/await}
|
||||
{#if customHandlerSchema && customHandlerSchema.properties && Object.keys(customHandlerSchema.properties).length === 0}
|
||||
@@ -368,7 +368,7 @@
|
||||
}}
|
||||
bind:args={handlerExtraArgs}
|
||||
shouldHideNoInputs
|
||||
class="text-xs"
|
||||
className="text-xs"
|
||||
/>
|
||||
{/await}
|
||||
{:else if workspaceConnectedToSlack == undefined}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy, getContext } from 'svelte'
|
||||
import { getFirstStepSchema } from '$lib/components/flows/flowStore'
|
||||
import { createEventDispatcher, onDestroy, getContext, untrack } from 'svelte'
|
||||
import { getFirstStepSchema } from '$lib/components/flows/flowStore.svelte'
|
||||
import type { FlowEditorContext } from '$lib/components/flows/types'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { Alert } from '$lib/components/common'
|
||||
@@ -13,13 +13,13 @@
|
||||
const { flowStore, flowStateStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let schema: Record<string, any> | undefined = undefined
|
||||
let schema: Record<string, any> | undefined = $state(undefined)
|
||||
|
||||
let error: string | undefined = undefined
|
||||
let mod: any | undefined = undefined
|
||||
let error: string | undefined = $state(undefined)
|
||||
let mod: any | undefined = $state(undefined)
|
||||
async function loadSchema() {
|
||||
try {
|
||||
const res = await getFirstStepSchema($flowStateStore, flowStore)
|
||||
const res = await getFirstStepSchema($flowStateStore, flowStore.val)
|
||||
schema = res.schema
|
||||
mod = res.mod
|
||||
dispatch('connectFirstNode', { connectFirstNode: res.connectFirstNode })
|
||||
@@ -27,7 +27,9 @@
|
||||
error = e
|
||||
}
|
||||
}
|
||||
$: $flowStore && $flowStateStore && loadSchema()
|
||||
$effect(() => {
|
||||
flowStore.val && $flowStateStore && untrack(() => loadSchema())
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
selected = !selected
|
||||
@@ -39,7 +41,7 @@
|
||||
dispatch('select', undefined)
|
||||
})
|
||||
|
||||
let selected: boolean = false
|
||||
let selected: boolean = $state(false)
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape' && selected) {
|
||||
@@ -50,14 +52,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
let firstUpdate = true
|
||||
$: if (schema && !selected && firstUpdate) {
|
||||
firstUpdate = false
|
||||
setTimeout(() => {
|
||||
selected = true
|
||||
dispatch('select', schema)
|
||||
}, 200)
|
||||
}
|
||||
let firstUpdate = $state(true)
|
||||
$effect(() => {
|
||||
if (schema && !selected && firstUpdate) {
|
||||
firstUpdate = false
|
||||
setTimeout(() => {
|
||||
selected = true
|
||||
dispatch('select', schema)
|
||||
}, 200)
|
||||
}
|
||||
})
|
||||
|
||||
export function resetSelected(dispatchEvent?: boolean) {
|
||||
selected = false
|
||||
@@ -67,7 +71,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<div class="h-full">
|
||||
{#if schema && mod}
|
||||
@@ -77,10 +81,10 @@
|
||||
selected ? 'bg-surface-selected' : 'hover:bg-surface-hover'
|
||||
)}
|
||||
disabled={!schema}
|
||||
on:click={handleClick}
|
||||
onclick={handleClick}
|
||||
>
|
||||
<FlowModuleSchemaItemViewer
|
||||
on:click={handleClick}
|
||||
onclick={handleClick}
|
||||
deletable={false}
|
||||
id={mod.id}
|
||||
label={mod.summary ||
|
||||
@@ -90,24 +94,26 @@
|
||||
: 'To be defined')}
|
||||
path={`path` in mod.value && mod.summary ? mod.value.path : ''}
|
||||
>
|
||||
<div slot="icon">
|
||||
{#if mod.value.type === 'rawscript'}
|
||||
<LanguageIcon lang={mod.value.language} width={16} height={16} />
|
||||
{:else if mod.value.type === 'script'}
|
||||
{#if mod.value.path.startsWith('hub/')}
|
||||
<div>
|
||||
<IconedResourceType
|
||||
width="20px"
|
||||
height="20px"
|
||||
name={mod.value.path.split('/')[2]}
|
||||
silent={true}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<Building size={14} />
|
||||
{#snippet icon()}
|
||||
<div>
|
||||
{#if mod.value.type === 'rawscript'}
|
||||
<LanguageIcon lang={mod.value.language} width={16} height={16} />
|
||||
{:else if mod.value.type === 'script'}
|
||||
{#if mod.value.path.startsWith('hub/')}
|
||||
<div>
|
||||
<IconedResourceType
|
||||
width="20px"
|
||||
height="20px"
|
||||
name={mod.value.path.split('/')[2]}
|
||||
silent={true}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<Building size={14} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</FlowModuleSchemaItemViewer>
|
||||
</button>
|
||||
{:else}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import {
|
||||
FlowService,
|
||||
type Flow,
|
||||
type FlowModule,
|
||||
DraftService,
|
||||
type PathScript,
|
||||
type OpenFlow,
|
||||
@@ -23,14 +24,16 @@
|
||||
encodeState,
|
||||
generateRandomString,
|
||||
orderedJsonStringify,
|
||||
readFieldsRecursively,
|
||||
replaceFalseWithUndefined,
|
||||
type StateStore,
|
||||
type Value
|
||||
} from '$lib/utils'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { Drawer } from '$lib/components/common'
|
||||
import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
|
||||
|
||||
import { onMount, setContext, type ComponentType } from 'svelte'
|
||||
import { onMount, setContext, untrack, type ComponentType } from 'svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import { Badge, Button, UndoRedo } from './common'
|
||||
@@ -48,7 +51,7 @@
|
||||
import { getAllModules } from './flows/flowExplorer'
|
||||
import { type FlowCopilotContext } from './copilot/flow'
|
||||
import FlowAIButton from './copilot/chat/flow/FlowAIButton.svelte'
|
||||
import { loadFlowModuleState } from './flows/flowStateUtils'
|
||||
import { loadFlowModuleState } from './flows/flowStateUtils.svelte'
|
||||
import FlowBuilderTutorials from './FlowBuilderTutorials.svelte'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import FlowTutorials from './FlowTutorials.svelte'
|
||||
@@ -71,27 +74,51 @@
|
||||
import { Triggers } from './triggers/triggers.svelte'
|
||||
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let pathStoreInit: string | undefined = undefined
|
||||
export let newFlow: boolean
|
||||
export let selectedId: string | undefined
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let loading = false
|
||||
export let flowStore: Writable<OpenFlow>
|
||||
export let flowStateStore: Writable<FlowState>
|
||||
export let savedFlow: FlowWithDraftAndDraftTriggers | undefined = undefined
|
||||
export let diffDrawer: DiffDrawer | undefined = undefined
|
||||
export let customUi: FlowBuilderWhitelabelCustomUi = {}
|
||||
export let disableAi: boolean = false
|
||||
export let disabledFlowInputs = false
|
||||
export let savedPrimarySchedule: ScheduleTrigger | undefined = undefined // used to set the primary schedule in the legacy primaryScheduleStore
|
||||
export let version: number | undefined = undefined
|
||||
export let setSavedraftCb: ((cb: () => void) => void) | undefined = undefined
|
||||
export let draftTriggersFromUrl: Trigger[] | undefined = undefined
|
||||
export let selectedTriggerIndexFromUrl: number | undefined = undefined
|
||||
interface Props {
|
||||
initialPath?: string
|
||||
pathStoreInit?: string | undefined
|
||||
newFlow: boolean
|
||||
selectedId: string | undefined
|
||||
initialArgs?: Record<string, any>
|
||||
loading?: boolean
|
||||
flowStore: StateStore<OpenFlow>
|
||||
flowStateStore: Writable<FlowState>
|
||||
savedFlow?: FlowWithDraftAndDraftTriggers | undefined
|
||||
diffDrawer?: DiffDrawer | undefined
|
||||
customUi?: FlowBuilderWhitelabelCustomUi
|
||||
disableAi?: boolean
|
||||
disabledFlowInputs?: boolean
|
||||
savedPrimarySchedule?: ScheduleTrigger | undefined // used to set the primary schedule in the legacy primaryScheduleStore
|
||||
version?: number | undefined
|
||||
setSavedraftCb?: ((cb: () => void) => void) | undefined
|
||||
draftTriggersFromUrl?: Trigger[] | undefined
|
||||
selectedTriggerIndexFromUrl?: number | undefined
|
||||
children?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
initialPath = $bindable(''),
|
||||
pathStoreInit = undefined,
|
||||
newFlow,
|
||||
selectedId,
|
||||
initialArgs = {},
|
||||
loading = false,
|
||||
flowStore,
|
||||
flowStateStore,
|
||||
savedFlow = $bindable(undefined),
|
||||
diffDrawer = undefined,
|
||||
customUi = {},
|
||||
disableAi = false,
|
||||
disabledFlowInputs = false,
|
||||
savedPrimarySchedule = undefined,
|
||||
version = undefined,
|
||||
setSavedraftCb = undefined,
|
||||
draftTriggersFromUrl = undefined,
|
||||
selectedTriggerIndexFromUrl = undefined,
|
||||
children
|
||||
}: Props = $props()
|
||||
|
||||
let initialPathStore = writable(initialPath)
|
||||
$: initialPathStore.set(initialPath)
|
||||
|
||||
// used for new flows for captures
|
||||
let fakeInitialPath =
|
||||
@@ -103,13 +130,13 @@
|
||||
generateRandomString(12)
|
||||
|
||||
// Used by multiplayer deploy collision warning
|
||||
let deployedValue: Value | undefined = undefined // Value to diff against
|
||||
let deployedBy: string | undefined = undefined // Author
|
||||
let confirmCallback: () => void = () => {} // What happens when user clicks `override` in warning
|
||||
let open: boolean = false // Is confirmation modal open
|
||||
let deployedValue: Value | undefined = $state(undefined) // Value to diff against
|
||||
let deployedBy: string | undefined = $state(undefined) // Author
|
||||
let confirmCallback: () => void = $state(() => {}) // What happens when user clicks `override` in warning
|
||||
let open: boolean = $state(false) // Is confirmation modal open
|
||||
|
||||
// Draft triggers confirmation modal
|
||||
let draftTriggersModalOpen = false
|
||||
let draftTriggersModalOpen = $state(false)
|
||||
let confirmDeploymentCallback: (triggersToDeploy: Trigger[]) => void = () => {}
|
||||
|
||||
async function handleDraftTriggersConfirmed(event: CustomEvent<{ selectedTriggers: Trigger[] }>) {
|
||||
@@ -119,13 +146,11 @@
|
||||
confirmDeploymentCallback(selectedTriggers)
|
||||
}
|
||||
|
||||
$: setContext('customUi', customUi)
|
||||
|
||||
export function getInitialAndModifiedValues(): SavedAndModifiedValue {
|
||||
return {
|
||||
savedValue: savedFlow,
|
||||
modifiedValue: {
|
||||
...$flowStore,
|
||||
...flowStore.val,
|
||||
draft_triggers: structuredClone(triggersState.getDraftTriggersSnapshot())
|
||||
}
|
||||
}
|
||||
@@ -175,8 +200,8 @@
|
||||
triggersState.selectedTriggerIndex = index
|
||||
}
|
||||
|
||||
let loadingSave = false
|
||||
let loadingDraft = false
|
||||
let loadingSave = $state(false)
|
||||
let loadingDraft = $state(false)
|
||||
|
||||
export async function saveDraft(forceSave = false): Promise<void> {
|
||||
if (!newFlow && !savedFlow) {
|
||||
@@ -185,11 +210,13 @@
|
||||
if (savedFlow) {
|
||||
const draftOrDeployed = cleanValueProperties(savedFlow.draft || savedFlow)
|
||||
const currentDraftTriggers = structuredClone(triggersState.getDraftTriggersSnapshot())
|
||||
const current = cleanValueProperties({
|
||||
...$flowStore,
|
||||
path: $pathStore,
|
||||
draft_triggers: currentDraftTriggers
|
||||
})
|
||||
const current = cleanValueProperties(
|
||||
$state.snapshot({
|
||||
...flowStore.val,
|
||||
path: $pathStore,
|
||||
draft_triggers: currentDraftTriggers
|
||||
})
|
||||
)
|
||||
if (!forceSave && orderedJsonStringify(draftOrDeployed) === orderedJsonStringify(current)) {
|
||||
sendUserToast('No changes detected, ignoring', false, [
|
||||
{
|
||||
@@ -204,7 +231,7 @@
|
||||
}
|
||||
loadingDraft = true
|
||||
try {
|
||||
const flow = cleanInputs($flowStore)
|
||||
const flow = cleanInputs(flowStore.val)
|
||||
try {
|
||||
localStorage.removeItem('flow')
|
||||
localStorage.removeItem(`flow-${$pathStore}`)
|
||||
@@ -261,13 +288,13 @@
|
||||
savedFlow = {
|
||||
...(newFlow || savedFlow?.draft_only
|
||||
? {
|
||||
...structuredClone($flowStore),
|
||||
...structuredClone($state.snapshot(flowStore.val)),
|
||||
path: $pathStore,
|
||||
draft_only: true
|
||||
}
|
||||
: savedFlow),
|
||||
draft: {
|
||||
...structuredClone($flowStore),
|
||||
...structuredClone($state.snapshot(flowStore.val)),
|
||||
path: $pathStore,
|
||||
draft_triggers: structuredClone(triggersState.getDraftTriggersSnapshot())
|
||||
}
|
||||
@@ -314,9 +341,9 @@
|
||||
|
||||
if (
|
||||
deployedValue &&
|
||||
$flowStore &&
|
||||
flowStore.val &&
|
||||
orderedJsonStringify(deployedValue) ===
|
||||
orderedJsonStringify(replaceFalseWithUndefined({ ...$flowStore, path: $pathStore }))
|
||||
orderedJsonStringify(replaceFalseWithUndefined({ ...flowStore.val, path: $pathStore }))
|
||||
) {
|
||||
await saveFlow(deploymentMsg)
|
||||
} else {
|
||||
@@ -359,7 +386,7 @@
|
||||
|
||||
loadingSave = true
|
||||
try {
|
||||
const flow = cleanInputs($flowStore)
|
||||
const flow = cleanInputs(flowStore.val)
|
||||
// console.log('flow', computeUnlockedSteps(flow)) // del
|
||||
// loadingSave = false // del
|
||||
// return
|
||||
@@ -441,11 +468,11 @@
|
||||
})
|
||||
}
|
||||
|
||||
const { draft_triggers: _, ...newSavedFlow } = $flowStore as OpenFlow & {
|
||||
const { draft_triggers: _, ...newSavedFlow } = flowStore.val as OpenFlow & {
|
||||
draft_triggers: Trigger[]
|
||||
}
|
||||
savedFlow = {
|
||||
...structuredClone(newSavedFlow),
|
||||
...structuredClone($state.snapshot(newSavedFlow)),
|
||||
path: $pathStore
|
||||
} as Flow
|
||||
setDraftTriggers([])
|
||||
@@ -453,19 +480,13 @@
|
||||
dispatch('deploy', $pathStore)
|
||||
} catch (err) {
|
||||
dispatch('deployError', err)
|
||||
sendUserToast(`The flow could not be saved: ${err.body}`, true)
|
||||
sendUserToast(`The flow could not be saved: ${err.body ?? err}`, true)
|
||||
loadingSave = false
|
||||
}
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
$: {
|
||||
if ($flowStore || $selectedIdStore) {
|
||||
saveSessionDraft()
|
||||
}
|
||||
}
|
||||
|
||||
function saveSessionDraft() {
|
||||
timeout && clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
@@ -473,7 +494,7 @@
|
||||
localStorage.setItem(
|
||||
initialPath && initialPath != '' ? `flow-${initialPath}` : 'flow',
|
||||
encodeState({
|
||||
flow: $flowStore,
|
||||
flow: flowStore.val,
|
||||
path: $pathStore,
|
||||
selectedId: $selectedIdStore,
|
||||
draft_triggers: triggersState.getDraftTriggersSnapshot(),
|
||||
@@ -492,10 +513,10 @@
|
||||
return $selectedIdStore
|
||||
}
|
||||
|
||||
const previewArgsStore = writable<Record<string, any>>(initialArgs)
|
||||
const previewArgsStore = $state({ val: initialArgs })
|
||||
const scriptEditorDrawer = writable<ScriptEditorDrawer | undefined>(undefined)
|
||||
const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined)
|
||||
const history = initHistory($flowStore)
|
||||
const moving = writable<{ id: string } | undefined>(undefined)
|
||||
const history = initHistory(flowStore.val)
|
||||
const pathStore = writable<string>(pathStoreInit ?? initialPath)
|
||||
const captureOn = writable<boolean>(false)
|
||||
const showCaptureHint = writable<boolean | undefined>(undefined)
|
||||
@@ -504,7 +525,6 @@
|
||||
editPanelSize: 0,
|
||||
payloadData: undefined
|
||||
})
|
||||
$: initialPath && ($pathStore = initialPath)
|
||||
|
||||
const testStepStore = writable<Record<string, any>>({})
|
||||
|
||||
@@ -536,14 +556,16 @@
|
||||
})
|
||||
|
||||
// Add triggers context store
|
||||
const triggersState = new Triggers(
|
||||
[
|
||||
{ type: 'webhook', path: '', isDraft: false },
|
||||
{ type: 'email', path: '', isDraft: false },
|
||||
...(draftTriggersFromUrl ?? savedFlow?.draft?.draft_triggers ?? [])
|
||||
],
|
||||
selectedTriggerIndexFromUrl,
|
||||
saveSessionDraft
|
||||
const triggersState = $state(
|
||||
new Triggers(
|
||||
[
|
||||
{ type: 'webhook', path: '', isDraft: false },
|
||||
{ type: 'email', path: '', isDraft: false },
|
||||
...(draftTriggersFromUrl ?? savedFlow?.draft?.draft_triggers ?? [])
|
||||
],
|
||||
selectedTriggerIndexFromUrl,
|
||||
saveSessionDraft
|
||||
)
|
||||
)
|
||||
|
||||
setContext<TriggerContext>('TriggerContext', {
|
||||
@@ -575,10 +597,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: selectedId && select(selectedId)
|
||||
|
||||
$: initialPath && initialPath != '' && $workspaceStore && loadTriggers()
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
let classes = event.target?.['className']
|
||||
if (
|
||||
@@ -591,13 +609,13 @@
|
||||
switch (event.key) {
|
||||
case 'Z':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
$flowStore = redo(history)
|
||||
flowStore.val = redo(history)
|
||||
event.preventDefault()
|
||||
}
|
||||
break
|
||||
case 'z':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
$flowStore = undo(history, $flowStore)
|
||||
flowStore.val = undo(history, flowStore.val)
|
||||
$selectedIdStore = 'Input'
|
||||
event.preventDefault()
|
||||
}
|
||||
@@ -638,7 +656,7 @@
|
||||
'settings-metadata',
|
||||
'constants',
|
||||
'preprocessor',
|
||||
...dfsApply($flowStore.value.modules, (module) => module.id)
|
||||
...dfsApply(flowStore.val.value.modules, (module) => module.id)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -663,7 +681,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let flowCopilotContext: FlowCopilotContext = {
|
||||
let flowCopilotContext: FlowCopilotContext = $state({
|
||||
shouldUpdatePropertyType: writable<{
|
||||
[key: string]: 'static' | 'javascript' | undefined
|
||||
}>({}),
|
||||
@@ -674,16 +692,16 @@
|
||||
[key: string]: string | undefined
|
||||
}>({}),
|
||||
stepInputsLoading: writable<boolean>(false)
|
||||
}
|
||||
})
|
||||
|
||||
setContext('FlowCopilotContext', flowCopilotContext)
|
||||
|
||||
let renderCount = 0
|
||||
let flowTutorials: FlowTutorials | undefined = undefined
|
||||
let renderCount = $state(0)
|
||||
let flowTutorials: FlowTutorials | undefined = $state(undefined)
|
||||
|
||||
let jsonViewerDrawer: Drawer | undefined = undefined
|
||||
let yamlEditorDrawer: Drawer | undefined = undefined
|
||||
let flowHistory: FlowHistory | undefined = undefined
|
||||
let jsonViewerDrawer: Drawer | undefined = $state(undefined)
|
||||
let yamlEditorDrawer: Drawer | undefined = $state(undefined)
|
||||
let flowHistory: FlowHistory | undefined = $state(undefined)
|
||||
|
||||
export function triggerTutorial() {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
@@ -701,9 +719,7 @@
|
||||
icon: ComponentType<Icon>
|
||||
action: () => void
|
||||
disabled?: boolean
|
||||
}[] = []
|
||||
|
||||
$: onCustomUiChange(customUi)
|
||||
}[] = $state([])
|
||||
|
||||
function onCustomUiChange(customUi: FlowBuilderWhitelabelCustomUi | undefined) {
|
||||
moreItems = [
|
||||
@@ -748,12 +764,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
let flowPreviewButtons: FlowPreviewButtons
|
||||
let flowPreviewButtons: FlowPreviewButtons | undefined = $state()
|
||||
|
||||
run(() => {
|
||||
initialPathStore.set(initialPath)
|
||||
})
|
||||
run(() => {
|
||||
setContext('customUi', customUi)
|
||||
})
|
||||
run(() => {
|
||||
if (flowStore.val || $selectedIdStore) {
|
||||
readFieldsRecursively(flowStore.val)
|
||||
untrack(() => saveSessionDraft())
|
||||
}
|
||||
})
|
||||
run(() => {
|
||||
initialPath && ($pathStore = initialPath)
|
||||
})
|
||||
run(() => {
|
||||
selectedId && untrack(() => select(selectedId))
|
||||
})
|
||||
run(() => {
|
||||
initialPath && initialPath != '' && $workspaceStore && untrack(() => loadTriggers())
|
||||
})
|
||||
run(() => {
|
||||
customUi && untrack(() => onCustomUiChange(customUi))
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
<svelte:window onkeydown={onKeyDown} />
|
||||
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
|
||||
<DeployOverrideConfirmationModal
|
||||
bind:deployedBy
|
||||
@@ -761,7 +802,7 @@
|
||||
bind:open
|
||||
{diffDrawer}
|
||||
bind:deployedValue
|
||||
currentValue={$flowStore}
|
||||
currentValue={flowStore.val}
|
||||
/>
|
||||
|
||||
<DraftTriggersConfirmationModal
|
||||
@@ -791,18 +832,18 @@
|
||||
<div class="flex w-full max-w-md gap-4 items-center">
|
||||
<Summary
|
||||
disabled={customUi?.topBar?.editableSummary == false}
|
||||
bind:value={$flowStore.summary}
|
||||
bind:value={flowStore.val.summary}
|
||||
/>
|
||||
|
||||
<UndoRedo
|
||||
undoProps={{ disabled: $history.index === 0 }}
|
||||
redoProps={{ disabled: $history.index === $history.history.length - 1 }}
|
||||
on:undo={() => {
|
||||
const currentModules = $flowStore?.value?.modules
|
||||
const currentModules = flowStore.val?.value?.modules
|
||||
|
||||
$flowStore = undo(history, $flowStore)
|
||||
flowStore.val = undo(history, flowStore.val)
|
||||
|
||||
const newModules = $flowStore?.value?.modules
|
||||
const newModules = flowStore.val?.value?.modules
|
||||
const restoredModules = newModules?.filter(
|
||||
(node) => !currentModules?.some((currentNode) => currentNode?.id === node?.id)
|
||||
)
|
||||
@@ -820,7 +861,7 @@
|
||||
$selectedIdStore = 'Input'
|
||||
}}
|
||||
on:redo={() => {
|
||||
$flowStore = redo(history)
|
||||
flowStore.val = redo(history)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -853,7 +894,7 @@
|
||||
<div class="flex justify-start w-full">
|
||||
<div>
|
||||
<button
|
||||
on:click={async () => {
|
||||
onclick={async () => {
|
||||
select('settings-metadata')
|
||||
document.getElementById('path')?.focus()
|
||||
}}
|
||||
@@ -871,7 +912,7 @@
|
||||
readonly
|
||||
value={$pathStore && $pathStore != '' ? $pathStore : 'Choose a path'}
|
||||
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none"
|
||||
on:focus={({ currentTarget }) => {
|
||||
onfocus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
@@ -915,7 +956,11 @@
|
||||
mode: 'normal',
|
||||
deployed: deployedValue ?? savedFlow,
|
||||
draft: savedFlow?.draft,
|
||||
current: { ...$flowStore, path: $pathStore, draft_triggers: currentDraftTriggers }
|
||||
current: {
|
||||
...flowStore.val,
|
||||
path: $pathStore,
|
||||
draft_triggers: currentDraftTriggers
|
||||
}
|
||||
})
|
||||
}}
|
||||
disabled={!savedFlow}
|
||||
@@ -980,7 +1025,7 @@
|
||||
}
|
||||
}}
|
||||
on:testWithArgs={(e) => {
|
||||
$previewArgsStore = JSON.parse(JSON.stringify(e.detail))
|
||||
previewArgsStore.val = JSON.parse(JSON.stringify(e.detail))
|
||||
flowPreviewButtons?.openPreview(true)
|
||||
}}
|
||||
{savedFlow}
|
||||
|
||||
@@ -45,8 +45,7 @@
|
||||
modules={flow?.value?.modules}
|
||||
failureModule={flow?.value?.failure_module}
|
||||
preprocessorModule={flow?.value?.preprocessor_module}
|
||||
on:select={(e) => {
|
||||
let nodeId = e?.detail
|
||||
onSelect={(nodeId) => {
|
||||
if (nodeId === 'triggers') {
|
||||
dispatch('triggerDetail')
|
||||
return
|
||||
@@ -55,7 +54,7 @@
|
||||
} else if (nodeId === 'preprocessor') {
|
||||
stepDetail = flow?.value?.preprocessor_module
|
||||
} else {
|
||||
stepDetail = dfs(flow?.value?.modules ?? [], (m) => m).find((m) => m?.id === e?.detail)
|
||||
stepDetail = dfs(flow?.value?.modules ?? [], (m) => m).find((m) => m?.id === nodeId)
|
||||
}
|
||||
stepDetail = stepDetail ?? nodeId
|
||||
dispatch('select', stepDetail)
|
||||
|
||||
@@ -11,13 +11,23 @@
|
||||
import { CornerDownLeft, Play, RefreshCw, X } from 'lucide-svelte'
|
||||
import type { Schema } from '$lib/common'
|
||||
|
||||
export let open: boolean
|
||||
interface Props {
|
||||
open: boolean
|
||||
jobId?: string | undefined
|
||||
job?: Job | undefined
|
||||
modules: FlowModule[]
|
||||
previewArgs?: Record<string, any>
|
||||
whileLoop?: boolean
|
||||
}
|
||||
|
||||
export let jobId: string | undefined = undefined
|
||||
export let job: Job | undefined = undefined
|
||||
export let modules: FlowModule[]
|
||||
export let previewArgs: Record<string, any> = {}
|
||||
export let whileLoop = false
|
||||
let {
|
||||
open,
|
||||
jobId = $bindable(undefined),
|
||||
job = $bindable(undefined),
|
||||
modules,
|
||||
previewArgs = $bindable({}),
|
||||
whileLoop = false
|
||||
}: Props = $props()
|
||||
|
||||
export const forloopSchema: Schema = {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema' as string | undefined,
|
||||
@@ -54,10 +64,10 @@
|
||||
type: 'object'
|
||||
}
|
||||
|
||||
let selectedJobStep: string | undefined = undefined
|
||||
let selectedJobStep: string | undefined = $state(undefined)
|
||||
|
||||
let isRunning: boolean = false
|
||||
let jobProgressReset: () => void
|
||||
let isRunning: boolean = $state(false)
|
||||
let jobProgressReset: (() => void) | undefined = $state(undefined)
|
||||
|
||||
export function test() {
|
||||
runPreview(previewArgs, undefined)
|
||||
@@ -70,7 +80,7 @@
|
||||
args: Record<string, any>,
|
||||
restartedFrom: RestartedFrom | undefined
|
||||
) {
|
||||
jobProgressReset()
|
||||
jobProgressReset?.()
|
||||
const newFlow = { value: { modules }, summary: '' }
|
||||
jobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom)
|
||||
isRunning = true
|
||||
@@ -89,12 +99,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if (job?.type === 'CompletedJob') {
|
||||
isRunning = false
|
||||
}
|
||||
$effect(() => {
|
||||
if (job?.type === 'CompletedJob') {
|
||||
isRunning = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
<svelte:window onkeydown={onKeyDown} />
|
||||
|
||||
<div class="flex flex-col space-y-2 h-screen bg-surface px-6 py-2 w-full" id="flow-preview-content">
|
||||
<div class="flex flex-row justify-between w-full items-center gap-x-2">
|
||||
@@ -156,7 +168,7 @@
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
class="py-4 max-w-3xl"
|
||||
className="py-4 max-w-3xl"
|
||||
schema={whileLoop ? whileLoopSchema : forloopSchema}
|
||||
bind:args={previewArgs}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { AlertTriangle, ArrowRight, CornerDownLeft, Play, RefreshCw, X } from 'lucide-svelte'
|
||||
import { emptyString, sendUserToast } from '$lib/utils'
|
||||
import { dfs } from './flows/dfs'
|
||||
import { sliceModules } from './flows/flowStateUtils'
|
||||
import { sliceModules } from './flows/flowStateUtils.svelte'
|
||||
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import JsonInputs from './JsonInputs.svelte'
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
export function test() {
|
||||
renderCount++
|
||||
runPreview($previewArgs, undefined)
|
||||
runPreview(previewArgs.val, undefined)
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -75,9 +75,9 @@
|
||||
|
||||
function extractFlow(previewMode: 'upTo' | 'whole'): OpenFlow {
|
||||
if (previewMode === 'whole') {
|
||||
return $flowStore
|
||||
return flowStore.val
|
||||
} else {
|
||||
const flow: Flow = JSON.parse(JSON.stringify($flowStore))
|
||||
const flow: Flow = JSON.parse(JSON.stringify(flowStore.val))
|
||||
const idOrders = dfs(flow.value.modules, (x) => x.id)
|
||||
let upToIndex = idOrders.indexOf($selectedId)
|
||||
|
||||
@@ -97,13 +97,13 @@
|
||||
initial = false
|
||||
}
|
||||
try {
|
||||
lastPreviewFlow = JSON.stringify($flowStore)
|
||||
lastPreviewFlow = JSON.stringify(flowStore.val)
|
||||
jobProgressReset()
|
||||
const newFlow = extractFlow(previewMode)
|
||||
jobId = await runFlowPreview(args, newFlow, $pathStore, restartedFrom)
|
||||
isRunning = true
|
||||
if (inputSelected) {
|
||||
savedArgs = $previewArgs
|
||||
savedArgs = previewArgs.val
|
||||
inputSelected = undefined
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -120,7 +120,7 @@
|
||||
case 'Enter':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
event.preventDefault()
|
||||
runPreview($previewArgs, undefined)
|
||||
runPreview(previewArgs.val, undefined)
|
||||
}
|
||||
break
|
||||
|
||||
@@ -156,20 +156,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
let savedArgs = $previewArgs
|
||||
let savedArgs = previewArgs.val
|
||||
let inputSelected: 'captures' | 'history' | 'saved' | undefined = undefined
|
||||
async function selectInput(input, type?: 'captures' | 'history' | 'saved' | undefined) {
|
||||
if (!input) {
|
||||
$previewArgs = savedArgs
|
||||
previewArgs.val = savedArgs
|
||||
inputSelected = undefined
|
||||
setTimeout(() => {
|
||||
preventEscape = false
|
||||
}, 100)
|
||||
} else {
|
||||
$previewArgs = input
|
||||
previewArgs.val = input
|
||||
inputSelected = type
|
||||
preventEscape = true
|
||||
jsonEditor?.setCode(JSON.stringify($previewArgs ?? {}, null, '\t'))
|
||||
jsonEditor?.setCode(JSON.stringify(previewArgs.val ?? {}, null, '\t'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
|
||||
async function loadIndividualStepsStates() {
|
||||
// console.log('loadIndividualStepsStates')
|
||||
dfs($flowStore.value.modules, async (module) => {
|
||||
dfs(flowStore.val.value.modules, async (module) => {
|
||||
// console.log('module', $flowStateStore[module.id], module.id)
|
||||
const prev = $flowStateStore[module.id]?.previewResult
|
||||
if (prev && prev != NEVER_TESTED_THIS_FAR) {
|
||||
@@ -283,7 +283,7 @@
|
||||
variant="border"
|
||||
title={`Re-start this flow from step ${selectedJobStep} (included).`}
|
||||
on:click={() => {
|
||||
runPreview($previewArgs, {
|
||||
runPreview(previewArgs.val, {
|
||||
flow_job_id: jobId,
|
||||
step_id: selectedJobStep,
|
||||
branch_or_iteration_n: 0
|
||||
@@ -308,7 +308,7 @@
|
||||
color="blue"
|
||||
startIcon={{ icon: RefreshCw }}
|
||||
on:click={() => {
|
||||
runPreview($previewArgs, {
|
||||
runPreview(previewArgs.val, {
|
||||
flow_job_id: jobId,
|
||||
step_id: selectedJobStep,
|
||||
branch_or_iteration_n: 0
|
||||
@@ -354,7 +354,7 @@
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Restart flow"
|
||||
on:click|once={() => {
|
||||
runPreview($previewArgs, {
|
||||
runPreview(previewArgs.val, {
|
||||
flow_job_id: jobId,
|
||||
step_id: selectedJobStep,
|
||||
branch_or_iteration_n: branchOrIterationN
|
||||
@@ -375,7 +375,7 @@
|
||||
color="dark"
|
||||
size="sm"
|
||||
btnClasses="w-full max-w-lg"
|
||||
on:click={() => runPreview($previewArgs, undefined)}
|
||||
on:click={() => runPreview(previewArgs.val, undefined)}
|
||||
id="flow-editor-test-flow-drawer"
|
||||
shortCut={{ Icon: CornerDownLeft }}
|
||||
>
|
||||
@@ -385,7 +385,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-full flex flex-col gap-y-1">
|
||||
{#if lastPreviewFlow && JSON.stringify($flowStore) != lastPreviewFlow}
|
||||
{#if lastPreviewFlow && JSON.stringify(flowStore.val) != lastPreviewFlow}
|
||||
<div class="pt-1">
|
||||
<div
|
||||
class="bg-orange-200 text-orange-600 border border-orange-600 p-2 flex items-center gap-2 rounded"
|
||||
@@ -409,7 +409,7 @@
|
||||
runnableId={$initialPathStore}
|
||||
stablePathForCaptures={$initialPathStore || fakeInitialPath}
|
||||
runnableType={'FlowPath'}
|
||||
previewArgs={$previewArgs}
|
||||
previewArgs={previewArgs.val}
|
||||
on:openTriggers
|
||||
on:select={(e) => {
|
||||
selectInput(e.detail.payload, e.detail?.type)
|
||||
@@ -419,7 +419,7 @@
|
||||
>
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<InputSelectedBadge
|
||||
on:click={() => schemaFormWithArgPicker?.resetSelected()}
|
||||
onReject={() => schemaFormWithArgPicker?.resetSelected()}
|
||||
{inputSelected}
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
@@ -433,7 +433,7 @@
|
||||
}}
|
||||
lightMode
|
||||
on:change={(e) => {
|
||||
jsonEditor?.setCode(JSON.stringify($previewArgs ?? {}, null, '\t'))
|
||||
jsonEditor?.setCode(JSON.stringify(previewArgs.val ?? {}, null, '\t'))
|
||||
refresh()
|
||||
}}
|
||||
/>
|
||||
@@ -445,7 +445,7 @@
|
||||
bind:this={jsonEditor}
|
||||
on:select={(e) => {
|
||||
if (e.detail) {
|
||||
$previewArgs = e.detail
|
||||
previewArgs.val = e.detail
|
||||
}
|
||||
}}
|
||||
updateOnBlur={false}
|
||||
@@ -458,10 +458,10 @@
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
schema={$flowStore.schema}
|
||||
bind:args={$previewArgs}
|
||||
schema={flowStore.val.schema}
|
||||
bind:args={previewArgs.val}
|
||||
on:change={() => {
|
||||
savedArgs = $previewArgs
|
||||
savedArgs = previewArgs.val
|
||||
}}
|
||||
bind:isValid
|
||||
/>
|
||||
|
||||
@@ -29,7 +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'
|
||||
import { buildPrefix } from './graph/graphBuilder.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -1253,31 +1253,29 @@
|
||||
success={jobId != undefined && isSuccess(job?.['success'])}
|
||||
flowModuleStates={$localModuleStates}
|
||||
bind:expandedSubflows
|
||||
on:select={(e) => {
|
||||
onSelect={(e) => {
|
||||
console.log('onSelect', e)
|
||||
if (rightColumnSelect != 'node_definition') {
|
||||
rightColumnSelect = 'node_status'
|
||||
}
|
||||
if (typeof e.detail == 'string') {
|
||||
if (e.detail == 'Input') {
|
||||
if (typeof e == 'string') {
|
||||
if (e == 'Input') {
|
||||
selectedNode = 'start'
|
||||
stepDetail = undefined
|
||||
} else if (e.detail == 'Result') {
|
||||
} else if (e == 'Result') {
|
||||
selectedNode = 'end'
|
||||
stepDetail = 'end'
|
||||
} else {
|
||||
const mod = dfs(job?.raw_flow?.modules ?? [], (m) => m).find(
|
||||
(m) => m?.id === e?.detail
|
||||
)
|
||||
const mod = dfs(job?.raw_flow?.modules ?? [], (m) => m).find((m) => m?.id === e)
|
||||
stepDetail = mod
|
||||
selectedNode = e?.detail
|
||||
selectedNode = e
|
||||
}
|
||||
} else {
|
||||
stepDetail = e.detail
|
||||
selectedNode = e.detail.id
|
||||
stepDetail = e
|
||||
selectedNode = e.id
|
||||
}
|
||||
}}
|
||||
on:selectedIteration={async (e) => {
|
||||
let detail = e.detail
|
||||
onSelectedIteration={async (detail) => {
|
||||
if (detail.manuallySet) {
|
||||
let rootJobId = detail.id
|
||||
await tick()
|
||||
|
||||
@@ -8,20 +8,30 @@
|
||||
import FlowViewerInner from './FlowViewerInner.svelte'
|
||||
import FlowInputViewer from './FlowInputViewer.svelte'
|
||||
|
||||
export let flow: {
|
||||
summary: string
|
||||
description?: string
|
||||
value: FlowValue
|
||||
schema?: any
|
||||
interface Props {
|
||||
flow: {
|
||||
summary: string
|
||||
description?: string
|
||||
value: FlowValue
|
||||
schema?: any
|
||||
}
|
||||
initialOpen?: number | undefined
|
||||
noSide?: boolean
|
||||
noGraph?: boolean
|
||||
tab?: 'ui' | 'raw' | 'schema'
|
||||
noSummary?: boolean
|
||||
noGraphDownload?: boolean
|
||||
}
|
||||
export let initialOpen: number | undefined = undefined
|
||||
export let noSide = false
|
||||
|
||||
export let noGraph: boolean = false
|
||||
|
||||
export let tab: 'ui' | 'raw' | 'schema' = noGraph ? 'schema' : 'ui'
|
||||
export let noSummary = false
|
||||
export let noGraphDownload = false
|
||||
let {
|
||||
flow,
|
||||
initialOpen = undefined,
|
||||
noSide = false,
|
||||
noGraph = false,
|
||||
tab = $bindable(noGraph ? 'schema' : 'ui'),
|
||||
noSummary = false,
|
||||
noGraphDownload = false
|
||||
}: Props = $props()
|
||||
|
||||
let open: { [id: number]: boolean } = {}
|
||||
if (initialOpen) {
|
||||
@@ -38,7 +48,7 @@
|
||||
<Tab value="raw">Raw</Tab>
|
||||
<Tab value="schema">Input Schema</Tab>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
{#snippet content()}
|
||||
<TabContent value="ui">
|
||||
<div class="flow-root w-full pb-4">
|
||||
{#if !noSummary}
|
||||
@@ -65,5 +75,5 @@
|
||||
<div class="my-4"></div>
|
||||
<SchemaViewer schema={flow.schema} />
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tabs>
|
||||
|
||||
@@ -11,16 +11,20 @@
|
||||
import HighlightTheme from './HighlightTheme.svelte'
|
||||
import { filteredContentForExport } from './flows/utils'
|
||||
|
||||
export let flow: {
|
||||
summary: string
|
||||
description?: string
|
||||
value: FlowValue
|
||||
schema?: any
|
||||
interface Props {
|
||||
flow: {
|
||||
summary: string
|
||||
description?: string
|
||||
value: FlowValue
|
||||
schema?: any
|
||||
}
|
||||
}
|
||||
|
||||
$: flowFiltered = filteredContentForExport(flow)
|
||||
let { flow }: Props = $props()
|
||||
|
||||
let rawType: 'json' | 'yaml' = 'yaml'
|
||||
let flowFiltered = $derived(filteredContentForExport(flow))
|
||||
|
||||
let rawType: 'json' | 'yaml' = $state('yaml')
|
||||
|
||||
function trimStringToLines(inputString: string, maxLines: number = 100): string {
|
||||
const lines = inputString?.split('\n') ?? []
|
||||
@@ -29,7 +33,7 @@
|
||||
return linesToKeep.join('\n')
|
||||
}
|
||||
|
||||
let code: string = ''
|
||||
let code: string = $state('')
|
||||
|
||||
function computeCode() {
|
||||
const str =
|
||||
@@ -44,11 +48,13 @@
|
||||
code = str
|
||||
}
|
||||
|
||||
let shouldDisplayLoadMore = false
|
||||
let shouldDisplayLoadMore = $state(false)
|
||||
|
||||
$: flowFiltered && rawType && computeCode()
|
||||
$effect(() => {
|
||||
flowFiltered && rawType && computeCode()
|
||||
})
|
||||
|
||||
let maxLines = 100
|
||||
let maxLines = $state(100)
|
||||
</script>
|
||||
|
||||
<HighlightTheme />
|
||||
@@ -62,7 +68,7 @@
|
||||
>
|
||||
<Tab value="yaml">YAML</Tab>
|
||||
<Tab value="json">JSON</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
{#snippet content()}
|
||||
<div class="relative pt-2">
|
||||
<Button
|
||||
on:click={() =>
|
||||
@@ -105,7 +111,7 @@
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
GranularAclService,
|
||||
GroupService
|
||||
} from '$lib/gen'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { Alert, Button, Drawer, DrawerContent } from './common'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
@@ -19,6 +18,7 @@
|
||||
import Label from './Label.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Select from './Select.svelte'
|
||||
|
||||
export let name: string
|
||||
let can_write = false
|
||||
@@ -184,25 +184,20 @@
|
||||
</Alert>
|
||||
<div class="flex items-center gap-1">
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
bind:selected={ownerKind}
|
||||
on:selected={() => (ownerItem = '')}
|
||||
let:item
|
||||
>
|
||||
<ToggleButton value="user" label="User" {item} />
|
||||
<ToggleButton value="group" label="Group" {item} />
|
||||
<ToggleButtonGroup bind:selected={ownerKind} on:selected={() => (ownerItem = '')}>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="user" label="User" {item} />
|
||||
<ToggleButton value="group" label="Group" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
{#key ownerKind}
|
||||
<AutoComplete
|
||||
required
|
||||
noInputStyles
|
||||
items={ownerKind === 'user'
|
||||
{@const items =
|
||||
ownerKind === 'user'
|
||||
? usernames.filter((x) => !perms?.map((y) => y.owner_name).includes('u/' + x))
|
||||
: groups.filter((x) => !perms?.map((y) => y.owner_name).includes('g/' + x))}
|
||||
bind:selectedItem={ownerItem}
|
||||
/>
|
||||
<Select items={items.map((x) => ({ label: x, value: x }))} bind:value={ownerItem} />
|
||||
{#if ownerKind == 'group'}
|
||||
<Button
|
||||
title="View Group"
|
||||
@@ -278,7 +273,6 @@
|
||||
<ToggleButtonGroup
|
||||
disabled={owner_name == 'u/' + $userStore?.username && !$userStore?.is_admin}
|
||||
selected={role}
|
||||
let:item
|
||||
on:selected={async (e) => {
|
||||
const role = e.detail
|
||||
// const wasInFolder = (folder?.owners ?? []).includes(folder)
|
||||
@@ -315,28 +309,30 @@
|
||||
loadFolder()
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
value="viewer"
|
||||
label="Viewer"
|
||||
tooltip="A viewer of a folder has read-only access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
|
||||
{item}
|
||||
/>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="viewer"
|
||||
label="Viewer"
|
||||
tooltip="A viewer of a folder has read-only access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
|
||||
{item}
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="writer"
|
||||
label="Writer"
|
||||
tooltip="A writer of a folder has read AND write access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="writer"
|
||||
label="Writer"
|
||||
tooltip="A writer of a folder has read AND write access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
|
||||
{item}
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="admin"
|
||||
label="Admin"
|
||||
tooltip="An admin of a folder has read AND write access to all the elements inside the folders and can manage the permissions as well as add new admins"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="admin"
|
||||
label="Admin"
|
||||
tooltip="An admin of a folder has read AND write access to all the elements inside the folders and can manage the permissions as well as add new admins"
|
||||
{item}
|
||||
/>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
type InstanceGroup
|
||||
} from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Button } from './common'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
@@ -18,6 +17,7 @@
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import Section from './Section.svelte'
|
||||
import Label from './Label.svelte'
|
||||
import Select from './Select.svelte'
|
||||
|
||||
export let name: string
|
||||
let can_write = false
|
||||
@@ -129,7 +129,7 @@
|
||||
<Section label={`Members (${members?.length ?? 0})`}>
|
||||
{#if can_write}
|
||||
<div class="flex items-start">
|
||||
<AutoComplete required noInputStyles items={usernames} bind:selectedItem={username} />
|
||||
<Select items={usernames?.map((u) => ({ value: u, label: u }))} bind:value={username} />
|
||||
<Button variant="contained" color="blue" size="sm" btnClasses="!ml-4" on:click={addToGroup}>
|
||||
Add member
|
||||
</Button>
|
||||
@@ -150,7 +150,6 @@
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
selected={role}
|
||||
let:item
|
||||
on:selected={async (e) => {
|
||||
const role = e.detail
|
||||
// const wasInGroup = (group?.members ?? []).includes(group)
|
||||
@@ -211,31 +210,33 @@
|
||||
loadGroup()
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
value="member"
|
||||
size="xs"
|
||||
label="Member"
|
||||
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="admin"
|
||||
size="xs"
|
||||
label="Admin"
|
||||
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
|
||||
{item}
|
||||
/>
|
||||
{#if role === 'manager'}
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="manager"
|
||||
value="member"
|
||||
size="xs"
|
||||
label="Manager"
|
||||
tooltip="A manager of a group can manage the group, adding and removing users and
|
||||
change their roles. Being a manager does not make you a member"
|
||||
label="Member"
|
||||
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="admin"
|
||||
size="xs"
|
||||
label="Admin"
|
||||
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
|
||||
{item}
|
||||
/>
|
||||
{#if role === 'manager'}
|
||||
<ToggleButton
|
||||
value="manager"
|
||||
size="xs"
|
||||
label="Manager"
|
||||
tooltip="A manager of a group can manage the group, adding and removing users and
|
||||
change their roles. Being a manager does not make you a member"
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -8,21 +8,34 @@
|
||||
import HistoricList from './HistoricList.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
|
||||
export let runnableId: string | undefined = undefined
|
||||
export let runnableType: RunnableType | undefined = undefined
|
||||
export let loading: boolean = false
|
||||
export let selected: string | undefined = undefined
|
||||
export let showAuthor = false
|
||||
export let placement: 'bottom-start' | 'top-start' | 'bottom-end' | 'top-end' = 'top-end'
|
||||
export let limitPayloadSize = false
|
||||
export let searchArgs: Record<string, any> | undefined = undefined
|
||||
interface Props {
|
||||
runnableId?: string | undefined
|
||||
runnableType?: RunnableType | undefined
|
||||
loading?: boolean
|
||||
selected?: string | undefined
|
||||
showAuthor?: boolean
|
||||
placement?: 'bottom-start' | 'top-start' | 'bottom-end' | 'top-end'
|
||||
limitPayloadSize?: boolean
|
||||
searchArgs?: Record<string, any> | undefined
|
||||
}
|
||||
|
||||
let historicList: HistoricList | undefined = undefined
|
||||
let {
|
||||
runnableId = undefined,
|
||||
runnableType = undefined,
|
||||
loading = $bindable(false),
|
||||
selected = $bindable(undefined),
|
||||
showAuthor = false,
|
||||
placement = 'top-end',
|
||||
limitPayloadSize = false,
|
||||
searchArgs = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let historicList: HistoricList | undefined = $state(undefined)
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let jobs: Job[] = []
|
||||
let jobs: Job[] = $state([])
|
||||
let hasMoreCurrentRuns = false
|
||||
let page = 1
|
||||
let page = $state(1)
|
||||
|
||||
async function handleSelected(data: any) {
|
||||
if (selected === data.id) {
|
||||
@@ -34,7 +47,10 @@
|
||||
const fullPayload = await data.getFullPayload?.()
|
||||
dispatch('select', { args: fullPayload, jobId: data.id })
|
||||
} else {
|
||||
dispatch('select', { args: structuredClone(data.payloadData), jobId: data.id })
|
||||
dispatch('select', {
|
||||
args: structuredClone($state.snapshot(data.payloadData)),
|
||||
jobId: data.id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +93,11 @@
|
||||
} else if (runnableType === 'ScriptHash') {
|
||||
return 'script,preview'
|
||||
}
|
||||
return 'all'
|
||||
return ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
{#if runnableId}
|
||||
<JobLoader
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { InputService, type RunnableType } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { onDestroy, untrack } from 'svelte'
|
||||
import InfiniteList from './InfiniteList.svelte'
|
||||
import JobSchemaPicker from './schema/JobSchemaPicker.svelte'
|
||||
|
||||
export let runnableId: string | undefined
|
||||
export let runnableType: RunnableType | undefined
|
||||
export let selected: string | undefined = undefined
|
||||
export let showAuthor = false
|
||||
export let placement: 'bottom-start' | 'top-start' | 'bottom-end' | 'top-end' = 'bottom-start'
|
||||
export let limitPayloadSize = false
|
||||
export let searchArgs: Record<string, any> | undefined = undefined
|
||||
interface Props {
|
||||
runnableId: string | undefined
|
||||
runnableType: RunnableType | undefined
|
||||
selected?: string | undefined
|
||||
showAuthor?: boolean
|
||||
placement?: 'bottom-start' | 'top-start' | 'bottom-end' | 'top-end'
|
||||
limitPayloadSize?: boolean
|
||||
searchArgs?: Record<string, any> | undefined
|
||||
}
|
||||
|
||||
let infiniteList: InfiniteList | undefined = undefined
|
||||
let {
|
||||
runnableId,
|
||||
runnableType,
|
||||
selected = undefined,
|
||||
showAuthor = false,
|
||||
placement = 'bottom-start',
|
||||
limitPayloadSize = false,
|
||||
searchArgs = undefined
|
||||
}: Props = $props()
|
||||
|
||||
let infiniteList: InfiniteList | undefined = $state(undefined)
|
||||
let loadInputsPageFn: ((page: number, perPage: number) => Promise<any>) | undefined = undefined
|
||||
let viewerOpen = false
|
||||
let viewerOpen = $state(false)
|
||||
let openStates: Record<string, boolean> = {} // Track open state for each item
|
||||
|
||||
export async function refresh(clearCurrentRuns: boolean = false) {
|
||||
@@ -101,18 +113,20 @@
|
||||
viewerOpen = Object.values(openStates).some((state) => state)
|
||||
}
|
||||
|
||||
$: $workspaceStore && runnableId && runnableType && infiniteList && initLoadInputs()
|
||||
$effect(() => {
|
||||
$workspaceStore && runnableId && runnableType && infiniteList && untrack(() => initLoadInputs())
|
||||
})
|
||||
</script>
|
||||
|
||||
<InfiniteList bind:this={infiniteList} selectedItemId={selected} on:error on:select>
|
||||
<svelte:fragment slot="columns">
|
||||
{#snippet columns()}
|
||||
<colgroup>
|
||||
<col class="w-8" />
|
||||
<col class="w-16" />
|
||||
<col />
|
||||
</colgroup>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment let:item let:hover>
|
||||
{/snippet}
|
||||
{#snippet children({ item, hover })}
|
||||
<JobSchemaPicker
|
||||
job={item}
|
||||
hovering={hover}
|
||||
@@ -125,10 +139,10 @@
|
||||
}}
|
||||
{limitPayloadSize}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="empty">
|
||||
{/snippet}
|
||||
{#snippet empty()}
|
||||
<div class="text-center text-tertiary text-xs py-2">
|
||||
{runnableId ? 'No previous inputs' : 'Save draft to see previous runs'}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</InfiniteList>
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
<script>
|
||||
import { FileText } from 'lucide-svelte'
|
||||
import { APP_TO_ICON_COMPONENT } from './icons'
|
||||
export let name
|
||||
export let silent = false
|
||||
export let after = false
|
||||
export let height = '24px'
|
||||
export let width = '24px'
|
||||
export let center = false
|
||||
export let isSelected = false
|
||||
export let formatExtension = undefined
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
* @property {any} name
|
||||
* @property {boolean} [silent]
|
||||
* @property {boolean} [after]
|
||||
* @property {string} [height]
|
||||
* @property {string} [width]
|
||||
* @property {boolean} [center]
|
||||
* @property {boolean} [isSelected]
|
||||
* @property {any} [formatExtension]
|
||||
*/
|
||||
|
||||
$: iconComponent = name === "teams"
|
||||
? APP_TO_ICON_COMPONENT.ms_teams_webhook
|
||||
: APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
|
||||
/** @type {Props} */
|
||||
let {
|
||||
name,
|
||||
silent = false,
|
||||
after = false,
|
||||
height = '24px',
|
||||
width = '24px',
|
||||
center = false,
|
||||
isSelected = false,
|
||||
formatExtension = undefined
|
||||
} = $props()
|
||||
|
||||
let iconComponent = $derived(
|
||||
name === 'teams'
|
||||
? APP_TO_ICON_COMPONENT.ms_teams_webhook
|
||||
: APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="truncate flex flex-row gap-2 {center ? 'justify-center items-center' : ''} -pl-2">
|
||||
@@ -20,12 +37,13 @@
|
||||
{name}
|
||||
{/if}
|
||||
{#if iconComponent}
|
||||
{@const SvelteComponent = iconComponent}
|
||||
<span class={isSelected ? 'text-secondary' : 'text-secondary grayscale'}>
|
||||
<svelte:component this={iconComponent} {height} {width} />
|
||||
<SvelteComponent {height} {width} />
|
||||
</span>
|
||||
{:else if formatExtension}
|
||||
<span class={isSelected ? 'text-secondary' : 'text-secondary grayscale'}>
|
||||
<svelte:component this={FileText} {height} {width} />
|
||||
<FileText {height} {width} />
|
||||
</span>
|
||||
{:else}
|
||||
<span style="width: {width}; height: {height}" class="bg-gray-100 rounded-full"></span>
|
||||
@@ -34,4 +52,3 @@
|
||||
{name}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,26 +6,48 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let loading = false
|
||||
export let items: any[] | undefined = undefined
|
||||
export let selectedItemId: any | undefined = undefined
|
||||
export let isEmpty: boolean = true
|
||||
export let length: number = 0
|
||||
export let rounded: boolean = true
|
||||
export let noBorder: boolean = false
|
||||
export let extraRowClasses: { bgSelected: string; bgHover: string; class: string } = {
|
||||
bgSelected: '',
|
||||
bgHover: '',
|
||||
class: ''
|
||||
interface Props {
|
||||
loading?: boolean
|
||||
items?: any[] | undefined
|
||||
selectedItemId?: any | undefined
|
||||
isEmpty?: boolean
|
||||
length?: number
|
||||
rounded?: boolean
|
||||
noBorder?: boolean
|
||||
extraRowClasses?: { bgSelected: string; bgHover: string; class: string }
|
||||
neverShowLoader?: boolean
|
||||
columns?: import('svelte').Snippet
|
||||
extra_row?: import('svelte').Snippet<[any]>
|
||||
children?: import('svelte').Snippet<[any]>
|
||||
empty?: import('svelte').Snippet<[any]>
|
||||
}
|
||||
export let neverShowLoader = false
|
||||
|
||||
let {
|
||||
loading = $bindable(false),
|
||||
items = $bindable(undefined),
|
||||
selectedItemId = $bindable(undefined),
|
||||
isEmpty = $bindable(true),
|
||||
length = $bindable(0),
|
||||
rounded = true,
|
||||
noBorder = false,
|
||||
extraRowClasses = {
|
||||
bgSelected: '',
|
||||
bgHover: '',
|
||||
class: ''
|
||||
},
|
||||
neverShowLoader = false,
|
||||
columns,
|
||||
extra_row,
|
||||
children,
|
||||
empty
|
||||
}: Props = $props()
|
||||
|
||||
const perPage = 20
|
||||
|
||||
let hasMore = false
|
||||
let hasMore = $state(false)
|
||||
let page = 1
|
||||
let hasAlreadyFailed = false
|
||||
let hovered: any | undefined = undefined
|
||||
let hovered: any | undefined = $state(undefined)
|
||||
let initLoad = false
|
||||
let loadInputs: ((page: number, perPage: number) => Promise<any[]>) | undefined = undefined
|
||||
let deleteItemFn: ((id: any) => Promise<any>) | undefined = undefined
|
||||
@@ -39,7 +61,7 @@
|
||||
page = 1
|
||||
}
|
||||
|
||||
let loadingMore = false
|
||||
let loadingMore = $state(false)
|
||||
|
||||
export async function loadData(loadOption: 'refresh' | 'forceRefresh' | 'loadMore' = 'loadMore') {
|
||||
// console.log('loadData', loadOption, length, items?.length)
|
||||
@@ -146,7 +168,7 @@
|
||||
{noBorder}
|
||||
{neverShowLoader}
|
||||
>
|
||||
<slot name="columns" />
|
||||
{@render columns?.()}
|
||||
|
||||
<tbody class="h-full w-full">
|
||||
<Row
|
||||
@@ -158,7 +180,7 @@
|
||||
)}
|
||||
on:hover={(e) => (hovered = e.detail ? 'extraRow' : undefined)}
|
||||
>
|
||||
<slot name="extra-row" hover={hovered === 'extraRow'} />
|
||||
{@render extra_row?.({ hover: hovered === 'extraRow' })}
|
||||
</Row>
|
||||
{#each items ?? [] as item, index}
|
||||
{@const hover = item.id === hovered}
|
||||
@@ -171,16 +193,16 @@
|
||||
)}
|
||||
on:hover={(e) => (hovered = e.detail ? item.id : undefined)}
|
||||
>
|
||||
<slot {item} {hover} />
|
||||
{@render children?.({ item, hover })}
|
||||
</Row>
|
||||
{/each}
|
||||
</tbody>
|
||||
|
||||
<svelte:fragment slot="emptyMessage">
|
||||
{#snippet emptyMessage()}
|
||||
{#if (!items || items?.length === 0) && (!loading || neverShowLoader)}
|
||||
<slot name="empty" {items} />
|
||||
{@render empty?.({ items })}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</DataTable>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script context="module">
|
||||
<script module>
|
||||
const dynamicTemplateRegexPairs = buildPrefixRegex([
|
||||
'flow_input',
|
||||
'results',
|
||||
@@ -10,7 +10,7 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { InputCat } from '$lib/utils'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { createEventDispatcher, getContext, untrack } from 'svelte'
|
||||
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
@@ -37,40 +37,61 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import FlowPlugConnect from './FlowPlugConnect.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
export let schema: Schema | { properties?: Record<string, any>; required?: string[] }
|
||||
export let arg: InputTransform | any
|
||||
export let argName: string
|
||||
export let headerTooltip: string | undefined = undefined
|
||||
export let headerTooltipIconClass = ''
|
||||
export let HeaderTooltipIcon = InfoIcon
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let inputCheck: boolean = true
|
||||
export let previousModuleId: string | undefined
|
||||
export let pickForField: string | undefined = undefined
|
||||
export let variableEditor: VariableEditor | undefined = undefined
|
||||
export let itemPicker: ItemPicker | undefined = undefined
|
||||
export let noDynamicToggle = false
|
||||
export let argExtra: Record<string, any> = {}
|
||||
export let pickableProperties: PickableProperties | undefined = undefined
|
||||
export let enableAi = false
|
||||
export let hideHelpButton = false
|
||||
|
||||
let monaco: SimpleEditor | undefined = undefined
|
||||
let monacoTemplate: TemplateEditor | undefined = undefined
|
||||
let argInput: ArgInput | undefined = undefined
|
||||
interface Props {
|
||||
schema: Schema | { properties?: Record<string, any>; required?: string[] }
|
||||
arg: InputTransform | any
|
||||
argName: string
|
||||
headerTooltip?: string | undefined
|
||||
headerTooltipIconClass?: string
|
||||
HeaderTooltipIcon?: any
|
||||
extraLib?: string
|
||||
inputCheck?: boolean
|
||||
previousModuleId: string | undefined
|
||||
pickForField?: string | undefined
|
||||
variableEditor?: VariableEditor | undefined
|
||||
itemPicker?: ItemPicker | undefined
|
||||
noDynamicToggle?: boolean
|
||||
argExtra?: Record<string, any>
|
||||
pickableProperties?: PickableProperties | undefined
|
||||
enableAi?: boolean
|
||||
hideHelpButton?: boolean
|
||||
class?: string
|
||||
editor?: SimpleEditor | undefined
|
||||
}
|
||||
|
||||
let {
|
||||
schema = $bindable(),
|
||||
arg = $bindable(),
|
||||
argName = $bindable(),
|
||||
headerTooltip = undefined,
|
||||
headerTooltipIconClass = '',
|
||||
HeaderTooltipIcon = InfoIcon,
|
||||
extraLib = $bindable('missing extraLib'),
|
||||
inputCheck = $bindable(true),
|
||||
previousModuleId,
|
||||
pickForField = $bindable(undefined),
|
||||
variableEditor = undefined,
|
||||
itemPicker = undefined,
|
||||
noDynamicToggle = false,
|
||||
argExtra = {},
|
||||
pickableProperties = undefined,
|
||||
enableAi = false,
|
||||
hideHelpButton = false,
|
||||
class: className = '',
|
||||
editor = $bindable(undefined)
|
||||
}: Props = $props()
|
||||
|
||||
let monaco: SimpleEditor | undefined = $state(undefined)
|
||||
let monacoTemplate: TemplateEditor | undefined = $state(undefined)
|
||||
let argInput: ArgInput | undefined = $state(undefined)
|
||||
let focusedPrev = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: inputCat = computeInputCat(
|
||||
schema?.properties?.[argName]?.type,
|
||||
schema?.properties?.[argName]?.format,
|
||||
schema?.properties?.[argName]?.items?.type,
|
||||
schema?.properties?.[argName]?.enum,
|
||||
schema?.properties?.[argName]?.contentEncoding
|
||||
)
|
||||
|
||||
let propertyType = getPropertyType(arg)
|
||||
$effect(() => {
|
||||
editor = monaco
|
||||
})
|
||||
|
||||
const { shouldUpdatePropertyType, exprsToSet } =
|
||||
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
||||
@@ -79,6 +100,18 @@
|
||||
getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||
const { inputMatches, focusProp, propPickerConfig, clearFocus } = propPickerWrapperContext ?? {}
|
||||
|
||||
let inputCat = $derived(
|
||||
computeInputCat(
|
||||
schema?.properties?.[argName]?.type,
|
||||
schema?.properties?.[argName]?.format,
|
||||
schema?.properties?.[argName]?.items?.type,
|
||||
schema?.properties?.[argName]?.enum,
|
||||
schema?.properties?.[argName]?.contentEncoding
|
||||
)
|
||||
)
|
||||
|
||||
let propertyType = $state(getPropertyType(arg))
|
||||
|
||||
function setExpr() {
|
||||
const newArg = $exprsToSet?.[argName]
|
||||
if (newArg) {
|
||||
@@ -98,8 +131,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
$: $exprsToSet?.[argName] && setExpr()
|
||||
|
||||
function updatePropertyType() {
|
||||
propertyType = $shouldUpdatePropertyType?.[argName] || 'static'
|
||||
shouldUpdatePropertyType?.set({
|
||||
@@ -108,10 +139,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
$: $shouldUpdatePropertyType?.[argName] &&
|
||||
arg?.type === $shouldUpdatePropertyType?.[argName] &&
|
||||
updatePropertyType()
|
||||
|
||||
function getPropertyType(arg: InputTransform | any): 'static' | 'javascript' {
|
||||
let type: 'static' | 'javascript' = arg?.type ?? 'static'
|
||||
|
||||
@@ -153,7 +180,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let codeInjectionDetected = false
|
||||
let codeInjectionDetected = $state(false)
|
||||
|
||||
function checkCodeInjection(rawValue: string) {
|
||||
if (!arg || !rawValue || rawValue.length < 3 || !dynamicTemplateRegexPairs) {
|
||||
@@ -247,11 +274,10 @@
|
||||
}
|
||||
|
||||
let prevArg: any = undefined
|
||||
$: inputCat && propertyType && arg && onArgChange()
|
||||
function onArgChange() {
|
||||
const newArg = { arg, propertyType, inputCat }
|
||||
if (!deepEqual(newArg, prevArg)) {
|
||||
prevArg = structuredClone(newArg)
|
||||
prevArg = structuredClone($state.snapshot(newArg))
|
||||
updateStaticInput(inputCat, propertyType, arg)
|
||||
}
|
||||
}
|
||||
@@ -285,23 +311,39 @@
|
||||
}
|
||||
focusedPrev = focused
|
||||
}
|
||||
$: updateFocused(focused)
|
||||
|
||||
$: schema?.properties?.[argName]?.default && setDefaultCode()
|
||||
|
||||
let resourceTypes: string[] | undefined = undefined
|
||||
let resourceTypes: string[] | undefined = $state(undefined)
|
||||
|
||||
async function loadResourceTypes() {
|
||||
resourceTypes = await getResourceTypes()
|
||||
}
|
||||
|
||||
let focused = false
|
||||
let stepInputGen: StepInputGen | undefined = undefined
|
||||
let focused = $state(false)
|
||||
let stepInputGen: StepInputGen | undefined = $state(undefined)
|
||||
|
||||
loadResourceTypes()
|
||||
|
||||
$: connecting =
|
||||
$effect(() => {
|
||||
$exprsToSet?.[argName] && untrack(() => setExpr())
|
||||
})
|
||||
$effect(() => {
|
||||
$shouldUpdatePropertyType?.[argName] &&
|
||||
arg?.type === $shouldUpdatePropertyType?.[argName] &&
|
||||
untrack(() => updatePropertyType())
|
||||
})
|
||||
$effect(() => {
|
||||
inputCat && propertyType && arg && untrack(() => onArgChange())
|
||||
})
|
||||
$effect(() => {
|
||||
;[focused]
|
||||
untrack(() => updateFocused(focused))
|
||||
})
|
||||
$effect(() => {
|
||||
schema?.properties?.[argName]?.default && untrack(() => setDefaultCode())
|
||||
})
|
||||
let connecting = $derived(
|
||||
$propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if arg != undefined}
|
||||
@@ -311,7 +353,7 @@
|
||||
$propPickerConfig?.propName == argName
|
||||
? 'bg-surface border-l-4 border-blue-500 shadow-md rounded-l-md z-50 '
|
||||
: 'hover:rounded-md',
|
||||
$$props.class
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div class="flex flex-row justify-between gap-1 pb-1 px-2">
|
||||
@@ -375,7 +417,6 @@
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
selected={propertyType}
|
||||
let:item
|
||||
on:selected={(e) => {
|
||||
if (e.detail == propertyType) return
|
||||
const staticTemplate = isStaticTemplate(inputCat)
|
||||
@@ -428,41 +469,43 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton
|
||||
tooltip={`Write text or surround javascript with \`\$\{\` and \`\}\`. Use \`results\` to connect to another node\'s output.`}
|
||||
light
|
||||
value="static"
|
||||
size="xs2"
|
||||
label={'${}'}
|
||||
{item}
|
||||
/>
|
||||
{:else}
|
||||
<ToggleButton small label="Static" value="static" {item} />
|
||||
{/if}
|
||||
{#snippet children({ item })}
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton
|
||||
tooltip={`Write text or surround javascript with \`\$\{\` and \`\}\`. Use \`results\` to connect to another node\'s output.`}
|
||||
light
|
||||
value="static"
|
||||
size="xs2"
|
||||
label={'${}'}
|
||||
{item}
|
||||
/>
|
||||
{:else}
|
||||
<ToggleButton small label="Static" value="static" {item} />
|
||||
{/if}
|
||||
|
||||
{#if codeInjectionDetected && propertyType == 'static'}
|
||||
<Button
|
||||
size="xs2"
|
||||
color="light"
|
||||
btnClasses="font-normal text-xs w-fit bg-green-100 text-green-800 hover:bg-green-100 dark:text-green-300 dark:bg-green-700 dark:hover:bg-green-600"
|
||||
on:click={() => setJavaScriptExpr(arg.value)}
|
||||
>
|
||||
<span class="font-normal whitespace-nowrap flex gap-2 items-center"
|
||||
><FunctionSquare size={14} /> detected -
|
||||
<span class="font-bold">TAB</span>
|
||||
</span>
|
||||
</Button>
|
||||
{:else}
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
tooltip="JavaScript expression ('flow_input' or 'results')."
|
||||
value="javascript"
|
||||
icon={FunctionSquare}
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
{#if codeInjectionDetected && propertyType == 'static'}
|
||||
<Button
|
||||
size="xs2"
|
||||
color="light"
|
||||
btnClasses="font-normal text-xs w-fit bg-green-100 text-green-800 hover:bg-green-100 dark:text-green-300 dark:bg-green-700 dark:hover:bg-green-600"
|
||||
on:click={() => setJavaScriptExpr(arg.value)}
|
||||
>
|
||||
<span class="font-normal whitespace-nowrap flex gap-2 items-center"
|
||||
><FunctionSquare size={14} /> detected -
|
||||
<span class="font-bold">TAB</span>
|
||||
</span>
|
||||
</Button>
|
||||
{:else}
|
||||
<ToggleButton
|
||||
small
|
||||
light
|
||||
tooltip="JavaScript expression ('flow_input' or 'results')."
|
||||
value="javascript"
|
||||
icon={FunctionSquare}
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -491,8 +534,8 @@
|
||||
</div>
|
||||
|
||||
<div class="max-w-xs"></div>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="relative" on:keyup={handleKeyUp}>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="relative" onkeyup={handleKeyUp}>
|
||||
<!-- {#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'}
|
||||
<span
|
||||
class={'text-white z-50 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute top-0 right-0 bg-blue-500'}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { VariableService, type InputTransform } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { allTrue } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { untrack } from 'svelte'
|
||||
import { Button } from './common'
|
||||
import StepInputsGen from './copilot/StepInputsGen.svelte'
|
||||
import type { PickableProperties } from './flows/previousResults'
|
||||
@@ -12,31 +12,44 @@
|
||||
import VariableEditor from './VariableEditor.svelte'
|
||||
import { Plus } from 'lucide-svelte'
|
||||
|
||||
export let schema: Schema | { properties?: Record<string, any> }
|
||||
export let args: Record<string, InputTransform | any> = {}
|
||||
|
||||
export let isValid: boolean = true
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let previousModuleId: string | undefined = undefined
|
||||
|
||||
export let filter: string[] | undefined = undefined
|
||||
export let noDynamicToggle = false
|
||||
export let pickableProperties: PickableProperties | undefined = undefined
|
||||
export let enableAi = false
|
||||
|
||||
let clazz: string = ''
|
||||
export { clazz as class }
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: isValid = allTrue(inputCheck) ?? false
|
||||
|
||||
$: if (args == undefined || typeof args !== 'object') {
|
||||
args = {}
|
||||
interface Props {
|
||||
schema: Schema | { properties?: Record<string, any> }
|
||||
args?: Record<string, InputTransform | any>
|
||||
isValid?: boolean
|
||||
extraLib?: string
|
||||
previousModuleId?: string | undefined
|
||||
filter?: string[] | undefined
|
||||
noDynamicToggle?: boolean
|
||||
pickableProperties?: PickableProperties | undefined
|
||||
enableAi?: boolean
|
||||
class?: string
|
||||
}
|
||||
|
||||
let {
|
||||
schema = $bindable(),
|
||||
args = $bindable({}),
|
||||
isValid = $bindable(true),
|
||||
extraLib = $bindable('missing extraLib'),
|
||||
previousModuleId = undefined,
|
||||
filter = undefined,
|
||||
noDynamicToggle = false,
|
||||
pickableProperties = undefined,
|
||||
enableAi = false,
|
||||
class: clazz = ''
|
||||
}: Props = $props()
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = $state({})
|
||||
|
||||
$effect(() => {
|
||||
isValid = allTrue(inputCheck) ?? false
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (args == undefined || typeof args !== 'object') {
|
||||
args = {}
|
||||
}
|
||||
})
|
||||
|
||||
export function setArgs(nargs: Record<string, InputTransform | any>) {
|
||||
args = nargs
|
||||
}
|
||||
@@ -51,18 +64,18 @@
|
||||
args = nargs
|
||||
}
|
||||
|
||||
let pickForField: string | undefined
|
||||
let itemPicker: ItemPicker | undefined = undefined
|
||||
let variableEditor: VariableEditor | undefined = undefined
|
||||
let pickForField: string | undefined = $state()
|
||||
let itemPicker: ItemPicker | undefined = $state(undefined)
|
||||
let variableEditor: VariableEditor | undefined = $state(undefined)
|
||||
|
||||
let keys: string[] = []
|
||||
$: {
|
||||
let keys: string[] = $state([])
|
||||
$effect(() => {
|
||||
let lkeys = Object.keys(schema?.properties ?? {})
|
||||
if (schema?.properties && JSON.stringify(lkeys) != JSON.stringify(keys)) {
|
||||
keys = lkeys
|
||||
removeExtraKey()
|
||||
untrack(() => removeExtraKey())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="w-full {clazz}">
|
||||
@@ -84,15 +97,17 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if keys.length > 0}
|
||||
{#each keys as argName (argName)}
|
||||
{#each keys as argName, index (argName)}
|
||||
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
|
||||
<div class="pt-2 relative">
|
||||
<InputTransformForm
|
||||
{previousModuleId}
|
||||
bind:arg={args[argName]}
|
||||
bind:schema
|
||||
bind:argName
|
||||
bind:inputCheck={inputCheck[argName]}
|
||||
bind:argName={keys[index]}
|
||||
bind:inputCheck={
|
||||
() => inputCheck[argName] ?? false, (value) => (inputCheck[argName] = value)
|
||||
}
|
||||
bind:extraLib
|
||||
{variableEditor}
|
||||
{itemPicker}
|
||||
@@ -100,10 +115,6 @@
|
||||
{noDynamicToggle}
|
||||
{pickableProperties}
|
||||
{enableAi}
|
||||
on:change={(e) => {
|
||||
const { argName } = e.detail
|
||||
dispatch('changeArg', { argName })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -128,22 +139,21 @@
|
||||
...x
|
||||
}))}
|
||||
>
|
||||
<div
|
||||
slot="submission"
|
||||
class="flex flex-row-reverse w-full border-t border-gray-200 rounded-bl-lg rounded-br-lg"
|
||||
>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
on:click={() => {
|
||||
variableEditor?.initNew?.()
|
||||
}}
|
||||
>
|
||||
New variable
|
||||
</Button>
|
||||
</div>
|
||||
{#snippet submission()}
|
||||
<div class="flex flex-row-reverse w-full border-t border-gray-200 rounded-bl-lg rounded-br-lg">
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
on:click={() => {
|
||||
variableEditor?.initNew?.()
|
||||
}}
|
||||
>
|
||||
New variable
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</ItemPicker>
|
||||
|
||||
<VariableEditor bind:this={variableEditor} />
|
||||
|
||||
@@ -41,10 +41,14 @@
|
||||
import TeamSelector from './TeamSelector.svelte'
|
||||
import ChannelSelector from './ChannelSelector.svelte'
|
||||
|
||||
export let setting: Setting
|
||||
export let version: string
|
||||
export let values: Writable<Record<string, any>>
|
||||
export let loading = true
|
||||
interface Props {
|
||||
setting: Setting
|
||||
version: string
|
||||
values: Writable<Record<string, any>>
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
let { setting, version, values, loading = true }: Props = $props()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
if (
|
||||
@@ -57,9 +61,9 @@
|
||||
let latestKeyRenewalAttempt: {
|
||||
result: string
|
||||
attempted_at: string
|
||||
} | null
|
||||
} | null = $state(null)
|
||||
|
||||
let isFetching = false
|
||||
let isFetching = $state(false)
|
||||
|
||||
function showSetting(setting: string, values: Record<string, any>) {
|
||||
if (setting == 'dev_instance') {
|
||||
@@ -70,11 +74,11 @@
|
||||
return true
|
||||
}
|
||||
|
||||
let licenseKeyChanged = false
|
||||
let renewing = false
|
||||
let opening = false
|
||||
let licenseKeyChanged = $state(false)
|
||||
let renewing = $state(false)
|
||||
let opening = $state(false)
|
||||
|
||||
let to: string = ''
|
||||
let to: string = $state('')
|
||||
|
||||
async function reloadKeyrenewalAttemptInfo() {
|
||||
latestKeyRenewalAttempt = await SettingService.getLatestKeyRenewalAttempt()
|
||||
@@ -135,9 +139,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
let pythonAvailableVersions: ListAvailablePythonVersionsResponse = []
|
||||
let pythonAvailableVersions: ListAvailablePythonVersionsResponse = $state([])
|
||||
|
||||
let isPyFetching = false
|
||||
let isPyFetching = $state(false)
|
||||
async function fetch_available_python_versions() {
|
||||
if (isPyFetching) return
|
||||
isPyFetching = true
|
||||
@@ -200,7 +204,7 @@
|
||||
</script>
|
||||
|
||||
<!-- {JSON.stringify($values, null, 2)} -->
|
||||
{#if (!setting.cloudonly || isCloudHosted()) && showSetting(setting.key, $values) && !(setting.hiddenIfNull && $values[setting.key] == null)}
|
||||
{#if (!setting.cloudonly || isCloudHosted()) && showSetting(setting.key, $values) && !(setting.hiddenIfNull && $values[setting.key] == null) && !(setting.hiddenIfEmpty && !$values[setting.key])}
|
||||
{#if setting.ee_only != undefined && !$enterpriseLicense}
|
||||
<div class="flex text-xs items-center gap-1 text-yellow-500 whitespace-nowrap">
|
||||
<AlertTriangle size={16} />
|
||||
@@ -209,7 +213,7 @@
|
||||
{/if}
|
||||
{#if setting.fieldType == 'select'}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
@@ -218,20 +222,22 @@
|
||||
</span>
|
||||
{/if}
|
||||
</label>
|
||||
<ToggleButtonGroup bind:selected={$values[setting.key]} let:item={toggleButton}>
|
||||
{#each setting.select_items ?? [] as item}
|
||||
<ToggleButton
|
||||
value={item.value ?? item.label}
|
||||
label={item.label}
|
||||
tooltip={item.tooltip}
|
||||
item={toggleButton}
|
||||
/>
|
||||
{/each}
|
||||
<ToggleButtonGroup bind:selected={$values[setting.key]}>
|
||||
{#snippet children({ item: toggleButton })}
|
||||
{#each setting.select_items ?? [] as item}
|
||||
<ToggleButton
|
||||
value={item.value ?? item.label}
|
||||
label={item.label}
|
||||
tooltip={item.tooltip}
|
||||
item={toggleButton}
|
||||
/>
|
||||
{/each}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else if setting.fieldType == 'select_python'}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
@@ -241,56 +247,59 @@
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<ToggleButtonGroup bind:selected={$values[setting.key]} let:item={toggleButtonn}>
|
||||
{#each setting.select_items ?? [] as item}
|
||||
<ToggleButton
|
||||
value={item.value ?? item.label}
|
||||
label={item.label}
|
||||
tooltip={item.tooltip}
|
||||
item={toggleButtonn}
|
||||
/>
|
||||
{/each}
|
||||
<PopoverMelt closeButton={!isPyFetching}>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#if setting.select_items?.some((e) => e.label == $values[setting.key] || e.value == $values[setting.key])}
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
btnClasses="px-1.5 py-1.5 text-2xs bg-surface-secondary border-0"
|
||||
nonCaptureEvent={true}>Select Custom</Button
|
||||
>
|
||||
{:else}
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
btnClasses="px-1.5 py-1.5 text-2xs border-0 shadow-md"
|
||||
nonCaptureEvent={true}>Custom | {$values[setting.key]}</Button
|
||||
>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{#if isPyFetching}
|
||||
<div class="p-4">
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
{:else}
|
||||
<ToggleButtonGroup
|
||||
bind:selected={$values[setting.key]}
|
||||
let:item={toggleButtonn}
|
||||
class="mr-10 h-full"
|
||||
tabListClass="flex-wrap p-2"
|
||||
>
|
||||
{#each pythonAvailableVersions as item}
|
||||
<ToggleButton value={item} label={item} tooltip={item} item={toggleButtonn} />
|
||||
{/each}
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</PopoverMelt>
|
||||
<ToggleButtonGroup bind:selected={$values[setting.key]}>
|
||||
{#snippet children({ item: toggleButtonn })}
|
||||
{#each setting.select_items ?? [] as item}
|
||||
<ToggleButton
|
||||
value={item.value ?? item.label}
|
||||
label={item.label}
|
||||
tooltip={item.tooltip}
|
||||
item={toggleButtonn}
|
||||
/>
|
||||
{/each}
|
||||
<PopoverMelt closeButton={!isPyFetching}>
|
||||
{#snippet trigger()}
|
||||
{#if setting.select_items?.some((e) => e.label == $values[setting.key] || e.value == $values[setting.key])}
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
btnClasses="px-1.5 py-1.5 text-2xs bg-surface-secondary border-0"
|
||||
nonCaptureEvent={true}>Select Custom</Button
|
||||
>
|
||||
{:else}
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
btnClasses="px-1.5 py-1.5 text-2xs border-0 shadow-md"
|
||||
nonCaptureEvent={true}>Custom | {$values[setting.key]}</Button
|
||||
>
|
||||
{/if}
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
{#if isPyFetching}
|
||||
<div class="p-4">
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
{:else}
|
||||
<ToggleButtonGroup
|
||||
bind:selected={$values[setting.key]}
|
||||
class="mr-10 h-full"
|
||||
tabListClass="flex-wrap p-2"
|
||||
>
|
||||
{#snippet children({ item: toggleButtonn })}
|
||||
{#each pythonAvailableVersions as item}
|
||||
<ToggleButton value={item} label={item} tooltip={item} item={toggleButtonn} />
|
||||
{/each}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</PopoverMelt>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
@@ -439,27 +448,29 @@
|
||||
on {attemptedAt}
|
||||
</span>
|
||||
</div>
|
||||
<div slot="text">
|
||||
{#if latestKeyRenewalAttempt.result === 'success'}
|
||||
<span class="text-green-300">
|
||||
Latest key renewal succeeded on {attemptedAt}
|
||||
</span>
|
||||
{:else if isTrial}
|
||||
<span class="text-yellow-300">
|
||||
License key cannot be renewed during trial ({attemptedAt})
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-red-300">
|
||||
Latest key renewal failed on {attemptedAt}: {latestKeyRenewalAttempt.result.replace(
|
||||
'error: ',
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
{/if}
|
||||
<br />
|
||||
As long as invoices are paid and usage corresponds to the subscription, the key is
|
||||
renewed daily with a validity of 35 days (grace period).
|
||||
</div>
|
||||
{#snippet text()}
|
||||
<div>
|
||||
{#if latestKeyRenewalAttempt?.result === 'success'}
|
||||
<span class="text-green-300">
|
||||
Latest key renewal succeeded on {attemptedAt}
|
||||
</span>
|
||||
{:else if isTrial}
|
||||
<span class="text-yellow-300">
|
||||
License key cannot be renewed during trial ({attemptedAt})
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-red-300">
|
||||
Latest key renewal failed on {attemptedAt}: {latestKeyRenewalAttempt?.result.replace(
|
||||
'error: ',
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
{/if}
|
||||
<br />
|
||||
As long as invoices are paid and usage corresponds to the subscription, the key
|
||||
is renewed daily with a validity of 35 days (grace period).
|
||||
</div>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -515,7 +526,7 @@
|
||||
<div class="flex w-full max-w-lg mt-1 gap-2 items-center">
|
||||
<select
|
||||
class="max-w-24"
|
||||
on:change={(e) => {
|
||||
onchange={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
$values[setting.key][i] = {
|
||||
[e.target['value']]: ''
|
||||
@@ -540,7 +551,7 @@
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Slack channel"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
$values[setting.key][i] = {
|
||||
slack_channel: e.target['value']
|
||||
@@ -598,7 +609,7 @@
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<button on:click={fetchTeams} class="flex items-center gap-1 mt-2">
|
||||
<button onclick={fetchTeams} class="flex items-center gap-1 mt-2">
|
||||
<RefreshCcw size={16} class={isFetching ? 'animate-spin' : ''} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -607,7 +618,7 @@
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
$values[setting.key][i] = {
|
||||
email: e.target['value']
|
||||
@@ -621,7 +632,7 @@
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
$values[setting.key] = $values[setting.key].filter(
|
||||
(_, index) => index !== i
|
||||
)
|
||||
@@ -718,7 +729,7 @@
|
||||
type="number"
|
||||
id="writer_memory_budget"
|
||||
placeholder="300"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
if (e.target.valueAsNumber) {
|
||||
$values[setting.key].writer_memory_budget =
|
||||
@@ -776,7 +787,7 @@
|
||||
type="number"
|
||||
id="max_indexed_job_log_size"
|
||||
placeholder="1024"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
if (e.target.valueAsNumber) {
|
||||
$values[setting.key].max_indexed_job_log_size =
|
||||
|
||||
@@ -16,20 +16,29 @@
|
||||
import InstanceSetting from './InstanceSetting.svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export let tab: string = 'Core'
|
||||
export let hideTabs: boolean = false
|
||||
export let hideSave: boolean = false
|
||||
export let closeDrawer: (() => void) | undefined = () => {}
|
||||
interface Props {
|
||||
tab?: string
|
||||
hideTabs?: boolean
|
||||
hideSave?: boolean
|
||||
closeDrawer?: (() => void) | undefined
|
||||
}
|
||||
|
||||
let {
|
||||
tab = $bindable('Core'),
|
||||
hideTabs = false,
|
||||
hideSave = false,
|
||||
closeDrawer = () => {}
|
||||
}: Props = $props()
|
||||
|
||||
let values: Writable<Record<string, any>> = writable({})
|
||||
let initialOauths: Record<string, any> = {}
|
||||
let initialRequirePreexistingUserForOauth: boolean = false
|
||||
let requirePreexistingUserForOauth: boolean = false
|
||||
let requirePreexistingUserForOauth: boolean = $state(false)
|
||||
|
||||
let initialValues: Record<string, any> = {}
|
||||
let snowflakeAccountIdentifier = ''
|
||||
let version: string = ''
|
||||
let loading = true
|
||||
let snowflakeAccountIdentifier = $state('')
|
||||
let version: string = $state('')
|
||||
let loading = $state(true)
|
||||
|
||||
loadSettings()
|
||||
loadVersion()
|
||||
@@ -39,7 +48,7 @@
|
||||
async function loadVersion() {
|
||||
version = await SettingsService.backendVersion()
|
||||
}
|
||||
let oauths: Record<string, any> = {}
|
||||
let oauths: Record<string, any> = $state({})
|
||||
|
||||
async function loadSettings() {
|
||||
loading = true
|
||||
@@ -240,13 +249,13 @@
|
||||
</script>
|
||||
|
||||
<div class="pb-8">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<Tabs {hideTabs} bind:selected={tab}>
|
||||
{#each settingsKeys as category}
|
||||
<Tab value={category}>{category}</Tab>
|
||||
{/each}
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
{#snippet content()}
|
||||
{#each Object.keys(settings) as category}
|
||||
<TabContent value={category}>
|
||||
{#if category == 'SMTP'}
|
||||
@@ -332,7 +341,7 @@
|
||||
bind:snowflakeAccountIdentifier
|
||||
bind:requirePreexistingUserForOauth
|
||||
>
|
||||
<svelte:fragment slot="scim">
|
||||
{#snippet scim()}
|
||||
<div class="flex-col flex gap-2 pb-4">
|
||||
{#each scimSamlSetting as setting}
|
||||
<InstanceSetting
|
||||
@@ -344,7 +353,7 @@
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</AuthSettings>
|
||||
{/if}
|
||||
<div>
|
||||
@@ -362,7 +371,7 @@
|
||||
</div>
|
||||
</TabContent>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let email: string
|
||||
let email: string = $state('')
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
const key = event.key
|
||||
@@ -52,7 +52,7 @@
|
||||
goto('#superadmin-settings')
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
: []
|
||||
)
|
||||
}
|
||||
@@ -62,45 +62,47 @@
|
||||
email = ''
|
||||
}
|
||||
|
||||
let selected: 'operator' | 'developer' | 'admin' = 'developer'
|
||||
let selected: 'operator' | 'developer' | 'admin' = $state('developer')
|
||||
</script>
|
||||
|
||||
<Popover floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: MailPlus }}>
|
||||
Invite
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<input
|
||||
type="email"
|
||||
on:keyup={handleKeyUp}
|
||||
onkeyup={handleKeyUp}
|
||||
placeholder="email"
|
||||
bind:value={email}
|
||||
class="mr-4"
|
||||
/>
|
||||
<ToggleButtonGroup bind:selected let:item>
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButtonGroup bind:selected>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="operator"
|
||||
label="Operator"
|
||||
tooltip="An operator can only execute and view scripts/flows/apps from your workspace, and only those that he has visibility on."
|
||||
{item}
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="developer"
|
||||
label="Developer"
|
||||
tooltip="A Developer can execute and view scripts/flows/apps, but they can also create new ones and edit those they are allowed to by their path (either u/ or Writer or Admin of their folder found at /f)."
|
||||
{item}
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
value="admin"
|
||||
label="Admin"
|
||||
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value="admin"
|
||||
label="Admin"
|
||||
tooltip="An admin has full control over a specific Windmill workspace, including the ability to manage users, edit entities, and control permissions within the workspace."
|
||||
{item}
|
||||
/>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -112,5 +114,5 @@
|
||||
Invite
|
||||
</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user