mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-20 16:02:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db033e36a | ||
|
|
a804e013c2 | ||
|
|
189a2815a0 | ||
|
|
4ef38233c1 | ||
|
|
ee7db8c8e3 | ||
|
|
183361df85 | ||
|
|
7c64f0a5b2 | ||
|
|
2a88fcbfd8 | ||
|
|
a859705efd | ||
|
|
e0479e05f7 | ||
|
|
b57afc8f68 | ||
|
|
0c661220cf | ||
|
|
53eeef0503 | ||
|
|
f626a361c4 | ||
|
|
ba76d8749a | ||
|
|
ff4ea86155 | ||
|
|
25a460be85 | ||
|
|
e5db2782f5 | ||
|
|
b2111b7d11 | ||
|
|
1042a06a12 | ||
|
|
0567d74abf | ||
|
|
3f0cd6349d | ||
|
|
f1f9fe513b | ||
|
|
a43923b326 | ||
|
|
9d8a75c3ca | ||
|
|
59d731c35d | ||
|
|
4bfda45c72 | ||
|
|
7007f14a10 | ||
|
|
da999d0f9b | ||
|
|
37c88272f5 | ||
|
|
97ee8c35ea | ||
|
|
a82184beb1 | ||
|
|
faaef3016d | ||
|
|
5c5b98e0b8 | ||
|
|
d166c563d9 | ||
|
|
4fa9025786 | ||
|
|
a1dc0fd7c2 | ||
|
|
a0813939fe |
@@ -1,5 +1,87 @@
|
||||
# Changelog
|
||||
|
||||
## [1.353.0](https://github.com/windmill-labs/windmill/compare/v1.352.0...v1.353.0) (2024-06-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** add an indicator when a for loop has no iterator expression ([#3961](https://github.com/windmill-labs/windmill/issues/3961)) ([ee7db8c](https://github.com/windmill-labs/windmill/commit/ee7db8c8e33eb990279d5badc95dc54b0c6124dd))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** Add missing on change event when connecting an input ([#3964](https://github.com/windmill-labs/windmill/issues/3964)) ([a804e01](https://github.com/windmill-labs/windmill/commit/a804e013c2672478469edd5e512d7ce63ba92bcd))
|
||||
* **frontend:** improve disable style of the select component ([#3938](https://github.com/windmill-labs/windmill/issues/3938)) ([183361d](https://github.com/windmill-labs/windmill/commit/183361df85e5efbcea4a53bddc3e641cbf73acd2))
|
||||
* Improve cancel selected jobs action ([#3960](https://github.com/windmill-labs/windmill/issues/3960)) ([4ef3823](https://github.com/windmill-labs/windmill/commit/4ef38233c1a21b143456fe79d5675b2ad8f3606e))
|
||||
|
||||
## [1.352.0](https://github.com/windmill-labs/windmill/compare/v1.351.0...v1.352.0) (2024-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add extra metadata to folders/resources/types + path prefix filtering for all + improve groups UI ([#3936](https://github.com/windmill-labs/windmill/issues/3936)) ([b57afc8](https://github.com/windmill-labs/windmill/commit/b57afc8f68f691ad5ef7fb16aafe6fae7d1138e9))
|
||||
* fallback to default hub if official script not found on private hub ([#3951](https://github.com/windmill-labs/windmill/issues/3951)) ([0c66122](https://github.com/windmill-labs/windmill/commit/0c661220cf1fb7eb37ad7f39b1fa6d29570edda2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cache wasm ([53eeef0](https://github.com/windmill-labs/windmill/commit/53eeef05032f04637249c2557d6d864f9446a9a1))
|
||||
* improve perf of forloop with parallelism massively ([e0479e0](https://github.com/windmill-labs/windmill/commit/e0479e05f7b1a212906afd8c5e268f3d48266311))
|
||||
|
||||
## [1.351.0](https://github.com/windmill-labs/windmill/compare/v1.350.3...v1.351.0) (2024-06-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** improve approval form edition + add a delete button t… ([#3946](https://github.com/windmill-labs/windmill/issues/3946)) ([25a460b](https://github.com/windmill-labs/windmill/commit/25a460be858ce020c40cd120e5aef2a79ba362d6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve support of relative paths in subflows and apps ([ba76d87](https://github.com/windmill-labs/windmill/commit/ba76d8749a9524ed356611c09f874da60f3b954a))
|
||||
|
||||
## [1.350.3](https://github.com/windmill-labs/windmill/compare/v1.350.2...v1.350.3) (2024-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** fix push flow support for windows ([1042a06](https://github.com/windmill-labs/windmill/commit/1042a06a12ae31d31facd1dfab10cbd2b10c86f5))
|
||||
|
||||
## [1.350.2](https://github.com/windmill-labs/windmill/compare/v1.350.1...v1.350.2) (2024-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** support windows more thoroughly + fix generate flow locks ([#3944](https://github.com/windmill-labs/windmill/issues/3944)) ([3f0cd63](https://github.com/windmill-labs/windmill/commit/3f0cd6349de37358f3942772254e2509383305b4))
|
||||
|
||||
## [1.350.1](https://github.com/windmill-labs/windmill/compare/v1.350.0...v1.350.1) (2024-06-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** fix inital sync pull generating wrong flow lockfile ([a43923b](https://github.com/windmill-labs/windmill/commit/a43923b3266b7b5781b0a5dc672c401377ae6e20))
|
||||
|
||||
## [1.350.0](https://github.com/windmill-labs/windmill/compare/v1.349.1...v1.350.0) (2024-06-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** add indicator when required field are missing ([#3935](https://github.com/windmill-labs/windmill/issues/3935)) ([7007f14](https://github.com/windmill-labs/windmill/commit/7007f14a1073f8f43b2500fae9c75d4222928802))
|
||||
|
||||
## [1.349.1](https://github.com/windmill-labs/windmill/compare/v1.349.0...v1.349.1) (2024-06-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* show workers without worker groups ([5c5b98e](https://github.com/windmill-labs/windmill/commit/5c5b98e0b8f0926d5f81c1894de8d367201da2d8))
|
||||
|
||||
## [1.349.0](https://github.com/windmill-labs/windmill/compare/v1.348.2...v1.349.0) (2024-06-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** improve range ([#3924](https://github.com/windmill-labs/windmill/issues/3924)) ([a1dc0fd](https://github.com/windmill-labs/windmill/commit/a1dc0fd7c22244d97429dbb65ded23e27badec6a))
|
||||
|
||||
## [1.348.2](https://github.com/windmill-labs/windmill/compare/v1.348.1...v1.348.2) (2024-06-19)
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.
|
||||
|
||||
RUN cd /backend/windmill-api && . ./build_openapi.sh
|
||||
COPY /backend/parsers/windmill-parser-wasm/pkg/ /backend/parsers/windmill-parser-wasm/pkg/
|
||||
COPY /typescript-client/docs/ /frontend/static/tsdocs/
|
||||
|
||||
RUN npm run generate-backend-client
|
||||
ENV NODE_OPTIONS "--max-old-space-size=8192"
|
||||
|
||||
+12
@@ -22,6 +22,16 @@
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -34,6 +44,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET last_ping = now() WHERE id = $1 AND workspace_id = $2",
|
||||
"query": "UPDATE queue SET last_ping = now() WHERE id = $1 AND workspace_id = $2 AND canceled = false",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -11,5 +11,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "45c9ecf8b1f8cbca7c75dab24a1eb6da8ceb45258ee5817ec71e73bebbe415bd"
|
||||
"hash": "099e7c7a66968575f896e0c11ecd9cfe9a2ec315d6589e940be157a0563f81af"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET suspend = suspend - 1 WHERE id = $1 AND suspend > 0",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0c349ac832cbf055fce755c8e2081eacf2b836b0396ec48a6cc2405fb7aa76de"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET running = false, started_at = null WHERE id = $1",
|
||||
"query": "UPDATE queue SET running = false, started_at = null WHERE id = $1 AND canceled = false",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -10,5 +10,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c05be905e46c5b0a2186ba859a725495e55df7f2ad839aea22d0286525eb823e"
|
||||
"hash": "215e0d320a304c8cb9ef12e7ea98a4eafb2456c123f9b6b96bb4ba2409166e5a"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue\n SET last_ping = now()\n WHERE id = $1 AND last_ping < now()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "262c7b21e77a8d2943fefb9cabe1e60c7c3b4e3ce7ed6b2b3eb78dd99b7d8fcf"
|
||||
}
|
||||
+17
-5
@@ -35,31 +35,41 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "is_expired",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 9,
|
||||
"name": "is_refreshed",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 10,
|
||||
"name": "refresh_error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 11,
|
||||
"name": "is_linked",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 12,
|
||||
"name": "is_oauth?",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 13,
|
||||
"name": "account",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
@@ -77,6 +87,8 @@
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, edited_at = now()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -9,10 +9,11 @@
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "489a62b5943a7a21ce487aa7b72a63dfc6300dd93bc29f5ec4cb1bfc471ad0bf"
|
||||
"hash": "42df4b40b3bbf14010f07e29892776992bfeb383d590e379244ad23703e536a4"
|
||||
}
|
||||
+13
-1
@@ -32,6 +32,16 @@
|
||||
"ordinal": 5,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -45,7 +55,9 @@
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "45e4d13f5806122faecdb1d9ab18159555b652869a036b006f4a151e999b17b7"
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE resource SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5eb1e072a6cc6ace25a5806b5adfdc0ae5c89c556daf491f39e629951a912c6c"
|
||||
}
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, $2, $3, $4, $5)",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, now())",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -9,10 +9,12 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Jsonb"
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e33d0bec7e08c0bb4e6f26c93a636707714699e8984b9f537085e773849c8b43"
|
||||
"hash": "5f3f1f1ca72b0392f227d22adef9ef795049e60a823e8ce754a3f9d91ef1b6bb"
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"name": "bool",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6e6d781fe63602541f7cb4806d5ddde0792e1861ea8761b4875677c3d0594a86"
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, description = $4, resource_type = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "726f77c2c1ef63eece7a1176bcbb916987fc29b04b889544a064dc7cd5eab36f"
|
||||
}
|
||||
+12
@@ -22,6 +22,16 @@
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -33,6 +43,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7ded28aae42230da6e3aa6fa952268e8e15cc8b16770c20ec6be2a9e82ab93c9"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "98c8e2c58b38beedcb474251c2554a2f1b768ec7ed1a419313f4842f26425e5d"
|
||||
}
|
||||
+21
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
|
||||
"query": "SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -27,6 +27,21 @@
|
||||
"ordinal": 4,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "summary",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -41,8 +56,11 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "8543f029d9784234e4c6a6dcd7b03e62d544b98be261334ee210594e0bb839f2"
|
||||
"hash": "9c6d44ffae63b4050ef3a66cb05d5a4ee50dea4b30093df2e0ceb684201ca4b0"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET canceled = true, canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = $3 AND workspace_id = $4 RETURNING id",
|
||||
"query": "UPDATE queue SET canceled = true, canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = $3 AND workspace_id = $4 AND canceled = false RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -21,5 +21,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f1f7a27738deae46de6acc10cbff701ff0c3872cc5b387e304bd9a058bb0c8a8"
|
||||
"hash": "aa923ddc251f5daf4bf075cb251dfddf87877f12fbbc8fbf3cfd08f43bbad8a0"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder\n (workspace_id, name, display_name, owners, extra_perms)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING",
|
||||
"query": "INSERT INTO folder\n (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -9,10 +9,11 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Jsonb"
|
||||
"Jsonb",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f2aee3ae39c90e40dd1835befc339e5381cc9104933cf8e90d840a9bf638ff52"
|
||||
"hash": "ad3b0176e3d41a0c1d79a8eb59da2a9cc4fee3473b3f6ea69f397d91e9356ba1"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b7ac3383a058ab3bbd7280300cebc5f81eeb9e96622ad2557f810a8b91641b89"
|
||||
}
|
||||
+12
@@ -22,6 +22,16 @@
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -33,6 +43,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id FROM queue WHERE parent_job = $1 AND suspend > 0 ORDER by suspend",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b8d97d300ffe6fae99f2396ae07cef03903752d17ce3fdadca47d86da75139aa"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4",
|
||||
"query": "UPDATE resource SET path = $1, value = $2, edited_at = now() WHERE path = $3 AND workspace_id = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -13,5 +13,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8c0131a9cc61f2daa258d49767242bcaab6bb34a977ff7fb0c18aa9202d11f47"
|
||||
"hash": "be10033b37dc9cbc38cc56afa8ef93cd7dbe267525d78cbfea87bb7c7cbf8ec4"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE folder SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c2a6e8f38d2293c45ede26b89a976f1a32f5bc2323a28f640cf3301a299f1cf4"
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, edited_at = now()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,10 +8,11 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ad9ccde8d831461f1f312d530867714577a825cd40d6655ed61b0b343b5d4482"
|
||||
"hash": "c4ee16065fa021cf6443c2f29f1ca986b9730eae9df26dd33c3ea21e35df62c4"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)\n DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c69719d0a63b0ca434c3317529e00e4d0df0104b6c1dbdf6d0f68f5047a2ad5e"
|
||||
}
|
||||
+21
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE name = $1 AND workspace_id = $2",
|
||||
"query": "SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE name = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -27,6 +27,21 @@
|
||||
"ordinal": 4,
|
||||
"name": "extra_perms",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "summary",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -40,8 +55,11 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3911bf3bbc82d87366a5297496fd2350a252bcd69f1c7c972bf566ee7eb28b0a"
|
||||
"hash": "cb20f04352364f112ec564617722354577dc5f77169fd4573317454e47361d65"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource (workspace_id, path, value, description, resource_type, created_by, edited_at) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme', $2, now()) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cccf9e216d84dfbb801b92d697496f44d93b344190a26c8393867b3a268b9aba"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE resource_type SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d1b508e1d8496e5e7aee9db555f9740128d87fe5183f9b7b72f550a5502e1cbb"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description)\n VALUES ($1, $2, $3, $4)",
|
||||
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, edited_at)\n VALUES ($1, $2, $3, $4, $5, now())",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,10 +8,11 @@
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text"
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5061c0d054bf4f028e7fe51a8f9389024c6ae4492755cadac0f7167e5300bda0"
|
||||
"hash": "d249343a1d9fce2f7ca9e6e447eb32d7d11b61acdab80dda24db4a9ba3e1f7e6"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE queue SET suspend = 0 WHERE parent_job = $1 AND suspend = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d323e898931b200eeec8c5608c37043ed1c47b6f6a53dbf562ad09ece29e82e4"
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4"
|
||||
}
|
||||
+12
@@ -22,6 +22,16 @@
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "edited_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_by",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -31,6 +41,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f581f5ad5c386cef1037e3e8dcb759164e21a72ddc859304421ccb96233fa277"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE resource SET value = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
"query": "UPDATE resource SET value = $1, edited_at = now() WHERE path = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -12,5 +12,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8876fa929ffb175cd976a2bca1195704aa9fe7215013ae29e49ef15cb201ba57"
|
||||
"hash": "fd790067c02d384aead5ff8f94efb37cc20d2f7a3749d21a1de5642e888b5f42"
|
||||
}
|
||||
Generated
+135
-136
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.348.2"
|
||||
version = "1.353.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -24,7 +24,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.348.2"
|
||||
version = "1.353.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
6aa8832322866e9f6ff90979db9b2b8f4bf6d4ce
|
||||
9ef2d9c4aebfee15935634274a2c7c685331a865
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
alter table resource add column edited_at timestamptz, add column created_by varchar(50);
|
||||
alter table resource_type add column edited_at timestamptz, add column created_by varchar(50);
|
||||
alter table folder add column summary text, add column edited_at timestamptz, add column created_by varchar(50);
|
||||
+9
-14
@@ -1185,7 +1185,7 @@ async fn handle_zombie_flows(
|
||||
SELECT *
|
||||
FROM queue
|
||||
WHERE running = true AND suspend = 0 AND suspend_until IS null AND scheduled_for <= now() AND (job_kind = 'flow' OR job_kind = 'flowpreview')
|
||||
AND last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval
|
||||
AND last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval AND canceled = false
|
||||
"#,
|
||||
).bind(FLOW_ZOMBIE_TRANSITION_TIMEOUT.as_str())
|
||||
.fetch_all(db)
|
||||
@@ -1205,7 +1205,7 @@ async fn handle_zombie_flows(
|
||||
);
|
||||
// if the flow hasn't started and is a zombie, we can simply restart it
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET running = false, started_at = null WHERE id = $1",
|
||||
"UPDATE queue SET running = false, started_at = null WHERE id = $1 AND canceled = false",
|
||||
flow.id
|
||||
)
|
||||
.execute(db)
|
||||
@@ -1237,11 +1237,12 @@ async fn handle_zombie_flows(
|
||||
.await?;
|
||||
|
||||
for flow in flows2 {
|
||||
let in_queue =
|
||||
sqlx::query_as::<_, QueuedJob>("SELECT * FROM queue WHERE id = $1 AND running = true")
|
||||
.bind(flow.parent_flow_id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
let in_queue = sqlx::query_as::<_, QueuedJob>(
|
||||
"SELECT * FROM queue WHERE id = $1 AND running = true AND canceled = false",
|
||||
)
|
||||
.bind(flow.parent_flow_id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
if let Some(job) = in_queue {
|
||||
tracing::error!(
|
||||
"parallel Zombie flow detected: {} in workspace {}. Last ping was: {:?}.",
|
||||
@@ -1271,7 +1272,7 @@ async fn cancel_zombie_flow_job(
|
||||
flow.id,
|
||||
flow.workspace_id
|
||||
);
|
||||
let (mut ntx, _) = cancel_job(
|
||||
let (ntx, _) = cancel_job(
|
||||
"monitor",
|
||||
Some(message),
|
||||
flow.id,
|
||||
@@ -1283,12 +1284,6 @@ async fn cancel_zombie_flow_job(
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET running = false, started_at = null WHERE id = $1",
|
||||
flow.id
|
||||
)
|
||||
.execute(&mut *ntx)
|
||||
.await?;
|
||||
ntx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.348.2
|
||||
version: 1.353.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -2373,6 +2373,10 @@ paths:
|
||||
- variable
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: path_start
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: variable list
|
||||
@@ -2854,6 +2858,10 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: path_start
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: resource list
|
||||
@@ -6440,6 +6448,10 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: path_start
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: schedule list
|
||||
@@ -6917,6 +6929,8 @@ paths:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
summary:
|
||||
type: string
|
||||
owners:
|
||||
type: array
|
||||
items:
|
||||
@@ -6951,6 +6965,8 @@ paths:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
summary:
|
||||
type: string
|
||||
owners:
|
||||
type: array
|
||||
items:
|
||||
@@ -9842,6 +9858,11 @@ components:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
created_by:
|
||||
type: string
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- path
|
||||
- resource_type
|
||||
@@ -9875,6 +9896,11 @@ components:
|
||||
type: boolean
|
||||
account:
|
||||
type: number
|
||||
created_by:
|
||||
type: string
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- path
|
||||
- resource_type
|
||||
@@ -9892,6 +9918,11 @@ components:
|
||||
schema: {}
|
||||
description:
|
||||
type: string
|
||||
created_by:
|
||||
type: string
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- name
|
||||
|
||||
@@ -10126,6 +10157,13 @@ components:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
summary:
|
||||
type: string
|
||||
created_by:
|
||||
type: string
|
||||
edited_at:
|
||||
type: string
|
||||
format: date-time
|
||||
required:
|
||||
- name
|
||||
- owners
|
||||
|
||||
@@ -279,6 +279,14 @@ async fn list_apps(
|
||||
sqlb.and_where_is_not_null("favorite.path");
|
||||
}
|
||||
|
||||
if let Some(path_start) = &lq.path_start {
|
||||
sqlb.and_where_like_left("app.path", path_start);
|
||||
}
|
||||
|
||||
if let Some(path_exact) = &lq.path_exact {
|
||||
sqlb.and_where_eq("app.path", "?".bind(path_exact));
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let rows = sqlx::query_as::<_, ListableApp>(&sql)
|
||||
|
||||
@@ -143,10 +143,10 @@ async fn list_flows(
|
||||
sqlb.and_where_eq("archived", lq.show_archived.unwrap_or(false));
|
||||
|
||||
if let Some(ps) = &lq.path_start {
|
||||
sqlb.and_where_like_left("path", "?".bind(ps));
|
||||
sqlb.and_where_like_left("o.path", ps);
|
||||
}
|
||||
if let Some(p) = &lq.path_exact {
|
||||
sqlb.and_where_eq("path", "?".bind(p));
|
||||
sqlb.and_where_eq("o.path", "?".bind(p));
|
||||
}
|
||||
if let Some(cb) = &lq.edited_by {
|
||||
sqlb.and_where_eq("edited_by", "?".bind(cb));
|
||||
|
||||
@@ -56,11 +56,15 @@ pub struct Folder {
|
||||
pub display_name: String,
|
||||
pub owners: Vec<String>,
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub summary: Option<String>,
|
||||
pub created_by: Option<String>,
|
||||
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NewFolder {
|
||||
pub name: String,
|
||||
pub summary: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
pub owners: Option<Vec<String>>,
|
||||
pub extra_perms: Option<serde_json::Value>,
|
||||
@@ -68,6 +72,7 @@ pub struct NewFolder {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateFolder {
|
||||
pub summary: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
pub owners: Option<Vec<String>>,
|
||||
pub extra_perms: Option<serde_json::Value>,
|
||||
@@ -90,7 +95,7 @@ async fn list_folders(
|
||||
|
||||
let rows = sqlx::query_as!(
|
||||
Folder,
|
||||
"SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
|
||||
"SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3",
|
||||
w_id,
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
@@ -199,12 +204,14 @@ async fn create_folder(
|
||||
|
||||
sqlx::query_as!(
|
||||
Folder,
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, $2, $3, $4, $5)",
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at) VALUES ($1, $2, $3, $4, $5, $6, $7, now())",
|
||||
w_id,
|
||||
ng.name,
|
||||
ng.display_name.unwrap_or(ng.name.clone()),
|
||||
&owners,
|
||||
extra_perms,
|
||||
ng.summary,
|
||||
authed.username
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -283,6 +290,12 @@ async fn update_folder(
|
||||
sqlb.set("display_name", "?".bind(&display_name));
|
||||
}
|
||||
|
||||
if let Some(summary) = ng.summary {
|
||||
sqlb.set("summary", "?".bind(&summary));
|
||||
}
|
||||
|
||||
sqlb.set("edited_at", "now()");
|
||||
|
||||
if !authed.is_admin {
|
||||
let prefixed_username = format!("u/{}", authed.username);
|
||||
if ng.owners.as_ref().is_some_and(|x| {
|
||||
@@ -380,7 +393,7 @@ pub async fn get_folderopt<'c>(
|
||||
) -> Result<Option<Folder>> {
|
||||
let folderopt = sqlx::query_as!(
|
||||
Folder,
|
||||
"SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE name = $1 AND workspace_id = $2",
|
||||
"SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE name = $1 AND workspace_id = $2",
|
||||
name,
|
||||
w_id
|
||||
)
|
||||
|
||||
@@ -346,18 +346,26 @@ async fn cancel_job_api(
|
||||
},
|
||||
};
|
||||
|
||||
let (mut tx, job_option) = windmill_queue::cancel_job(
|
||||
&audit_author.username,
|
||||
reason,
|
||||
id,
|
||||
&w_id,
|
||||
tx,
|
||||
&db,
|
||||
rsmq,
|
||||
false,
|
||||
opt_authed.is_none(),
|
||||
let (mut tx, job_option) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(120),
|
||||
windmill_queue::cancel_job(
|
||||
&audit_author.username,
|
||||
reason,
|
||||
id,
|
||||
&w_id,
|
||||
tx,
|
||||
&db,
|
||||
rsmq,
|
||||
false,
|
||||
opt_authed.is_none(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"timeout after 120s while cancelling job {id} in {w_id}: {e:#}"
|
||||
))
|
||||
})??;
|
||||
|
||||
if let Some(id) = job_option {
|
||||
audit_log(
|
||||
@@ -453,18 +461,26 @@ async fn force_cancel(
|
||||
},
|
||||
};
|
||||
|
||||
let (mut tx, job_option) = windmill_queue::cancel_job(
|
||||
&audit_author.username,
|
||||
reason,
|
||||
id,
|
||||
&w_id,
|
||||
tx,
|
||||
&db,
|
||||
rsmq,
|
||||
true,
|
||||
opt_authed.is_none(),
|
||||
let (mut tx, job_option) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(120),
|
||||
windmill_queue::cancel_job(
|
||||
&audit_author.username,
|
||||
reason,
|
||||
id,
|
||||
&w_id,
|
||||
tx,
|
||||
&db,
|
||||
rsmq,
|
||||
true,
|
||||
opt_authed.is_none(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"timeout after 120s while cancelling job {id} in {w_id}: {e:#}"
|
||||
))
|
||||
})??;
|
||||
|
||||
if let Some(id) = job_option {
|
||||
audit_log(
|
||||
@@ -3751,7 +3767,7 @@ async fn run_flow_dependencies_job(
|
||||
PushIsolationLevel::IsolatedRoot(db.clone(), rsmq),
|
||||
&w_id,
|
||||
JobPayload::RawFlowDependencies { path: req.path, flow_value: req.flow_value },
|
||||
PushArgs::empty(),
|
||||
HashMap::from([("skip_flow_update".to_string(), to_raw_value(&true))]).into(),
|
||||
authed.display_username(),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
|
||||
@@ -102,6 +102,14 @@ async fn list_apps(
|
||||
sqlb.and_where_is_not_null("favorite.path");
|
||||
}
|
||||
|
||||
if let Some(path_start) = &lq.path_start {
|
||||
sqlb.and_where_like_left("app.path", path_start);
|
||||
}
|
||||
|
||||
if let Some(path_exact) = &lq.path_exact {
|
||||
sqlb.and_where_eq("app.path", "?".bind(path_exact));
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let rows = sqlx::query_as::<_, ListableApp>(&sql)
|
||||
|
||||
@@ -69,6 +69,8 @@ pub struct ResourceType {
|
||||
pub name: String,
|
||||
pub schema: Option<serde_json::Value>,
|
||||
pub description: Option<String>,
|
||||
pub created_by: Option<String>,
|
||||
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -92,6 +94,8 @@ pub struct Resource {
|
||||
pub description: Option<String>,
|
||||
pub resource_type: String,
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub created_by: Option<String>,
|
||||
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Deserialize)]
|
||||
@@ -102,6 +106,8 @@ pub struct ListableResource {
|
||||
pub description: Option<String>,
|
||||
pub resource_type: String,
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub created_by: Option<String>,
|
||||
pub edited_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub is_linked: Option<bool>,
|
||||
pub is_refreshed: Option<bool>,
|
||||
pub is_oauth: Option<bool>,
|
||||
@@ -128,6 +134,7 @@ struct EditResource {
|
||||
pub struct ListResourceQuery {
|
||||
resource_type: Option<String>,
|
||||
resource_type_exclude: Option<String>,
|
||||
path_start: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
@@ -209,6 +216,8 @@ async fn list_resources(
|
||||
"variable.is_oauth",
|
||||
"variable.account",
|
||||
"account.refresh_error",
|
||||
"resource.created_by",
|
||||
"resource.edited_at",
|
||||
])
|
||||
.left()
|
||||
.join("variable")
|
||||
@@ -241,6 +250,10 @@ async fn list_resources(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path_start) = &lq.path_start {
|
||||
sqlb.and_where_like_left("resource.path", path_start);
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let rows = sqlx::query_as::<_, ListableResource>(&sql)
|
||||
@@ -642,14 +655,15 @@ async fn create_resource(
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO resource
|
||||
(workspace_id, path, value, description, resource_type)
|
||||
VALUES ($1, $2, $3, $4, $5) ON CONFLICT (workspace_id, path)
|
||||
DO UPDATE SET value = $3, description = $4, resource_type = $5",
|
||||
(workspace_id, path, value, description, resource_type, created_by, edited_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, now()) ON CONFLICT (workspace_id, path)
|
||||
DO UPDATE SET value = $3, description = $4, resource_type = $5, edited_at = now()",
|
||||
w_id,
|
||||
resource.path,
|
||||
raw_json as sqlx::types::Json<&RawValue>,
|
||||
resource.description,
|
||||
resource.resource_type,
|
||||
authed.username
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -772,6 +786,8 @@ async fn update_resource(
|
||||
sqlb.set_str("description", ndesc);
|
||||
}
|
||||
|
||||
sqlb.set_str("edited_at", "now()");
|
||||
|
||||
sqlb.returning("path");
|
||||
let authed = maybe_refresh_folders(path, &w_id, authed, &db).await;
|
||||
|
||||
@@ -853,7 +869,7 @@ async fn update_resource_value(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE resource SET value = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
"UPDATE resource SET value = $1, edited_at = now() WHERE path = $2 AND workspace_id = $3",
|
||||
nv.value,
|
||||
path,
|
||||
w_id
|
||||
@@ -979,12 +995,13 @@ async fn create_resource_type(
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO resource_type
|
||||
(workspace_id, name, schema, description)
|
||||
VALUES ($1, $2, $3, $4)",
|
||||
(workspace_id, name, schema, description, created_by, edited_at)
|
||||
VALUES ($1, $2, $3, $4, $5, now())",
|
||||
w_id,
|
||||
resource_type.name,
|
||||
resource_type.schema,
|
||||
resource_type.description,
|
||||
authed.username
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -1120,6 +1137,7 @@ async fn update_resource_type(
|
||||
if let Some(ndesc) = ns.description {
|
||||
sqlb.set_str("description", ndesc);
|
||||
}
|
||||
sqlb.set_str("edited_at", "now()");
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ pub struct ListScheduleQuery {
|
||||
pub path: Option<String>,
|
||||
pub is_flow: Option<bool>,
|
||||
pub args: Option<String>,
|
||||
pub path_start: Option<String>,
|
||||
}
|
||||
|
||||
async fn list_schedule(
|
||||
@@ -339,6 +340,9 @@ async fn list_schedule(
|
||||
if let Some(args) = &lsq.args {
|
||||
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
|
||||
}
|
||||
if let Some(path_start) = &lsq.path_start {
|
||||
sqlb.and_where_like_left("path", path_start);
|
||||
}
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let rows = sqlx::query_as::<_, Schedule>(&sql)
|
||||
.fetch_all(&mut *tx)
|
||||
|
||||
@@ -242,10 +242,10 @@ async fn list_scripts(
|
||||
sqlb.and_where_eq("archived", false);
|
||||
}
|
||||
if let Some(ps) = &lq.path_start {
|
||||
sqlb.and_where_like_left("path", "?".bind(ps));
|
||||
sqlb.and_where_like_left("o.path", ps);
|
||||
}
|
||||
if let Some(p) = &lq.path_exact {
|
||||
sqlb.and_where_eq("path", "?".bind(p));
|
||||
sqlb.and_where_eq("o.path", "?".bind(p));
|
||||
}
|
||||
if let Some(cb) = &lq.created_by {
|
||||
sqlb.and_where_eq("created_by", "?".bind(cb));
|
||||
|
||||
@@ -47,7 +47,7 @@ fn serve_path(path: &str) -> Response<Body> {
|
||||
let mut res = Response::builder()
|
||||
.header(header::CONTENT_TYPE, mime.as_ref())
|
||||
.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
if mime.as_ref() == mime::APPLICATION_JAVASCRIPT {
|
||||
if mime.as_ref() == mime::APPLICATION_JAVASCRIPT || path.ends_with(".wasm") {
|
||||
res = res.header(header::CACHE_CONTROL, "max-age=31536000");
|
||||
} else if (mime.type_(), mime.subtype()) == (mime::TEXT, mime::CSS) {
|
||||
res = res.header(header::CACHE_CONTROL, "max-age=31536000");
|
||||
|
||||
@@ -589,8 +589,7 @@ where
|
||||
parts.extensions.insert(authed.clone());
|
||||
if authed.scopes.is_some()
|
||||
&& (path_vec.len() < 3
|
||||
|| (path_vec[4] != "jobs"
|
||||
&& path_vec[4] != "jobs_u"))
|
||||
|| (path_vec[4] != "jobs" && path_vec[4] != "jobs_u"))
|
||||
{
|
||||
return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
@@ -2834,6 +2833,24 @@ async fn update_username_in_workpsace<'c>(
|
||||
.unwrap();
|
||||
|
||||
// ---- resources----
|
||||
sqlx::query!(
|
||||
r#"UPDATE resource SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3"#,
|
||||
new_username,
|
||||
old_username,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"UPDATE resource_type SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3"#,
|
||||
new_username,
|
||||
old_username,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"UPDATE resource SET path = REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1') WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#,
|
||||
new_username,
|
||||
@@ -3037,6 +3054,15 @@ async fn update_username_in_workpsace<'c>(
|
||||
|
||||
// ---- folders ----
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE folder SET created_by = $1 WHERE created_by = $2 AND workspace_id = $3",
|
||||
new_username,
|
||||
old_username,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut **tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE folder SET owners = ARRAY_REPLACE(owners, 'u/' || $2, 'u/' || $1) WHERE ('u/' || $2) = ANY(owners) AND workspace_id = $3",
|
||||
new_username,
|
||||
|
||||
@@ -81,10 +81,16 @@ async fn list_contextual_variables(
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ListVariableQuery {
|
||||
path_start: Option<String>,
|
||||
}
|
||||
|
||||
async fn list_variables(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(lq): Query<ListVariableQuery>,
|
||||
) -> JsonResult<Vec<ListableVariable>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
@@ -97,10 +103,13 @@ async fn list_variables(
|
||||
from variable
|
||||
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $1
|
||||
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = $1
|
||||
WHERE variable.workspace_id = $1 AND variable.path NOT LIKE 'u/' || $2 || '/secret_arg/%' ORDER BY path",
|
||||
WHERE variable.workspace_id = $1 AND variable.path NOT LIKE 'u/' || $2 || '/secret_arg/%'
|
||||
AND variable.path LIKE $3 || '%'
|
||||
ORDER BY path",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.bind(&authed.username)
|
||||
.bind(&lq.path_start.unwrap_or_default())
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -523,7 +532,7 @@ async fn update_variable(
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4",
|
||||
"UPDATE resource SET path = $1, value = $2, edited_at = now() WHERE path = $3 AND workspace_id = $4",
|
||||
npath,
|
||||
v,
|
||||
path,
|
||||
|
||||
@@ -1605,29 +1605,33 @@ async fn create_workspace(
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
|
||||
nw.id,
|
||||
username,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_custom', 'App Custom Components', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
|
||||
nw.id,
|
||||
username,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING",
|
||||
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by, edited_at) VALUES ($1, 'app_groups', 'App Groups', ARRAY[]::TEXT[], '{\"g/all\": false}', $2, now()) ON CONFLICT DO NOTHING",
|
||||
nw.id,
|
||||
username,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING",
|
||||
"INSERT INTO resource (workspace_id, path, value, description, resource_type, created_by, edited_at) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme', $2, now()) ON CONFLICT DO NOTHING",
|
||||
nw.id,
|
||||
username,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -11,4 +11,6 @@ use serde::Deserialize;
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListAppQuery {
|
||||
pub starred_only: Option<bool>,
|
||||
pub path_exact: Option<String>,
|
||||
pub path_start: Option<String>,
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::{
|
||||
use crate::{
|
||||
error::{to_anyhow, Error},
|
||||
utils::http_get_from_hub,
|
||||
DB, HUB_BASE_URL,
|
||||
DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use serde::de::Error as _;
|
||||
@@ -368,9 +368,11 @@ pub async fn get_hub_script_by_path(
|
||||
.strip_prefix("hub/")
|
||||
.ok_or_else(|| Error::BadRequest("Impossible to remove prefix hex".to_string()))?;
|
||||
|
||||
let content = http_get_from_hub(
|
||||
let hub_base_url = HUB_BASE_URL.read().await.clone();
|
||||
|
||||
let result = http_get_from_hub(
|
||||
http_client,
|
||||
&format!("{}/raw/{}.ts", *HUB_BASE_URL.read().await, path),
|
||||
&format!("{}/raw/{}.ts", hub_base_url, path),
|
||||
true,
|
||||
None,
|
||||
db,
|
||||
@@ -378,8 +380,39 @@ pub async fn get_hub_script_by_path(
|
||||
.await?
|
||||
.text()
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
Ok(content)
|
||||
.map_err(to_anyhow);
|
||||
|
||||
match result {
|
||||
Ok(result) => Ok(result),
|
||||
Err(e) => {
|
||||
if hub_base_url != DEFAULT_HUB_BASE_URL
|
||||
&& path
|
||||
.split("/")
|
||||
.next()
|
||||
.is_some_and(|x| x.parse::<i32>().is_ok_and(|x| x < 10_000_000))
|
||||
{
|
||||
tracing::info!(
|
||||
"Not found on private hub, fallback to default hub for {}",
|
||||
path
|
||||
);
|
||||
let content = http_get_from_hub(
|
||||
http_client,
|
||||
&format!("{}/raw/{}.ts", DEFAULT_HUB_BASE_URL, path),
|
||||
true,
|
||||
None,
|
||||
db,
|
||||
)
|
||||
.await?
|
||||
.text()
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
|
||||
Ok(content)
|
||||
} else {
|
||||
Err(e)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_full_hub_script_by_path(
|
||||
@@ -392,9 +425,11 @@ pub async fn get_full_hub_script_by_path(
|
||||
.strip_prefix("hub/")
|
||||
.ok_or_else(|| Error::BadRequest("Impossible to remove prefix hex".to_string()))?;
|
||||
|
||||
let value = http_get_from_hub(
|
||||
let hub_base_url = HUB_BASE_URL.read().await.clone();
|
||||
|
||||
let result = http_get_from_hub(
|
||||
http_client,
|
||||
&format!("{}/raw2/{}", *HUB_BASE_URL.read().await, path),
|
||||
&format!("{}/raw2/{}", hub_base_url, path),
|
||||
true,
|
||||
None,
|
||||
db,
|
||||
@@ -402,8 +437,39 @@ pub async fn get_full_hub_script_by_path(
|
||||
.await?
|
||||
.json::<HubScript>()
|
||||
.await
|
||||
.context("Decoding hub response to script")?;
|
||||
Ok(value)
|
||||
.context("Decoding hub response to script");
|
||||
|
||||
match result {
|
||||
Ok(result) => Ok(result),
|
||||
Err(e) => {
|
||||
if hub_base_url != DEFAULT_HUB_BASE_URL
|
||||
&& path
|
||||
.split("/")
|
||||
.next()
|
||||
.is_some_and(|x| x.parse::<i32>().is_ok_and(|x| x < 10_000_000))
|
||||
{
|
||||
tracing::info!(
|
||||
"Not found on private hub, fallback to default hub for {}",
|
||||
path
|
||||
);
|
||||
let value = http_get_from_hub(
|
||||
http_client,
|
||||
&format!("{}/raw2/{}", DEFAULT_HUB_BASE_URL, path),
|
||||
true,
|
||||
None,
|
||||
db,
|
||||
)
|
||||
.await?
|
||||
.json::<HubScript>()
|
||||
.await
|
||||
.context("Decoding hub response to script")?;
|
||||
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(e)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
||||
@@ -148,7 +148,7 @@ pub async fn cancel_single_job<'c>(
|
||||
&& !force_cancel
|
||||
{
|
||||
let id = sqlx::query_scalar!(
|
||||
"UPDATE queue SET canceled = true, canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = $3 AND workspace_id = $4 RETURNING id",
|
||||
"UPDATE queue SET canceled = true, canceled_by = $1, canceled_reason = $2, scheduled_for = now(), suspend = 0 WHERE id = $3 AND workspace_id = $4 AND canceled = false RETURNING id",
|
||||
username,
|
||||
reason,
|
||||
job_running.id,
|
||||
@@ -160,32 +160,39 @@ pub async fn cancel_single_job<'c>(
|
||||
tracing::info!("Soft cancelling job {}", id);
|
||||
}
|
||||
} else {
|
||||
let reason: String = reason
|
||||
.clone()
|
||||
.unwrap_or_else(|| "unexplicited reasons".to_string());
|
||||
let e = serde_json::json!({"message": format!("Job canceled: {reason} by {username}"), "name": "Canceled", "reason": reason, "canceler": username});
|
||||
append_logs(
|
||||
&job_running.id,
|
||||
w_id.to_string(),
|
||||
format!("canceled by {username}: (force cancel: {force_cancel})"),
|
||||
db,
|
||||
)
|
||||
.await;
|
||||
let add_job = add_completed_job_error(
|
||||
&db,
|
||||
job_running,
|
||||
job_running.mem_peak.unwrap_or(0),
|
||||
Some(CanceledBy { username: Some(username.to_string()), reason: Some(reason) }),
|
||||
e,
|
||||
rsmq.clone(),
|
||||
"server",
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
let username = username.to_string();
|
||||
let job_running = job_running.clone();
|
||||
let w_id = w_id.to_string();
|
||||
let db = db.clone();
|
||||
let rsmq = rsmq.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let reason: String = reason
|
||||
.clone()
|
||||
.unwrap_or_else(|| "unexplicited reasons".to_string());
|
||||
let e = serde_json::json!({"message": format!("Job canceled: {reason} by {username}"), "name": "Canceled", "reason": reason, "canceler": username});
|
||||
append_logs(
|
||||
&job_running.id,
|
||||
w_id.to_string(),
|
||||
format!("canceled by {username}: (force cancel: {force_cancel})"),
|
||||
&db,
|
||||
)
|
||||
.await;
|
||||
let add_job = add_completed_job_error(
|
||||
&db,
|
||||
&job_running,
|
||||
job_running.mem_peak.unwrap_or(0),
|
||||
Some(CanceledBy { username: Some(username.to_string()), reason: Some(reason) }),
|
||||
e,
|
||||
rsmq.clone(),
|
||||
"server",
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = add_job {
|
||||
tracing::error!("Failed to add canceled job: {}", e);
|
||||
}
|
||||
if let Err(e) = add_job {
|
||||
tracing::error!("Failed to add canceled job: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(mut rsmq) = rsmq.clone() {
|
||||
rsmq.change_message_visibility(&job_running.tag, &job_running.id.to_string(), 0)
|
||||
@@ -235,6 +242,7 @@ pub async fn cancel_job<'c>(
|
||||
jobs.extend(new_jobs.clone());
|
||||
jobs_to_cancel.extend(new_jobs);
|
||||
}
|
||||
jobs.reverse();
|
||||
|
||||
let (ntx, _) = cancel_single_job(
|
||||
username,
|
||||
@@ -661,7 +669,7 @@ pub async fn add_completed_job<
|
||||
parent_job
|
||||
);
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET last_ping = now() WHERE id = $1 AND workspace_id = $2",
|
||||
"UPDATE queue SET last_ping = now() WHERE id = $1 AND workspace_id = $2 AND canceled = false",
|
||||
parent_job,
|
||||
&queued_job.workspace_id
|
||||
)
|
||||
@@ -2678,7 +2686,7 @@ fn restructure_cloudevents_metadata(
|
||||
let mut hm = serde_json::from_str::<Option<HashMap<String, Box<JsonRawValue>>>>(&str)
|
||||
.map_err(|e| Error::BadRequest(format!("invalid json: {}", e)))?
|
||||
.unwrap_or_else(HashMap::new);
|
||||
hm.insert("WEBHOOK__METADATA__".to_string(), to_raw_value(&p));
|
||||
hm.insert("WEBHOOK__METADATA__".to_string(), to_raw_value(&p));
|
||||
Ok(hm)
|
||||
}
|
||||
}
|
||||
@@ -2836,7 +2844,10 @@ where
|
||||
.unwrap()
|
||||
.starts_with("application/cloudevents-batch+json")
|
||||
{
|
||||
Err(Error::BadRequest(format!("Cloud events batching is not supported yet")).into_response())
|
||||
Err(
|
||||
Error::BadRequest(format!("Cloud events batching is not supported yet"))
|
||||
.into_response(),
|
||||
)
|
||||
} else if content_type
|
||||
.unwrap()
|
||||
.starts_with("application/x-www-form-urlencoded")
|
||||
|
||||
@@ -85,7 +85,10 @@ pub async fn gen_lockfile(
|
||||
.replace("W_ID", w_id)
|
||||
.replace("BASE_INTERNAL_URL", base_internal_url)
|
||||
.replace("TOKEN", token)
|
||||
.replace("CURRENT_PATH", script_path)
|
||||
.replace(
|
||||
"CURRENT_PATH",
|
||||
&crate::common::use_flow_root_path(script_path)
|
||||
)
|
||||
.replace("RAW_GET_ENDPOINT", "raw")
|
||||
),
|
||||
)
|
||||
@@ -305,7 +308,10 @@ pub async fn build_loader(
|
||||
.replace("W_ID", w_id)
|
||||
.replace("BASE_INTERNAL_URL", base_internal_url)
|
||||
.replace("TOKEN", token)
|
||||
.replace("CURRENT_PATH", current_path)
|
||||
.replace(
|
||||
"CURRENT_PATH",
|
||||
&crate::common::use_flow_root_path(current_path),
|
||||
)
|
||||
.replace("RAW_GET_ENDPOINT", "raw_unpinned");
|
||||
if nodejs_mode {
|
||||
write_file(
|
||||
|
||||
@@ -1568,13 +1568,14 @@ pub async fn save_in_cache(
|
||||
|
||||
if let Err(e) = sqlx::query!(
|
||||
"INSERT INTO resource
|
||||
(workspace_id, path, value, resource_type)
|
||||
VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path)
|
||||
DO UPDATE SET value = $3",
|
||||
(workspace_id, path, value, resource_type, created_by, edited_at)
|
||||
VALUES ($1, $2, $3, $4, $5, now()) ON CONFLICT (workspace_id, path)
|
||||
DO UPDATE SET value = $3, edited_at = now()",
|
||||
job.workspace_id,
|
||||
cached_path,
|
||||
raw_json as sqlx::types::Json<CachedResource>,
|
||||
"cache"
|
||||
"cache",
|
||||
job.created_by
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
@@ -1599,3 +1600,19 @@ pub async fn clean_cache() -> error::Result<()> {
|
||||
tracing::info!("Finished cleaning cache");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE_FLOW_ROOT: Regex = Regex::new(r"(?i)(.*?)(?:/branchone-\d+/|/branchall-\d+/|/loop-\d+/)").unwrap();
|
||||
|
||||
}
|
||||
|
||||
pub fn use_flow_root_path(flow_path: &str) -> String {
|
||||
if let Some(captures) = RE_FLOW_ROOT.captures(flow_path) {
|
||||
return captures
|
||||
.get(1)
|
||||
.map(|m| format!("{}/flow", m.as_str()))
|
||||
.unwrap_or_else(|| flow_path.to_string());
|
||||
} else {
|
||||
return flow_path.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +355,8 @@ async fn build_import_map(
|
||||
base_internal_url: &str,
|
||||
job_dir: &str,
|
||||
) -> error::Result<()> {
|
||||
let script_path_split = script_path.split("/");
|
||||
let rooted_path = crate::common::use_flow_root_path(script_path);
|
||||
let script_path_split = rooted_path.split("/");
|
||||
let script_path_parts_len = script_path_split.clone().count();
|
||||
let mut relative_mounts = "".to_string();
|
||||
for c in 0..script_path_parts_len {
|
||||
|
||||
@@ -686,7 +686,6 @@ pub async fn eval_fetch_timeout(
|
||||
// let instant = Instant::now();
|
||||
|
||||
let mut js_runtime: JsRuntime = JsRuntime::new(options);
|
||||
tracing::error!("js_runtime created");
|
||||
// tracing::info!("ttc: {:?}", instant.elapsed());
|
||||
|
||||
js_runtime.add_near_heap_limit_callback(move |x,y| {
|
||||
|
||||
@@ -247,13 +247,13 @@ pub async fn handle_python_job(
|
||||
base_internal_url: &str,
|
||||
envs: HashMap<String, String>,
|
||||
) -> windmill_common::error::Result<Box<RawValue>> {
|
||||
let script_path = job.script_path();
|
||||
let script_path = crate::common::use_flow_root_path(job.script_path());
|
||||
let additional_python_paths = handle_python_deps(
|
||||
job_dir,
|
||||
requirements_o,
|
||||
inner_content,
|
||||
&job.workspace_id,
|
||||
script_path,
|
||||
&script_path,
|
||||
&job.id,
|
||||
db,
|
||||
worker_name,
|
||||
@@ -281,7 +281,7 @@ pub async fn handle_python_job(
|
||||
transforms,
|
||||
spread,
|
||||
main_name,
|
||||
) = prepare_wrapper(job_dir, inner_content, script_path, job.args.as_ref()).await?;
|
||||
) = prepare_wrapper(job_dir, inner_content, &script_path, job.args.as_ref()).await?;
|
||||
|
||||
create_args_and_out_file(&client, job, job_dir, db).await?;
|
||||
|
||||
|
||||
@@ -40,16 +40,20 @@ globalThis.console = new console.Console((msg, level) =>
|
||||
);
|
||||
globalThis.AbortController = abortSignal.AbortController;
|
||||
globalThis.AbortSignal = abortSignal.AbortSignal;
|
||||
|
||||
Object.assign(globalThis, {
|
||||
clearInterval: timers.clearInterval,
|
||||
clearTimeout: timers.clearTimeout,
|
||||
setInterval: timers.setInterval,
|
||||
setTimeout: timers.setTimeout,
|
||||
});
|
||||
|
||||
// Object.assign(globalThis, {
|
||||
// console: nonEnumerable(
|
||||
// new console.Console((msg, level) => core.print(msg, level > 1))
|
||||
// ),
|
||||
|
||||
// // timers
|
||||
// clearInterval: writable(timers.clearInterval),
|
||||
// clearTimeout: writable(timers.clearTimeout),
|
||||
// setInterval: writable(timers.setInterval),
|
||||
// setTimeout: writable(timers.setTimeout),
|
||||
|
||||
// // fetch
|
||||
// Request: nonEnumerable(request.Request),
|
||||
@@ -146,32 +150,32 @@ globalThis.AbortSignal = abortSignal.AbortSignal;
|
||||
// [webidl.brand]: nonEnumerable(webidl.brand),
|
||||
// });
|
||||
|
||||
function nonEnumerable(value) {
|
||||
return {
|
||||
value,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
};
|
||||
}
|
||||
// function nonEnumerable(value) {
|
||||
// return {
|
||||
// value,
|
||||
// writable: true,
|
||||
// enumerable: false,
|
||||
// configurable: true,
|
||||
// };
|
||||
// }
|
||||
|
||||
function writable(value) {
|
||||
return {
|
||||
value,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
};
|
||||
}
|
||||
// function writable(value) {
|
||||
// return {
|
||||
// value,
|
||||
// writable: true,
|
||||
// enumerable: true,
|
||||
// configurable: true,
|
||||
// };
|
||||
// }
|
||||
|
||||
function readOnly(value) {
|
||||
return {
|
||||
value,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
configurable: true,
|
||||
};
|
||||
}
|
||||
// function readOnly(value) {
|
||||
// return {
|
||||
// value,
|
||||
// enumerable: true,
|
||||
// writable: false,
|
||||
// configurable: true,
|
||||
// };
|
||||
// }
|
||||
|
||||
// function getterOnly(getter) {
|
||||
// return {
|
||||
|
||||
@@ -425,29 +425,18 @@ pub async fn update_flow_status_after_job_completion_internal<
|
||||
tx.commit().await?;
|
||||
|
||||
if parallelism.is_some() {
|
||||
// this ensure that the lock is taken in the same order and thus avoid deadlocks
|
||||
let ids = sqlx::query_scalar!(
|
||||
"SELECT id FROM queue WHERE parent_job = $1 AND suspend > 0 ORDER by suspend",
|
||||
flow
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET suspend = 0 WHERE parent_job = $1 AND suspend = $2",
|
||||
flow,
|
||||
nindex
|
||||
)
|
||||
.fetch_all(db)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!("error while locking jobs to decrease parallelism of: {e:#}"))
|
||||
Error::InternalErr(format!(
|
||||
"error resuming job at suspend {nindex} and parent {flow}: {e:#}"
|
||||
))
|
||||
})?;
|
||||
for id in ids {
|
||||
sqlx::query!(
|
||||
"UPDATE queue SET suspend = suspend - 1 WHERE id = $1 AND suspend > 0",
|
||||
id
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"error decreasing suspend for {id}: {e:#}"
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
@@ -1986,6 +1975,18 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
let mut tx: QueueTransaction<'_, R> = (rsmq.clone(), db.begin().await?).into();
|
||||
|
||||
for i in (0..len).into_iter() {
|
||||
if i % 100 == 0 && i != 0 {
|
||||
tracing::info!(id = %flow_job.id, root_id = %job_root, "pushed (non-commited yet) first {i} subflows of {len}");
|
||||
sqlx::query!(
|
||||
"UPDATE queue
|
||||
SET last_ping = now()
|
||||
WHERE id = $1 AND last_ping < now()",
|
||||
flow_job.id,
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
}
|
||||
tracing::debug!(id = %flow_job.id, root_id = %job_root, "pushing job {i} of {len}");
|
||||
let payload_tag = match &job_payloads {
|
||||
ContinuePayload::SingleJob(payload) => payload.clone(),
|
||||
ContinuePayload::BranchAllJobs(payloads) => payloads[i].clone(),
|
||||
@@ -2085,6 +2086,8 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
Err(e) => (None, Some(e)),
|
||||
};
|
||||
|
||||
tracing::debug!(id = %flow_job.id, root_id = %job_root, "computed args for job {i} of {len}");
|
||||
|
||||
let value_with_parallel = module.get_value_with_parallel()?;
|
||||
|
||||
let root_job = if {
|
||||
@@ -2116,6 +2119,8 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
None
|
||||
};
|
||||
|
||||
tracing::debug!(id = %flow_job.id, root_id = %job_root, "computed perms for job {i} of {len}");
|
||||
|
||||
let tx2 = PushIsolationLevel::Transaction(tx);
|
||||
let (uuid, mut inner_tx) = push(
|
||||
&db,
|
||||
@@ -2154,6 +2159,8 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
|
||||
if value_with_parallel.type_ == "forloopflow" {
|
||||
if let Some(p) = value_with_parallel.parallelism {
|
||||
tracing::debug!(id = %flow_job.id, root_id = %job_root, "updating suspend for forloopflow job {uuid}");
|
||||
|
||||
if i as u16 >= p {
|
||||
sqlx::query!(
|
||||
"UPDATE queue
|
||||
@@ -2165,6 +2172,7 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
.execute(&mut inner_tx)
|
||||
.await?;
|
||||
}
|
||||
tracing::debug!(id = %flow_job.id, root_id = %job_root, "updated suspend for {uuid}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2206,6 +2214,7 @@ async fn push_next_flow_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
tracing::debug!(id = %flow_job.id, root_id = %job_root, "updated parallel monitor lock for {uuid}");
|
||||
}
|
||||
}
|
||||
let first_uuid = uuids[0];
|
||||
|
||||
@@ -527,15 +527,27 @@ pub async fn handle_flow_dependency_job<R: rsmq_async::RsmqConnection + Send + S
|
||||
"status": "Flow lock generation was canceled",
|
||||
})));
|
||||
}
|
||||
let skip_flow_update = job
|
||||
.args
|
||||
.as_ref()
|
||||
.map(|x| {
|
||||
x.get("skip_flow_update")
|
||||
.map(|v| serde_json::from_str::<bool>(v.get()).ok())
|
||||
.flatten()
|
||||
})
|
||||
.flatten()
|
||||
.unwrap_or(false);
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE flow SET value = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
new_flow_value,
|
||||
job_path,
|
||||
job.workspace_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
if !skip_flow_update {
|
||||
sqlx::query!(
|
||||
"UPDATE flow SET value = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
new_flow_value,
|
||||
job_path,
|
||||
job.workspace_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
if let Err(e) = handle_deployment_metadata(
|
||||
@@ -785,7 +797,10 @@ async fn lock_modules<'c>(
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
token,
|
||||
&path.clone().unwrap_or_else(|| job_path.to_string()),
|
||||
&format!(
|
||||
"{}/flow",
|
||||
&path.clone().unwrap_or_else(|| job_path.to_string())
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
@@ -913,7 +928,7 @@ async fn lock_modules_app(
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
token,
|
||||
job.script_path(),
|
||||
&format!("{}/app", job.script_path()),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
+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.348.2";
|
||||
export const VERSION = "v1.353.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
+7
-6
@@ -12,6 +12,7 @@ import {
|
||||
yamlParse,
|
||||
} from "./deps.ts";
|
||||
import { GlobalOptions, isSuperset } from "./types.ts";
|
||||
import { readInlinePathSync } from "./utils.ts";
|
||||
|
||||
export interface AppFile {
|
||||
value: any;
|
||||
@@ -31,13 +32,13 @@ export async function pushApp(
|
||||
return;
|
||||
}
|
||||
alreadySynced.push(localPath);
|
||||
|
||||
remotePath.replaceAll(SEP, "/");
|
||||
let app: any = undefined;
|
||||
// deleting old app if it exists in raw mode
|
||||
try {
|
||||
app = await AppService.getAppByPath({
|
||||
workspace,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
//ignore
|
||||
@@ -59,11 +60,11 @@ export async function pushApp(
|
||||
const o: Record<string, any> = v as any;
|
||||
if (o["content"] && o["content"].startsWith("!inline")) {
|
||||
const basePath = localPath + o["content"].split(" ")[1];
|
||||
o["content"] = Deno.readTextFileSync(basePath);
|
||||
o["content"] = readInlinePathSync(basePath);
|
||||
}
|
||||
if (o["lock"] && o["lock"].startsWith("!inline")) {
|
||||
const basePath = localPath + o["lock"].split(" ")[1];
|
||||
o["lock"] = Deno.readTextFileSync(basePath);
|
||||
o["lock"] = readInlinePathSync(basePath);
|
||||
}
|
||||
} else {
|
||||
replaceInlineScripts(v);
|
||||
@@ -83,7 +84,7 @@ export async function pushApp(
|
||||
log.info(colors.bold.yellow(`Updating app ${remotePath}...`));
|
||||
await AppService.updateApp({
|
||||
workspace,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath,
|
||||
requestBody: {
|
||||
deployment_message: message,
|
||||
...localApp,
|
||||
@@ -95,7 +96,7 @@ export async function pushApp(
|
||||
await AppService.createApp({
|
||||
workspace,
|
||||
requestBody: {
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath,
|
||||
deployment_message: message,
|
||||
...localApp,
|
||||
},
|
||||
|
||||
+30
-21
@@ -1,24 +1,33 @@
|
||||
export type EnumType = string[] | undefined;
|
||||
|
||||
export interface SchemaProperty {
|
||||
type: string | undefined
|
||||
description?: string
|
||||
pattern?: string
|
||||
default?: any
|
||||
enum?: string[]
|
||||
contentEncoding?: 'base64' | 'binary'
|
||||
format?: string
|
||||
items?: {
|
||||
type?: 'string' | 'number' | 'bytes' | 'object'
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
}
|
||||
min?: number
|
||||
max?: number
|
||||
currency?: string
|
||||
currencyLocale?: string
|
||||
multiselect?: boolean
|
||||
customErrorMessage?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
required?: string[]
|
||||
showExpr?: string
|
||||
type: string | undefined;
|
||||
description?: string;
|
||||
pattern?: string;
|
||||
default?: any;
|
||||
enum?: EnumType;
|
||||
contentEncoding?: "base64" | "binary";
|
||||
format?: string;
|
||||
items?: {
|
||||
type?: "string" | "number" | "bytes" | "object";
|
||||
contentEncoding?: "base64";
|
||||
enum?: string[];
|
||||
};
|
||||
min?: number;
|
||||
max?: number;
|
||||
currency?: string;
|
||||
currencyLocale?: string;
|
||||
multiselect?: boolean;
|
||||
customErrorMessage?: string;
|
||||
properties?: { [name: string]: SchemaProperty };
|
||||
required?: string[];
|
||||
showExpr?: string;
|
||||
password?: boolean;
|
||||
order?: string[];
|
||||
nullable?: boolean;
|
||||
dateFormat?: string;
|
||||
title?: string;
|
||||
placeholder?: string;
|
||||
oneOf?: SchemaProperty[];
|
||||
originalType?: string;
|
||||
}
|
||||
|
||||
+7
-5
@@ -12,11 +12,12 @@ import {
|
||||
yamlParse,
|
||||
} from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import { exts, resolve, track_job } from "./script.ts";
|
||||
import { resolve, track_job } from "./script.ts";
|
||||
import { defaultFlowDefinition } from "./bootstrap/flow_bootstrap.ts";
|
||||
import { generateFlowLockInternal } from "./metadata.ts";
|
||||
import { SyncOptions, mergeConfigWithConfigFile } from "./conf.ts";
|
||||
import { FSFSElement, elementsToMap, ignoreF } from "./sync.ts";
|
||||
import { readInlinePathSync } from "./utils.ts";
|
||||
|
||||
export interface FlowFile {
|
||||
summary: string;
|
||||
@@ -46,7 +47,7 @@ export function replaceInlineScripts(
|
||||
) {
|
||||
const path = lock.split(" ")[1];
|
||||
try {
|
||||
m.value.lock = Deno.readTextFileSync(localPath + path);
|
||||
m.value.lock = readInlinePathSync(localPath + path);
|
||||
} catch {
|
||||
log.error(`Lock file ${path} not found`);
|
||||
}
|
||||
@@ -78,6 +79,7 @@ export async function pushFlow(
|
||||
return;
|
||||
}
|
||||
alreadySynced.push(localPath);
|
||||
remotePath = remotePath.replaceAll(SEP, "/");
|
||||
let flow: Flow | undefined = undefined;
|
||||
try {
|
||||
flow = await FlowService.getFlowByPath({
|
||||
@@ -104,9 +106,9 @@ export async function pushFlow(
|
||||
log.info(colors.bold.yellow(`Updating flow ${remotePath}...`));
|
||||
await FlowService.updateFlow({
|
||||
workspace: workspace,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
requestBody: {
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
deployment_message: message,
|
||||
...localFlow,
|
||||
},
|
||||
@@ -116,7 +118,7 @@ export async function pushFlow(
|
||||
await FlowService.createFlow({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
deployment_message: message,
|
||||
...localFlow,
|
||||
},
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ addEventListener("error", (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
export const VERSION = "v1.348.2";
|
||||
export const VERSION = "v1.353.0";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
|
||||
+95
-25
@@ -39,7 +39,7 @@ import {
|
||||
newPathAssigner,
|
||||
yamlOptions,
|
||||
} from "./sync.ts";
|
||||
import { generateHash } from "./utils.ts";
|
||||
import { generateHash, readInlinePathSync } from "./utils.ts";
|
||||
import { SyncCodebase } from "./codebase.ts";
|
||||
import { FlowFile, replaceInlineScripts } from "./flow.ts";
|
||||
|
||||
@@ -100,11 +100,11 @@ export async function generateFlowLockInternal(
|
||||
workspace: Workspace,
|
||||
justUpdateMetadataLock?: boolean
|
||||
): Promise<string | undefined> {
|
||||
if (folder.endsWith("/")) {
|
||||
if (folder.endsWith(SEP)) {
|
||||
folder = folder.substring(0, folder.length - 1);
|
||||
}
|
||||
const remote_path = folder
|
||||
.replaceAll("\\", "/")
|
||||
.replaceAll(SEP, "/")
|
||||
.substring(0, folder.length - ".flow".length);
|
||||
if (!justUpdateMetadataLock) {
|
||||
log.info(`Generating lock for flow ${folder} at ${remote_path}`);
|
||||
@@ -146,19 +146,21 @@ export async function generateFlowLockInternal(
|
||||
);
|
||||
//removeChangedLocks
|
||||
flowValue.value = await updateFlow(workspace, flowValue.value, remote_path);
|
||||
|
||||
const inlineScripts = extractInlineScriptsForFlows(
|
||||
flowValue.value.modules,
|
||||
newPathAssigner("bun")
|
||||
);
|
||||
inlineScripts
|
||||
.filter((s) => s.path.endsWith(".lock"))
|
||||
.forEach((s) => {
|
||||
Deno.writeTextFileSync(
|
||||
Deno.cwd() + SEP + folder + SEP + s.path,
|
||||
s.content
|
||||
);
|
||||
});
|
||||
}
|
||||
const inlineScripts = extractInlineScriptsForFlows(
|
||||
flowValue.value.modules,
|
||||
newPathAssigner("bun")
|
||||
);
|
||||
inlineScripts
|
||||
.filter((s) => s.path.endsWith(".lock"))
|
||||
.forEach((s) => {
|
||||
Deno.writeTextFileSync(
|
||||
Deno.cwd() + SEP + folder + SEP + s.path,
|
||||
s.content
|
||||
);
|
||||
});
|
||||
|
||||
hashes = await generateFlowHash(folder);
|
||||
|
||||
for (const [path, hash] of Object.entries(hashes)) {
|
||||
@@ -183,7 +185,7 @@ export async function generateScriptMetadataInternal(
|
||||
): Promise<string | undefined> {
|
||||
const remotePath = scriptPath
|
||||
.substring(0, scriptPath.indexOf("."))
|
||||
.replaceAll("\\", "/");
|
||||
.replaceAll(SEP, "/");
|
||||
|
||||
const language = inferContentTypeFromFilePath(scriptPath, opts.defaultTs);
|
||||
|
||||
@@ -254,7 +256,8 @@ export async function generateScriptMetadataInternal(
|
||||
metadataParsedContent.lock = "";
|
||||
}
|
||||
} else {
|
||||
metadataParsedContent.lock = "!inline " + remotePath + ".script.lock";
|
||||
metadataParsedContent.lock =
|
||||
"!inline " + remotePath.replaceAll(SEP, "/") + ".script.lock";
|
||||
}
|
||||
|
||||
let metaPath = remotePath + ".script.yaml";
|
||||
@@ -345,7 +348,7 @@ async function updateScriptLock(
|
||||
}
|
||||
const lockPath = remotePath + ".script.lock";
|
||||
await Deno.writeTextFile(lockPath, lock);
|
||||
metadataContent.lock = "!inline " + lockPath;
|
||||
metadataContent.lock = "!inline " + lockPath.replaceAll(SEP, "/");
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}`
|
||||
@@ -511,10 +514,18 @@ export function argSigToJsonSchemaType(
|
||||
| null;
|
||||
}
|
||||
| { str: string[] | null }
|
||||
| { object: { key: string; typ: any }[] },
|
||||
| { object: { key: string; typ: any }[] }
|
||||
| {
|
||||
oneof: [
|
||||
{
|
||||
label: string;
|
||||
properties: { key: string; typ: any }[];
|
||||
}
|
||||
];
|
||||
},
|
||||
oldS: SchemaProperty
|
||||
): void {
|
||||
const newS: SchemaProperty = { type: "" };
|
||||
let newS: SchemaProperty = { type: "" };
|
||||
if (t === "int") {
|
||||
newS.type = "integer";
|
||||
} else if (t === "float") {
|
||||
@@ -533,15 +544,43 @@ export function argSigToJsonSchemaType(
|
||||
} else if (t === "bytes") {
|
||||
newS.type = "string";
|
||||
newS.contentEncoding = "base64";
|
||||
newS.originalType = "bytes";
|
||||
} else if (t === "datetime") {
|
||||
newS.type = "string";
|
||||
newS.format = "date-time";
|
||||
} else if (typeof t !== "string" && "oneof" in t) {
|
||||
newS.type = "object";
|
||||
if (t.oneof) {
|
||||
newS.oneOf = t.oneof.map((obj) => {
|
||||
const oldObjS =
|
||||
oldS.oneOf?.find((o) => o?.title === obj.label) ?? undefined;
|
||||
const properties: Record<string, any> = {};
|
||||
for (const prop of obj.properties) {
|
||||
if (oldObjS?.properties && prop.key in oldObjS?.properties) {
|
||||
properties[prop.key] = oldObjS?.properties[prop.key];
|
||||
} else {
|
||||
properties[prop.key] = { description: "", type: "" };
|
||||
}
|
||||
argSigToJsonSchemaType(prop.typ, properties[prop.key]);
|
||||
}
|
||||
return {
|
||||
type: "object",
|
||||
title: obj.label,
|
||||
properties,
|
||||
order: oldObjS?.order ?? undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
} else if (typeof t !== "string" && `object` in t) {
|
||||
newS.type = "object";
|
||||
if (t.object) {
|
||||
const properties: Record<string, any> = {};
|
||||
for (const prop of t.object) {
|
||||
properties[prop.key] = {};
|
||||
if (oldS.properties && prop.key in oldS.properties) {
|
||||
properties[prop.key] = oldS.properties[prop.key];
|
||||
} else {
|
||||
properties[prop.key] = { description: "", type: "" };
|
||||
}
|
||||
argSigToJsonSchemaType(prop.typ, properties[prop.key]);
|
||||
}
|
||||
newS.properties = properties;
|
||||
@@ -549,7 +588,11 @@ export function argSigToJsonSchemaType(
|
||||
} else if (typeof t !== "string" && `str` in t) {
|
||||
newS.type = "string";
|
||||
if (t.str) {
|
||||
newS.originalType = "enum";
|
||||
newS.enum = t.str;
|
||||
} else {
|
||||
newS.originalType = "string";
|
||||
newS.enum = undefined;
|
||||
}
|
||||
} else if (typeof t !== "string" && `resource` in t) {
|
||||
newS.type = "object";
|
||||
@@ -571,6 +614,32 @@ export function argSigToJsonSchemaType(
|
||||
newS.type = "object";
|
||||
}
|
||||
|
||||
const preservedFields = [
|
||||
"description",
|
||||
"pattern",
|
||||
"min",
|
||||
"max",
|
||||
"currency",
|
||||
"currencyLocale",
|
||||
"multiselect",
|
||||
"customErrorMessage",
|
||||
"required",
|
||||
"showExpr",
|
||||
"password",
|
||||
"order",
|
||||
"dateFormat",
|
||||
"title",
|
||||
"placeholder",
|
||||
];
|
||||
|
||||
preservedFields.forEach((field) => {
|
||||
// @ts-ignore
|
||||
if (oldS[field] !== undefined) {
|
||||
// @ts-ignore
|
||||
newS[field] = oldS[field];
|
||||
}
|
||||
});
|
||||
|
||||
if (oldS.type != newS.type) {
|
||||
for (const prop of Object.getOwnPropertyNames(newS)) {
|
||||
if (prop != "description") {
|
||||
@@ -600,18 +669,19 @@ export function argSigToJsonSchemaType(
|
||||
oldS.format = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// end of refactoring TODO //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export async function replaceLock(o?: { lock?: string | string[] }) {
|
||||
export function replaceLock(o?: { lock?: string | string[] }) {
|
||||
if (Array.isArray(o?.lock)) {
|
||||
o.lock = o.lock.join("\n");
|
||||
}
|
||||
if (o?.lock?.startsWith("!inline ")) {
|
||||
try {
|
||||
const lockPath = o?.lock?.split(" ")[1];
|
||||
o.lock = await Deno.readTextFile(lockPath);
|
||||
o.lock = readInlinePathSync(lockPath);
|
||||
} catch (e) {
|
||||
log.info(
|
||||
colors.yellow(`Failed to read lockfile, doing as if it was empty: ${e}`)
|
||||
@@ -645,7 +715,7 @@ export async function parseMetadataFile(
|
||||
metadataFilePath = scriptPath + ".script.yaml";
|
||||
await Deno.stat(metadataFilePath);
|
||||
const payload: any = yamlParse(await Deno.readTextFile(metadataFilePath));
|
||||
await replaceLock(payload);
|
||||
replaceLock(payload);
|
||||
|
||||
return {
|
||||
path: metadataFilePath,
|
||||
@@ -685,7 +755,7 @@ export async function parseMetadataFile(
|
||||
scriptInitialMetadata = yamlParse(
|
||||
await Deno.readTextFile(metadataFilePath)
|
||||
) as ScriptMetadata;
|
||||
await replaceLock(scriptInitialMetadata);
|
||||
replaceLock(scriptInitialMetadata);
|
||||
} catch (e) {
|
||||
log.info(
|
||||
colors.yellow(
|
||||
|
||||
+4
-3
@@ -12,6 +12,7 @@ import {
|
||||
log,
|
||||
Resource,
|
||||
ResourceService,
|
||||
SEP,
|
||||
Table,
|
||||
} from "./deps.ts";
|
||||
|
||||
@@ -32,7 +33,7 @@ export async function pushResource(
|
||||
try {
|
||||
resource = await ResourceService.getResource({
|
||||
workspace: workspace,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
});
|
||||
} catch {
|
||||
// flow doesn't exist
|
||||
@@ -45,7 +46,7 @@ export async function pushResource(
|
||||
|
||||
await ResourceService.updateResource({
|
||||
workspace: workspace,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
requestBody: { ...localResource },
|
||||
});
|
||||
} else {
|
||||
@@ -61,7 +62,7 @@ export async function pushResource(
|
||||
await ResourceService.createResource({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
...localResource,
|
||||
},
|
||||
});
|
||||
|
||||
+3
-3
@@ -143,7 +143,7 @@ export async function handleFile(
|
||||
alreadySynced.push(path);
|
||||
const remotePath = path
|
||||
.substring(0, path.indexOf("."))
|
||||
.replaceAll("\\", "/");
|
||||
.replaceAll(SEP, "/");
|
||||
|
||||
const language = inferContentTypeFromFilePath(path, opts?.defaultTs);
|
||||
|
||||
@@ -186,7 +186,7 @@ export async function handleFile(
|
||||
try {
|
||||
remote = await ScriptService.getScriptByPath({
|
||||
workspace: workspaceId,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
});
|
||||
log.debug(`Script ${remotePath} exists on remote`);
|
||||
} catch {
|
||||
@@ -214,7 +214,7 @@ export async function handleFile(
|
||||
content,
|
||||
description: typed?.description ?? "",
|
||||
language: language as NewScript["language"],
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
summary: typed?.summary ?? "",
|
||||
kind: typed?.kind,
|
||||
lock: typed?.lock,
|
||||
|
||||
+9
-6
@@ -214,11 +214,11 @@ export function extractInlineScriptsForFlows(
|
||||
const path = basePath + ext;
|
||||
const content = m.value.content;
|
||||
const r = [{ path: path, content: content }];
|
||||
m.value.content = "!inline " + path;
|
||||
m.value.content = "!inline " + path.replaceAll(SEP, "/");
|
||||
const lock = m.value.lock;
|
||||
if (lock && lock != "") {
|
||||
const lockPath = basePath + "lock";
|
||||
m.value.lock = "!inline " + lockPath;
|
||||
m.value.lock = "!inline " + lockPath.replaceAll(SEP, "/");
|
||||
r.push({ path: lockPath, content: lock });
|
||||
}
|
||||
return r;
|
||||
@@ -264,7 +264,7 @@ export function extractInlineScriptsForApps(
|
||||
const r = [];
|
||||
if (o["content"]) {
|
||||
const content = o["content"];
|
||||
o["content"] = "!inline " + basePath + ext;
|
||||
o["content"] = "!inline " + basePath.replaceAll(SEP, "/") + ext;
|
||||
r.push({
|
||||
path: basePath + ext,
|
||||
content: content,
|
||||
@@ -272,7 +272,7 @@ export function extractInlineScriptsForApps(
|
||||
}
|
||||
if (o["lock"] && o["lock"] != "") {
|
||||
const lock = o["lock"];
|
||||
o["lock"] = "!inline " + basePath + "lock";
|
||||
o["lock"] = "!inline " + basePath.replaceAll(SEP, "/") + "lock";
|
||||
r.push({
|
||||
path: basePath + "lock",
|
||||
content: lock,
|
||||
@@ -437,7 +437,10 @@ function ZipFSElement(
|
||||
parsed["lock"] != "" &&
|
||||
parsed["codebase"] == undefined
|
||||
) {
|
||||
parsed["lock"] = "!inline " + removeSuffix(p, ".json") + ".lock";
|
||||
parsed["lock"] =
|
||||
"!inline " +
|
||||
removeSuffix(p.replaceAll(SEP, "/"), ".json") +
|
||||
".lock";
|
||||
} else {
|
||||
parsed["lock"] = undefined;
|
||||
}
|
||||
@@ -1127,7 +1130,7 @@ async function push(opts: GlobalOptions & SyncOptions) {
|
||||
|
||||
log.info(
|
||||
colors.gray(
|
||||
"Computing the files to update on the remote to match local (taking .wmillignore into account)"
|
||||
"Computing the files to update on the remote to match local (taking wmill.yaml includes/excludes into account)"
|
||||
)
|
||||
);
|
||||
const remote = ZipFSElement(
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
|
||||
import { colors, log, path, yamlParse, yamlStringify } from "./deps.ts";
|
||||
import { SEP, colors, log, path, yamlParse, yamlStringify } from "./deps.ts";
|
||||
import { pushApp } from "./apps.ts";
|
||||
import { pushFolder } from "./folder.ts";
|
||||
import { pushFlow } from "./flow.ts";
|
||||
@@ -111,14 +111,14 @@ export async function pushObj(
|
||||
const typeEnding = getTypeStrFromPath(p);
|
||||
|
||||
if (typeEnding === "app") {
|
||||
const appName = p.split(".app" + path.sep)[0];
|
||||
const appName = p.split(".app" + SEP)[0];
|
||||
await pushApp(workspace, appName, appName + ".app", message);
|
||||
} else if (typeEnding === "folder") {
|
||||
await pushFolder(workspace, p, befObj, newObj);
|
||||
} else if (typeEnding === "variable") {
|
||||
await pushVariable(workspace, p, befObj, newObj, plainSecrets);
|
||||
} else if (typeEnding === "flow") {
|
||||
const flowName = p.split(".flow" + path.sep)[0];
|
||||
const flowName = p.split(".flow" + SEP)[0];
|
||||
await pushFlow(workspace, flowName, flowName + ".flow", message);
|
||||
} else if (typeEnding === "resource") {
|
||||
await pushResource(workspace, p, befObj, newObj);
|
||||
@@ -169,10 +169,10 @@ export function getTypeStrFromPath(
|
||||
| "user"
|
||||
| "group"
|
||||
| "settings" {
|
||||
if (p.includes(".flow" + path.sep)) {
|
||||
if (p.includes(".flow" + SEP)) {
|
||||
return "flow";
|
||||
}
|
||||
if (p.includes(".app" + path.sep)) {
|
||||
if (p.includes(".app" + SEP)) {
|
||||
return "app";
|
||||
}
|
||||
const parsed = path.parse(p);
|
||||
|
||||
+9
-1
@@ -2,7 +2,7 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck This file is copied from a JS project, so it's not type-safe.
|
||||
|
||||
import { log, encodeHex } from "./deps.ts";
|
||||
import { log, encodeHex, SEP } from "./deps.ts";
|
||||
|
||||
export function deepEqual<T>(a: T, b: T): boolean {
|
||||
if (a === b) return true;
|
||||
@@ -117,3 +117,11 @@ export async function generateHashFromBuffer(
|
||||
const hashBuffer = await crypto.subtle.digest("SHA-256", content);
|
||||
return encodeHex(hashBuffer);
|
||||
}
|
||||
|
||||
// export async function readInlinePath(path: string): Promise<string> {
|
||||
// return await Deno.readTextFile(path.replaceAll("/", SEP));
|
||||
// }
|
||||
|
||||
export function readInlinePathSync(path: string): string {
|
||||
return Deno.readTextFileSync(path.replaceAll("/", SEP));
|
||||
}
|
||||
|
||||
+4
-3
@@ -12,6 +12,7 @@ import {
|
||||
Confirm,
|
||||
ListableVariable,
|
||||
log,
|
||||
SEP,
|
||||
Table,
|
||||
VariableService,
|
||||
} from "./deps.ts";
|
||||
@@ -60,7 +61,7 @@ export async function pushVariable(
|
||||
try {
|
||||
variable = await VariableService.getVariable({
|
||||
workspace: workspace,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
decryptSecret: plainSecrets,
|
||||
includeEncrypted: true,
|
||||
});
|
||||
@@ -79,7 +80,7 @@ export async function pushVariable(
|
||||
|
||||
await VariableService.updateVariable({
|
||||
workspace,
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
alreadyEncrypted: !plainSecrets,
|
||||
requestBody: {
|
||||
...localVariable,
|
||||
@@ -93,7 +94,7 @@ export async function pushVariable(
|
||||
workspace,
|
||||
alreadyEncrypted: !plainSecrets,
|
||||
requestBody: {
|
||||
path: remotePath.replaceAll("\\", "/"),
|
||||
path: remotePath.replaceAll(SEP, "/"),
|
||||
...localVariable,
|
||||
},
|
||||
});
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.348.2",
|
||||
"version": "1.353.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-components",
|
||||
"version": "1.348.2",
|
||||
"version": "1.353.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^4.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-components",
|
||||
"version": "1.348.2",
|
||||
"version": "1.353.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -249,6 +249,11 @@
|
||||
"svelte": "./package/components/EditableSchemaForm.svelte",
|
||||
"default": "./package/components/EditableSchemaForm.svelte"
|
||||
},
|
||||
"./components/ScriptEditor.svelte": {
|
||||
"types": "./package/components/ScriptEditor.svelte.d.ts",
|
||||
"svelte": "./package/components/ScriptEditor.svelte",
|
||||
"default": "./package/components/ScriptEditor.svelte"
|
||||
},
|
||||
"./components/scriptEditor/LogPanel.svelte": {
|
||||
"types": "./package/components/scriptEditor/LogPanel.svelte.d.ts",
|
||||
"svelte": "./package/components/scriptEditor/LogPanel.svelte",
|
||||
@@ -328,6 +333,9 @@
|
||||
"components/scriptEditor/LogPanel.svelte": [
|
||||
"./package/components/scriptEditor/LogPanel.svelte.d.ts"
|
||||
],
|
||||
"components/ScriptEditor.svelte": [
|
||||
"./package/components/ScriptEditor.svelte.d.ts"
|
||||
],
|
||||
"components/common/kbd/Kbd.svelte": [
|
||||
"./package/components/common/kbd/Kbd.svelte.d.ts"
|
||||
],
|
||||
|
||||
@@ -94,18 +94,6 @@
|
||||
.Template-editor span.mtk20 {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(155, 155, 155, 0.5);
|
||||
border: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@@ -157,5 +145,7 @@
|
||||
|
||||
.app-editor-input {
|
||||
@apply rounded-component border border-gray-300 dark:border-gray-500 focus:border-gray-300 focus:dark:border-gray-500 focus:!ring-1 focus:!ring-blue-300;
|
||||
@apply placeholder:text-gray-400 dark:placeholder:text-gray-600;
|
||||
@apply placeholder:text-gray-400 dark:placeholder:text-gray-200;
|
||||
@apply bg-surface;
|
||||
@apply disabled:placeholder:text-gray-200 disabled:placeholder:dark:text-gray-500 disabled:text-gray-200 disabled:dark:text-gray-500 disabled:border-gray-200 disabled:dark:border-gray-600;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { EnumType, SchemaProperty } from '$lib/common'
|
||||
import { setInputCat as computeInputCat, emptyString } from '$lib/utils'
|
||||
import { setInputCat as computeInputCat, debounce, emptyString } from '$lib/utils'
|
||||
import { DollarSign, Pipette, Plus, X } from 'lucide-svelte'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import Multiselect from 'svelte-multiselect'
|
||||
import { fade } from 'svelte/transition'
|
||||
import JsonEditor from './apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
|
||||
import { Badge, Button, SecondsInput } from './common'
|
||||
import { Button, SecondsInput } from './common'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import type ItemPicker from './ItemPicker.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
@@ -29,6 +29,7 @@
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import SchemaFormDnd from './schema/SchemaFormDND.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
@@ -77,6 +78,7 @@
|
||||
export let order: string[] | undefined = undefined
|
||||
export let editor: SimpleEditor | undefined = undefined
|
||||
export let orderEditable = false
|
||||
export let shouldDispatchChanges: boolean = false
|
||||
|
||||
let oneOfSelected: string | undefined = undefined
|
||||
async function updateOneOfSelected(oneOf: SchemaProperty[] | undefined) {
|
||||
@@ -233,6 +235,18 @@
|
||||
let itemsLimit = 50
|
||||
|
||||
$: validateInput(pattern, value, required)
|
||||
|
||||
let oldValue = value
|
||||
|
||||
function compareValues(value) {
|
||||
if (!deepEqual(oldValue, value)) {
|
||||
oldValue = value
|
||||
dispatch('change')
|
||||
}
|
||||
}
|
||||
|
||||
let debounced = debounce(() => compareValues(value), 50)
|
||||
$: shouldDispatchChanges && debounced(value)
|
||||
</script>
|
||||
|
||||
<S3FilePicker
|
||||
@@ -269,15 +283,7 @@
|
||||
<div class="flex space-x-1">
|
||||
{#if inputCat == 'number'}
|
||||
{#if extra['min'] != undefined && extra['max'] != undefined}
|
||||
<div class="flex w-full gap-1">
|
||||
<span>{extra['min']}</span>
|
||||
|
||||
<div class="grow">
|
||||
<Range bind:value min={extra['min']} max={extra['max']} />
|
||||
</div>
|
||||
<span>{extra['max']}</span>
|
||||
<span class="mx-2"><Badge large color="blue">{value}</Badge></span>
|
||||
</div>
|
||||
<Range bind:value min={extra['min']} max={extra['max']} {defaultValue} />
|
||||
{:else if extra['seconds'] !== undefined}
|
||||
<SecondsInput bind:seconds={value} on:focus />
|
||||
{:else if extra?.currency}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Button } from './common'
|
||||
import { Clock } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
// import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
// import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
|
||||
@@ -11,6 +12,8 @@
|
||||
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 date: string | undefined = undefined
|
||||
let time: string | undefined = undefined
|
||||
@@ -72,17 +75,25 @@
|
||||
type="date"
|
||||
bind:value={date}
|
||||
{autofocus}
|
||||
class="!w-3/4 app-editor-input"
|
||||
{disabled}
|
||||
class={twMerge('h-8 text-sm !w-3/4 ', inputClass)}
|
||||
min={minDate}
|
||||
max={maxDate}
|
||||
/>
|
||||
<input type="time" bind:value={time} class="!w-1/4 min-w-[100px] app-editor-input" />
|
||||
<input
|
||||
type="time"
|
||||
bind:value={time}
|
||||
class={twMerge('h-8 text-sm !w-1/4 min-w-[100px] ', inputClass)}
|
||||
{disabled}
|
||||
/>
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
wrapperClasses="h-8"
|
||||
startIcon={{
|
||||
icon: Clock
|
||||
}}
|
||||
{disabled}
|
||||
size="xs"
|
||||
portalTarget={`#${randomId}`}
|
||||
dropdownItems={useDropdown
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import type { FlowState } from './flows/flowState'
|
||||
import { initHistory } from '$lib/history'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import type { FlowEditorContext, FlowInput } from './flows/types'
|
||||
import { dfs } from './flows/dfs'
|
||||
import { loadSchemaFromModule } from './flows/flowInfers'
|
||||
import { CornerDownLeft, Play } from 'lucide-svelte'
|
||||
@@ -439,7 +439,8 @@
|
||||
flowStore,
|
||||
testStepStore,
|
||||
saveDraft: () => {},
|
||||
initialPath: ''
|
||||
initialPath: '',
|
||||
flowInputsStore: writable<FlowInput | undefined>(undefined)
|
||||
})
|
||||
|
||||
$: updateFlow($flowStore)
|
||||
|
||||
@@ -260,48 +260,50 @@
|
||||
<div class="flex flex-row gap-2">
|
||||
{argName}
|
||||
{#if !uiOnly}
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
let:close
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
nonCaptureEvent
|
||||
startIcon={{ icon: Pen }}
|
||||
iconOnly
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<Label label="Name">
|
||||
<div class="flex flex-col gap-2">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full !bg-surface"
|
||||
value={argName}
|
||||
id={argName + i}
|
||||
on:keydown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
<div on:click|stopPropagation|preventDefault>
|
||||
<Popup
|
||||
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
|
||||
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
|
||||
let:close
|
||||
>
|
||||
<svelte:fragment slot="button">
|
||||
<Button
|
||||
color="light"
|
||||
size="xs2"
|
||||
nonCaptureEvent
|
||||
startIcon={{ icon: Pen }}
|
||||
iconOnly
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<Label label="Name">
|
||||
<div class="flex flex-col gap-2">
|
||||
<input
|
||||
type="text"
|
||||
class="w-full !bg-surface"
|
||||
value={argName}
|
||||
id={argName + i}
|
||||
on:keydown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
renameProperty(argName, argName + i)
|
||||
close(null)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
renameProperty(argName, argName + i)
|
||||
close(null)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
renameProperty(argName, argName + i)
|
||||
close(null)
|
||||
}}
|
||||
>
|
||||
Rename
|
||||
</Button>
|
||||
</div>
|
||||
</Label>
|
||||
</Popup>
|
||||
}}
|
||||
>
|
||||
Rename
|
||||
</Button>
|
||||
</div>
|
||||
</Label>
|
||||
</Popup>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte'
|
||||
import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte'
|
||||
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import type { FlowEditorContext, FlowInput } from './flows/types'
|
||||
import { cleanInputs, emptyFlowModuleState } from './flows/utils'
|
||||
import { Calendar, Pen, Save, DiffIcon } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@@ -377,7 +377,8 @@
|
||||
pathStore,
|
||||
testStepStore,
|
||||
saveDraft,
|
||||
initialPath
|
||||
initialPath,
|
||||
flowInputsStore: writable<FlowInput | undefined>({})
|
||||
})
|
||||
|
||||
async function loadSchedule() {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import Section from './Section.svelte'
|
||||
import { Eye, Plus } from 'lucide-svelte'
|
||||
import Label from './Label.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let name: string
|
||||
let can_write = false
|
||||
@@ -67,6 +70,7 @@
|
||||
async function loadFolder(): Promise<void> {
|
||||
try {
|
||||
folder = await FolderService.getFolder({ workspace: $workspaceStore!, name })
|
||||
summary = folder.summary ?? ''
|
||||
can_write =
|
||||
$userStore != undefined &&
|
||||
(folder?.owners.includes('u/' + $userStore.username) ||
|
||||
@@ -108,6 +112,7 @@
|
||||
let ownerKind: 'user' | 'group' = 'user'
|
||||
let groupCreated: string | undefined = undefined
|
||||
let newGroupName: string = ''
|
||||
let summary: string = ''
|
||||
|
||||
async function addGroup() {
|
||||
await GroupService.createGroup({
|
||||
@@ -119,6 +124,19 @@
|
||||
loadGroups()
|
||||
ownerItem = newGroupName
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function updateFolder() {
|
||||
await FolderService.updateFolder({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: { summary }
|
||||
})
|
||||
sendUserToast('Folder summary updated')
|
||||
dispatch('update')
|
||||
loadFolder()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={newGroup}>
|
||||
@@ -148,6 +166,15 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Section label="Metadata" class="mb-4">
|
||||
<Label label="Summary">
|
||||
<div class="flex flex-row gap-2">
|
||||
<input placeholder="Short summary to be displayed when listed" bind:value={summary} />
|
||||
<Button size="sm" on:click={updateFolder} disabled={!can_write}>Save</Button>
|
||||
</div>
|
||||
</Label>
|
||||
</Section>
|
||||
|
||||
<Section label={`Permissions (${perms?.length ?? 0})`}>
|
||||
<div class="flex flex-col gap-6">
|
||||
{#if can_write}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import autosize from '$lib/autosize'
|
||||
import { Button } from './common'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
@@ -17,6 +16,8 @@
|
||||
import { canWrite } from '$lib/utils'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import Section from './Section.svelte'
|
||||
import Label from './Label.svelte'
|
||||
|
||||
export let name: string
|
||||
let can_write = false
|
||||
@@ -27,6 +28,7 @@
|
||||
let members: { member_name: string; role: Role }[] | undefined = undefined
|
||||
let usernames: string[] | undefined = []
|
||||
let username: string = ''
|
||||
let summary = ''
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -78,9 +80,11 @@
|
||||
role: getRole(x)
|
||||
}
|
||||
})
|
||||
summary = group.summary ?? ''
|
||||
} catch (e) {
|
||||
can_write = false
|
||||
members = []
|
||||
summary = ''
|
||||
group = {
|
||||
name
|
||||
}
|
||||
@@ -101,37 +105,28 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<h1>{name}</h1>
|
||||
{#if group}
|
||||
<div class="flex flex-col gap-1">
|
||||
<textarea
|
||||
disabled={!can_write}
|
||||
rows="2"
|
||||
use:autosize
|
||||
bind:value={group.summary}
|
||||
placeholder="Summary of the group"
|
||||
/>
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
disabled={!can_write}
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
await GroupService.updateGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: { summary: group?.summary }
|
||||
})
|
||||
dispatch('update')
|
||||
sendUserToast('New summary saved')
|
||||
}}>Save Summary</Button
|
||||
>
|
||||
</div>
|
||||
<Section label="Metadata" class="mb-4">
|
||||
<Label label="Summary">
|
||||
<div class="flex flex-row gap-2">
|
||||
<input placeholder="Short summary to be displayed when listed" bind:value={summary} />
|
||||
<Button
|
||||
size="sm"
|
||||
on:click={async () => {
|
||||
await GroupService.updateGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: { summary }
|
||||
})
|
||||
dispatch('update')
|
||||
sendUserToast('Group summary updated')
|
||||
loadGroup()
|
||||
}}>Save</Button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<Skeleton layout={[[4]]} />
|
||||
{/if}
|
||||
<h2>Members ({members?.length ?? 0})</h2>
|
||||
</Label>
|
||||
</Section>
|
||||
|
||||
<Section label={`Members (${members?.length ?? 0})`}>
|
||||
{#if can_write}
|
||||
<div class="flex items-start">
|
||||
<AutoComplete required noInputStyles items={usernames} bind:selectedItem={username} />
|
||||
@@ -221,13 +216,6 @@
|
||||
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"
|
||||
/>
|
||||
|
||||
<!-- <ToggleButton position="center" value="manager" size="xs"
|
||||
>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.</Tooltip
|
||||
></ToggleButton
|
||||
> -->
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="admin"
|
||||
@@ -235,6 +223,15 @@
|
||||
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."
|
||||
/>
|
||||
{#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"
|
||||
/>
|
||||
{/if}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -281,58 +278,6 @@
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
{/if}
|
||||
|
||||
<!-- <h2 class="mt-10"
|
||||
>Groups managing this group <Tooltip>Any member of those groups can manage this group</Tooltip
|
||||
></h2
|
||||
>
|
||||
{#if can_write}
|
||||
<div class="flex items-start">
|
||||
<AutoComplete items={groups} bind:selectedItem={new_managing_group} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="sm"
|
||||
btnClasses="!ml-4"
|
||||
on:click={addToManagingGroup}
|
||||
>
|
||||
Add group managing this group
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if managing_groups.length == 0}
|
||||
<p class="text-tertiary text-sm">No group is managing this group</p>
|
||||
{:else}
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th>group</th>
|
||||
<th />
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each managing_groups as managing_group}<tr>
|
||||
<td>{managing_group.split('/')[1]}</td>
|
||||
<td>
|
||||
{#if can_write}
|
||||
<button
|
||||
class="ml-2 text-red-500"
|
||||
on:click={async () => {
|
||||
await GranularAclService.removeGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: managing_group
|
||||
}
|
||||
})
|
||||
loadGroup()
|
||||
}}>remove</button
|
||||
>
|
||||
{/if}</td
|
||||
>
|
||||
</tr>{/each}
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
{/if} -->
|
||||
{:else}
|
||||
<div class="flex flex-col">
|
||||
{#each new Array(6) as _}
|
||||
@@ -340,4 +285,4 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{#if members}
|
||||
<Popover
|
||||
><div class="inline-flex gap-1 items-end"
|
||||
><span class="text-tertiary text-xs mb-0.5">({members.length})</span>
|
||||
><span class="text-tertiary text-xs">({members.length})</span>
|
||||
<div class="max-w-xs truncate"
|
||||
><span class="text-tertiary text-xs">{members?.join(', ')}</span></div
|
||||
></div
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { InputCat } from '$lib/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
@@ -42,6 +42,8 @@
|
||||
let monacoTemplate: TemplateEditor | undefined = undefined
|
||||
let argInput: ArgInput | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: inputCat = computeInputCat(
|
||||
schema.properties[argName].type,
|
||||
schema.properties[argName].format,
|
||||
@@ -327,6 +329,7 @@
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
dispatch('change', { argName })
|
||||
return true
|
||||
})
|
||||
}}
|
||||
@@ -372,6 +375,9 @@
|
||||
}}
|
||||
bind:code={arg.value}
|
||||
fontSize={14}
|
||||
on:change={() => {
|
||||
dispatch('change', { argName })
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -385,6 +391,10 @@
|
||||
on:blur={() => {
|
||||
focused = false
|
||||
}}
|
||||
shouldDispatchChanges
|
||||
on:change={() => {
|
||||
dispatch('change', { argName })
|
||||
}}
|
||||
label={argName}
|
||||
bind:editor={monaco}
|
||||
bind:description={schema.properties[argName].description}
|
||||
@@ -416,6 +426,9 @@
|
||||
<SimpleEditor
|
||||
bind:this={monaco}
|
||||
bind:code={arg.expr}
|
||||
on:change={() => {
|
||||
dispatch('change', { argName })
|
||||
}}
|
||||
{extraLib}
|
||||
lang="javascript"
|
||||
shouldBindKey={false}
|
||||
@@ -426,6 +439,9 @@
|
||||
return false
|
||||
})
|
||||
}}
|
||||
on:change={() => {
|
||||
dispatch('change', { argName })
|
||||
}}
|
||||
on:blur={() => {
|
||||
focused = false
|
||||
}}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { VariableService, type InputTransform } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { allTrue } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Button } from './common'
|
||||
import StepInputsGen from './copilot/StepInputsGen.svelte'
|
||||
import type { PickableProperties } from './flows/previousResults'
|
||||
@@ -27,6 +28,9 @@
|
||||
export { clazz as class }
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: isValid = allTrue(inputCheck) ?? false
|
||||
|
||||
$: if (args == undefined || typeof args !== 'object') {
|
||||
@@ -94,6 +98,10 @@
|
||||
{noDynamicToggle}
|
||||
{pickableProperties}
|
||||
{enableAi}
|
||||
on:change={(e) => {
|
||||
const { argName } = e.detail
|
||||
dispatch('changeArg', { argName })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { setInputCat as computeInputCat, emptyString } from '$lib/utils'
|
||||
import { Badge, Button } from './common'
|
||||
import { Button } from './common'
|
||||
import { createEventDispatcher, tick } from 'svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import type { EnumType, SchemaProperty } from '$lib/common'
|
||||
@@ -227,14 +227,7 @@
|
||||
<div class="flex space-x-1">
|
||||
{#if inputCat == 'number'}
|
||||
{#if extra['min'] != undefined && extra['max'] != undefined}
|
||||
<div class="flex w-full gap-1">
|
||||
<span>{extra['min']}</span>
|
||||
<div class="grow">
|
||||
<Range bind:value min={extra['min']} max={extra['max']} />
|
||||
</div>
|
||||
<span>{extra['max']}</span>
|
||||
<span class="mx-2"><Badge large color="blue">{value}</Badge></span>
|
||||
</div>
|
||||
<Range bind:value min={extra['min']} max={extra['max']} {defaultValue} />
|
||||
{:else if extra?.currency}
|
||||
<CurrencyInput
|
||||
inputClasses={{
|
||||
|
||||
@@ -279,8 +279,8 @@
|
||||
}}
|
||||
>
|
||||
{#if !folderCreated}
|
||||
<div class="flex flex-row">
|
||||
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
|
||||
<div class="flex flex-col gap-2">
|
||||
<input placeholder="New folder name" bind:value={newFolderName} />
|
||||
<Button size="md" startIcon={{ icon: Plus }} disabled={!newFolderName} on:click={addFolder}>
|
||||
New folder
|
||||
</Button>
|
||||
|
||||
@@ -1,305 +1,119 @@
|
||||
<script>
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { fly, fade } from 'svelte/transition'
|
||||
<script lang="ts">
|
||||
import RangeSlider from 'svelte-range-slider-pips'
|
||||
|
||||
// Props
|
||||
export let min = 0
|
||||
export let max = 100
|
||||
export let initialValue = 0
|
||||
export let id = null
|
||||
export let value = typeof initialValue === 'string' ? parseInt(initialValue) : initialValue
|
||||
export let disabled: boolean = false
|
||||
export let defaultValue: number | undefined = undefined
|
||||
|
||||
// Node Bindings
|
||||
let container = null
|
||||
let thumb = null
|
||||
let progressBar = null
|
||||
let element = null
|
||||
let step: number = 1
|
||||
|
||||
// Internal State
|
||||
let elementX = null
|
||||
let currentThumb = null
|
||||
let holding = false
|
||||
let thumbHover = false
|
||||
let keydownAcceleration = 0
|
||||
let accelerationTimer = null
|
||||
let slider: HTMLElement
|
||||
|
||||
// Dispatch 'change' events
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
// Mouse shield used onMouseDown to prevent any mouse events penetrating other elements,
|
||||
// ie. hover events on other elements while dragging. Especially for Safari
|
||||
const mouseEventShield = document.createElement('div')
|
||||
mouseEventShield.setAttribute('class', 'mouse-over-shield')
|
||||
mouseEventShield.addEventListener('mouseover', (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
|
||||
function resizeWindow() {
|
||||
elementX = element.getBoundingClientRect().left
|
||||
function calculateAxisStep(min: number, max: number): number {
|
||||
const range = max - min
|
||||
return range < 100 ? 1 : range / 20
|
||||
}
|
||||
|
||||
// Allows both bind:value and on:change for parent value retrieval
|
||||
function setValue(val) {
|
||||
value = val
|
||||
dispatch('change', { value })
|
||||
$: if (value === null) {
|
||||
value = 0
|
||||
}
|
||||
|
||||
function onTrackEvent(e) {
|
||||
// Update value immediately before beginning drag
|
||||
updateValueOnEvent(e)
|
||||
onDragStart(e)
|
||||
$: axisStep = calculateAxisStep(min, max)
|
||||
|
||||
const format = (v, i, p) => {
|
||||
return `${v}`
|
||||
}
|
||||
|
||||
function onDragStart(e) {
|
||||
// If mouse event add a pointer events shield
|
||||
if (e.type === 'mousedown') document.body.append(mouseEventShield)
|
||||
currentThumb = thumb
|
||||
}
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (disabled) return
|
||||
|
||||
function onDragEnd(e) {
|
||||
// If using mouse - remove pointer event shield
|
||||
if (e.type === 'mouseup') {
|
||||
if (document.body.contains(mouseEventShield)) document.body.removeChild(mouseEventShield)
|
||||
// Needed to check whether thumb and mouse overlap after shield removed
|
||||
if (isMouseInElement(e, thumb)) thumbHover = true
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
if (value > min) {
|
||||
value = Math.max(value - step, min)
|
||||
}
|
||||
break
|
||||
case 'ArrowRight':
|
||||
if (value < max) {
|
||||
value = Math.min(value + step, max)
|
||||
}
|
||||
break
|
||||
}
|
||||
currentThumb = null
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
if (document.body.contains(mouseEventShield)) document.body.removeChild(mouseEventShield)
|
||||
})
|
||||
|
||||
// Check if mouse event cords overlay with an element's area
|
||||
function isMouseInElement(event, element) {
|
||||
let rect = element.getBoundingClientRect()
|
||||
let { clientX: x, clientY: y } = event
|
||||
if (x < rect.left || x >= rect.right) return false
|
||||
if (y < rect.top || y >= rect.bottom) return false
|
||||
return true
|
||||
}
|
||||
|
||||
// Accessible keypress handling
|
||||
function onKeyPress(e) {
|
||||
// Max out at +/- 10 to value per event (50 events / 5)
|
||||
// 100 below is to increase the amount of events required to reach max velocity
|
||||
if (keydownAcceleration < 50) keydownAcceleration++
|
||||
let throttled = Math.ceil(keydownAcceleration / 5)
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
|
||||
if (value + throttled > max || value >= max) {
|
||||
setValue(max)
|
||||
} else {
|
||||
setValue(value + throttled)
|
||||
}
|
||||
}
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
|
||||
if (value - throttled < min || value <= min) {
|
||||
setValue(min)
|
||||
} else {
|
||||
setValue(value - throttled)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset acceleration after 100ms of no events
|
||||
clearTimeout(accelerationTimer)
|
||||
accelerationTimer = setTimeout(() => (keydownAcceleration = 1), 100)
|
||||
}
|
||||
|
||||
function calculateNewValue(clientX) {
|
||||
resizeWindow()
|
||||
// Find distance between cursor and element's left cord (20px / 2 = 10px) - Center of thumb
|
||||
let delta = clientX - (elementX + 10)
|
||||
|
||||
// Use width of the container minus (5px * 2 sides) offset for percent calc
|
||||
let percent = (delta * 100) / (container.clientWidth - 10)
|
||||
|
||||
// Limit percent 0 -> 100
|
||||
percent = percent < 0 ? 0 : percent > 100 ? 100 : percent
|
||||
|
||||
// Limit value min -> max
|
||||
setValue(Math.round((percent * (max - min)) / 100 + min))
|
||||
}
|
||||
|
||||
// Handles both dragging of touch/mouse as well as simple one-off click/touches
|
||||
function updateValueOnEvent(e) {
|
||||
// touchstart && mousedown are one-off updates, otherwise expect a currentPointer node
|
||||
if (!currentThumb && e.type !== 'touchstart' && e.type !== 'mousedown') return false
|
||||
|
||||
if (e.stopPropagation) e.stopPropagation()
|
||||
if (e.preventDefault) e.preventDefault()
|
||||
|
||||
// Get client's x cord either touch or mouse
|
||||
const clientX =
|
||||
e.type === 'touchmove' || e.type === 'touchstart' ? e.touches[0].clientX : e.clientX
|
||||
|
||||
calculateNewValue(clientX)
|
||||
}
|
||||
|
||||
// React to left position of element relative to window
|
||||
$: if (element) elementX = element.getBoundingClientRect().left
|
||||
|
||||
// Set a class based on if dragging
|
||||
$: holding = Boolean(currentThumb)
|
||||
|
||||
// Update progressbar and thumb styles to represent value
|
||||
$: if (progressBar && thumb) {
|
||||
// Limit value min -> max
|
||||
value = value > min ? value : min
|
||||
value = value < max ? value : max
|
||||
|
||||
let percent = ((value - min) * 100) / (max - min)
|
||||
let offsetLeft = (container.clientWidth - 10) * (percent / 100) + 5
|
||||
|
||||
// Update thumb position + active range track width
|
||||
thumb.style.left = `${offsetLeft}px`
|
||||
progressBar.style.width = `${offsetLeft}px`
|
||||
}
|
||||
// Calculate the handle width based on the length of the max value
|
||||
$: handleWidth = `${Math.max(max.toString().length ?? 2, 2)}em`
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:touchmove|nonpassive={updateValueOnEvent}
|
||||
on:touchcancel={onDragEnd}
|
||||
on:touchend={onDragEnd}
|
||||
on:mousemove={updateValueOnEvent}
|
||||
on:mouseup={onDragEnd}
|
||||
on:resize={resizeWindow}
|
||||
/>
|
||||
<div class="range">
|
||||
<div class="flex flex-row w-full mx-2 items-center gap-8">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
||||
<div
|
||||
class="range__wrapper"
|
||||
tabindex="0"
|
||||
on:keydown={onKeyPress}
|
||||
bind:this={element}
|
||||
role="slider"
|
||||
aria-valuemin={min}
|
||||
aria-valuemax={max}
|
||||
aria-valuenow={value}
|
||||
{id}
|
||||
class={'grow'}
|
||||
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; --handle-width: {handleWidth}; --handle-border: 4px;"
|
||||
on:pointerdown|stopPropagation
|
||||
on:mousedown|stopPropagation={onTrackEvent}
|
||||
on:touchstart|stopPropagation={onTrackEvent}
|
||||
>
|
||||
<div class="range__track" bind:this={container}>
|
||||
<div class="range__track--highlighted" bind:this={progressBar} />
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
class="range__thumb"
|
||||
class:range__thumb--holding={holding}
|
||||
bind:this={thumb}
|
||||
on:touchstart={onDragStart}
|
||||
on:mousedown={onDragStart}
|
||||
on:mouseover={() => (thumbHover = true)}
|
||||
on:mouseout={() => (thumbHover = false)}
|
||||
on:keydown={handleKeyDown}
|
||||
>{#if max <= min}
|
||||
<div class="text-secondary text-sm"
|
||||
>Impossible to display range: {`max (${max}) <= min (${min})`}</div
|
||||
>
|
||||
{#if holding || thumbHover}
|
||||
<div class="range__tooltip" in:fly={{ y: 7, duration: 200 }} out:fade={{ duration: 100 }}>
|
||||
{value}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<RangeSlider
|
||||
id="range-slider-form"
|
||||
springValues={{ stiffness: 1, damping: 1 }}
|
||||
bind:slider
|
||||
min={min == undefined ? 0 : +min}
|
||||
max={max == undefined ? 1 : +max}
|
||||
on:change={(e) => {
|
||||
value = e.detail.value
|
||||
}}
|
||||
{defaultValue}
|
||||
{disabled}
|
||||
values={[value]}
|
||||
pips
|
||||
float
|
||||
first="label"
|
||||
last="label"
|
||||
step={step ?? 1}
|
||||
pipstep={(axisStep ?? 1) / (step ?? 1)}
|
||||
formatter={format}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<input bind:value type="number" class="!w-16 h-8 !text-xs mb-6" {max} {min} {disabled} />
|
||||
</div>
|
||||
|
||||
<svelte:head>
|
||||
<style>
|
||||
.mouse-over-shield {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgba(255, 0, 0, 0);
|
||||
z-index: 10000;
|
||||
cursor: grabbing;
|
||||
}
|
||||
</style>
|
||||
</svelte:head>
|
||||
|
||||
<style>
|
||||
.range {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
:global(#range-slider-form.rangeSlider) {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.range__wrapper {
|
||||
min-width: 100%;
|
||||
position: relative;
|
||||
padding: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
:global(.dark #range-slider-form.rangeSlider) {
|
||||
background-color: #3b4252;
|
||||
}
|
||||
|
||||
.range__wrapper:focus-visible > .range__track {
|
||||
box-shadow: 0 0 0 2px white, 0 0 0 3px var(--track-focus, #6185ff);
|
||||
}
|
||||
|
||||
.range__track {
|
||||
height: 6px;
|
||||
background-color: var(--track-bgcolor, #d0d0d0);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.range__track--highlighted {
|
||||
background-color: var(--track-highlight-bgcolor, #6185ff);
|
||||
background: var(--track-highlight-bg, linear-gradient(90deg, #6185ff, #9c65ff));
|
||||
width: 0;
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.range__thumb {
|
||||
:global(#range-slider-form.rangeSlider .rangeHandle) {
|
||||
width: var(--handle-width, 2em) !important;
|
||||
height: 2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: var(--thumb-bgcolor, white);
|
||||
cursor: pointer;
|
||||
border-radius: 999px;
|
||||
margin-top: -8px;
|
||||
transition: box-shadow 100ms;
|
||||
user-select: none;
|
||||
box-shadow: var(
|
||||
--thumb-boxshadow,
|
||||
0 1px 1px 0 rgba(0, 0, 0, 0.14),
|
||||
0 0px 2px 1px rgba(0, 0, 0, 0.2)
|
||||
);
|
||||
}
|
||||
|
||||
.range__thumb--holding {
|
||||
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 2px 1px rgba(0, 0, 0, 0.2),
|
||||
0 0 0 6px var(--thumb-holding-outline, rgba(113, 119, 250, 0.3));
|
||||
:global(#range-slider-form.rangeSlider .rangeFloat) {
|
||||
opacity: 1;
|
||||
background: transparent;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.range__tooltip {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: -33px;
|
||||
color: var(--tooltip-text, white);
|
||||
width: 38px;
|
||||
padding: 4px 0;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
background-color: var(--tooltip-bgcolor, #6185ff);
|
||||
background: var(--tooltip-bg, linear-gradient(45deg, #6185ff, #9c65ff));
|
||||
}
|
||||
|
||||
.range__tooltip::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 7px;
|
||||
width: 7px;
|
||||
background-color: var(--tooltip-bgcolor, #6185ff);
|
||||
bottom: -3px;
|
||||
left: calc(50% - 3px);
|
||||
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
|
||||
transform: rotate(-45deg);
|
||||
border-radius: 0 0 0 3px;
|
||||
:global(.dark #range-slider-form.rangeSlider > .rangePips > .pip) {
|
||||
color: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
export let minTimeSet: string | undefined = undefined
|
||||
export let maxTimeSet: string | undefined = undefined
|
||||
export let selectedIds: string[] = []
|
||||
export let canSelect: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const SUCCESS_COLOR = '#4ade80'
|
||||
@@ -142,7 +143,7 @@
|
||||
}
|
||||
|
||||
function highlightSelectedPoints(ids: string[]) {
|
||||
if (ids.length === 0) {
|
||||
if (!canSelect || ids.length === 0) {
|
||||
data.datasets[0].backgroundColor = FAIL_COLOR
|
||||
data.datasets[1].backgroundColor = SUCCESS_COLOR
|
||||
} else {
|
||||
@@ -237,8 +238,10 @@
|
||||
}
|
||||
},
|
||||
onClick: (e, u) => {
|
||||
const ids = u.map((j) => data.datasets[j.datasetIndex].data[j.index].id)
|
||||
selectedIds = ids
|
||||
if (canSelect) {
|
||||
const ids = u.map((j) => data.datasets[j.datasetIndex].data[j.index].id)
|
||||
selectedIds = ids
|
||||
}
|
||||
},
|
||||
|
||||
scales: {
|
||||
@@ -265,7 +268,6 @@
|
||||
} as any
|
||||
|
||||
$: data && scatterOptions && highlightSelectedPoints(selectedIds)
|
||||
|
||||
</script>
|
||||
|
||||
<!-- {JSON.stringify(minTime)}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import { List, Loader2, Save } from 'lucide-svelte'
|
||||
import FlowRetries from './flows/content/FlowRetries.svelte'
|
||||
import WorkerTagPicker from './WorkerTagPicker.svelte'
|
||||
import Label from './Label.svelte'
|
||||
|
||||
let optionTabSelected: 'error_handler' | 'recovery_handler' | 'retries' = 'error_handler'
|
||||
|
||||
@@ -441,16 +442,16 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex flex-col gap-12">
|
||||
<div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold">Metadata</h2>
|
||||
<div class="w-full py-2">
|
||||
<h2 class="text-base font-semibold mb-2">Metadata</h2>
|
||||
<Label label="Summary">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
type="text"
|
||||
placeholder="Schedule summary"
|
||||
class="text-sm w-full font-semibold"
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
class="text-sm w-full"
|
||||
bind:value={summary}
|
||||
on:keyup={() => {
|
||||
if (!edit && summary?.length > 0 && !dirtyPath) {
|
||||
@@ -464,40 +465,42 @@
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
{#if !edit}
|
||||
<Path
|
||||
bind:dirty={dirtyPath}
|
||||
bind:this={pathC}
|
||||
checkInitialPathExistence
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="schedule"
|
||||
kind="schedule"
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex justify-start w-full">
|
||||
<Badge
|
||||
color="gray"
|
||||
class="center-center !bg-surface-secondary !text-tertiary !h-[24px] rounded-r-none border"
|
||||
>
|
||||
Schedule path (not editable)
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={path}
|
||||
size={path?.length || 50}
|
||||
class="font-mono !text-xs grow shrink overflow-x-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none"
|
||||
on:focus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
<Label label="Path">
|
||||
{#if !edit}
|
||||
<Path
|
||||
bind:dirty={dirtyPath}
|
||||
bind:this={pathC}
|
||||
checkInitialPathExistence
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="schedule"
|
||||
kind="schedule"
|
||||
/>
|
||||
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-start w-full">
|
||||
<Badge
|
||||
color="gray"
|
||||
class="center-center !bg-surface-secondary !text-tertiary !h-[24px] rounded-r-none border"
|
||||
>
|
||||
Schedule path (not editable)
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={path}
|
||||
size={path?.length || 50}
|
||||
class="font-mono !text-xs grow shrink overflow-x-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none"
|
||||
on:focus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
|
||||
</div>
|
||||
{/if}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<Section label="Schedule">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { Calendar } from 'lucide-svelte'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { ScriptSchedule } from '$lib/scripts'
|
||||
import Label from './Label.svelte'
|
||||
|
||||
export let initialPath: string
|
||||
export let schema: Record<string, any> | undefined
|
||||
@@ -37,15 +38,17 @@
|
||||
$: initialPath && loadSchedules()
|
||||
</script>
|
||||
|
||||
<div class="w-full py-2">
|
||||
<div class="w-full flex flex-col gap-4 mb-4">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
type="text"
|
||||
placeholder="Schedule summary"
|
||||
class="text-sm w-full font-semibold mb-4"
|
||||
bind:value={$schedule.summary}
|
||||
/>
|
||||
<Label label="Summary">
|
||||
<input
|
||||
autofocus
|
||||
type="text"
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
class="text-sm w-full"
|
||||
bind:value={$schedule.summary}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<CronInput bind:schedule={$schedule.cron} bind:timezone={$schedule.timezone} />
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
e.stopPropagation()
|
||||
$selectedComponent = [id]
|
||||
}}
|
||||
inputClass={twMerge('windmillapp w-full py-1.5 px-2 text-sm', 'app-editor-input')}
|
||||
minDate={resolvedConfig.minDateTime
|
||||
? formatDate(resolvedConfig.minDateTime, 'yyyy-MM-dd')
|
||||
: undefined}
|
||||
@@ -145,6 +146,7 @@
|
||||
? formatDate(resolvedConfig.maxDateTime, 'yyyy-MM-dd')
|
||||
: undefined}
|
||||
on:focus={() => ($selectedComponent = [id])}
|
||||
disabled={resolvedConfig.disabled}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user