Compare commits

..
Author SHA1 Message Date
pyranota ca72c97a32 Create dirs 2024-11-15 01:55:16 +00:00
pyranota 9a5baa8e53 Merge branch 'main' into python-uv 2024-11-14 12:51:10 +00:00
pyranota 0e3d835609 Disable UV for ansible
Will be enabled later.
Needs proper testing and its better to split onto 2 PRs with first modifying python and second ansible
2024-11-14 12:45:32 +00:00
pyranota 7a8a0bb163 Revert docker-image.yml 2024-11-14 11:47:56 +00:00
pyranota deff3b0045 Change TMP for windows 2024-11-14 11:39:52 +00:00
pyranota 1b885ce2c2 Merge branch 'python-uv' of github.com:windmill-labs/windmill into python-uv 2024-11-14 11:30:21 +00:00
pyranota cec82a9cd2 Dont pin python to specific version 2024-11-14 11:30:02 +00:00
pyranota 8a792c9c9d Merge branch 'main' into python-uv 2024-11-14 10:08:09 +00:00
pyranota 0ea405415e Merge branch 'main' into python-uv 2024-11-13 22:08:22 +00:00
Alexander Petric d598731913 fixing for windows 2024-11-13 17:07:14 -05:00
pyranota 5a96d64183 Merge branch 'main' into python-uv 2024-11-13 18:39:10 +00:00
pyranota a017459e12 Remove line from Dockerfile
We dont need it and to trigger build
2024-11-13 18:38:49 +00:00
Ruben Fiszel eff3e1c43a Update python_executor.rs 2024-11-03 02:57:00 +01:00
Ruben Fiszel 844acbf00b Merge branch 'main' into python-uv 2024-11-02 19:48:30 +01:00
pyranota e0040fe3b3 Merge branch 'main' into python-uv 2024-10-31 14:03:32 +00:00
pyranota d0bad936f0 Merge branch 'main' into python-uv 2024-10-31 09:50:25 +00:00
pyranota 63340e6ce2 Merge branch 'main' into python-uv 2024-10-31 08:53:45 +00:00
pyranota c839bae160 Fix flock and windows 2024-10-29 17:51:18 +00:00
pyranota 253452013c Merge branch 'python-uv' of github.com:windmill-labs/windmill into python-uv 2024-10-29 17:35:18 +00:00
pyranota af70339d07 Fix NSJAIL INDEX_URL 2024-10-29 17:34:52 +00:00
pyranota a01c26f0e2 Merge branch 'main' into python-uv 2024-10-29 17:26:25 +00:00
pyranota b1bcdae01d Merge branch 'python-uv' of github.com:windmill-labs/windmill into python-uv 2024-10-23 12:04:53 +00:00
pyranota b1a4f2ab50 Add --link-mode=copy and remove -v 2024-10-23 12:04:27 +00:00
Ruben Fiszel 1ad54e887e Update docker-image.yml 2024-10-23 11:41:46 +02:00
Ruben Fiszel a446660345 Update docker-image.yml 2024-10-23 11:41:01 +02:00
Ruben Fiszel c21182f502 Update Dockerfile 2024-10-23 11:40:04 +02:00
pyranota 54698b3eef Merge branch 'main' into python-uv 2024-10-23 09:37:29 +00:00
pyranota 59e8004f99 Return deleted flag 2024-10-22 16:57:15 +00:00
pyranota 1271a65b5a Handle flags for NSJAIL 2024-10-22 16:00:43 +00:00
pyranota 414b9c338b Remove unused import 2024-10-22 16:00:07 +00:00
pyranota 53909750ef Support S3 2024-10-22 15:47:47 +00:00
pyranota 60e7fa764c Initially refactor cache (No S3) 2024-10-22 15:31:56 +00:00
pyranota a0c86ef4c7 Pip fallback overwrite UV's cache 2024-10-22 14:27:32 +00:00
pyranota 5805554ac8 Fix backend compilation error 2024-10-22 14:15:19 +00:00
pyranota bd7607d162 Remove --disable-pip-version-check
Reason:
   warning: pip's `--disable-pip-version-check` has no effect
2024-10-21 18:23:39 +00:00
pyranota 7985a7bafa Refactor fallback
no_uv disable compile and install
where no_uv_install and no_uv_compile are a bit more specific
2024-10-21 18:16:10 +00:00
pyranota 13896a5741 Integrate with NSJAIL and prepare fallbacks 2024-10-21 18:08:35 +00:00
pyranota e881ed5e00 Merge branch 'main' into python-uv 2024-10-17 13:18:58 +00:00
pyranota d6a654aaff feat: Handle pip install by uv
Dirty and untested, but already something working
2024-10-10 12:51:57 +00:00
132 changed files with 2209 additions and 5465 deletions
+1 -1
View File
@@ -6,4 +6,4 @@ frontend/src/routes/test.svelte
CaddyfileRemoteMalo
*.swp
**/.idea/
.direnv
.direnv
-32
View File
@@ -1,37 +1,5 @@
# Changelog
## [1.425.1](https://github.com/windmill-labs/windmill/compare/v1.425.0...v1.425.1) (2024-11-16)
### Bug Fixes
* revert bool to text support in pg ([#4727](https://github.com/windmill-labs/windmill/issues/4727)) ([17d8933](https://github.com/windmill-labs/windmill/commit/17d893315bd86942c7895cdb3e9c3ab34977b258))
## [1.425.0](https://github.com/windmill-labs/windmill/compare/v1.424.0...v1.425.0) (2024-11-15)
### Features
* Handle `pip install` by `uv` ([#4517](https://github.com/windmill-labs/windmill/issues/4517)) ([f240d13](https://github.com/windmill-labs/windmill/commit/f240d1322a3c1669dfb69ebda6d05effcca26ae3))
* **monitoring:** workspace critical alerts ([#4684](https://github.com/windmill-labs/windmill/issues/4684)) ([c32038a](https://github.com/windmill-labs/windmill/commit/c32038a76d9d00703d8b865af72f083ac43414e3))
## [1.424.0](https://github.com/windmill-labs/windmill/compare/v1.423.2...v1.424.0) (2024-11-14)
### Features
* allow setting password and login type from superadmin UI ([2a1bff3](https://github.com/windmill-labs/windmill/commit/2a1bff3160b65eadba399229b5f53950ec2c0d48))
* **backend:** monitor minimal version of living workers ([#4704](https://github.com/windmill-labs/windmill/issues/4704)) ([50ff183](https://github.com/windmill-labs/windmill/commit/50ff183baeaa2a39c2b0188ee8dd6172b08ae801))
* Support mistral anthropic for ai ([#4692](https://github.com/windmill-labs/windmill/issues/4692)) ([556b4a4](https://github.com/windmill-labs/windmill/commit/556b4a41a1d010bb8a1796f485d343c1e412d278))
### Bug Fixes
* add countCompletedJobs api ([2a78359](https://github.com/windmill-labs/windmill/commit/2a78359af7b8e4700634c2ad2745c1d14d2da4f7))
* add queue_couts api ([10b6b1d](https://github.com/windmill-labs/windmill/commit/10b6b1dc04cb2b2d4ec5ceb26a5dbd2ac7bd1394))
* autoscaling when count < min worker set to min_workers ([180bb82](https://github.com/windmill-labs/windmill/commit/180bb826436f9960def8c6cf3e9692714ffdf917))
* deployment callbacks have a concurrency limit of 1 on same path ([c44fc4f](https://github.com/windmill-labs/windmill/commit/c44fc4f5ab8bcb5dffec08d5ed4ccd61feb1cb13))
## [1.423.2](https://github.com/windmill-labs/windmill/compare/v1.423.1...v1.423.2) (2024-11-13)
+1 -1
View File
@@ -2,7 +2,7 @@ target/
.env
oauth.json
oauth2.json
windmill-api/openapi-deref.yaml
tracing.folded
heaptrack*
index/
windmill-api/openapi-*.*
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE alerts SET acknowledged_workspace = true, acknowledged = true WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "00588a40dde5189ac1c61505f17acb0f4c244c60477427505bf5bd1b104d3bf9"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET openai_resource_path = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "034583442e6f8ae38d6c4e4aac26f17c8d9d0e657f28276228fc90d3e22e1304"
}
@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO alerts (alert_type, message, acknowledged, acknowledged_workspace, workspace_id, resource)\n VALUES ('critical_error', $1, $2, $3, $4, $5)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Bool",
"Bool",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "044e2b428ee6e2dd4543c87ad8835e239cf7567d18b8b3fa6608ea3a9d206ca7"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id\n FROM alerts\n ORDER BY created_at DESC\n LIMIT $1 OFFSET $2",
"query": "SELECT id, alert_type, message, created_at, acknowledged \n FROM alerts \n WHERE acknowledged = $1\n ORDER BY created_at DESC \n LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
@@ -27,15 +27,11 @@
"ordinal": 4,
"name": "acknowledged",
"type_info": "Bool"
},
{
"ordinal": 5,
"name": "workspace_id",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Bool",
"Int8",
"Int8"
]
@@ -45,9 +41,8 @@
false,
false,
false,
null,
true
]
},
"hash": "4d30c5a2894d655741d167f5589f5816583f0bae78a0dd3270dedb377dfa944a"
"hash": "0b955f2cff82a2d4ba3840588143e08952f029480d4a42503ecc3c5e70437995"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM healthchecks WHERE check_type = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "0ee63ef2dd5c88edba2a1f56d31f29876724922f148dad7af35b36efbf70207a"
}
@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET ai_resource = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "1610c79b8d238e3a35d795f34a2a0100b933b7f199d41cc2f554d57c811d55f3"
}
@@ -65,8 +65,8 @@
},
{
"ordinal": 12,
"name": "ai_resource",
"type_info": "Jsonb"
"name": "openai_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 13,
@@ -117,11 +117,6 @@
"ordinal": 22,
"name": "deploy_ui",
"type_info": "Jsonb"
},
{
"ordinal": 23,
"name": "mute_critical_alerts",
"type_info": "Bool"
}
],
"parameters": {
@@ -152,7 +147,6 @@
true,
false,
true,
true,
true
]
},
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n -- slack_team_id, \n -- slack_name, \n -- slack_command_script, \n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\", \n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\", \n webhook, \n deploy_to, \n error_handler, \n ai_resource, \n code_completion_enabled, \n error_handler_extra_args, \n error_handler_muted_on_cancel, \n large_file_storage, \n git_sync,\n default_app,\n default_scripts,\n workspace.name\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1",
"query": "SELECT\n -- slack_team_id, \n -- slack_name, \n -- slack_command_script, \n -- CASE WHEN slack_email = 'missing@email.xyz' THEN NULL ELSE slack_email END AS slack_email,\n auto_invite_domain IS NOT NULL AS \"auto_invite_enabled!\",\n CASE WHEN auto_invite_operator IS TRUE THEN 'operator' ELSE 'developer' END AS \"auto_invite_as!\", \n CASE WHEN auto_add IS TRUE THEN 'add' ELSE 'invite' END AS \"auto_invite_mode!\", \n webhook, \n deploy_to, \n error_handler, \n openai_resource_path, \n code_completion_enabled, \n error_handler_extra_args, \n error_handler_muted_on_cancel, \n large_file_storage, \n git_sync,\n default_app,\n default_scripts,\n workspace.name\n FROM workspace_settings\n LEFT JOIN workspace ON workspace.id = workspace_settings.workspace_id\n WHERE workspace_id = $1",
"describe": {
"columns": [
{
@@ -35,8 +35,8 @@
},
{
"ordinal": 6,
"name": "ai_resource",
"type_info": "Jsonb"
"name": "openai_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 7,
@@ -102,5 +102,5 @@
false
]
},
"hash": "0331a81262e2d3c1bcfaeb64617b11eb68ab4599d64e5e5af639a9ac5d791fd0"
"hash": "188534f4b29f6461b1a6214d060f183c830b19a403ebb7b8be55a691675010c3"
}
@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO alerts (alert_type, message, acknowledged, acknowledged_workspace, workspace_id, resource)\n VALUES ('recovered_critical_error', $1, $2, $3, $4, $5)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Bool",
"Bool",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "28987898c7e4b172d466bf08f33c2da733e71f8a253b0122c17b9e021919809e"
}
@@ -1,54 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id\n FROM alerts\n WHERE COALESCE(acknowledged, false) = $1\n ORDER BY created_at DESC\n LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "alert_type",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "message",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "acknowledged",
"type_info": "Bool"
},
{
"ordinal": 5,
"name": "workspace_id",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Bool",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
null,
true
]
},
"hash": "344b3a5d9683273a956b5156fed5ab9fdff1a7252198e4ae290d8c8f937ff177"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('recovered_critical_error', $1, $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "3a94ad52c6b7cde844fa868167248cd9ff63e5fdfa1d93d8fbec32a257b6b05e"
}
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE alerts SET acknowledged = true, acknowledged_workspace = true WHERE resource = $1 AND alert_type = 'critical_error'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "4a3a8207627418ba7b7eabaccd7ec72884f2a1eacf0ab0e0c6ccbf56c654c14a"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('critical_error', $1, $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "4d22084a5d9860832f30e8f08cbfa1848ed3c1336fa4790f45b8189c4ac97d91"
}
@@ -65,8 +65,8 @@
},
{
"ordinal": 12,
"name": "ai_resource",
"type_info": "Jsonb"
"name": "openai_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 13,
@@ -117,11 +117,6 @@
"ordinal": 22,
"name": "deploy_ui",
"type_info": "Jsonb"
},
{
"ordinal": 23,
"name": "mute_critical_alerts",
"type_info": "Bool"
}
],
"parameters": {
@@ -152,7 +147,6 @@
true,
false,
true,
true,
true
]
},
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT created_by, CONCAT(coalesce(queue.logs, ''), coalesce(job_logs.logs, '')) as logs, coalesce(job_logs.log_offset, 0) as log_offset, job_logs.log_file_index\n FROM queue \n LEFT JOIN job_logs ON job_logs.job_id = queue.id \n WHERE queue.id = $1 AND queue.workspace_id = $2",
"query": "SELECT created_by, CONCAT(coalesce(queue.logs, ''), coalesce(job_logs.logs, '')) as logs, job_logs.log_offset, job_logs.log_file_index\n FROM queue \n LEFT JOIN job_logs ON job_logs.job_id = queue.id \n WHERE queue.id = $1 AND queue.workspace_id = $2",
"describe": {
"columns": [
{
@@ -33,9 +33,9 @@
"nullable": [
false,
null,
null,
false,
true
]
},
"hash": "b2c30137b3b64f8c3f6aea062e8687e119599641beee0a0d6c89c5d90e82d0db"
"hash": "57111a97ff906fd79ecd8a571212cc4fea9cb9c16fe950f082ad5469bfeefc82"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET openai_resource_path = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "6268eabd561502a44e273d6391102278974623f7a7bcad6891d7abadf4d3ea03"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE alerts\n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true\n ELSE acknowledged_workspace\n END\n WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Text"
]
},
"nullable": []
},
"hash": "65da41c7ded54cdee8d33211561c068b72294cc99ff44ed0a13179df508ebc6a"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET ai_resource = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "6940ddc5ee8d2a1048213d46f52d964b199604ba66dedbe9f89cea727d726a2d"
}
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE alerts \n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $1::text IS NOT NULL THEN true\n ELSE acknowledged_workspace\n END\n WHERE ($1::text IS NOT NULL AND workspace_id = $1)\n OR ($1::text IS NULL)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "7c32176755c6ea2b6ae531860d436caae3fa256fc0803749ec5107632669adb3"
}
@@ -1,55 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id\n FROM alerts\n WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2\n ORDER BY created_at DESC\n LIMIT $3 OFFSET $4",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "alert_type",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "message",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "acknowledged",
"type_info": "Bool"
},
{
"ordinal": 5,
"name": "workspace_id",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Bool",
"Int8",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
null,
true
]
},
"hash": "7c5a29de07cbe42326a15d4d7fd9714c20b767508e27368be6de38d7b18a3a4d"
}
@@ -1,12 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE alerts SET acknowledged = true",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "a59fae29ebcc9aa53308b777ead3d2b652618ac454f139db738f4499cb4eb079"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE alerts SET acknowledged = true WHERE acknowledged = false",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "a7c5008aa7ea43d0afac7d9f19846976ed7af2e90270902f001115e023cb947d"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value\n FROM resource\n WHERE path = $1 AND workspace_id = $2",
"query": "SELECT value\n FROM resource\n WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
@@ -19,5 +19,5 @@
true
]
},
"hash": "286fa00c088df146c08c1934556f06a4243c66787ab8759a8045e60effd2fb77"
"hash": "acdaa5151f8f7f37bb8c8c5a7d146789887e47db9695fc26b1dfaedd735e1e60"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE alerts SET acknowledged = true WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": []
},
"hash": "be3ae231557e794172336bc27d725f862dcf039bbb3d75ced9d54c86f53d2580"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id\n FROM alerts\n WHERE workspace_id = $1\n ORDER BY created_at DESC\n LIMIT $2 OFFSET $3",
"query": "SELECT id, alert_type, message, created_at, acknowledged \n FROM alerts \n ORDER BY created_at DESC \n LIMIT $1 OFFSET $2",
"describe": {
"columns": [
{
@@ -27,16 +27,10 @@
"ordinal": 4,
"name": "acknowledged",
"type_info": "Bool"
},
{
"ordinal": 5,
"name": "workspace_id",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Int8",
"Int8"
]
@@ -46,9 +40,8 @@
false,
false,
false,
null,
true
]
},
"hash": "777e7084edf9a5d14f299ff479ae43c8ead47a6a531d4b031a1342c9b2f16506"
"hash": "cc5ab80241b88c5befea279f16c4ec68cec17b31dcd277b321f652917346496b"
}
@@ -1,12 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "SELECT ai_resource, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
"query": "SELECT openai_resource_path, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "ai_resource",
"type_info": "Jsonb"
"name": "openai_resource_path",
"type_info": "Varchar"
},
{
"ordinal": 1,
@@ -24,5 +24,5 @@
false
]
},
"hash": "0230bd64d2b6719c3536a106e18a68c7b43b3a9a9efa92b5517132e9c0d8a25b"
"hash": "ceb97024ebf1a1c00ea1ed4952f66b229ca4622c537cc252d8ed52b4d24270ee"
}
@@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT ai_resource FROM workspace_settings WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "ai_resource",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true
]
},
"hash": "ceda377fa534656ac12a7f41db77db1f054ec751855c8db7352b0e9c20b63ef8"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_settings SET mute_critical_alerts = $1 WHERE workspace_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Bool",
"Text"
]
},
"nullable": []
},
"hash": "f22168826350797e88153b65a5b1e906a7868f34c062f154b2ee4f24c57aa5c3"
}
@@ -1,12 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "SELECT mute_critical_alerts FROM workspace_settings WHERE workspace_id = $1",
"query": "SELECT openai_resource_path FROM workspace_settings WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "mute_critical_alerts",
"type_info": "Bool"
"name": "openai_resource_path",
"type_info": "Varchar"
}
],
"parameters": {
@@ -18,5 +18,5 @@
true
]
},
"hash": "e05dbe046e846c092a96b6b0a9d872c721169d418586be2d15cee6b929049098"
"hash": "f8b689ec3e09f14dc02ac9e9f98d252a14d3a2acfadcd9cd6ba27ca9799f4706"
}
+36 -36
View File
@@ -843,9 +843,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.50.0"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5"
checksum = "53dcf5e7d9bd1517b8b998e170e650047cea8a2b85fe1835abe3210713e541b7"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1032,9 +1032,9 @@ dependencies = [
[[package]]
name = "axum"
version = "0.7.8"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c41b948da08fb481a94546cd874843adc1142278b0af4badf9b1b78599d68d"
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
dependencies = [
"async-trait",
"axum-core",
@@ -1598,9 +1598,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8"
dependencies = [
"jobserver",
"libc",
@@ -1783,9 +1783,9 @@ dependencies = [
[[package]]
name = "comfy-table"
version = "7.1.3"
version = "7.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9"
checksum = "e0d05af1e006a2407bedef5af410552494ce5be9090444dbbcb57258c1af3d56"
dependencies = [
"strum 0.26.3",
"strum_macros 0.26.4",
@@ -3355,9 +3355,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.35"
version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
dependencies = [
"crc32fast",
"libz-sys",
@@ -4831,9 +4831,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.163"
version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fdaeca4cf44ed4ac623e86ef41f056e848dbeab7ec043ecb7326ba300b36fd0"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
[[package]]
name = "libgit2-sys"
@@ -10610,7 +10610,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"axum",
@@ -10652,7 +10652,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"argon2",
@@ -10737,7 +10737,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -10755,7 +10755,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"chrono",
"serde",
@@ -10768,7 +10768,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"rsmq_async",
@@ -10783,7 +10783,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"async-stream",
@@ -10830,7 +10830,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"regex",
"rsmq_async",
@@ -10845,7 +10845,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"bytes",
@@ -10867,7 +10867,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"itertools 0.13.0",
"lazy_static",
@@ -10879,7 +10879,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -10888,7 +10888,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -10900,7 +10900,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"gosyn",
@@ -10912,7 +10912,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -10924,7 +10924,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10935,7 +10935,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10946,7 +10946,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -10964,7 +10964,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -10981,7 +10981,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -10993,7 +10993,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -11011,7 +11011,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -11032,7 +11032,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"serde_json",
@@ -11042,7 +11042,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -11075,7 +11075,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -11085,7 +11085,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.425.1"
version = "1.423.2"
dependencies = [
"anyhow",
"async-recursion",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.425.1"
version = "1.423.2"
authors.workspace = true
edition.workspace = true
@@ -29,7 +29,7 @@ members = [
]
[workspace.package]
version = "1.425.1"
version = "1.423.2"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
+1 -1
View File
@@ -1 +1 @@
51dcbf93b0d127af9f33fa346cc63fcd2475d4fa
0d9c8813acd28848515c736e7b684220b5a785a3
@@ -1,7 +0,0 @@
-- Add down migration script here
ALTER TABLE workspace_settings
ALTER COLUMN ai_resource TYPE text
USING ai_resource->>'path';
ALTER TABLE workspace_settings
RENAME COLUMN ai_resource to openai_resource_path;
@@ -1,7 +0,0 @@
-- Add up migration script here
ALTER TABLE workspace_settings
ALTER COLUMN openai_resource_path TYPE jsonb
USING jsonb_build_object('provider', 'openai', 'path', openai_resource_path);
ALTER TABLE workspace_settings
RENAME COLUMN openai_resource_path TO ai_resource;
@@ -1,4 +0,0 @@
ALTER TABLE alerts DROP COLUMN workspace_id;
ALTER TABLE alerts DROP COLUMN acknowledged_workspace;
ALTER TABLE alerts DROP COLUMN resource;
ALTER TABLE workspace_settings DROP COLUMN mute_critical_alerts;
@@ -1,4 +0,0 @@
ALTER TABLE alerts ADD COLUMN workspace_id TEXT DEFAULT NULL;
ALTER TABLE alerts ADD COLUMN acknowledged_workspace BOOL DEFAULT NULL;
ALTER TABLE alerts ADD COLUMN resource TEXT DEFAULT NULL;
ALTER TABLE workspace_settings ADD COLUMN mute_critical_alerts BOOL DEFAULT NULL;
+2 -2
View File
@@ -998,13 +998,13 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
LOCK_CACHE_DIR,
TMP_LOGS_DIR,
UV_CACHE_DIR,
PY311_CACHE_DIR,
PIP_CACHE_DIR,
TAR_PIP_CACHE_DIR,
DENO_CACHE_DIR,
DENO_CACHE_DIR_DEPS,
DENO_CACHE_DIR_NPM,
BUN_CACHE_DIR,
PY311_CACHE_DIR,
PIP_CACHE_DIR,
BUN_DEPSTAR_CACHE_DIR,
BUN_BUNDLE_CACHE_DIR,
GO_CACHE_DIR,
+17 -24
View File
@@ -34,12 +34,12 @@ use windmill_common::{
error,
flow_status::FlowStatusModule,
global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING,
EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ERROR_CHANNELS_SETTING,
DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_BASE_URL_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
},
@@ -54,8 +54,8 @@ use windmill_common::{
update_min_version, DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SMTP_CONFIG,
WORKER_CONFIG, WORKER_GROUP,
},
BASE_URL, CRITICAL_ALERT_MUTE_UI_ENABLED, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL,
HUB_BASE_URL, JOB_RETENTION_SECS, METRICS_DEBUG_ENABLED, METRICS_ENABLED,
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
METRICS_DEBUG_ENABLED, METRICS_ENABLED, CRITICAL_ALERT_MUTE_UI_ENABLED
};
use windmill_queue::cancel_job;
use windmill_worker::{
@@ -231,20 +231,13 @@ pub async fn load_tag_per_workspace_workspaces(db: &DB) -> error::Result<()> {
}
pub async fn reload_critical_alert_mute_ui_setting(db: &DB) -> error::Result<()> {
if let Ok(Some(serde_json::Value::Bool(t))) =
load_value_from_global_settings(db, CRITICAL_ALERT_MUTE_UI_SETTING).await
{
CRITICAL_ALERT_MUTE_UI_ENABLED.store(t, Ordering::Relaxed);
if t {
if let Err(e) = sqlx::query!("UPDATE alerts SET acknowledged = true")
.execute(db)
.await
{
tracing::error!("Error updating alerts: {}", e.to_string());
}
let mute = load_value_from_global_settings(db, CRITICAL_ALERT_MUTE_UI_SETTING).await;
match mute {
Ok(Some(serde_json::Value::Bool(t))) => {
CRITICAL_ALERT_MUTE_UI_ENABLED.store(t, Ordering::Relaxed);
}
}
_ => (),
};
Ok(())
}
@@ -1330,7 +1323,7 @@ async fn handle_zombie_jobs<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
ON CONFLICT (job_id) DO UPDATE SET logs = job_logs.logs || '\nRestarted job after not receiving job''s ping for too long the ' || now() || '\n\n' WHERE job_logs.job_id = $1", r.id)
.execute(db).await;
tracing::error!(error_message);
report_critical_error(error_message, db.clone(), Some(&r.workspace_id), None).await;
report_critical_error(error_message, db.clone()).await;
}
}
@@ -1444,7 +1437,7 @@ async fn handle_zombie_flows(
flow.id, flow.workspace_id
);
tracing::error!(error_message);
report_critical_error(error_message, db.clone(), Some(&flow.workspace_id), None).await;
report_critical_error(error_message, db.clone()).await;
// 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 AND canceled = false",
@@ -1464,7 +1457,7 @@ async fn handle_zombie_flows(
format!("Flow {id} was cancelled because it")
}
);
report_critical_error(reason.clone(), db.clone(), Some(&flow.workspace_id), None).await;
report_critical_error(reason.clone(), db.clone()).await;
cancel_zombie_flow_job(db, flow, &rsmq, reason).await?;
}
}
+10 -228
View File
@@ -1,7 +1,7 @@
{
"openapi": "3.0.3",
"info": {
"version": "1.423.2",
"version": "1.418.0",
"title": "Windmill API",
"contact": {
"name": "Windmill Team",
@@ -286,7 +286,7 @@
}
}
},
"/w/{workspace}/users/get/{username}": {
"/w/{workspace}/users/{username}": {
"get": {
"summary": "get user (require admin privilege)",
"operationId": "getUser",
@@ -436,106 +436,6 @@
}
}
},
"/users/set_password_of/{user}": {
"post": {
"summary": "set password for a specific user (require super admin)",
"operationId": "setPasswordForUser",
"tags": [
"user"
],
"parameters": [
{
"name": "user",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "set password",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
},
"required": [
"password"
]
}
}
}
},
"responses": {
"200": {
"description": "password set",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/users/set_login_type/{user}": {
"post": {
"summary": "set login type for a specific user (require super admin)",
"operationId": "setLoginTypeForUser",
"tags": [
"user"
],
"parameters": [
{
"name": "user",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "set login type",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"login_type": {
"type": "string"
}
},
"required": [
"login_type"
]
}
}
}
},
"responses": {
"200": {
"description": "login type set",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/users/create": {
"post": {
"summary": "create user",
@@ -1205,9 +1105,6 @@
},
"tls_implicit": {
"type": "boolean"
},
"disable_tls": {
"type": "boolean"
}
},
"required": [
@@ -1216,8 +1113,7 @@
"password",
"port",
"from",
"tls_implicit",
"disable_tls"
"tls_implicit"
]
}
},
@@ -2564,8 +2460,8 @@
"deploy_to": {
"type": "string"
},
"ai_resource": {
"$ref": "#/components/schemas/AiResource"
"openai_resource_path": {
"type": "string"
},
"code_completion_enabled": {
"type": "boolean"
@@ -3005,8 +2901,8 @@
"code_completion_enabled"
],
"properties": {
"ai_resource": {
"$ref": "#/components/schemas/AiResource"
"openai_resource_path": {
"type": "string"
},
"code_completion_enabled": {
"type": "boolean"
@@ -3050,10 +2946,7 @@
"schema": {
"type": "object",
"properties": {
"ai_provider": {
"type": "string"
},
"exists_ai_resource": {
"exists_openai_resource_path": {
"type": "boolean"
},
"code_completion_enabled": {
@@ -3061,8 +2954,7 @@
}
},
"required": [
"ai_provider",
"exists_ai_resource",
"exists_openai_resource_path",
"code_completion_enabled"
]
}
@@ -13268,30 +13160,6 @@
}
}
},
"/workers/queue_counts": {
"get": {
"summary": "get counts of jobs waiting for an executor per tag",
"operationId": "getCountsOfJobsWaitingPerTag",
"tags": [
"worker"
],
"responses": {
"200": {
"description": "queue counts",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": {
"type": "integer"
}
}
}
}
}
}
}
},
"/configs/list_worker_groups": {
"get": {
"summary": "list worker groups",
@@ -14942,20 +14810,6 @@
"schema": {
"type": "string"
}
},
{
"name": "content_type",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "content_disposition",
"in": "query",
"schema": {
"type": "string"
}
}
],
"requestBody": {
@@ -16262,21 +16116,6 @@
}
},
"schemas": {
"AiResource": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"provider": {
"type": "string"
}
},
"required": [
"path",
"provider"
]
},
"Script": {
"type": "object",
"properties": {
@@ -17387,7 +17226,7 @@
"jobs.disapproval",
"jobs.delete",
"account.delete",
"ai.request",
"openai.request",
"resources.create",
"resources.update",
"resources.delete",
@@ -18228,23 +18067,6 @@
"route_path": {
"type": "string"
},
"static_asset_config": {
"type": "object",
"properties": {
"s3": {
"type": "string"
},
"storage": {
"type": "string"
},
"filename": {
"type": "string"
}
},
"required": [
"s3"
]
},
"is_flow": {
"type": "boolean"
},
@@ -18304,23 +18126,6 @@
"route_path": {
"type": "string"
},
"static_asset_config": {
"type": "object",
"properties": {
"s3": {
"type": "string"
},
"storage": {
"type": "string"
},
"filename": {
"type": "string"
}
},
"required": [
"s3"
]
},
"is_flow": {
"type": "boolean"
},
@@ -18363,23 +18168,6 @@
"route_path": {
"type": "string"
},
"static_asset_config": {
"type": "object",
"properties": {
"s3": {
"type": "string"
},
"storage": {
"type": "string"
},
"filename": {
"type": "string"
}
},
"required": [
"s3"
]
},
"is_flow": {
"type": "boolean"
},
@@ -19129,12 +18917,6 @@
"type": "object"
}
},
"s3_inputs": {
"type": "array",
"items": {
"type": "object"
}
},
"execution_mode": {
"type": "string",
"enum": [
File diff suppressed because it is too large Load Diff
+8 -166
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.425.1
version: 1.423.2
title: Windmill API
contact:
@@ -1681,8 +1681,8 @@ paths:
type: string
deploy_to:
type: string
ai_resource:
$ref: "#/components/schemas/AiResource"
openai_resource_path:
type: string
code_completion_enabled:
type: boolean
error_handler:
@@ -1701,8 +1701,6 @@ paths:
type: string
default_scripts:
$ref: "#/components/schemas/WorkspaceDefaultScripts"
mute_critical_alerts:
type: boolean
required:
- code_completion_enabled
- automatic_billing
@@ -1968,8 +1966,8 @@ paths:
required:
- code_completion_enabled
properties:
ai_resource:
$ref: "#/components/schemas/AiResource"
openai_resource_path:
type: string
code_completion_enabled:
type: boolean
responses:
@@ -1997,15 +1995,12 @@ paths:
schema:
type: object
properties:
ai_provider:
type: string
exists_ai_resource:
exists_openai_resource_path:
type: boolean
code_completion_enabled:
type: boolean
required:
- ai_provider
- exists_ai_resource
- exists_openai_resource_path
- code_completion_enabled
/w/{workspace}/workspaces/edit_error_handler:
@@ -2719,112 +2714,6 @@ paths:
items:
$ref: "#/components/schemas/ContextualVariable"
/w/{workspace}/workspaces/critical_alerts:
get:
summary: Get all critical alerts for this workspace
operationId: workspaceGetCriticalAlerts
tags:
- setting
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- in: query
name: page
schema:
type: integer
default: 1
description: The page number to retrieve (minimum value is 1)
- in: query
name: page_size
schema:
type: integer
default: 10
maximum: 100
description: Number of alerts per page (maximum is 100)
- in: query
name: acknowledged
schema:
type: boolean
nullable: true
description: Filter by acknowledgment status; true for acknowledged, false for unacknowledged, and omit for all alerts
responses:
"200":
description: Successfully retrieved all critical alerts
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/CriticalAlert'
/w/{workspace}/workspaces/critical_alerts/{id}/acknowledge:
post:
summary: Acknowledge a critical alert for this workspace
operationId: workspaceAcknowledgeCriticalAlert
tags:
- setting
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- in: path
name: id
required: true
schema:
type: integer
description: The ID of the critical alert to acknowledge
responses:
"200":
description: Successfully acknowledged the critical alert
content:
application/json:
schema:
type: string
example: "Critical alert acknowledged"
/w/{workspace}/workspaces/critical_alerts/acknowledge_all:
post:
summary: Acknowledge all unacknowledged critical alerts for this workspace
operationId: workspaceAcknowledgeAllCriticalAlerts
tags:
- setting
parameters:
- $ref: "#/components/parameters/WorkspaceId"
responses:
"200":
description: Successfully acknowledged all unacknowledged critical alerts.
content:
application/json:
schema:
type: string
example: "All unacknowledged critical alerts acknowledged"
/w/{workspace}/workspaces/critical_alerts/mute:
post:
summary: Mute critical alert UI for this workspace
operationId: workspaceMuteCriticalAlertsUI
tags:
- setting
parameters:
- $ref: "#/components/parameters/WorkspaceId"
requestBody:
description: Boolean flag to mute critical alerts.
required: true
content:
application/json:
schema:
type: object
properties:
mute_critical_alerts:
type: boolean
description: Whether critical alerts should be muted.
example: true
responses:
'200':
description: Successfully updated mute critical alert settings.
content:
application/json:
schema:
type: string
example: "Updated mute critical alert UI settings for workspace: workspace_id"
/oauth/login_callback/{client_name}:
post:
security: []
@@ -6162,39 +6051,6 @@ paths:
required:
- database_length
/w/{workspace}/jobs/completed/count_jobs:
get:
summary: count number of completed jobs with filter
operationId: countCompletedJobs
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- name: completed_after_s_ago
in: query
schema:
type: integer
- name: success
in: query
schema:
type: boolean
- name: tags
in: query
schema:
type: string
- name: all_workspaces
in: query
schema:
type: boolean
responses:
"200":
description: Count of completed jobs
content:
application/json:
schema:
type: integer
/w/{workspace}/jobs/queue/list_filtered_uuids:
get:
summary: get the ids of all jobs matching the given filters
@@ -10338,16 +10194,6 @@ components:
schemas:
$ref: "../../openflow.openapi.yaml#/components/schemas"
AiResource:
type: object
properties:
path:
type: string
provider:
type: string
required:
- path
- provider
Script:
type: object
properties:
@@ -11166,7 +11012,7 @@ components:
- "jobs.disapproval"
- "jobs.delete"
- "account.delete"
- "ai.request"
- "openai.request"
- "resources.create"
- "resources.update"
- "resources.delete"
@@ -13053,7 +12899,3 @@ components:
type: boolean
nullable: true
description: Acknowledgment status of the alert, can be true, false, or null if not set
workspace_id:
type: string
nullable: true
description: Workspace id if the alert is in the scope of a workspace
-507
View File
@@ -1,507 +0,0 @@
use crate::{
db::{ApiAuthed, DB},
variables::decrypt,
};
use anthropic::AnthropicCache;
use axum::{
body::Bytes,
extract::{Path, Query},
response::IntoResponse,
routing::post,
Extension, Router,
};
use lazy_static::lazy_static;
use mistral::MistralCache;
use openai::OpenaiCache;
use quick_cache::sync::Cache;
use reqwest::{Client, RequestBuilder};
use serde::{Deserialize, Deserializer};
use windmill_audit::audit_ee::audit_log;
use windmill_audit::ActionKind;
use windmill_common::error::{to_anyhow, Result};
use windmill_common::variables::build_crypt;
use windmill_common::error::Error;
use serde_json::value::{RawValue, Value};
use std::collections::HashMap;
lazy_static::lazy_static! {
static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
.timeout(std::time::Duration::from_secs(60 * 5))
.user_agent("windmill/beta")
.build().unwrap();
}
trait AiRequest {
fn prepare_request(self, path: &str, body: Bytes) -> Result<RequestBuilder>;
}
mod openai {
use super::*;
use super::{get_variable_or_self, KeyCache};
const API_VERSION: &str = "2023-05-15";
#[derive(Deserialize, Debug)]
struct OpenaiResource {
api_key: String,
organization_id: Option<String>,
}
#[derive(Deserialize, Debug)]
struct OpenaiClientCredentialsOauthResource {
client_id: String,
client_secret: String,
token_url: String,
user: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(untagged, rename_all = "snake_case")]
enum OpenaiConfig {
Resource(OpenaiResource),
ClientCredentialsOauthResource(OpenaiClientCredentialsOauthResource),
}
lazy_static::lazy_static! {
pub static ref OPENAI_AZURE_BASE_PATH: Option<String> = std::env::var("OPENAI_AZURE_BASE_PATH").ok();
}
#[derive(Deserialize, Debug)]
struct OpenaiCredentials {
access_token: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct OpenaiCache {
api_key: String,
organization_id: Option<String>,
azure_base_path: Option<String>,
user: Option<String>,
}
impl OpenaiCache {
pub fn new(
api_key: String,
organization_id: Option<String>,
azure_base_path: Option<String>,
user: Option<String>,
) -> Self {
Self { api_key, organization_id, azure_base_path, user }
}
}
const BASE_URL: &str = "https://api.openai.com/v1";
impl AiRequest for OpenaiCache {
fn prepare_request(self, openai_path: &str, mut body: Bytes) -> Result<RequestBuilder> {
let OpenaiCache { api_key, azure_base_path, organization_id, user } = self;
if user.is_some() {
tracing::debug!("Adding user to request body");
let mut json_body: HashMap<String, Box<RawValue>> = serde_json::from_slice(&body)
.map_err(|e| {
Error::InternalErr(format!("Failed to parse request body: {}", e))
})?;
let user_json_string = serde_json::Value::String(user.unwrap()).to_string(); // makes sure to escape characters
json_body.insert(
"user".to_string(),
RawValue::from_string(user_json_string)
.map_err(|e| Error::InternalErr(format!("Failed to parse user: {}", e)))?,
);
body = serde_json::to_vec(&json_body)
.map_err(|e| {
Error::InternalErr(format!("Failed to reserialize request body: {}", e))
})?
.into();
}
let base_url = if let Some(base_url) = azure_base_path {
base_url
} else {
BASE_URL.to_string()
};
let url = format!("{}/{}", base_url, openai_path);
let mut request = HTTP_CLIENT
.post(url)
.header("content-type", "application/json")
.body(body);
if base_url != BASE_URL {
request = request
.header("api-key", api_key)
.query(&[("api-version", API_VERSION)])
} else {
request = request.header("authorization", format!("Bearer {}", api_key))
}
if let Some(org_id) = organization_id {
request = request.header("OpenAI-Organization", org_id);
}
Ok(request)
}
}
async fn get_openai_key_using_credentials_flow(
mut resource: OpenaiClientCredentialsOauthResource,
db: &DB,
w_id: &str,
) -> Result<String> {
resource.client_id = get_variable_or_self(resource.client_id, db, w_id).await?;
resource.client_secret = get_variable_or_self(resource.client_secret, db, w_id).await?;
resource.token_url = get_variable_or_self(resource.token_url, db, w_id).await?;
let mut params = HashMap::new();
params.insert("grant_type", "client_credentials");
let response = HTTP_CLIENT
.post(resource.token_url)
.form(&params)
.basic_auth(resource.client_id, Some(resource.client_secret))
.send()
.await
.map_err(|err| {
Error::InternalErr(format!(
"Failed to get OpenAI credentials using credentials flow: {}",
err
))
})?;
let response = response.json::<OpenaiCredentials>().await.map_err(|err| {
Error::InternalErr(format!(
"Failed to parse OpenAI credentials from credentials flow: {}",
err
))
})?;
Ok(response.access_token)
}
pub async fn get_cached_value(db: &DB, w_id: &str, resource: Value) -> Result<KeyCache> {
let config = serde_json::from_value(resource)
.map_err(|e| Error::InternalErr(format!("validating openai resource {e:#}")))?;
let mut user = None::<String>;
let mut resource = match config {
OpenaiConfig::Resource(resource) => {
tracing::debug!("Getting OpenAI key from static resource");
resource
}
OpenaiConfig::ClientCredentialsOauthResource(resource) => {
tracing::debug!("Getting OpenAI key with client credentials flow");
user = resource.user.clone();
let token = get_openai_key_using_credentials_flow(resource, db, w_id).await?;
OpenaiResource { api_key: token, organization_id: None }
}
};
resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?;
if let Some(organization_id) = resource.organization_id {
resource.organization_id = Some(get_variable_or_self(organization_id, db, w_id).await?);
}
if user.is_some() {
user = Some(get_variable_or_self(user.unwrap(), db, w_id).await?);
}
let azure_base_path = sqlx::query_scalar!(
"SELECT value
FROM global_settings
WHERE name = 'openai_azure_base_path'",
)
.fetch_optional(db)
.await?;
let azure_base_path = if let Some(azure_base_path) = azure_base_path {
Some(
serde_json::from_value::<String>(azure_base_path).map_err(|e| {
Error::InternalErr(format!("validating openai azure base path {e:#}"))
})?,
)
} else {
OPENAI_AZURE_BASE_PATH.clone()
};
let workspace_cache = OpenaiCache::new(
resource.api_key.clone(),
resource.organization_id.clone(),
azure_base_path.clone(),
user.clone(),
);
Ok(KeyCache::Openai(workspace_cache))
}
}
mod anthropic {
use super::*;
#[derive(Clone, Deserialize, Debug)]
pub struct AnthropicCache {
#[serde(rename = "apiKey")]
pub api_key: String,
}
const API_VERSION: &str = "2023-06-01";
impl AnthropicCache {
pub fn new(api_key: String) -> Self {
Self { api_key }
}
}
const BASE_URL: &str = "https://api.anthropic.com";
impl AiRequest for AnthropicCache {
fn prepare_request(self, anthropic_path: &str, body: Bytes) -> Result<RequestBuilder> {
let AnthropicCache { api_key } = self;
let url = format!("{}/{}", BASE_URL, anthropic_path);
let request = HTTP_CLIENT
.post(url)
.header("x-api-key", api_key)
.header("anthropic-version", API_VERSION)
.header("content-type", "application/json")
.body(body);
Ok(request)
}
}
pub async fn get_cached_value(db: &DB, w_id: &str, resource: Value) -> Result<KeyCache> {
let mut resource: AnthropicCache = serde_json::from_value(resource)
.map_err(|e| Error::InternalErr(format!("validating anthropic resource {e:#}")))?;
resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?;
let workspace_cache = AnthropicCache::new(resource.api_key);
Ok(KeyCache::Anthropic(workspace_cache))
}
}
mod mistral {
use super::*;
#[derive(Deserialize, Clone, Debug)]
pub struct MistralCache {
#[serde(rename = "apiKey")]
pub api_key: String,
}
impl MistralCache {
pub fn new(api_key: String) -> Self {
Self { api_key }
}
}
const BASE_URL: &str = "https://api.mistral.ai";
impl AiRequest for MistralCache {
fn prepare_request(self, mistral_path: &str, body: Bytes) -> Result<RequestBuilder> {
let MistralCache { api_key } = self;
let url = format!("{}/{}", BASE_URL, mistral_path);
let request = HTTP_CLIENT
.post(url)
.header("content-type", "application/json")
.header("Accept", "application/json")
.header("authorization", format!("Bearer {}", api_key))
.body(body);
Ok(request)
}
}
pub async fn get_cached_value(db: &DB, w_id: &str, resource: Value) -> Result<KeyCache> {
let mut resource: MistralCache = serde_json::from_value(resource)
.map_err(|e| Error::InternalErr(format!("validating mistral resource {e:#}")))?;
resource.api_key = get_variable_or_self(resource.api_key, db, w_id).await?;
let workspace_cache = MistralCache::new(resource.api_key);
Ok(KeyCache::Mistral(workspace_cache))
}
}
#[derive(Clone, Debug)]
pub enum KeyCache {
Openai(OpenaiCache),
Anthropic(AnthropicCache),
Mistral(MistralCache),
}
#[derive(Clone, Debug)]
pub struct AiCache {
pub path: String,
pub cached_key: KeyCache,
pub expires_at: std::time::Instant,
}
impl AiCache {
pub fn new(path: String, cached_key: KeyCache) -> Self {
Self {
path,
cached_key,
expires_at: std::time::Instant::now() + std::time::Duration::from_secs(60),
}
}
fn is_expired(&self) -> bool {
self.expires_at < std::time::Instant::now()
}
}
lazy_static! {
pub static ref AI_KEY_CACHE: Cache<String, AiCache> = Cache::new(500);
}
struct Variable {
value: String,
is_secret: bool,
}
#[derive(Deserialize, Debug)]
struct ProxyQueryParams {
no_cache: Option<bool>,
}
async fn get_variable_or_self(path: String, db: &DB, w_id: &str) -> Result<String> {
if !path.starts_with("$var:") {
return Ok(path);
}
let path = path.strip_prefix("$var:").unwrap().to_string();
let mut variable = sqlx::query_as!(
Variable,
"SELECT value, is_secret
FROM variable
WHERE path = $1 AND workspace_id = $2",
&path,
&w_id
)
.fetch_one(db)
.await?;
if variable.is_secret {
let mc = build_crypt(db, w_id).await?;
variable.value = decrypt(&mc, variable.value)?;
}
Ok(variable.value)
}
#[derive(Deserialize, Debug)]
pub struct AiResource {
pub path: String,
#[serde(deserialize_with = "check_if_valid_ai_provider")]
pub provider: String,
}
fn check_if_valid_ai_provider<'de, D>(provider: D) -> std::result::Result<String, D::Error>
where
D: Deserializer<'de>,
{
let provider = String::deserialize(provider)?;
match provider.as_str() {
"anthropic" | "openai" | "mistral" => Ok(provider),
_ => Err(serde::de::Error::custom(
"Only the following Ai providers are supported: openai, anthropic and mistral"
.to_string(),
)),
}
}
pub fn workspaced_service() -> Router {
let router = Router::new().route("/proxy/*ai", post(proxy));
router
}
async fn proxy(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path((w_id, ai_path)): Path<(String, String)>,
Query(query_params): Query<ProxyQueryParams>,
body: Bytes,
) -> impl IntoResponse {
let workspace_cache = AI_KEY_CACHE.get(&w_id);
let ai_cache = match workspace_cache {
Some(cache) if !cache.is_expired() && !query_params.no_cache.unwrap_or(false) => {
cache.cached_key
}
_ => {
let ai_resource = sqlx::query_scalar!(
"SELECT ai_resource FROM workspace_settings WHERE workspace_id = $1",
&w_id
)
.fetch_one(&db)
.await?;
if ai_resource.is_none() {
return Err(Error::InternalErr("AI resource not configured".to_string()));
}
let ai_resource = serde_json::from_value::<AiResource>(ai_resource.unwrap())
.map_err(|e| Error::BadRequest(e.to_string()))?;
let ai_resource_path = ai_resource.path;
let resource = sqlx::query_scalar!(
"SELECT value
FROM resource
WHERE path = $1 AND workspace_id = $2",
&ai_resource_path,
&w_id
)
.fetch_optional(&db)
.await?
.ok_or_else(|| {
Error::InternalErr(format!(
"Could not find the {} resource at path {ai_resource_path}, update the resource path in the workspace settings", ai_resource.provider
))
})?;
if resource.is_none() {
return Err(Error::InternalErr(format!(
"{} resource missing value",
ai_resource.provider
)));
}
let resource = resource.unwrap();
let ai_cache = match ai_resource.provider.as_str() {
"openai" => openai::get_cached_value(&db, &w_id, resource).await,
"anthropic" => anthropic::get_cached_value(&db, &w_id, resource).await,
"mistral" => mistral::get_cached_value(&db, &w_id, resource).await,
provider => {
return Err(Error::BadRequest(format!("{} is not supported", provider)))
}
};
let ai_cache = ai_cache?;
AI_KEY_CACHE.insert(
w_id.clone(),
AiCache::new(ai_resource_path, ai_cache.clone()),
);
ai_cache
}
};
let (path, request) = match ai_cache {
KeyCache::Openai(cached) => ("openai_path", cached.prepare_request(&ai_path, body)),
KeyCache::Anthropic(cached) => ("anthropic_path", cached.prepare_request(&ai_path, body)),
KeyCache::Mistral(cached) => ("mistral_path", cached.prepare_request(&ai_path, body)),
};
let response = request?.send().await.map_err(to_anyhow)?;
let mut tx = db.begin().await?;
audit_log(
&mut *tx,
&authed,
"ai.request",
ActionKind::Execute,
&w_id,
Some(&authed.email),
Some([(path, &format!("{:?}", ai_path)[..])].into()),
)
.await?;
tx.commit().await?;
if response.error_for_status_ref().is_err() {
let err_msg = response.text().await.unwrap_or("".to_string());
return Err(Error::AiError(err_msg));
}
let status_code = response.status();
let headers = response.headers().clone();
let stream = response.bytes_stream();
Ok((status_code, headers, axum::body::Body::from_stream(stream)))
}
+3 -48
View File
@@ -206,7 +206,6 @@ pub fn workspaced_service() -> Router {
.route("/queue/list_filtered_uuids", get(list_filtered_uuids))
.route("/queue/cancel_selection", post(cancel_selection))
.route("/completed/count", get(count_completed_jobs))
.route("/completed/count_jobs", get(count_completed_jobs_detail))
.route(
"/completed/list",
get(list_completed_jobs).layer(cors.clone()),
@@ -950,7 +949,7 @@ async fn get_job_logs(
Ok(content_plain(Body::from(logs)))
} else {
let text = sqlx::query!(
"SELECT created_by, CONCAT(coalesce(queue.logs, ''), coalesce(job_logs.logs, '')) as logs, coalesce(job_logs.log_offset, 0) as log_offset, job_logs.log_file_index
"SELECT created_by, CONCAT(coalesce(queue.logs, ''), coalesce(job_logs.logs, '')) as logs, job_logs.log_offset, job_logs.log_file_index
FROM queue
LEFT JOIN job_logs ON job_logs.job_id = queue.id
WHERE queue.id = $1 AND queue.workspace_id = $2",
@@ -971,10 +970,10 @@ async fn get_job_logs(
log_job_view(&db, opt_authed.as_ref(), &w_id, &id).await?;
#[cfg(all(feature = "enterprise", feature = "parquet"))]
if let Some(r) = get_logs_from_store(text.log_offset.unwrap_or(0), &logs, &text.log_file_index).await {
if let Some(r) = get_logs_from_store(text.log_offset, &logs, &text.log_file_index).await {
return r.map(content_plain);
}
if let Some(r) = get_logs_from_disk(text.log_offset.unwrap_or(0), &logs, &text.log_file_index).await {
if let Some(r) = get_logs_from_disk(text.log_offset, &logs, &text.log_file_index).await {
return r.map(content_plain);
}
@@ -1593,50 +1592,6 @@ async fn count_queue_jobs(
))
}
#[derive(Deserialize)]
pub struct CountCompletedJobsQuery {
completed_after_s_ago: Option<i64>,
success: Option<bool>,
tags: Option<String>,
all_workspaces: Option<bool>,
}
async fn count_completed_jobs_detail(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
Query(query): Query<CountCompletedJobsQuery>,
) -> error::JsonResult<i64> {
let mut sqlb = SqlBuilder::select_from("completed_job");
sqlb
.field("COUNT(*) as count");
if !query.all_workspaces.unwrap_or(false) {
sqlb.and_where_eq("workspace_id", "?".bind(&w_id));
}
if let Some(after_s_ago) = query.completed_after_s_ago {
let after = Utc::now() - chrono::Duration::seconds(after_s_ago);
sqlb.and_where_gt("started_at + duration_ms / 1000 * interval '1 second'", "?".bind(&after.to_rfc3339()));
}
if let Some(success) = query.success {
sqlb.and_where_eq("success", "?".bind(&success));
}
if let Some(tags) = query.tags {
sqlb.and_where_in("tag", &tags.split(",").map(|t| format!("'{}'", t)).collect::<Vec<_>>());
}
let sql = sqlb.sql()?;
let stats = sqlx::query_scalar::<_, i64>(&sql)
.fetch_one(&db)
.await?;
Ok(Json(stats))
}
async fn count_completed_jobs(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
+2 -4
View File
@@ -62,14 +62,13 @@ mod http_triggers;
mod indexer_ee;
mod inputs;
mod integration;
mod ai;
#[cfg(feature = "parquet")]
mod job_helpers_ee;
pub mod job_metrics;
pub mod jobs;
pub mod oauth2_ee;
mod oidc_ee;
mod openai;
mod raw_apps;
mod resources;
mod saml_ee;
@@ -95,7 +94,6 @@ mod workspaces_ee;
pub const DEFAULT_BODY_LIMIT: usize = 2097152 * 100; // 200MB
lazy_static::lazy_static! {
pub static ref REQUEST_SIZE_LIMIT: Arc<RwLock<usize>> = Arc::new(RwLock::new(DEFAULT_BODY_LIMIT));
@@ -280,7 +278,7 @@ pub async fn run_server(
.nest("/job_helpers", job_helpers_service)
.nest("/jobs", jobs::workspaced_service())
.nest("/oauth", oauth2_ee::workspaced_service())
.nest("/ai", ai::workspaced_service())
.nest("/openai", openai::workspaced_service())
.nest("/raw_apps", raw_apps::workspaced_service())
.nest("/resources", resources::workspaced_service())
.nest("/schedules", schedule::workspaced_service())
+344
View File
@@ -0,0 +1,344 @@
use std::collections::HashMap;
use crate::db::{ApiAuthed, DB};
use axum::{
body::Bytes,
extract::{Extension, Path, Query},
response::IntoResponse,
routing::post,
Router,
};
use quick_cache::sync::Cache;
use reqwest::Client;
use serde_json::value::RawValue;
use windmill_audit::audit_ee::audit_log;
use windmill_audit::ActionKind;
use windmill_common::{
error::{to_anyhow, Error},
variables::build_crypt,
};
use crate::variables::decrypt;
use serde::Deserialize;
lazy_static::lazy_static! {
static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
.timeout(std::time::Duration::from_secs(60 * 5))
.user_agent("windmill/beta")
.build().unwrap();
}
pub fn workspaced_service() -> Router {
let router = Router::new().route("/proxy/*openai_path", post(proxy));
router
}
#[derive(Deserialize)]
struct OpenaiResource {
api_key: String,
organization_id: Option<String>,
}
#[derive(Deserialize)]
struct OpenaiClientCredentialsOauthResource {
client_id: String,
client_secret: String,
token_url: String,
user: Option<String>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum OpenaiConfig {
Resource(OpenaiResource),
ClientCredentialsOauthResource(OpenaiClientCredentialsOauthResource),
}
struct Variable {
value: String,
is_secret: bool,
}
async fn get_variable_or_self(path: String, db: &DB, w_id: &String) -> Result<String, Error> {
if !path.starts_with("$var:") {
return Ok(path);
}
let path = path.strip_prefix("$var:").unwrap().to_string();
let mut variable = sqlx::query_as!(
Variable,
"SELECT value, is_secret
FROM variable
WHERE path = $1 AND workspace_id = $2",
&path,
&w_id
)
.fetch_one(db)
.await?;
if variable.is_secret {
let mc = build_crypt(&db, &w_id).await?;
variable.value = decrypt(&mc, variable.value)?;
}
Ok(variable.value)
}
lazy_static::lazy_static! {
pub static ref OPENAI_AZURE_BASE_PATH: Option<String> = std::env::var("OPENAI_AZURE_BASE_PATH").ok();
static ref OPENAI_KEY_CACHE: Cache<String, OpenaiKeyCache> = Cache::new(500);
}
#[derive(Deserialize)]
struct OpenaiCredentials {
access_token: String,
}
async fn get_openai_key_using_credentials_flow(
mut resource: OpenaiClientCredentialsOauthResource,
db: &DB,
w_id: &String,
) -> Result<String, Error> {
resource.client_id = get_variable_or_self(resource.client_id, &db, &w_id).await?;
resource.client_secret = get_variable_or_self(resource.client_secret, &db, &w_id).await?;
resource.token_url = get_variable_or_self(resource.token_url, &db, &w_id).await?;
let mut params = HashMap::new();
params.insert("grant_type", "client_credentials");
let response = HTTP_CLIENT
.post(resource.token_url)
.form(&params)
.basic_auth(resource.client_id, Some(resource.client_secret))
.send()
.await
.map_err(|err| {
Error::InternalErr(format!(
"Failed to get OpenAI credentials using credentials flow: {}",
err
))
})?;
let response = response.json::<OpenaiCredentials>().await.map_err(|err| {
Error::InternalErr(format!(
"Failed to parse OpenAI credentials from credentials flow: {}",
err
))
})?;
Ok(response.access_token)
}
#[derive(Clone)]
struct OpenaiKeyCache {
api_key: String,
organization_id: Option<String>,
azure_base_path: Option<String>,
user: Option<String>,
expires_at: std::time::Instant,
}
impl OpenaiKeyCache {
fn new(
api_key: String,
organization_id: Option<String>,
azure_base_path: Option<String>,
expires_at: std::time::Instant,
user: Option<String>,
) -> Self {
Self { api_key, organization_id, azure_base_path, expires_at, user }
}
fn is_expired(&self) -> bool {
self.expires_at < std::time::Instant::now()
}
}
#[derive(Deserialize)]
struct ProxyQueryParams {
no_cache: Option<bool>,
}
async fn proxy(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Path((w_id, openai_path)): Path<(String, String)>,
Query(query_params): Query<ProxyQueryParams>,
mut body: Bytes,
) -> impl IntoResponse {
let workspace_cache = OPENAI_KEY_CACHE.get(&w_id);
let (api_key, organization_id, azure_base_path, user) = if query_params
.no_cache
.unwrap_or(false)
|| workspace_cache.is_none()
|| workspace_cache.clone().unwrap().is_expired()
{
let openai_resource_path = sqlx::query_scalar!(
"SELECT openai_resource_path FROM workspace_settings WHERE workspace_id = $1",
&w_id
)
.fetch_one(&db)
.await?;
if openai_resource_path.is_none() {
return Err(Error::InternalErr(
"OpenAI resource not configured".to_string(),
));
}
let openai_resource_path = openai_resource_path.unwrap();
let resource = sqlx::query_scalar!(
"SELECT value
FROM resource
WHERE path = $1 AND workspace_id = $2",
&openai_resource_path,
&w_id
)
.fetch_optional(&db)
.await?
.ok_or_else(|| {
Error::InternalErr(format!(
"Could not find the OpenAI resource at path {openai_resource_path}, update the resource path in the workspace settings"
))
})?;
if resource.is_none() {
return Err(Error::InternalErr(
"OpenAI resource missing value".to_string(),
));
}
let config: OpenaiConfig = serde_json::from_value(resource.unwrap())
.map_err(|e| Error::InternalErr(format!("validating openai resource {e:#}")))?;
let mut user = None::<String>;
let mut resource = match config {
OpenaiConfig::Resource(resource) => {
tracing::debug!("Getting OpenAI key from static resource");
resource
}
OpenaiConfig::ClientCredentialsOauthResource(resource) => {
tracing::debug!("Getting OpenAI key with client credentials flow");
user = resource.user.clone();
let token = get_openai_key_using_credentials_flow(resource, &db, &w_id).await?;
OpenaiResource { api_key: token, organization_id: None }
}
};
resource.api_key = get_variable_or_self(resource.api_key, &db, &w_id).await?;
if resource.organization_id.is_some() {
resource.organization_id =
Some(get_variable_or_self(resource.organization_id.unwrap(), &db, &w_id).await?);
}
if user.is_some() {
user = Some(get_variable_or_self(user.unwrap(), &db, &w_id).await?);
}
let expires_at = std::time::Instant::now() + std::time::Duration::from_secs(60);
let azure_base_path = sqlx::query_scalar!(
"SELECT value
FROM global_settings
WHERE name = 'openai_azure_base_path'",
)
.fetch_optional(&db)
.await?;
let azure_base_path = if let Some(azure_base_path) = azure_base_path {
Some(
serde_json::from_value::<String>(azure_base_path).map_err(|e| {
Error::InternalErr(format!("validating openai azure base path {e:#}"))
})?,
)
} else {
OPENAI_AZURE_BASE_PATH.clone()
};
let workspace_cache = OpenaiKeyCache::new(
resource.api_key.clone(),
resource.organization_id.clone(),
azure_base_path.clone(),
expires_at,
user.clone(),
);
OPENAI_KEY_CACHE.insert(w_id.clone(), workspace_cache);
(
resource.api_key,
resource.organization_id,
azure_base_path,
user,
)
} else {
tracing::debug!("Using cached OpenAI key");
let workspace_cache = workspace_cache.unwrap();
(
workspace_cache.api_key.clone(),
workspace_cache.organization_id.clone(),
workspace_cache.azure_base_path.clone(),
workspace_cache.user.clone(),
)
};
if user.is_some() {
tracing::debug!("Adding user to request body");
let mut json_body: HashMap<String, Box<RawValue>> = serde_json::from_slice(&body)
.map_err(|e| Error::InternalErr(format!("Failed to parse request body: {}", e)))?;
let user_json_string = serde_json::Value::String(user.unwrap()).to_string(); // makes sure to escape characters
json_body.insert(
"user".to_string(),
RawValue::from_string(user_json_string)
.map_err(|e| Error::InternalErr(format!("Failed to parse user: {}", e)))?,
);
body = serde_json::to_vec(&json_body)
.map_err(|e| Error::InternalErr(format!("Failed to reserialize request body: {}", e)))?
.into();
}
let base_url = if let Some(base_url) = azure_base_path {
base_url
} else {
"https://api.openai.com/v1".to_string()
};
let url = format!("{}/{}", base_url, openai_path);
let mut request = HTTP_CLIENT
.post(url)
.header("content-type", "application/json")
.body(body);
if base_url != "https://api.openai.com/v1" {
request = request
.header("api-key", api_key)
.query(&[("api-version", "2023-05-15")])
} else {
request = request.header("authorization", format!("Bearer {}", api_key))
}
if let Some(org_id) = organization_id {
request = request.header("OpenAI-Organization", org_id);
}
let response = request.send().await.map_err(to_anyhow)?;
let mut tx = db.begin().await?;
audit_log(
&mut *tx,
&authed,
"openai.request",
ActionKind::Execute,
&w_id,
Some(&authed.email),
Some([("openai_path", &format!("{:?}", openai_path)[..])].into()),
)
.await?;
tx.commit().await?;
if response.error_for_status_ref().is_err() {
return Err(Error::OpenAIError(
response.text().await.unwrap_or("".to_string()),
));
}
let status_code = response.status();
let headers = response.headers().clone();
let stream = response.bytes_stream();
Ok((status_code, headers, axum::body::Body::from_stream(stream)))
}
+5 -6
View File
@@ -300,7 +300,7 @@ async fn get_resource(
.await?;
tx.commit().await?;
if resource_o.is_none() {
explain_resource_perm_error(&path, &w_id, &db, &authed).await?;
explain_resource_perm_error(&path, &w_id, &db).await?;
}
let resource = not_found_if_none(resource_o, "Resource", path)?;
Ok(Json(resource))
@@ -343,7 +343,7 @@ async fn get_resource_value(
tx.commit().await?;
if value_o.is_none() {
explain_resource_perm_error(&path, &w_id, &db, &authed).await?;
explain_resource_perm_error(&path, &w_id, &db).await?;
}
let value = not_found_if_none(value_o, "Resource", path)?;
@@ -354,7 +354,6 @@ async fn explain_resource_perm_error(
path: &str,
w_id: &str,
db: &sqlx::Pool<Postgres>,
authed: &ApiAuthed,
) -> windmill_common::error::Result<()> {
let extra_perms = sqlx::query_scalar!(
"SELECT extra_perms from resource WHERE path = $1 AND workspace_id = $2",
@@ -379,12 +378,12 @@ async fn explain_resource_perm_error(
.fetch_optional(db)
.await?;
return Err(Error::NotAuthorized(format!(
"Resource exists but you don't have access to it:\nresource perms: {}\nfolder perms: {}\nauthed as: {authed:?}",
"Resource exists but you don't have access to it:\nresource perms: {}\nfolder perms: {}",
serde_json::to_string_pretty(&extra_perms).unwrap_or_default(), serde_json::to_string_pretty(&folder_extra_perms).unwrap_or_default()
)));
} else {
return Err(Error::NotAuthorized(format!(
"Resource exists but you don't have access to it:\nresource perms: {}\nauthed as: {authed:?}",
"Resource exists but you don't have access to it:\nresource perms: {}",
serde_json::to_string_pretty(&extra_perms).unwrap_or_default()
)));
}
@@ -458,7 +457,7 @@ pub async fn get_resource_value_interpolated_internal(
.await?;
tx.commit().await?;
if value_o.is_none() {
explain_resource_perm_error(path, workspace, db, &authed).await?;
explain_resource_perm_error(path, workspace, db).await?;
}
let value = not_found_if_none(value_o, "Resource", path)?;
+72 -5
View File
@@ -434,15 +434,67 @@ pub async fn test_critical_channels() -> Result<String> {
Ok("Critical channels require EE".to_string())
}
use serde::Serialize;
#[derive(Serialize)]
pub struct CriticalAlert {
id: i32,
alert_type: String,
message: String,
created_at: chrono::DateTime<chrono::Utc>,
acknowledged: Option<bool>,
}
#[cfg(feature = "enterprise")]
#[derive(Deserialize)]
pub struct AlertQueryParams {
pub page: Option<i32>,
pub page_size: Option<i32>,
pub acknowledged: Option<bool>,
}
#[cfg(feature = "enterprise")]
pub async fn get_critical_alerts(
Extension(db): Extension<DB>,
authed: ApiAuthed,
Query(params): Query<crate::utils::AlertQueryParams>,
) -> JsonResult<Vec<crate::utils::CriticalAlert>> {
Query(params): Query<AlertQueryParams>,
) -> JsonResult<Vec<CriticalAlert>> {
require_super_admin(&db, &authed.email).await?;
crate::utils::get_critical_alerts(db, params, None).await
// Default pagination values if not provided
let page = params.page.unwrap_or(1).max(1);
let page_size = params.page_size.unwrap_or(10).min(100) as i64;
let offset = ((page - 1) * page_size as i32) as i64;
let alerts = if let Some(acknowledged) = params.acknowledged {
sqlx::query_as!(
CriticalAlert,
"SELECT id, alert_type, message, created_at, acknowledged
FROM alerts
WHERE acknowledged = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3",
acknowledged,
page_size,
offset
)
.fetch_all(&db)
.await?
} else {
sqlx::query_as!(
CriticalAlert,
"SELECT id, alert_type, message, created_at, acknowledged
FROM alerts
ORDER BY created_at DESC
LIMIT $1 OFFSET $2",
page_size,
offset
)
.fetch_all(&db)
.await?
};
Ok(Json(alerts))
}
#[cfg(not(feature = "enterprise"))]
@@ -458,7 +510,15 @@ pub async fn acknowledge_critical_alert(
) -> error::Result<String> {
require_super_admin(&db, &authed.email).await?;
crate::utils::acknowledge_critical_alert(db, None, id).await
sqlx::query!(
"UPDATE alerts SET acknowledged = true WHERE id = $1",
id
)
.execute(&db)
.await?;
tracing::info!("Acknowledged critical alert with id: {}", id);
Ok("Critical alert acknowledged".to_string())
}
#[cfg(not(feature = "enterprise"))]
@@ -473,7 +533,14 @@ pub async fn acknowledge_all_critical_alerts(
) -> error::Result<String> {
require_super_admin(&db, &authed.email).await?;
crate::utils::acknowledge_all_critical_alerts(db, None).await
sqlx::query!(
"UPDATE alerts SET acknowledged = true WHERE acknowledged = false"
)
.execute(&db)
.await?;
tracing::info!("Acknowledged all unacknowledged critical alerts");
Ok("All unacknowledged critical alerts acknowledged".to_string())
}
#[cfg(not(feature = "enterprise"))]
-162
View File
@@ -16,12 +16,6 @@ use windmill_common::{
DB,
};
#[cfg(feature = "enterprise")]
use windmill_common::error::JsonResult;
#[cfg(feature = "enterprise")]
use axum::Json;
#[derive(Deserialize)]
pub struct WithStarredInfoQuery {
pub with_starred_info: Option<bool>,
@@ -168,159 +162,3 @@ pub fn content_plain(body: Body) -> Response {
.body(body)
.unwrap()
}
use serde::Serialize;
#[derive(Serialize)]
pub struct CriticalAlert {
id: i32,
alert_type: String,
message: String,
created_at: chrono::DateTime<chrono::Utc>,
acknowledged: Option<bool>,
workspace_id: Option<String>,
}
#[cfg(feature = "enterprise")]
#[derive(Deserialize, Debug)]
pub struct AlertQueryParams {
pub page: Option<i32>,
pub page_size: Option<i32>,
pub acknowledged: Option<bool>,
}
#[cfg(feature = "enterprise")]
pub async fn get_critical_alerts(
db: DB,
params: AlertQueryParams,
workspace_id: Option<String>,
) -> JsonResult<Vec<CriticalAlert>> {
let page = params.page.unwrap_or(1).max(1);
let page_size = params.page_size.unwrap_or(10).min(100) as i64;
let offset = ((page - 1) * page_size as i32) as i64;
let alerts = if let Some(workspace_id) = workspace_id {
// `workspace_id` is provided => workspace admin
if params.acknowledged.is_none() {
// Case: return all rows where `workspace_id` matches
sqlx::query_as!(
CriticalAlert,
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id
FROM alerts
WHERE workspace_id = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3",
workspace_id,
page_size,
offset
)
.fetch_all(&db)
.await?
} else {
// Case: return rows where `acknowledged_workspace` matches `params.acknowledged`
sqlx::query_as!(
CriticalAlert,
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id
FROM alerts
WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2
ORDER BY created_at DESC
LIMIT $3 OFFSET $4",
workspace_id,
params.acknowledged,
page_size,
offset
)
.fetch_all(&db)
.await?
}
} else {
// `workspace_id` is not provided => superadmin
if params.acknowledged.is_none() {
// Case: Return all rows unfiltered with global acknowledged as acknowledged
sqlx::query_as!(
CriticalAlert,
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id
FROM alerts
ORDER BY created_at DESC
LIMIT $1 OFFSET $2",
page_size,
offset
)
.fetch_all(&db)
.await?
} else {
// Case: Return rows where global acknowledged matches params.acknowledged
sqlx::query_as!(
CriticalAlert,
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id
FROM alerts
WHERE COALESCE(acknowledged, false) = $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3",
params.acknowledged,
page_size,
offset
)
.fetch_all(&db)
.await?
}
};
Ok(Json(alerts))
}
#[cfg(feature = "enterprise")]
pub async fn acknowledge_critical_alert(
db: DB,
workspace_id: Option<String>,
id: i32,
) -> error::Result<String> {
sqlx::query!(
"UPDATE alerts
SET
acknowledged = true,
acknowledged_workspace = CASE
WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true
ELSE acknowledged_workspace
END
WHERE id = $1",
id,
workspace_id
)
.execute(&db)
.await?;
tracing::info!(
"Acknowledged critical alert with id: {}{}",
id,
workspace_id.map_or_else(|| "".to_string(), |w| format!(" for workspace_id: {}", w))
);
Ok("Critical alert acknowledged".to_string())
}
#[cfg(feature = "enterprise")]
pub async fn acknowledge_all_critical_alerts(
db: DB,
workspace_id: Option<String>,
) -> error::Result<String> {
sqlx::query!(
"UPDATE alerts
SET
acknowledged = true,
acknowledged_workspace = CASE
WHEN $1::text IS NOT NULL THEN true
ELSE acknowledged_workspace
END
WHERE ($1::text IS NOT NULL AND workspace_id = $1)
OR ($1::text IS NULL)",
workspace_id
)
.execute(&db)
.await?;
tracing::info!(
"Acknowledged all unacknowledged critical alerts{}",
workspace_id.map_or_else(|| "".to_string(), |w| format!(" for workspace_id: {}", w))
);
Ok("All unacknowledged critical alerts acknowledged".to_string())
}
@@ -754,14 +754,12 @@ async fn disable_with_error(db: &DB, ws_trigger: &WebsocketTrigger, error: Strin
)
.execute(db).await {
Ok(_) => {
report_critical_error(format!("Disabling websocket {} because of error: {}", ws_trigger.url, error), db.clone(), Some(&ws_trigger.workspace_id), None).await;
report_critical_error(format!("Disabling websocket {} because of error: {}", ws_trigger.url, error), db.clone()).await;
},
Err(disable_err) => {
report_critical_error(
format!("Could not disable websocket {} with err {}, disabling because of error {}", ws_trigger.path, disable_err, error),
db.clone(),
Some(&ws_trigger.workspace_id),
None,
db.clone()
).await;
}
}
@@ -898,7 +896,7 @@ async fn listen_to_websocket(
}
if should_handle {
if let Err(err) = run_job(&db, rsmq.clone(), &ws_trigger, text).await {
report_critical_error(format!("Failed to trigger job from websocket {}: {:?}", ws_trigger.url, err), db.clone(), Some(&ws_trigger.workspace_id), None).await;
report_critical_error(format!("Failed to trigger job from websocket {}: {:?}", ws_trigger.url, err), db.clone()).await;
};
}
},
+17 -137
View File
@@ -8,7 +8,6 @@
use std::collections::HashMap;
use crate::ai::{AiResource, AI_KEY_CACHE};
use crate::db::ApiAuthed;
use crate::users_ee::send_email_if_possible;
use crate::utils::get_instance_username_or_create_pending;
@@ -119,11 +118,7 @@ pub fn workspaced_service() -> Router {
.route("/change_workspace_name", post(change_workspace_name))
.route("/change_workspace_id", post(change_workspace_id))
.route("/usage", get(get_usage))
.route("/used_triggers", get(get_used_triggers))
.route("/critical_alerts", get(get_critical_alerts))
.route("/critical_alerts/:id/acknowledge", post(acknowledge_critical_alert))
.route("/critical_alerts/acknowledge_all", post(acknowledge_all_critical_alerts))
.route("/critical_alerts/mute", post(mute_critical_alerts));
.route("/used_triggers", get(get_used_triggers));
#[cfg(feature = "stripe")]
{
@@ -173,7 +168,7 @@ pub struct WorkspaceSettings {
pub plan: Option<String>,
pub webhook: Option<String>,
pub deploy_to: Option<String>,
pub ai_resource: Option<serde_json::Value>,
pub openai_resource_path: Option<String>,
pub code_completion_enabled: bool,
pub error_handler: Option<String>,
pub error_handler_extra_args: Option<serde_json::Value>,
@@ -184,7 +179,6 @@ pub struct WorkspaceSettings {
pub default_app: Option<String>,
pub automatic_billing: bool,
pub default_scripts: Option<serde_json::Value>,
pub mute_critical_alerts: Option<bool>,
}
#[derive(FromRow, Serialize, Debug)]
@@ -240,7 +234,7 @@ struct EditWebhook {
#[derive(Deserialize)]
struct EditCopilotConfig {
ai_resource: Option<serde_json::Value>,
openai_resource_path: Option<String>,
code_completion_enabled: bool,
}
@@ -651,33 +645,23 @@ async fn edit_copilot_config(
let mut tx = db.begin().await?;
if let Some(ai_resource) = &eo.ai_resource {
let path = serde_json::from_value::<AiResource>(ai_resource.clone())
.map_err(|e| Error::BadRequest(e.to_string()))?
.path;
if let Some(openai_resource_path) = &eo.openai_resource_path {
sqlx::query!(
"UPDATE workspace_settings SET ai_resource = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
ai_resource,
"UPDATE workspace_settings SET openai_resource_path = $1, code_completion_enabled = $2 WHERE workspace_id = $3",
openai_resource_path,
eo.code_completion_enabled,
&w_id
)
.execute(&mut *tx)
.await?;
if let Some(cached) = AI_KEY_CACHE.get(&w_id) {
if cached.path != path {
AI_KEY_CACHE.remove(&w_id);
}
}
} else {
sqlx::query!(
"UPDATE workspace_settings SET ai_resource = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
"UPDATE workspace_settings SET openai_resource_path = NULL, code_completion_enabled = $1 WHERE workspace_id = $2",
eo.code_completion_enabled,
&w_id,
)
.execute(&mut *tx)
.await?;
AI_KEY_CACHE.remove(&w_id);
}
audit_log(
&mut *tx,
@@ -688,7 +672,10 @@ async fn edit_copilot_config(
Some(&authed.email),
Some(
[
("ai_resource", &format!("{:?}", eo.ai_resource)[..]),
(
"openai_resource_path",
&format!("{:?}", eo.openai_resource_path)[..],
),
(
"code_completion_enabled",
&format!("{:?}", eo.code_completion_enabled)[..],
@@ -705,8 +692,7 @@ async fn edit_copilot_config(
#[derive(Serialize)]
struct CopilotInfo {
pub ai_provider: String,
pub exists_ai_resource: bool,
pub exists_openai_resource_path: bool,
pub code_completion_enabled: bool,
}
async fn get_copilot_info(
@@ -715,32 +701,16 @@ async fn get_copilot_info(
) -> JsonResult<CopilotInfo> {
let mut tx = db.begin().await?;
let record = sqlx::query!(
"SELECT ai_resource, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
"SELECT openai_resource_path, code_completion_enabled FROM workspace_settings WHERE workspace_id = $1",
&w_id
)
.fetch_one(&mut *tx)
.await
.map_err(|e| Error::InternalErr(format!("getting ai_resource and code_completion_enabled: {e:#}")))?;
.map_err(|e| Error::InternalErr(format!("getting openai_resource_path and code_completion_enabled: {e:#}")))?;
tx.commit().await?;
let (ai_provider, exists_ai_resource) = if let Some(ai_resource) = record.ai_resource {
let ai_resource = serde_json::from_value::<AiResource>(ai_resource);
let exist = ai_resource.is_ok();
(
if exist {
ai_resource.unwrap().provider
} else {
"".to_string()
},
exist,
)
} else {
("".to_string(), false)
};
Ok(Json(CopilotInfo {
ai_provider,
exists_ai_resource,
exists_openai_resource_path: record.openai_resource_path.is_some(),
code_completion_enabled: record.code_completion_enabled,
}))
}
@@ -2253,7 +2223,7 @@ struct SimplifiedSettings {
error_handler: Option<String>,
error_handler_extra_args: Option<Value>,
error_handler_muted_on_cancel: bool,
ai_resource: Option<serde_json::Value>,
openai_resource_path: Option<String>,
code_completion_enabled: bool,
large_file_storage: Option<Value>,
git_sync: Option<Value>,
@@ -2618,7 +2588,7 @@ async fn tarball_workspace(
webhook,
deploy_to,
error_handler,
ai_resource,
openai_resource_path,
code_completion_enabled,
error_handler_extra_args,
error_handler_muted_on_cancel,
@@ -3080,93 +3050,3 @@ async fn get_usage(Extension(db): Extension<DB>, Path(w_id): Path<String>) -> Re
.unwrap_or(0);
Ok(usage.to_string())
}
#[cfg(feature = "enterprise")]
pub async fn get_critical_alerts(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
authed: ApiAuthed,
Query(params): Query<crate::utils::AlertQueryParams>,
) -> JsonResult<Vec<crate::utils::CriticalAlert>> {
require_admin(authed.is_admin, &authed.username)?;
crate::utils::get_critical_alerts(db, params, Some(w_id)).await
}
#[cfg(not(feature = "enterprise"))]
pub async fn get_critical_alerts() -> Error {
Error::NotFound("Critical Alerts require EE".to_string())
}
#[cfg(feature = "enterprise")]
pub async fn acknowledge_critical_alert(
Extension(db): Extension<DB>,
Path((w_id, id)): Path<(String, i32)>,
authed: ApiAuthed,
) -> Result<String> {
require_admin(authed.is_admin, &authed.username)?;
crate::utils::acknowledge_critical_alert(db, Some(w_id), id).await
}
#[cfg(not(feature = "enterprise"))]
pub async fn acknowledge_critical_alert() -> Error {
Error::NotFound("Critical Alerts require EE".to_string())
}
#[cfg(feature = "enterprise")]
pub async fn acknowledge_all_critical_alerts(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
authed: ApiAuthed,
) -> Result<String> {
require_admin(authed.is_admin, &authed.username)?;
crate::utils::acknowledge_all_critical_alerts(db, Some(w_id)).await
}
#[cfg(not(feature = "enterprise"))]
pub async fn acknowledge_all_critical_alerts() -> Error {
Error::NotFound("Critical Alerts require EE".to_string())
}
#[cfg(feature = "enterprise")]
#[derive(Deserialize)]
pub struct MuteCriticalAlertRequest {
pub mute_critical_alerts: Option<bool>,
}
#[cfg(feature = "enterprise")]
async fn mute_critical_alerts(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
ApiAuthed { is_admin, username, .. }: ApiAuthed,
Json(m_r): Json<MuteCriticalAlertRequest>,
) -> Result<String> {
require_admin(is_admin, &username)?;
let mute_alerts = m_r.mute_critical_alerts.unwrap_or(false);
if mute_alerts {
sqlx::query!(
"UPDATE alerts SET acknowledged_workspace = true, acknowledged = true WHERE workspace_id = $1",
&w_id
)
.execute(&db)
.await?;
}
sqlx::query!(
"UPDATE workspace_settings SET mute_critical_alerts = $1 WHERE workspace_id = $2",
mute_alerts,
&w_id
)
.execute(&db)
.await?;
Ok(format!("Updated mute criticital alert ui settings for workspace: {}", &w_id))
}
#[cfg(not(feature = "enterprise"))]
pub async fn mute_critical_alerts() -> Error {
Error::NotFound("Critical Alerts require EE".to_string())
}
+2 -2
View File
@@ -59,7 +59,7 @@ pub enum Error {
#[error("Error: {0:#?}")]
JsonErr(serde_json::Value),
#[error("{0}")]
AiError(String),
OpenAIError(String),
#[error("{0}")]
AlreadyCompleted(String),
}
@@ -90,7 +90,7 @@ impl IntoResponse for Error {
Self::RequireAdmin(_) => axum::http::StatusCode::FORBIDDEN,
Self::SqlErr(_)
| Self::BadRequest(_)
| Self::AiError(_)
| Self::OpenAIError(_)
| Self::QuotaExceeded(_) => axum::http::StatusCode::BAD_REQUEST,
_ => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
};
+18 -101
View File
@@ -29,10 +29,8 @@ pub const DEFAULT_PER_PAGE: usize = 1000;
pub const GIT_VERSION: &str =
git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
use crate::CRITICAL_ALERT_MUTE_UI_ENABLED;
use std::sync::atomic::Ordering;
use crate::worker::CLOUD_HOSTED;
use crate::CRITICAL_ALERT_MUTE_UI_ENABLED;
lazy_static::lazy_static! {
pub static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
@@ -251,40 +249,15 @@ pub fn generate_lock_id(database_name: &str) -> i64 {
0x3d32ad9e * (CRC_IEEE.checksum(database_name.as_bytes()) as i64)
}
pub async fn report_critical_error(
error_message: String,
_db: DB,
workspace_id: Option<&str>,
resource: Option<&str>,
) -> () {
pub async fn report_critical_error(error_message: String, _db: DB) -> () {
tracing::error!("CRITICAL ERROR: {error_message}");
let mute_global = CRITICAL_ALERT_MUTE_UI_ENABLED.load(Ordering::Relaxed);
let mute_workspace = if let Some(workspace_id) = workspace_id {
match fetch_mute_workspace(&_db, workspace_id).await {
Ok(flag) => flag,
Err(err) => {
tracing::error!("Error fetching mute_workspace: {}", err);
false
}
}
} else {
false
};
// we ack_global if mute_global is true, or if mute_workspace is true
// but we ignore global mute setting for ack_workspace
let acknowledge_workspace = mute_workspace;
let acknowledge_global = mute_global || mute_workspace || ( workspace_id.is_some() && *CLOUD_HOSTED);
let mute = CRITICAL_ALERT_MUTE_UI_ENABLED.load(Ordering::Relaxed);
if let Err(err) = sqlx::query!(
"INSERT INTO alerts (alert_type, message, acknowledged, acknowledged_workspace, workspace_id, resource)
VALUES ('critical_error', $1, $2, $3, $4, $5)",
"INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('critical_error', $1, $2)",
error_message,
acknowledge_global,
acknowledge_workspace,
workspace_id,
resource,
mute
)
.execute(&_db)
.await
@@ -293,86 +266,30 @@ pub async fn report_critical_error(
}
#[cfg(feature = "enterprise")]
if *CLOUD_HOSTED && workspace_id.is_some() {
tracing::error!(error_message)
} else {
send_critical_alert(error_message, &_db, CriticalAlertKind::CriticalError, None).await;
}
send_critical_alert(error_message, &_db, CriticalAlertKind::CriticalError, None).await;
}
pub async fn report_recovered_critical_error(
message: String,
_db: DB,
workspace_id: Option<&str>,
resource: Option<&str>,
) -> () {
pub async fn report_recovered_critical_error(message: String, _db: DB) -> () {
tracing::info!("RECOVERED CRITICAL ERROR: {message}");
let mute = CRITICAL_ALERT_MUTE_UI_ENABLED.load(Ordering::Relaxed);
if let Err(err) = sqlx::query!(
"INSERT INTO alerts (alert_type, message, acknowledged, acknowledged_workspace, workspace_id, resource)
VALUES ('recovered_critical_error', $1, $2, $3, $4, $5)",
"INSERT INTO alerts (alert_type, message, acknowledged) VALUES ('recovered_critical_error', $1, $2)",
message,
true,
true,
workspace_id,
resource,
mute
)
.execute(&_db)
.await
{
tracing::error!("Failed to save recovered critical error to database: {}", err);
tracing::error!("Failed to save critical error to database: {}", err);
}
// acknowledge all alerts with the same resource
if let Some(resource) = resource {
if let Err(err) = sqlx::query!(
"UPDATE alerts SET acknowledged = true, acknowledged_workspace = true WHERE resource = $1 AND alert_type = 'critical_error'",
resource,
)
.execute(&_db)
.await
{
tracing::error!("Failed to acknowledge critical error alerts for resource {}: {}", resource, err);
}
}
#[cfg(feature = "enterprise")]
if *CLOUD_HOSTED && workspace_id.is_some() {
tracing::error!(message);
} else {
send_critical_alert(
message,
&_db,
CriticalAlertKind::RecoveredCriticalError,
None,
)
.await;
}
}
pub async fn fetch_mute_workspace(_db: &DB, workspace_id: &str) -> Result<bool> {
match sqlx::query!(
"SELECT mute_critical_alerts FROM workspace_settings WHERE workspace_id = $1",
workspace_id
send_critical_alert(
message,
&_db,
CriticalAlertKind::RecoveredCriticalError,
None,
)
.fetch_optional(_db)
.await
{
Ok(Some(record)) => Ok(record.mute_critical_alerts.unwrap_or(false)),
Ok(None) => {
tracing::warn!(
"Workspace ID {} not found in workspace_settings table",
workspace_id
);
Ok(false)
}
Err(err) => {
tracing::error!(
"Error querying workspace_settings for workspace_id {}: {}",
workspace_id,
err
);
Err(Error::SqlErr(err))
}
}
.await;
}
+7 -9
View File
@@ -924,8 +924,6 @@ pub async fn add_completed_job<
.unwrap_or("".to_string()),
),
db.clone(),
Some(&w_id),
None,
)
.await;
} else if queued_job.email == SCHEDULE_ERROR_HANDLER_USER_EMAIL {
@@ -994,7 +992,7 @@ pub async fn add_completed_job<
"Could not push workspace error handler for failed job ({base_url}/run/{}?workspace={w_id}): {}",
queued_job.id,
err
), db.clone(), Some(&w_id), None)
), db.clone())
.await;
}
}
@@ -1177,10 +1175,10 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel<
queued_job.id,
err
);
report_critical_error(error_message, db.clone(), Some(&w_id), None).await;
report_critical_error(error_message, db.clone()).await;
}
} else {
report_critical_error(error_message, db.clone(), Some(&w_id), None).await;
report_critical_error(error_message, db.clone()).await;
}
}
@@ -3672,15 +3670,15 @@ pub async fn push<'c, 'd, R: rsmq_async::RsmqConnection + Send + 'c>(
}
JobPayload::DeploymentCallback { path } => (
None,
Some(path.clone()),
Some(path),
None,
JobKind::DeploymentCallback,
None,
None,
None,
Some(format!("{workspace_id}:git_sync")),
Some(1),
Some(0),
None,
None,
None,
None,
None,
None,
+3 -9
View File
@@ -41,7 +41,7 @@ use tokio::{io::AsyncWriteExt, process::Child, time::Instant};
use crate::{
AuthedClient, AuthedClientBackgroundTask, JOB_DEFAULT_TIMEOUT, MAX_RESULT_SIZE,
MAX_TIMEOUT_DURATION, PATH_ENV,
MAX_TIMEOUT_DURATION,
};
pub async fn build_args_map<'a>(
@@ -860,17 +860,11 @@ pub async fn save_in_cache(
}
fn tentatively_improve_error(err: Error, executable: &str) -> Error {
#[cfg(unix)]
let err_msg = "No such file or directory (os error 2)";
#[cfg(windows)]
let err_msg = "program not found";
if err
.to_string()
.contains(&err_msg)
.contains("No such file or directory (os error 2)")
{
return Error::InternalErr(format!("Executable {executable} not found on worker. PATH: {}", *PATH_ENV));
return Error::InternalErr(format!("Executable {executable} not found on worker"));
}
return err;
}
+17 -54
View File
@@ -416,32 +416,24 @@ pub async fn do_postgresql(
}
fn map_as_single_type<T>(
vec: Option<&Vec<Value>>,
vec: &Vec<Value>,
f: impl Fn(&Value) -> Option<T>,
) -> anyhow::Result<Option<Vec<Option<T>>>> {
if let Some(vec) = vec {
Ok(Some(
vec.into_iter()
.map(|v| {
// first option is if the value is of the right type (if none, will stop the collection and throw error)
// second option is if the value is null
// allow nulls in arrays
if matches!(v, Value::Null) {
Some(None)
} else {
f(v).map(Some)
}
})
.collect::<Option<Vec<Option<T>>>>()
.ok_or_else(|| anyhow::anyhow!("Mixed types in array"))?,
))
} else {
Ok(None)
}
) -> anyhow::Result<Vec<Option<T>>> {
vec.into_iter()
.map(|v| {
// allow nulls in arrays
if matches!(v, Value::Null) {
Some(None)
} else {
f(v).map(Some)
}
})
.collect::<Option<Vec<Option<T>>>>()
.ok_or_else(|| anyhow::anyhow!("Mixed types in array"))
}
fn convert_vec_val(
vec: Option<&Vec<Value>>,
vec: &Vec<Value>,
arg_t: &String,
) -> windmill_common::error::Result<Box<dyn ToSql + Sync + Send>> {
match arg_t.as_str() {
@@ -497,9 +489,7 @@ fn convert_vec_val(
.unwrap_or_default()
})
})?)),
"jsonb" | "json" => Ok(Box::new(
vec.map(|v| v.clone().into_iter().map(Some).collect_vec()),
)),
"jsonb" | "json" => Ok(Box::new(vec.clone().into_iter().map(Some).collect_vec())),
"bytea" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_str().map(|x| {
engine::general_purpose::STANDARD
@@ -522,31 +512,9 @@ fn convert_val(
match value {
Value::Array(vec) if arg_t.ends_with("[]") => {
let arg_t = arg_t.trim_end_matches("[]").to_string();
convert_vec_val(Some(vec), &arg_t)
convert_vec_val(vec, &arg_t)
}
Value::Null if arg_t.ends_with("[]") => {
let arg_t = arg_t.trim_end_matches("[]").to_string();
convert_vec_val(None, &arg_t)
}
Value::Null => match arg_t.as_str() {
"bool" | "boolean" => Ok(Box::new(None::<bool>)),
"char" | "character" => Ok(Box::new(None::<i8>)),
"smallint" | "smallserial" | "int2" | "serial2" => Ok(Box::new(None::<i16>)),
"int" | "integer" | "int4" | "serial" => Ok(Box::new(None::<i32>)),
"numeric" | "decimal" => Ok(Box::new(None::<Decimal>)),
"oid" => Ok(Box::new(None::<u32>)),
"bigint" | "bigserial" | "int8" | "serial8" => Ok(Box::new(None::<i64>)),
"real" | "float4" => Ok(Box::new(None::<f32>)),
"double" | "float8" => Ok(Box::new(None::<f64>)),
"uuid" => Ok(Box::new(None::<Uuid>)),
"date" => Ok(Box::new(None::<chrono::NaiveDate>)),
"time" | "timetz" => Ok(Box::new(None::<chrono::NaiveTime>)),
"timestamp" | "timestamptz" => Ok(Box::new(None::<chrono::NaiveDateTime>)),
"jsonb" | "json" => Ok(Box::new(None::<Option<Value>>)),
"bytea" => Ok(Box::new(None::<Vec<u8>>)),
"text" | "varchar" => Ok(Box::new(None::<String>)),
_ => Err(anyhow::anyhow!("Unsupported JSON null type"))?,
},
Value::Null => Ok(Box::new(None::<bool>)),
Value::Bool(b) => Ok(Box::new(b.clone())),
Value::Number(n) if matches!(typ, Typ::Str(_)) => Ok(Box::new(n.to_string())),
Value::Number(n) if arg_t == "char" && n.is_i64() => {
@@ -615,11 +583,6 @@ fn convert_val(
.unwrap_or(vec![]);
Ok(Box::new(bytes))
}
Value::Object(_) if arg_t == "text" || arg_t == "varchar" => {
Ok(Box::new(serde_json::to_string(value).map_err(|err| {
Error::ExecutionErr(format!("Failed to convert JSON to text: {}", err))
})?))
}
Value::Object(_) => Ok(Box::new(value.clone())),
Value::String(s) => Ok(Box::new(s.clone())),
_ => Err(Error::ExecutionErr(format!(
+3 -17
View File
@@ -1,4 +1,4 @@
use std::{collections::HashMap, fs, process::Stdio};
use std::{collections::HashMap, process::Stdio};
use itertools::Itertools;
use regex::Regex;
@@ -320,7 +320,7 @@ pub async fn uv_pip_compile(
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let child_process = start_child_process(child_cmd, uv_cmd).await?;
let child_process = start_child_process(child_cmd, "/usr/local/bin/uv").await?;
append_logs(&job_id, &w_id, logs, db).await;
handle_child(
job_id,
@@ -1153,11 +1153,7 @@ pub async fn handle_python_reqs(
for (req, venv_p) in req_with_penv {
let mut logs1 = String::new();
if no_uv_install {
logs1.push_str("\n\n--- PIP INSTALL ---\n");
} else {
logs1.push_str("\n\n--- UV PIP INSTALL ---\n");
}
logs1.push_str("\n\n--- UV PIP INSTALL ---\n");
logs1.push_str(&format!("\n{req} is being installed for the first time.\n It will be cached for all ulterior uses."));
append_logs(&job_id, w_id, logs1, db).await;
@@ -1330,16 +1326,6 @@ pub async fn handle_python_reqs(
"finished setting up python dependencies {}",
job_id
);
if child.is_err() {
if let Err(e) = fs::remove_dir_all(&venv_p) {
tracing::warn!(
workspace_id = %w_id,
"failed to remove cache dir: {:?}",
e
);
}
}
child?;
#[cfg(all(feature = "enterprise", feature = "parquet"))]
+1 -1
View File
@@ -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.425.1";
export const VERSION = "v1.423.2";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+2 -2
View File
@@ -33,10 +33,10 @@
},
{
"kind": "bigrawscript",
"jobs": 4000
"jobs": 10000
},
{
"kind": "bigscriptinflow",
"jobs": 4000
"jobs": 10000
}
]
+1 -1
View File
@@ -54,7 +54,7 @@ export const OpenAPI: OpenAPIConfig = {
PASSWORD: undefined,
TOKEN: getEnv("WM_TOKEN"),
USERNAME: undefined,
VERSION: '1.425.1',
VERSION: '1.407.2',
WITH_CREDENTIALS: true,
interceptors: {
request: new Interceptors(),
+9 -519
View File
File diff suppressed because one or more lines are too long
+11 -434
View File
@@ -1,10 +1,5 @@
// This file is auto-generated by @hey-api/openapi-ts
export type AiResource = {
path: string;
provider: string;
};
export type Script = {
workspace_id?: string;
hash: string;
@@ -262,21 +257,18 @@ export type TruncatedToken = {
created_at: string;
last_used_at: string;
scopes?: Array<(string)>;
email?: string;
};
export type NewToken = {
label?: string;
expiration?: string;
scopes?: Array<(string)>;
workspace_id?: string;
};
export type NewTokenImpersonate = {
label?: string;
expiration?: string;
impersonate_email: string;
workspace_id?: string;
};
export type ListableVariable = {
@@ -325,7 +317,7 @@ export type AuditLog = {
id: number;
timestamp: string;
username: string;
operation: 'jobs.run' | 'jobs.run.script' | 'jobs.run.preview' | 'jobs.run.flow' | 'jobs.run.flow_preview' | 'jobs.run.script_hub' | 'jobs.run.dependencies' | 'jobs.run.identity' | 'jobs.run.noop' | 'jobs.flow_dependencies' | 'jobs' | 'jobs.cancel' | 'jobs.force_cancel' | 'jobs.disapproval' | 'jobs.delete' | 'account.delete' | 'ai.request' | 'resources.create' | 'resources.update' | 'resources.delete' | 'resource_types.create' | 'resource_types.update' | 'resource_types.delete' | 'schedule.create' | 'schedule.setenabled' | 'schedule.edit' | 'schedule.delete' | 'scripts.create' | 'scripts.update' | 'scripts.archive' | 'scripts.delete' | 'users.create' | 'users.delete' | 'users.update' | 'users.login' | 'users.login_failure' | 'users.logout' | 'users.accept_invite' | 'users.decline_invite' | 'users.token.create' | 'users.token.delete' | 'users.add_to_workspace' | 'users.add_global' | 'users.setpassword' | 'users.impersonate' | 'users.leave_workspace' | 'oauth.login' | 'oauth.login_failure' | 'oauth.signup' | 'variables.create' | 'variables.delete' | 'variables.update' | 'flows.create' | 'flows.update' | 'flows.delete' | 'flows.archive' | 'apps.create' | 'apps.update' | 'apps.delete' | 'folder.create' | 'folder.update' | 'folder.delete' | 'folder.add_owner' | 'folder.remove_owner' | 'group.create' | 'group.delete' | 'group.edit' | 'group.adduser' | 'group.removeuser' | 'igroup.create' | 'igroup.delete' | 'igroup.adduser' | 'igroup.removeuser' | 'variables.decrypt_secret' | 'workspaces.edit_command_script' | 'workspaces.edit_deploy_to' | 'workspaces.edit_auto_invite_domain' | 'workspaces.edit_webhook' | 'workspaces.edit_copilot_config' | 'workspaces.edit_error_handler' | 'workspaces.create' | 'workspaces.update' | 'workspaces.archive' | 'workspaces.unarchive' | 'workspaces.delete';
operation: 'jobs.run' | 'jobs.run.script' | 'jobs.run.preview' | 'jobs.run.flow' | 'jobs.run.flow_preview' | 'jobs.run.script_hub' | 'jobs.run.dependencies' | 'jobs.run.identity' | 'jobs.run.noop' | 'jobs.flow_dependencies' | 'jobs' | 'jobs.cancel' | 'jobs.force_cancel' | 'jobs.disapproval' | 'jobs.delete' | 'account.delete' | 'openai.request' | 'resources.create' | 'resources.update' | 'resources.delete' | 'resource_types.create' | 'resource_types.update' | 'resource_types.delete' | 'schedule.create' | 'schedule.setenabled' | 'schedule.edit' | 'schedule.delete' | 'scripts.create' | 'scripts.update' | 'scripts.archive' | 'scripts.delete' | 'users.create' | 'users.delete' | 'users.update' | 'users.login' | 'users.logout' | 'users.accept_invite' | 'users.decline_invite' | 'users.token.create' | 'users.token.delete' | 'users.add_to_workspace' | 'users.add_global' | 'users.setpassword' | 'users.impersonate' | 'users.leave_workspace' | 'oauth.login' | 'oauth.signup' | 'variables.create' | 'variables.delete' | 'variables.update' | 'flows.create' | 'flows.update' | 'flows.delete' | 'flows.archive' | 'apps.create' | 'apps.update' | 'apps.delete' | 'folder.create' | 'folder.update' | 'folder.delete' | 'folder.add_owner' | 'folder.remove_owner' | 'group.create' | 'group.delete' | 'group.edit' | 'group.adduser' | 'group.removeuser' | 'igroup.create' | 'igroup.delete' | 'igroup.adduser' | 'igroup.removeuser' | 'variables.decrypt_secret' | 'workspaces.edit_command_script' | 'workspaces.edit_deploy_to' | 'workspaces.edit_auto_invite_domain' | 'workspaces.edit_webhook' | 'workspaces.edit_copilot_config' | 'workspaces.edit_error_handler' | 'workspaces.create' | 'workspaces.update' | 'workspaces.archive' | 'workspaces.unarchive' | 'workspaces.delete';
action_kind: 'Created' | 'Updated' | 'Delete' | 'Execute';
resource?: string;
parameters?: {
@@ -333,7 +325,7 @@ export type AuditLog = {
};
};
export type operation = 'jobs.run' | 'jobs.run.script' | 'jobs.run.preview' | 'jobs.run.flow' | 'jobs.run.flow_preview' | 'jobs.run.script_hub' | 'jobs.run.dependencies' | 'jobs.run.identity' | 'jobs.run.noop' | 'jobs.flow_dependencies' | 'jobs' | 'jobs.cancel' | 'jobs.force_cancel' | 'jobs.disapproval' | 'jobs.delete' | 'account.delete' | 'ai.request' | 'resources.create' | 'resources.update' | 'resources.delete' | 'resource_types.create' | 'resource_types.update' | 'resource_types.delete' | 'schedule.create' | 'schedule.setenabled' | 'schedule.edit' | 'schedule.delete' | 'scripts.create' | 'scripts.update' | 'scripts.archive' | 'scripts.delete' | 'users.create' | 'users.delete' | 'users.update' | 'users.login' | 'users.login_failure' | 'users.logout' | 'users.accept_invite' | 'users.decline_invite' | 'users.token.create' | 'users.token.delete' | 'users.add_to_workspace' | 'users.add_global' | 'users.setpassword' | 'users.impersonate' | 'users.leave_workspace' | 'oauth.login' | 'oauth.login_failure' | 'oauth.signup' | 'variables.create' | 'variables.delete' | 'variables.update' | 'flows.create' | 'flows.update' | 'flows.delete' | 'flows.archive' | 'apps.create' | 'apps.update' | 'apps.delete' | 'folder.create' | 'folder.update' | 'folder.delete' | 'folder.add_owner' | 'folder.remove_owner' | 'group.create' | 'group.delete' | 'group.edit' | 'group.adduser' | 'group.removeuser' | 'igroup.create' | 'igroup.delete' | 'igroup.adduser' | 'igroup.removeuser' | 'variables.decrypt_secret' | 'workspaces.edit_command_script' | 'workspaces.edit_deploy_to' | 'workspaces.edit_auto_invite_domain' | 'workspaces.edit_webhook' | 'workspaces.edit_copilot_config' | 'workspaces.edit_error_handler' | 'workspaces.create' | 'workspaces.update' | 'workspaces.archive' | 'workspaces.unarchive' | 'workspaces.delete';
export type operation = 'jobs.run' | 'jobs.run.script' | 'jobs.run.preview' | 'jobs.run.flow' | 'jobs.run.flow_preview' | 'jobs.run.script_hub' | 'jobs.run.dependencies' | 'jobs.run.identity' | 'jobs.run.noop' | 'jobs.flow_dependencies' | 'jobs' | 'jobs.cancel' | 'jobs.force_cancel' | 'jobs.disapproval' | 'jobs.delete' | 'account.delete' | 'openai.request' | 'resources.create' | 'resources.update' | 'resources.delete' | 'resource_types.create' | 'resource_types.update' | 'resource_types.delete' | 'schedule.create' | 'schedule.setenabled' | 'schedule.edit' | 'schedule.delete' | 'scripts.create' | 'scripts.update' | 'scripts.archive' | 'scripts.delete' | 'users.create' | 'users.delete' | 'users.update' | 'users.login' | 'users.logout' | 'users.accept_invite' | 'users.decline_invite' | 'users.token.create' | 'users.token.delete' | 'users.add_to_workspace' | 'users.add_global' | 'users.setpassword' | 'users.impersonate' | 'users.leave_workspace' | 'oauth.login' | 'oauth.signup' | 'variables.create' | 'variables.delete' | 'variables.update' | 'flows.create' | 'flows.update' | 'flows.delete' | 'flows.archive' | 'apps.create' | 'apps.update' | 'apps.delete' | 'folder.create' | 'folder.update' | 'folder.delete' | 'folder.add_owner' | 'folder.remove_owner' | 'group.create' | 'group.delete' | 'group.edit' | 'group.adduser' | 'group.removeuser' | 'igroup.create' | 'igroup.delete' | 'igroup.adduser' | 'igroup.removeuser' | 'variables.decrypt_secret' | 'workspaces.edit_command_script' | 'workspaces.edit_deploy_to' | 'workspaces.edit_auto_invite_domain' | 'workspaces.edit_webhook' | 'workspaces.edit_copilot_config' | 'workspaces.edit_error_handler' | 'workspaces.create' | 'workspaces.update' | 'workspaces.archive' | 'workspaces.unarchive' | 'workspaces.delete';
export type action_kind = 'Created' | 'Updated' | 'Delete' | 'Execute';
@@ -550,11 +542,6 @@ export type HttpTrigger = {
edited_at: string;
script_path: string;
route_path: string;
static_asset_config?: {
s3: string;
storage?: string;
filename?: string;
};
is_flow: boolean;
extra_perms: {
[key: string]: (boolean);
@@ -572,11 +559,6 @@ export type NewHttpTrigger = {
path: string;
script_path: string;
route_path: string;
static_asset_config?: {
s3: string;
storage?: string;
filename?: string;
};
is_flow: boolean;
http_method: 'get' | 'post' | 'put' | 'delete' | 'patch';
is_async: boolean;
@@ -587,89 +569,12 @@ export type EditHttpTrigger = {
path: string;
script_path: string;
route_path?: string;
static_asset_config?: {
s3: string;
storage?: string;
filename?: string;
};
is_flow: boolean;
http_method: 'get' | 'post' | 'put' | 'delete' | 'patch';
is_async: boolean;
requires_auth: boolean;
};
export type TriggersCount = {
primary_schedule?: {
schedule?: string;
};
schedule_count?: number;
http_routes_count?: number;
webhook_count?: number;
email_count?: number;
websocket_count?: number;
};
export type WebsocketTrigger = {
path: string;
edited_by: string;
edited_at: string;
script_path: string;
url: string;
is_flow: boolean;
extra_perms: {
[key: string]: (boolean);
};
email: string;
workspace_id: string;
server_id?: string;
last_server_ping?: string;
error?: string;
enabled: boolean;
filters: Array<{
key: string;
value: unknown;
}>;
initial_messages: Array<WebsocketTriggerInitialMessage>;
url_runnable_args: ScriptArgs;
};
export type NewWebsocketTrigger = {
path: string;
script_path: string;
is_flow: boolean;
url: string;
enabled?: boolean;
filters: Array<{
key: string;
value: unknown;
}>;
initial_messages: Array<WebsocketTriggerInitialMessage>;
url_runnable_args: ScriptArgs;
};
export type EditWebsocketTrigger = {
url: string;
path: string;
script_path: string;
is_flow: boolean;
filters: Array<{
key: string;
value: unknown;
}>;
initial_messages: Array<WebsocketTriggerInitialMessage>;
url_runnable_args: ScriptArgs;
};
export type WebsocketTriggerInitialMessage = {
raw_message: string;
} | {
runnable_result: {
path: string;
args: ScriptArgs;
is_flow: boolean;
};
};
export type Group = {
name: string;
summary?: string;
@@ -755,7 +660,6 @@ export type GlobalUserInfo = {
name?: string;
company?: string;
username?: string;
operator_only?: boolean;
};
export type login_type = 'password' | 'github';
@@ -818,9 +722,6 @@ export type Policy = {
[key: string]: unknown;
};
};
s3_inputs?: Array<{
[key: string]: unknown;
}>;
execution_mode?: 'viewer' | 'publisher' | 'anonymous';
on_behalf_of?: string;
on_behalf_of_email?: string;
@@ -1067,46 +968,6 @@ export type JobSearchHit = {
dancer?: string;
};
export type LogSearchHit = {
dancer?: string;
};
export type AutoscalingEvent = {
id?: number;
worker_group?: string;
event_type?: string;
desired_workers?: number;
reason?: string;
applied_at?: string;
};
export type CriticalAlert = {
/**
* Unique identifier for the alert
*/
id?: number;
/**
* Type of alert (e.g., critical_error)
*/
alert_type?: string;
/**
* The message content of the alert
*/
message?: string;
/**
* Time when the alert was created
*/
created_at?: string;
/**
* Acknowledgment status of the alert, can be true, false, or null if not set
*/
acknowledged?: (boolean) | null;
/**
* Workspace id if the alert is in the scope of a workspace
*/
workspace_id?: (string) | null;
};
export type OpenFlow = {
summary: string;
description?: string;
@@ -1212,7 +1073,6 @@ export type RawScript = {
concurrent_limit?: number;
concurrency_time_window_s?: number;
custom_concurrency_key?: string;
is_trigger?: boolean;
};
export type language2 = 'deno' | 'bun' | 'python3' | 'go' | 'bash' | 'powershell' | 'postgresql' | 'mysql' | 'bigquery' | 'snowflake' | 'mssql' | 'graphql' | 'nativets' | 'php';
@@ -1225,7 +1085,6 @@ export type PathScript = {
hash?: string;
type: 'script';
tag_override?: string;
is_trigger?: boolean;
};
export type PathFlow = {
@@ -1659,30 +1518,6 @@ export type SetPasswordData = {
export type SetPasswordResponse = (string);
export type SetPasswordForUserData = {
/**
* set password
*/
requestBody: {
password: string;
};
user: string;
};
export type SetPasswordForUserResponse = (string);
export type SetLoginTypeForUserData = {
/**
* set login type
*/
requestBody: {
login_type: string;
};
user: string;
};
export type SetLoginTypeForUserResponse = (string);
export type CreateUserGloballyData = {
/**
* user info
@@ -1840,7 +1675,6 @@ export type TestSmtpData = {
port: number;
from: string;
tls_implicit: boolean;
disable_tls: boolean;
};
};
};
@@ -1859,25 +1693,6 @@ export type TestCriticalChannelsData = {
export type TestCriticalChannelsResponse = (string);
export type GetCriticalAlertsData = {
acknowledged?: (boolean) | null;
page?: number;
pageSize?: number;
};
export type GetCriticalAlertsResponse = (Array<CriticalAlert>);
export type AcknowledgeCriticalAlertData = {
/**
* The ID of the critical alert to acknowledge
*/
id: number;
};
export type AcknowledgeCriticalAlertResponse = (string);
export type AcknowledgeAllCriticalAlertsResponse = (string);
export type TestLicenseKeyData = {
/**
* test license key
@@ -2102,10 +1917,6 @@ export type ExistsEmailData = {
export type ExistsEmailResponse = (boolean);
export type ListUsersAsSuperAdminData = {
/**
* filter only active users
*/
activeOnly?: boolean;
/**
* which page to return (start at 1, default 1)
*/
@@ -2141,7 +1952,7 @@ export type GetSettingsResponse = ({
customer_id?: string;
webhook?: string;
deploy_to?: string;
ai_resource?: AiResource;
openai_resource_path?: string;
code_completion_enabled: boolean;
error_handler?: string;
error_handler_extra_args?: ScriptArgs;
@@ -2151,7 +1962,6 @@ export type GetSettingsResponse = ({
deploy_ui?: WorkspaceDeployUISettings;
default_app?: string;
default_scripts?: WorkspaceDefaultScripts;
mute_critical_alerts?: boolean;
});
export type GetDeployToData = {
@@ -2260,7 +2070,7 @@ export type EditCopilotConfigData = {
* WorkspaceCopilotConfig
*/
requestBody: {
ai_resource?: AiResource;
openai_resource_path?: string;
code_completion_enabled: boolean;
};
workspace: string;
@@ -2273,8 +2083,7 @@ export type GetCopilotInfoData = {
};
export type GetCopilotInfoResponse = ({
ai_provider: string;
exists_ai_resource: boolean;
exists_openai_resource_path: boolean;
code_completion_enabled: boolean;
});
@@ -2410,15 +2219,6 @@ export type GetWorkspaceUsageData = {
export type GetWorkspaceUsageResponse = (number);
export type GetUsedTriggersData = {
workspace: string;
};
export type GetUsedTriggersResponse = ({
http_routes_used: boolean;
websocket_used: boolean;
});
export type ListUsersData = {
workspace: string;
};
@@ -2582,46 +2382,6 @@ export type ListContextualVariablesData = {
export type ListContextualVariablesResponse = (Array<ContextualVariable>);
export type WorkspaceGetCriticalAlertsData = {
acknowledged?: (boolean) | null;
page?: number;
pageSize?: number;
workspace: string;
};
export type WorkspaceGetCriticalAlertsResponse = (Array<CriticalAlert>);
export type WorkspaceAcknowledgeCriticalAlertData = {
/**
* The ID of the critical alert to acknowledge
*/
id: number;
workspace: string;
};
export type WorkspaceAcknowledgeCriticalAlertResponse = (string);
export type WorkspaceAcknowledgeAllCriticalAlertsData = {
workspace: string;
};
export type WorkspaceAcknowledgeAllCriticalAlertsResponse = (string);
export type WorkspaceMuteCriticalAlertsUiData = {
/**
* Boolean flag to mute critical alerts.
*/
requestBody: {
/**
* Whether critical alerts should be muted.
*/
mute_critical_alerts?: boolean;
};
workspace: string;
};
export type WorkspaceMuteCriticalAlertsUiResponse = (string);
export type LoginWithOauthData = {
clientName: string;
/**
@@ -2714,10 +2474,7 @@ export type DisconnectSlackData = {
export type DisconnectSlackResponse = (string);
export type ListOauthLoginsResponse = ({
oauth: Array<{
type: string;
display_name?: string;
}>;
oauth: Array<(string)>;
saml?: string;
});
@@ -3254,20 +3011,6 @@ export type GetScriptByPathData = {
export type GetScriptByPathResponse = (Script);
export type GetTriggersCountOfScriptData = {
path: string;
workspace: string;
};
export type GetTriggersCountOfScriptResponse = (TriggersCount);
export type ListTokensOfScriptData = {
path: string;
workspace: string;
};
export type ListTokensOfScriptResponse = (Array<TruncatedToken>);
export type GetScriptByPathWithDraftData = {
path: string;
workspace: string;
@@ -3282,13 +3025,6 @@ export type GetScriptHistoryByPathData = {
export type GetScriptHistoryByPathResponse = (Array<ScriptHistory>);
export type GetScriptLatestVersionData = {
path: string;
workspace: string;
};
export type GetScriptLatestVersionResponse = (ScriptHistory);
export type UpdateScriptHistoryData = {
hash: string;
path: string;
@@ -3642,13 +3378,6 @@ export type GetFlowHistoryData = {
export type GetFlowHistoryResponse = (Array<FlowVersion>);
export type GetFlowLatestVersionData = {
path: string;
workspace: string;
};
export type GetFlowLatestVersionResponse = (FlowVersion);
export type GetFlowVersionData = {
path: string;
version: number;
@@ -3679,20 +3408,6 @@ export type GetFlowByPathData = {
export type GetFlowByPathResponse = (Flow);
export type GetTriggersCountOfFlowData = {
path: string;
workspace: string;
};
export type GetTriggersCountOfFlowResponse = (TriggersCount);
export type ListTokensOfFlowData = {
path: string;
workspace: string;
};
export type ListTokensOfFlowResponse = (Array<TruncatedToken>);
export type ToggleWorkspaceErrorHandlerForFlowData = {
path: string;
/**
@@ -3922,13 +3637,6 @@ export type GetAppHistoryByPathData = {
export type GetAppHistoryByPathResponse = (Array<AppHistory>);
export type GetAppLatestVersionData = {
path: string;
workspace: string;
};
export type GetAppLatestVersionResponse = (AppHistory);
export type UpdateAppHistoryData = {
id: number;
/**
@@ -4391,16 +4099,6 @@ export type GetCompletedCountResponse = ({
database_length: number;
});
export type CountCompletedJobsData = {
allWorkspaces?: boolean;
completedAfterSAgo?: number;
success?: boolean;
tags?: string;
workspace: string;
};
export type CountCompletedJobsResponse = (number);
export type ListFilteredUuidsData = {
/**
* get jobs from all workspaces (only valid if request come from the `admins` workspace)
@@ -5190,80 +4888,11 @@ export type ExistsRouteData = {
export type ExistsRouteResponse = (boolean);
export type CreateWebsocketTriggerData = {
/**
* new websocket trigger
*/
requestBody: NewWebsocketTrigger;
export type UsedData = {
workspace: string;
};
export type CreateWebsocketTriggerResponse = (string);
export type UpdateWebsocketTriggerData = {
path: string;
/**
* updated trigger
*/
requestBody: EditWebsocketTrigger;
workspace: string;
};
export type UpdateWebsocketTriggerResponse = (string);
export type DeleteWebsocketTriggerData = {
path: string;
workspace: string;
};
export type DeleteWebsocketTriggerResponse = (string);
export type GetWebsocketTriggerData = {
path: string;
workspace: string;
};
export type GetWebsocketTriggerResponse = (WebsocketTrigger);
export type ListWebsocketTriggersData = {
isFlow?: boolean;
/**
* which page to return (start at 1, default 1)
*/
page?: number;
/**
* filter by path
*/
path?: string;
pathStart?: string;
/**
* number of items to return for a given page (default 30, max 100)
*/
perPage?: number;
workspace: string;
};
export type ListWebsocketTriggersResponse = (Array<WebsocketTrigger>);
export type ExistsWebsocketTriggerData = {
path: string;
workspace: string;
};
export type ExistsWebsocketTriggerResponse = (boolean);
export type SetWebsocketTriggerEnabledData = {
path: string;
/**
* updated websocket trigger enable
*/
requestBody: {
enabled: boolean;
};
workspace: string;
};
export type SetWebsocketTriggerEnabledResponse = (string);
export type UsedResponse = (boolean);
export type ListInstanceGroupsResponse = (Array<InstanceGroup>);
@@ -5572,10 +5201,6 @@ export type GetQueueMetricsResponse = (Array<{
}>;
}>);
export type GetCountsOfJobsWaitingPerTagResponse = ({
[key: string]: (number);
});
export type ListWorkerGroupsResponse = (Array<{
name: string;
config: unknown;
@@ -5605,14 +5230,8 @@ export type DeleteConfigResponse = (string);
export type ListConfigsResponse = (Array<Config>);
export type ListAutoscalingEventsData = {
workerGroup: string;
};
export type ListAutoscalingEventsResponse = (Array<AutoscalingEvent>);
export type GetGranularAclsData = {
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger';
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger';
path: string;
workspace: string;
};
@@ -5622,7 +5241,7 @@ export type GetGranularAclsResponse = ({
});
export type AddGranularAclsData = {
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger';
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger';
path: string;
/**
* acl to add
@@ -5637,7 +5256,7 @@ export type AddGranularAclsData = {
export type AddGranularAclsResponse = (string);
export type RemoveGranularAclsData = {
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger' | 'websocket_trigger';
kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow' | 'folder' | 'app' | 'raw_app' | 'http_trigger';
path: string;
/**
* acl to add
@@ -5950,8 +5569,6 @@ export type MoveS3FileData = {
export type MoveS3FileResponse = (unknown);
export type FileUploadData = {
contentDisposition?: string;
contentType?: string;
fileExtension?: string;
fileKey?: string;
/**
@@ -6200,44 +5817,4 @@ export type SearchJobsIndexResponse = ({
* the jobs that matched the query
*/
hits?: Array<JobSearchHit>;
});
export type SearchLogsIndexData = {
hostname: string;
maxTs?: string;
minTs?: string;
mode: string;
searchQuery: string;
workerGroup?: string;
};
export type SearchLogsIndexResponse = ({
/**
* a list of the terms that couldn't be parsed (and thus ignored)
*/
query_parse_errors?: Array<(string)>;
/**
* log files that matched the query
*/
hits?: Array<LogSearchHit>;
});
export type CountSearchLogsIndexData = {
hosts: string;
maxTs?: string;
minTs?: string;
searchQuery: string;
};
export type CountSearchLogsIndexResponse = ({
/**
* a list of the terms that couldn't be parsed (and thus ignored)
*/
query_parse_errors?: Array<(string)>;
/**
* count of log lines that matched the query per hostname
*/
count_per_host?: {
[key: string]: unknown;
};
});
+1 -1
View File
@@ -60,7 +60,7 @@ export {
// }
// });
export const VERSION = "1.425.1";
export const VERSION = "1.423.2";
const command = new Command()
.name("wmill")
+6 -6
View File
@@ -4,10 +4,10 @@ import { colors } from "./deps.ts";
import { yamlParseFile } from "./deps.ts";
import { log } from "./deps.ts";
import { compareInstanceObjects, InstanceSyncOptions } from "./instance.ts";
import { isSuperset } from "./types.ts";
import { isSuperset, GlobalOptions } from "./types.ts";
import { deepEqual } from "./utils.ts";
import * as wmill from "./gen/services.gen.ts";
import { AiResource, Config, GlobalSetting } from "./gen/types.gen.ts";
import { Config, GlobalSetting } from "./gen/types.gen.ts";
import { removeWorkerPrefix } from "./worker_groups.ts";
import process from "node:process";
@@ -24,7 +24,7 @@ export interface SimplifiedSettings {
error_handler?: string;
error_handler_extra_args?: any;
error_handler_muted_on_cancel?: boolean;
ai_resource?: AiResource;
openai_resource_path?: string;
code_completion_enabled: boolean;
large_file_storage?: any;
git_sync?: any;
@@ -80,7 +80,7 @@ export async function pushWorkspaceSettings(
error_handler_extra_args: remoteSettings.error_handler_extra_args,
error_handler_muted_on_cancel:
remoteSettings.error_handler_muted_on_cancel,
ai_resource: remoteSettings.ai_resource,
openai_resource_path: remoteSettings.openai_resource_path,
code_completion_enabled: remoteSettings.code_completion_enabled,
large_file_storage: remoteSettings.large_file_storage,
git_sync: remoteSettings.git_sync,
@@ -153,14 +153,14 @@ export async function pushWorkspaceSettings(
}
}
if (
localSettings.ai_resource !== settings.ai_resource ||
localSettings.openai_resource_path !== settings.openai_resource_path ||
localSettings.code_completion_enabled !== settings.code_completion_enabled
) {
log.debug(`Updating openai settings...`);
await wmill.editCopilotConfig({
workspace,
requestBody: {
ai_resource: localSettings.ai_resource,
openai_resource_path: localSettings.openai_resource_path,
code_completion_enabled: localSettings.code_completion_enabled,
},
});
+2 -46
View File
@@ -1,15 +1,14 @@
{
"name": "windmill-components",
"version": "1.425.1",
"version": "1.423.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.425.1",
"version": "1.423.2",
"license": "AGPL-3.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@aws-crypto/sha256-js": "^4.0.0",
"@codingame/monaco-vscode-configuration-service-override": "~8.0.2",
"@codingame/monaco-vscode-files-service-override": "~8.0.2",
@@ -22,7 +21,6 @@
"@codingame/monaco-vscode-standalone-typescript-language-features": "~8.0.2",
"@json2csv/plainjs": "^7.0.6",
"@leeoniya/ufuzzy": "^1.0.8",
"@mistralai/mistralai": "^1.3.0",
"@popperjs/core": "^2.11.6",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.9.9",
@@ -177,30 +175,6 @@
"node": ">=6.0.0"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.32.1",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.32.1.tgz",
"integrity": "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg==",
"license": "MIT",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7"
}
},
"node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
"version": "18.19.64",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz",
"integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "11.6.1",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.1.tgz",
@@ -3685,14 +3659,6 @@
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz",
"integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA=="
},
"node_modules/@mistralai/mistralai": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.3.0.tgz",
"integrity": "sha512-G5DPCSC8sEhG3LUEZDYLD7qEZWDuZXgaX3IcoC3a/ydm9jFuh2pRZtknsgMx2sU8d7kxRuxblY3fPH5C38wnhQ==",
"peerDependencies": {
"zod": ">= 3"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -13772,16 +13738,6 @@
"integrity": "sha512-A7AMeJfGefk317I/3tBoUYRcDcNavKEkpiPN/nQsBz/viI2GvT7BtrqdPD6rGqBFN8Ax7v4obf+Cl32JF9DDVw==",
"dev": true
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zstddec": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz",
+1 -3
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.425.1",
"version": "1.423.2",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -82,7 +82,6 @@
},
"type": "module",
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@aws-crypto/sha256-js": "^4.0.0",
"@codingame/monaco-vscode-configuration-service-override": "~8.0.2",
"@codingame/monaco-vscode-files-service-override": "~8.0.2",
@@ -95,7 +94,6 @@
"@codingame/monaco-vscode-standalone-typescript-language-features": "~8.0.2",
"@json2csv/plainjs": "^7.0.6",
"@leeoniya/ufuzzy": "^1.0.8",
"@mistralai/mistralai": "^1.3.0",
"@popperjs/core": "^2.11.6",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.9.9",
+3 -4
View File
@@ -39,6 +39,7 @@
import { CornerDownLeft, Play } from 'lucide-svelte'
import Toggle from './Toggle.svelte'
import { setLicense } from '$lib/enterpriseUtils'
import { workspacedOpenai } from './copilot/lib'
import type { FlowCopilotContext, FlowCopilotModule } from './copilot/flow'
import { pickScript } from './flows/flowStateUtils'
import {
@@ -48,7 +49,6 @@
} from '$lib/relative_imports'
import Tooltip from './Tooltip.svelte'
import type { ScheduleTrigger, TriggerContext } from './triggers'
import { initAllAiWorkspace } from './copilot/lib'
import type { FlowPropPickerConfig, PropPickerContext } from './prop_picker'
import type { PickableProperties } from './flows/previousResults'
$: token = $page.url.searchParams.get('wm_token') ?? undefined
@@ -110,13 +110,12 @@
async function setCopilotInfo() {
if (workspace) {
initAllAiWorkspace(workspace)
workspacedOpenai.init(workspace, token)
try {
copilotInfo.set(await WorkspaceService.getCopilotInfo({ workspace }))
} catch (err) {
copilotInfo.set({
ai_provider: '',
exists_ai_resource: false,
exists_openai_resource_path: false,
code_completion_enabled: false
})
@@ -8,7 +8,6 @@
cleanValueProperties,
orderedJsonStringify,
orderedYamlStringify,
replaceFalseWithUndefined,
type Value
} from '$lib/utils'
import type { Script } from '$lib/gen'
@@ -55,9 +54,8 @@
diffViewer.closeDrawer()
}
function prepareDiff(data: Value) {
const metadata = structuredClone(cleanValueProperties(replaceFalseWithUndefined(data)))
const metadata = structuredClone(cleanValueProperties(data))
const content = metadata['content']
if (metadata['content'] !== undefined) {
metadata['content'] = 'check content diff'
+2 -5
View File
@@ -171,7 +171,6 @@
import { initVim } from './monaco_keybindings'
import { buildWorkerDefinition } from '$lib/monaco_workers/build_workers'
import { parseTypescriptDeps } from '$lib/relative_imports'
import type { AiProviderTypes } from './copilot/lib'
// import EditorTheme from './EditorTheme.svelte'
@@ -594,13 +593,11 @@
token.onCancellationRequested(() => {
abortController?.abort()
})
const aiProvider = $copilotInfo.ai_provider
const insertText = await editorCodeCompletion(
textUntilPosition,
textAfterPosition,
lang,
abortController,
aiProvider as AiProviderTypes
abortController
)
if (insertText) {
items = [
@@ -627,7 +624,7 @@
)
}
$: $copilotInfo.exists_ai_resource &&
$: $copilotInfo.exists_openai_resource_path &&
$copilotInfo.code_completion_enabled &&
$codeCompletionSessionEnabled &&
initialized &&
+21 -25
View File
@@ -25,7 +25,6 @@
encodeState,
formatCron,
orderedJsonStringify,
replaceFalseWithUndefined,
sleep,
type Value
} from '$lib/utils'
@@ -86,7 +85,6 @@
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui'
import FlowYamlEditor from './flows/header/FlowYamlEditor.svelte'
import { type TriggerContext, type ScheduleTrigger } from './triggers'
import type { AiProviderTypes } from './copilot/lib'
export let initialPath: string = ''
export let pathStoreInit: string | undefined = undefined
@@ -295,21 +293,12 @@
// We need it for diff
await syncWithDeployed()
if (
deployedValue &&
$flowStore &&
orderedJsonStringify(deployedValue) ===
orderedJsonStringify(replaceFalseWithUndefined({ ...$flowStore, path: $pathStore }))
) {
// Handle through confirmation modal
confirmCallback = async () => {
await saveFlow(deploymentMsg)
} else {
// Handle through confirmation modal
confirmCallback = async () => {
await saveFlow(deploymentMsg)
}
// Open confirmation modal
open = true
}
// Open confirmation modal
open = true
}
}
async function syncWithDeployed() {
@@ -318,12 +307,18 @@
path: initialPath,
withStarredInfo: true
})
deployedValue = replaceFalseWithUndefined({
deployedValue = {
...flow,
starred: undefined,
id: undefined,
edited_at: undefined,
edited_by: undefined,
workspace_id: undefined
})
workspace_id: undefined,
archived: undefined,
same_worker: undefined,
visible_to_runner_only: undefined,
ws_error_handler_muted: undefined
}
deployedBy = flow.edited_by
}
@@ -794,7 +789,6 @@
try {
push(history, $flowStore)
let module = stepOnly ? $copilotModulesStore[0] : $copilotModulesStore[idx]
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
copilotLoading = true
copilotStatus = "Generating code for step '" + module.id + "'..."
@@ -924,8 +918,7 @@
})
: undefined,
isFirstInLoop,
abortController,
aiProvider
abortController
)
unsubscribe()
}
@@ -941,7 +934,7 @@
pastModule.value.type === 'script')
) {
const stepSchema: Schema = JSON.parse(JSON.stringify($flowStateStore[module.id].schema)) // deep copy
if (isHubStep && pastModule !== undefined && $copilotInfo.exists_ai_resource) {
if (isHubStep && pastModule !== undefined && $copilotInfo.exists_openai_resource_path) {
// ask AI to set step inputs
abortController = new AbortController()
const { inputs, allExprs } = await glueCopilot(
@@ -951,8 +944,7 @@
value: RawScript | PathScript
},
isFirstInLoop,
abortController,
aiProvider
abortController
)
// create flow inputs used by AI for autocompletion
@@ -1000,7 +992,11 @@
$shouldUpdatePropertyType[key] = 'javascript'
})
} else {
if (isHubStep && pastModule !== undefined && !$copilotInfo.exists_ai_resource) {
if (
isHubStep &&
pastModule !== undefined &&
!$copilotInfo.exists_openai_resource_path
) {
sendUserToast(
'For better input generation, enable Windmill AI in the workspace settings',
true
@@ -14,7 +14,7 @@
import Alert from './common/alert/Alert.svelte'
import { isCloudHosted } from '$lib/cloud'
import { capitalize, classNames, sleep } from '$lib/utils'
import { enterpriseLicense, isCriticalAlertsUIOpen } from '$lib/stores'
import { enterpriseLicense } from '$lib/stores'
import CustomOauth from './CustomOauth.svelte'
import {
AlertCircle,
@@ -43,7 +43,6 @@
export let tab: string = 'Core'
export let hideTabs: boolean = false
export let hideSave: boolean = false
export let closeDrawer: (() => void) | undefined = () => {}
let values: Record<string, any> = {}
let initialOauths: Record<string, any> = {}
@@ -815,26 +814,6 @@
placeholder={setting.placeholder}
bind:value={values[setting.key]}
/>
{:else if setting.key == 'critical_alert_mute_ui'}
<div class="flex flex-col gap-y-2 my-2 py-2">
<Toggle
disabled={!$enterpriseLicense}
bind:checked={values[setting.key]}
options={{ right: setting.description }}
/>
<div class="flex flex-row">
<Button
disabled={!$enterpriseLicense}
size="sm"
on:click={() => {
isCriticalAlertsUIOpen.set(true)
closeDrawer?.()
}}
>
Show Critical Alerts
</Button>
</div>
</div>
{:else if setting.fieldType == 'critical_error_channels'}
<div class="w-full flex gap-x-16 flex-wrap">
<div class="w-full max-w-lg">
@@ -18,7 +18,6 @@
encodeState,
formatCron,
orderedJsonStringify,
replaceFalseWithUndefined,
type Value
} from '$lib/utils'
import Path from './Path.svelte'
@@ -279,28 +278,17 @@
// Fetch entire script, since we need it to show Diff
await syncWithDeployed()
if (
deployedValue &&
script &&
orderedJsonStringify({ ...deployedValue, hash: undefined }) ===
orderedJsonStringify(
replaceFalseWithUndefined({ ...script, hash: undefined, parent_hash: undefined })
)
) {
await editScript(stay, actual_parent_hash, deployMsg)
} else {
// Handle through confirmation modal
confirmCallback = async () => {
open = false
if (actual_parent_hash) {
await editScript(stay, actual_parent_hash, deployMsg)
} else {
sendUserToast('Could not fetch latest version of the script', true)
}
// Handle through confirmation modal
confirmCallback = async () => {
open = false
if (actual_parent_hash) {
await editScript(stay, actual_parent_hash, deployMsg)
} else {
sendUserToast('Could not fetch latest version of the script', true)
}
// Open confirmation modal
open = true
}
// Open confirmation modal
open = true
}
}
@@ -311,16 +299,20 @@
withStarredInfo: true
})
deployedValue = replaceFalseWithUndefined({
deployedValue = {
...latestScript,
starred: undefined,
workspace_id: undefined,
archived: undefined,
created_at: undefined,
created_by: undefined,
deleted: undefined,
extra_perms: undefined,
is_template: undefined,
lock: undefined,
lock_error_logs: undefined,
parent_hashes: undefined
})
}
deployedBy = latestScript.created_by
}
@@ -699,11 +691,8 @@
</Label>
<Label label="Path">
<svelte:fragment slot="header">
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/roles_and_permissions#path"
>
The unique identifier of the script in the workspace that defines
permissions
<Tooltip documentationLink="https://www.windmill.dev/docs/core_concepts/roles_and_permissions#path">
The unique identifier of the script in the workspace that defines permissions
</Tooltip>
</svelte:fragment>
<Path
@@ -325,7 +325,7 @@
</div>
</TabContent>
<TabContent value="" values={settingsKeys}>
<InstanceSettings bind:this={instanceSettings} hideTabs hideSave {tab} {closeDrawer}/>
<InstanceSettings bind:this={instanceSettings} hideTabs hideSave {tab} />
</TabContent>
</svelte:fragment>
</Tabs>
@@ -16,13 +16,10 @@
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import MultiSelect from '$lib/components/multiselect/MultiSelect.svelte'
import type { ObjectOption } from '../../../multiselect/types'
import { parseConfigOptions } from './utils'
import { deepEqual } from 'fast-equals'
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
export let id: string
export let render: boolean
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
@@ -33,83 +30,49 @@
const { app, worldStore, selectedComponent, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
let items: string[]
const resolvedConfig = initConfig(
components['multiselectcomponentv2'].initialData.configuration,
components['multiselectcomponent'].initialData.configuration,
configuration
)
const outputs = initOutput($worldStore, id, { result: [] as any[] })
let options: ObjectOption[] = []
let selectedOptions: ObjectOption[] = [...new Set(findOptionsByValue(outputs?.result.peak()))]
let css = initCss($app.css?.multiselectcomponent, customCss)
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
let w = 0
let open: boolean = false
const outputs = initOutput($worldStore, id, {
result: [] as string[]
})
$: if (resolvedConfig.items) {
options = parseConfigOptions(resolvedConfig.items)
}
let value: string[] | undefined = [...new Set(outputs?.result.peak())] as string[]
$componentControl[id] = {
setValue(newValues: any[]) {
selectOptionsByValue(newValues)
setValue(nvalue: string[]) {
value = [...new Set(nvalue)]
outputs?.result.set([...(value ?? [])])
}
}
$: if (resolvedConfig.defaultItems) {
selectOptionsByValue(resolvedConfig.defaultItems)
}
$: resolvedConfig.items && handleItems()
$: outerDiv &&
portalRef &&
css?.multiselect?.style &&
setOuterDivStyle(outerDiv, portalRef, css?.multiselect?.style)
$: if (render && portalRef && outerDiv && options?.length > 0) {
tick().then(() => {
moveOptionsToPortal()
})
}
function findOptionsByValue(values: any[]) {
if (!Array.isArray(values)) {
return []
function handleItems() {
if (Array.isArray(resolvedConfig.items)) {
items = resolvedConfig.items?.map((label) => {
return typeof label === 'string' ? label : `NOT_STRING`
})
}
return options?.filter((item) => {
let itemValue = item.value
try {
if (typeof itemValue == 'string') {
itemValue = JSON.parse(itemValue)
}
} catch (_) {}
return values.some((value) => deepEqual(itemValue, value))
}) as ObjectOption[]
}
function selectOptionsByValue(values: any[]) {
if (!Array.isArray(values)) {
outputs?.result.set([])
$: resolvedConfig.defaultItems && handleDefaultItems()
function handleDefaultItems() {
if (Array.isArray(resolvedConfig.defaultItems)) {
const nvalue = resolvedConfig.defaultItems?.map((label) => {
return typeof label === 'string' ? label : `NOT_STRING`
})
value = [...new Set(nvalue)]
outputs?.result.set([...(value ?? [])])
}
selectedOptions = findOptionsByValue(values)
outputs?.result.set([...toValues(selectedOptions)])
}
function toValues(options: ObjectOption[] | undefined) {
if (!options || !Array.isArray(options)) {
return []
}
return options.map((o) => {
let val = o.value
try {
if (typeof val === 'string') val = JSON.parse(val)
} catch (_) {}
return val
})
}
let css = initCss($app.css?.multiselectcomponent, customCss)
function setOuterDivStyle(outerDiv: HTMLDivElement, portalRef: HTMLDivElement, style: string) {
outerDiv.setAttribute('style', style)
@@ -118,6 +81,14 @@
ul?.setAttribute('style', extractCustomProperties(style))
}
$: outerDiv &&
portalRef &&
css?.multiselect?.style &&
setOuterDivStyle(outerDiv, portalRef, css?.multiselect?.style)
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
function moveOptionsToPortal() {
// Find ul element with class 'options' within the outerDiv
const ul = outerDiv?.querySelector('.options')
@@ -127,6 +98,14 @@
portalRef?.appendChild(ul)
}
}
$: if (render && portalRef && outerDiv && items?.length > 0) {
tick().then(() => {
moveOptionsToPortal()
})
}
let w = 0
let open: boolean = false
</script>
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
@@ -149,6 +128,7 @@
{/each}
<InitializeComponent {id} />
<AlignWrapper {render} hFull {verticalAlignment}>
<div
class="w-full app-editor-input"
@@ -163,7 +143,7 @@
use:floatingRef
bind:clientWidth={w}
>
{#if !selectedOptions || Array.isArray(selectedOptions)}
{#if !value || Array.isArray(value)}
<MultiSelect
bind:outerDiv
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
@@ -171,19 +151,15 @@
--sms-border={'none'}
--sms-min-height={'32px'}
--sms-focus-border={'none'}
bind:selected={selectedOptions}
{options}
bind:selected={value}
options={items}
placeholder={resolvedConfig.placeholder}
allowUserOptions={resolvedConfig.create}
key={(option) => {
if (typeof option == 'string' || typeof option == 'number') return option
return option.value
}}
on:change={(event) => {
if (event?.detail?.type === 'removeAll') {
outputs?.result.set([])
} else {
outputs?.result.set([...toValues(selectedOptions)])
outputs?.result.set([...(value ?? [])])
}
}}
on:open={() => {
@@ -205,10 +181,9 @@
e.target?.['parentElement']?.dispatchEvent(newe)
}}
>
{@html option.label === '' ? `&nbsp;` : option.label}
{option}
</div>
</MultiSelect>
<Portal name="app-multiselect-v2">
<div use:floatingContent class="z5000" hidden={!open}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -222,7 +197,7 @@
</div>
</Portal>
{:else}
Value {selectedOptions} is not an array
Value {value} is not an array
{/if}
</div>
</AlignWrapper>
@@ -1,48 +0,0 @@
import {
type ObjectOption,
isObjectOptionArray,
isStringArray
} from '$lib/components/multiselect/types'
import type { StaticAppInput } from '../../inputType'
export function parseConfigOptions(resolvedConfigItems: StaticAppInput) {
if (!resolvedConfigItems) {
return []
}
if (isObjectOptionArray(resolvedConfigItems)) {
return parseLabeledItems(resolvedConfigItems)
} else if (isStringArray(resolvedConfigItems)) {
return parseStringItems(resolvedConfigItems)
}
return []
}
export function parseLabeledItems(resolvedConfigItems: ObjectOption[]) {
return resolvedConfigItems?.map((item: ObjectOption) => {
if (!item || typeof item !== 'object') {
console.error(
'When labeled, MultiSelect component items should be an array of { label: string, value: string }.'
)
return {
label: 'not object',
value: 'not object'
}
}
return {
label: item?.label ?? 'undefined',
value:
typeof item?.value === 'object' ? JSON.stringify(item.value) : item?.value ?? 'undefined'
}
})
}
export function parseStringItems(resolvedConfigItems: string[]): ObjectOption[] {
return resolvedConfigItems?.map((option: string) => {
if (option === null || option === undefined || typeof option !== 'string') {
console.error('When not labeled, MultiSelect component items should be an array of strings.')
return { label: 'not string', value: 'not string' }
}
if (option === '') {
return { label: 'empty string', value: 'empty string' }
}
return { label: option, value: option }
})
}
@@ -41,8 +41,7 @@
copyToClipboard,
truncateRev,
orderedJsonStringify,
type Value,
replaceFalseWithUndefined
type Value
} from '../../../utils'
import type {
AppInput,
@@ -423,27 +422,13 @@
// We need it to show diff
// Handle through confirmation modal
await syncWithDeployed()
if (
deployedValue &&
savedApp &&
$app &&
orderedJsonStringify(deployedValue) ===
orderedJsonStringify(replaceFalseWithUndefined({
summary: $summary,
value: $app,
path: newPath || savedApp.draft?.path || savedApp.path,
policy
}))
) {
confirmCallback = async () => {
open = false
await updateApp(npath)
} else {
confirmCallback = async () => {
open = false
await updateApp(npath)
}
// Open confirmation modal
open = true
}
// Open confirmation modal
open = true
}
}
@@ -457,14 +442,15 @@
deployedBy = deployedApp.created_by
// Strip off extra information
deployedValue = replaceFalseWithUndefined({
deployedValue = {
...deployedApp,
starred: undefined,
id: undefined,
created_at: undefined,
created_by: undefined,
versions: undefined,
extra_perms: undefined
})
extra_perms: undefined //
}
}
async function updateApp(npath: string) {
@@ -2079,8 +2079,7 @@ This is a paragraph.
value: [
{ value: 'foo', label: 'Foo' },
{ value: 'bar', label: 'Bar' }
],
hasLabeledMode: true
]
} as StaticAppInput,
create: {
type: 'static',
@@ -2200,15 +2199,14 @@ This is a paragraph.
items: {
type: 'static',
fieldType: 'array',
subFieldType: 'simplestringselect',
value: ['Foo', 'Bar'],
hasLabeledMode: true
subFieldType: 'text',
value: ['Foo', 'Bar']
} as StaticAppInput,
defaultItems: {
type: 'static',
fieldType: 'array',
subFieldType: 'simplestringselect',
value: undefined
subFieldType: 'text',
value: []
} as StaticAppInput,
placeholder: {
type: 'static',
@@ -9,30 +9,15 @@
import Toggle from '$lib/components/Toggle.svelte'
import QuickAddColumn from './QuickAddColumn.svelte'
import RefreshDatabaseStudioTable from './RefreshDatabaseStudioTable.svelte'
import { isObjectOptionArray, isStringArray } from '$lib/components/multiselect/types'
export let componentInput: StaticInput<any[]> & {
loading?: boolean
expr?: string
isLabeled?: boolean
}
export let componentInput: StaticInput<any[]> & { loading?: boolean }
export let subFieldType: InputType | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
export let id: string | undefined
export let hasLabeledMode: boolean = false
const dispatch = createEventDispatcher()
const flipDurationMs = 200
let items = getItems(componentInput)
let raw: boolean = false
let labeled: boolean = subFieldType === 'labeledselect'
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
$: labeled !== undefined && handleLabeledChange()
$: items !== undefined && handleItemsChange()
function addElementByType() {
if (!Array.isArray(componentInput.value)) {
componentInput.value = []
@@ -47,13 +32,12 @@
} else if (subFieldType === 'object') {
value.push({})
} else if (subFieldType === 'labeledresource' || subFieldType === 'labeledselect') {
value.push({ label: 'label', value: 'value' })
value.push({ value: 'value', label: 'label' })
} else if (subFieldType === 'tab-select') {
value.push({ id: '', index: 0 })
} else if (
subFieldType === 'text' ||
subFieldType === 'textarea' ||
subFieldType === 'simplestringselect' ||
// TODO: Add support for these types
subFieldType === 'date' ||
subFieldType === 'time' ||
@@ -209,6 +193,8 @@
componentInput.value = reorderedValues
}
let items = getItems(componentInput)
function getItems(componentInput: StaticInput<any[]> & { loading?: boolean }) {
return (Array.isArray(componentInput.value) ? componentInput.value : [])
.filter((x) => x != undefined)
@@ -223,30 +209,15 @@
}
}
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
$: items != undefined && handleItemsChange()
function handleItemsChange() {
const newComponentInput = { ...componentInput }
newComponentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
newComponentInput.expr = JSON.stringify(newComponentInput.value)
newComponentInput.isLabeled = labeled
dispatch('componentInputChange', newComponentInput)
}
/** Transforms items string[] to ObjectOption] and vice versa. Mutating items will fire handleItemsChange. */
function handleLabeledChange() {
if (labeled && isStringArray(items.map((item) => item.value))) {
for (let i = 0; i < items.length; i++) {
const itemValue = items[i].value
items[i].value = { label: itemValue, value: itemValue }
}
} else if (!labeled && isObjectOptionArray(items.map((item) => item.value))) {
for (let i = 0; i < items.length; i++) {
const itemValue = items[i].value
items[i].value = itemValue.label
}
}
componentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
}
let raw: boolean = false
// let mounted = false
// $: if (componentInput.value && mounted) {
@@ -272,6 +243,7 @@
{#if Array.isArray(items) && componentInput.value}
<div class="flex flex-row items-center justify-between">
<div class="text-xs text-tertiary font-semibold">{pluralize(items.length, 'item')}</div>
{#if subFieldType === 'ag-grid' || subFieldType === 'table-column'}
<Toggle
options={{
@@ -281,16 +253,6 @@
bind:checked={raw}
/>
{/if}
{#if hasLabeledMode}
<Toggle
options={{
right: 'w/ label'
}}
size="xs"
bind:checked={labeled}
on:change={handleLabeledChange}
/>
{/if}
</div>
<section
use:dragHandleZone={{
@@ -309,7 +271,7 @@
<div class="grow min-w-0">
<SubTypeEditor
{id}
subFieldType={raw ? 'object' : labeled ? 'labeledselect' : subFieldType}
subFieldType={raw ? 'object' : subFieldType}
bind:componentInput
bind:value={item.value}
on:remove={() => deleteElementByType(index)}
@@ -43,7 +43,6 @@
export let documentationLink: string | undefined = undefined
export let markdownTooltip: string | undefined = undefined
export let securedContext = false
export let hasLabeledMode = false
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
@@ -172,7 +171,6 @@
{selectOptions}
{format}
{placeholder}
{hasLabeledMode}
bind:componentInput
/>
</div>
@@ -81,7 +81,6 @@
{recomputeOnInputChanged}
{showOnDemandOnlyToggle}
{securedContext}
hasLabeledMode={meta?.['hasLabeledMode']}
/>
{#if deletable}
<div class="flex flex-row-reverse -mt-4">
@@ -30,9 +30,6 @@
editor?.setCode(code)
}
let fullscreen = false
let focus = false
$: extraLib =
componentInput?.expr && $worldStore
? buildExtraLib($worldStore?.outputsById ?? {}, acceptSelf ? '' : id, $state, false)
@@ -55,6 +52,9 @@
inferDeps(code, $worldStore.outputsById, componentInput, app)
}
}
let fullscreen = false
let focus = false
</script>
{#if componentInput?.type === 'evalv2'}
@@ -3,7 +3,7 @@
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
import JsonEditor from './JsonEditor.svelte'
import { getContext, onMount } from 'svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '$lib/components/apps/types'
import IconSelectInput from './IconSelectInput.svelte'
import ColorInput from './ColorInput.svelte'
@@ -24,49 +24,17 @@
import EditableSchemaDrawer from '$lib/components/schema/EditableSchemaDrawer.svelte'
import AppPicker from '$lib/components/wizards/AppPicker.svelte'
export let componentInput: (StaticInput<any> & { isLabeled?: boolean }) | undefined
export let componentInput: StaticInput<any> | undefined
export let fieldType: InputType | undefined = undefined
export let subFieldType: InputType | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
export let placeholder: string | undefined = undefined
export let format: string | undefined = undefined
export let id: string | undefined
export let hasLabeledMode: boolean = false
const { onchange } = getContext<AppViewerContext>('AppViewerContext')
$: componentInput && onchange?.()
onMount(() => {
/** preserve the `labeled` toggling when switching from eval to static */
if (componentInput !== undefined) {
handleComponentInputChange(componentInput)
}
})
/**
* Every components: switching from `static` to `eval` preserves the user data
*
* Multiselect: toggling `labeled` needs to mutate input value and input fieldType.
* */
function handleComponentInputChange(newComponentInput: StaticInput<any>) {
componentInput = { ...componentInput, ...newComponentInput }
if (componentInput?.isLabeled) {
if (fieldType === 'array' && subFieldType === 'simplestringselect') {
subFieldType = 'labeledselect'
}
if (fieldType === 'simplestringselect') {
fieldType = 'labeledselect'
}
} else if (componentInput?.isLabeled === false) {
if (fieldType === 'array' && subFieldType === 'labeledselect') {
subFieldType = 'simplestringselect'
}
if (fieldType === 'labeledselect') {
fieldType = 'simplestringselect'
}
}
}
</script>
{#key subFieldType}
@@ -165,7 +133,7 @@
{/if}
{:else if fieldType === 'color'}
<ColorInput bind:value={componentInput.value} />
{:else if fieldType === 'object' || fieldType == 'labeledselect' || fieldType === 'simplestringselect'}
{:else if fieldType === 'object' || fieldType == 'labeledselect'}
{#if format?.startsWith('resource-') && (componentInput.value == undefined || typeof componentInput.value == 'string')}
<ResourcePicker
initialValue={componentInput.value?.split('$res:')?.[1] || ''}
@@ -194,14 +162,7 @@
</div>
{/if}
{:else if fieldType === 'array'}
<ArrayStaticInputEditor
{id}
{subFieldType}
bind:componentInput
on:deleteArrayItem
on:componentInputChange={(e) => handleComponentInputChange(e.detail)}
{hasLabeledMode}
/>
<ArrayStaticInputEditor {id} {subFieldType} bind:componentInput on:deleteArrayItem />
{:else if fieldType === 'schema'}
<div class="w-full">
<EditableSchemaDrawer bind:schema={componentInput.value} />
@@ -20,7 +20,6 @@ export type InputType =
| 'any'
| 'labeledresource'
| 'labeledselect'
| 'simplestringselect'
| 'tab-select'
| 'schema'
| 'ag-grid'
@@ -176,7 +175,6 @@ type InputConfiguration<T extends InputType, V extends InputType> = {
noStatic?: boolean
onDemandOnly?: boolean
hideRefreshButton?: boolean
isLabeled?: boolean
}
export type StaticOptions = {
@@ -209,7 +207,6 @@ export type AppInput =
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
| AppInputSpec<'array', object[], 'labeledresource'>
| AppInputSpec<'array', object[], 'labeledselect'>
| AppInputSpec<'array', string[], 'simplestringselect'>
| AppInputSpec<'labeledselect', object>
| AppInputSpec<'labeledresource', object>
| AppInputSpec<'array', object[], 'tab-select'>
@@ -221,7 +221,7 @@
JOBS_DISAPPROVAL: 'jobs.disapproval',
JOBS_DELETE: 'jobs.delete',
ACCOUNT_DELETE: 'account.delete',
AI_REQUEST: 'ai.request',
OPENAI_REQUEST: 'openai.request',
RESOURCES_CREATE: 'resources.create',
RESOURCES_UPDATE: 'resources.update',
RESOURCES_DELETE: 'resources.delete',
@@ -4,12 +4,7 @@
import { goto as gotoUrl } from '$app/navigation'
import Button from '../button/Button.svelte'
import type DiffDrawer from '$lib/components/DiffDrawer.svelte'
import {
cleanValueProperties,
orderedJsonStringify,
replaceFalseWithUndefined,
type Value
} from '$lib/utils'
import { cleanValueProperties, orderedJsonStringify, type Value } from '$lib/utils'
import { tick } from 'svelte'
import { page } from '$app/stores'
@@ -41,10 +36,7 @@
path: undefined
})
const current = cleanValueProperties({ ...(modifiedValue ?? {}), path: undefined })
if (
orderedJsonStringify(replaceFalseWithUndefined(draftOrDeployed)) ===
orderedJsonStringify(replaceFalseWithUndefined(current))
) {
if (orderedJsonStringify(draftOrDeployed) === orderedJsonStringify(current)) {
bypassBeforeNavigate = true
gotoUrl(goingTo)
} else {
@@ -44,7 +44,7 @@
<div
on:click={() => (open = false)}
transition:fadeFast|local
class={'absolute top-0 bottom-0 left-0 right-0 z-[9999]'}
class={'absolute top-0 bottom-0 left-0 right-0 z-50'}
role="dialog"
>
<div
@@ -12,7 +12,7 @@
}
</script>
{#if $copilotInfo.exists_ai_resource && $copilotInfo.code_completion_enabled}
{#if $copilotInfo.exists_openai_resource_path && $copilotInfo.code_completion_enabled}
<Toggle
size="xs"
bind:checked={$codeCompletionSessionEnabled}
@@ -1,13 +1,11 @@
<script lang="ts">
import { ExternalLink, Wand2 } from 'lucide-svelte'
import Button from '../common/button/Button.svelte'
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
import { getNonStreamingCompletion } from './lib'
import Popup from '../common/popup/Popup.svelte'
import { sendUserToast } from '$lib/toast'
import { copilotInfo } from '$lib/stores'
import { base } from '$lib/base'
import type { ChatCompletionMessageParam } from 'openai/resources/index.mjs'
export let schedule: string
@@ -16,35 +14,31 @@
let genLoading = false
let abortController = new AbortController()
$: instructionsField && setTimeout(() => instructionsField?.focus(), 100)
const SYSTEM =
"You are a helpful assitant for creating CRON schedules. The structure is 'second minute hour dayOfMonth month dayOfWeek'. Weekdays are Sunday (1), Monday (2), Tuesday (3), Wednesday (4), Thursday (5), Friday (6), Saturday (7). You only return the CRON string without any wrapping characters. If it is invalid, you will return an error message preceeded by 'ERROR:'."
const USER = 'CRON schedule instructions: {instructions}'
async function generateCron() {
genLoading = true
abortController = new AbortController()
const aiProvider = $copilotInfo.ai_provider
try {
const messages: ChatCompletionMessageParam[] = [
{
role: 'system',
content: SYSTEM
},
{
role: 'user',
content: USER.replace('{instructions}', instructions)
}
]
const response = await getNonStreamingCompletion(
messages,
abortController,
aiProvider as AiProviderTypes
[
{
role: 'system',
content: SYSTEM
},
{
role: 'user',
content: USER.replace('{instructions}', instructions)
}
],
abortController
)
if (response.startsWith('ERROR:')) {
throw response.replace('ERROR:', '').trim()
}
schedule = response
} catch (err) {
if (!abortController.signal.aborted) {
@@ -75,7 +69,7 @@
on:click={genLoading ? () => abortController?.abort() : undefined}
/>
</svelte:fragment>
{#if $copilotInfo.exists_ai_resource}
{#if $copilotInfo.exists_openai_resource_path}
<div class="flex w-96">
<input
bind:this={instructionsField}
@@ -110,7 +104,7 @@
<div class="block text-primary">
<p class="text-sm"
>Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
href="{base}/workspace_settings?tab=openai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>workspace settings <ExternalLink size={16} /></a
@@ -31,7 +31,7 @@
$: copilotStatus && copilotPopover?.refresh()
</script>
{#if $copilotInfo.exists_ai_resource}
{#if $copilotInfo.exists_openai_resource_path}
<ManualPopover bind:this={copilotPopover}>
<Button
size="xs"
@@ -137,7 +137,7 @@
<div class="block text-primary">
<p class="text-sm"
>Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
href="{base}/workspace_settings?tab=openai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>workspace settings <ExternalLink size={16} /></a
@@ -1,7 +1,7 @@
<script lang="ts">
import { Check, Loader2, Wand2 } from 'lucide-svelte'
import Button from '../common/button/Button.svelte'
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
import { getNonStreamingCompletion } from './lib'
import { sendUserToast } from '$lib/toast'
import type { Flow, InputTransform } from '$lib/gen'
import ManualPopover from '../ManualPopover.svelte'
@@ -62,7 +62,7 @@ ${YAML.stringify(availableData)}</available>
Reply with the most probable answer, do not explain or discuss.
Use javascript object dot notation to access the properties.
Only output the expression, do not explain or discuss.`
const aiProvider = $copilotInfo.ai_provider
generatedContent = await getNonStreamingCompletion(
[
{
@@ -70,8 +70,7 @@ Only output the expression, do not explain or discuss.`
content: user
}
],
abortController,
aiProvider as AiProviderTypes
abortController
)
} catch (err) {
if (!abortController.signal.aborted) {
@@ -83,7 +82,7 @@ Only output the expression, do not explain or discuss.`
}
export function onKeyUp(event: KeyboardEvent) {
if (!$copilotInfo.exists_ai_resource || !$stepInputCompletionEnabled) {
if (!$copilotInfo.exists_openai_resource_path || !$stepInputCompletionEnabled) {
return
}
if (event.key === 'Tab') {
@@ -118,7 +117,7 @@ Only output the expression, do not explain or discuss.`
cancelOnOutOfFocus()
}
$: if ($copilotInfo.exists_ai_resource && $stepInputCompletionEnabled && focused) {
$: if ($copilotInfo.exists_openai_resource_path && $stepInputCompletionEnabled && focused) {
automaticGeneration()
}
@@ -132,7 +131,7 @@ Only output the expression, do not explain or discuss.`
let out = true // hack to prevent regenerating answer when accepting the answer due to mouseenter on new icon
</script>
{#if $copilotInfo.exists_ai_resource && $stepInputCompletionEnabled}
{#if $copilotInfo.exists_openai_resource_path && $stepInputCompletionEnabled}
<ManualPopover showTooltip={!empty && generatedContent.length > 0} placement="bottom" class="p-2">
<Button
size="xs"
@@ -1,5 +1,5 @@
<script lang="ts">
import { getCompletion, getResponseFromEvent, type AiProviderTypes } from './lib'
import { getCompletion } from './lib'
import { isInitialCode } from '$lib/script_helpers'
import { Check, Loader2, Wand2 } from 'lucide-svelte'
import { copilotInfo, metadataCompletionEnabled } from '$lib/stores'
@@ -118,11 +118,11 @@ Generate a description for the flow below:
content: config.user.replace(`{${config.placeholderName}}`, placeholderContent)
}
]
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
const response = await getCompletion(messages, abortController, aiProvider)
const response = await getCompletion(messages, abortController)
generatedContent = ''
for await (const chunk of response) {
generatedContent += getResponseFromEvent(chunk, aiProvider)
const toks = chunk.choices[0]?.delta?.content || ''
generatedContent += toks
}
} catch (err) {
if (!abortController.signal.aborted) {
@@ -138,7 +138,7 @@ Generate a description for the flow below:
}
if (
$copilotInfo.exists_ai_resource &&
$copilotInfo.exists_openai_resource_path &&
$metadataCompletionEnabled &&
generateOnAppear &&
!content &&
@@ -158,7 +158,7 @@ Generate a description for the flow below:
}
$: active =
$copilotInfo.exists_ai_resource &&
$copilotInfo.exists_openai_resource_path &&
$metadataCompletionEnabled &&
!content &&
(loading || focused || !!generatedContent) &&
@@ -192,7 +192,7 @@ Generate a description for the flow below:
class={twMerge('relative', $$props.class)}
bind:clientWidth={width}
on:keydown={(event) => {
if (!$copilotInfo.exists_ai_resource || !$metadataCompletionEnabled) {
if (!$copilotInfo.exists_openai_resource_path || !$metadataCompletionEnabled) {
return
}
if (event.key === 'Tab') {
@@ -1,7 +1,7 @@
<script lang="ts">
import { Wand2 } from 'lucide-svelte'
import Button from '../common/button/Button.svelte'
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
import { getNonStreamingCompletion } from './lib'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher, getContext } from 'svelte'
import type { FlowEditorContext } from '../flows/types'
@@ -59,7 +59,7 @@ Here's a summary of the available data:
${YAML.stringify(availableData)}</available>
If the branching is made inside a for-loop, the iterator value is accessible as flow_input.iter.value
Only return the expression without any wrapper. Do not explain or discuss.`
const aiProvider = $copilotInfo.ai_provider
const result = await getNonStreamingCompletion(
[
{
@@ -67,8 +67,7 @@ Only return the expression without any wrapper. Do not explain or discuss.`
content: user
}
],
abortController,
aiProvider as AiProviderTypes
abortController
)
dispatch('setExpr', result)
@@ -83,7 +82,7 @@ Only return the expression without any wrapper. Do not explain or discuss.`
}
</script>
{#if $copilotInfo.exists_ai_resource && $stepInputCompletionEnabled}
{#if $copilotInfo.exists_openai_resource_path && $stepInputCompletionEnabled}
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
@@ -2,7 +2,7 @@
import { base } from '$lib/base'
import { Button } from '../common'
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
import { getNonStreamingCompletion } from './lib'
import { sendUserToast } from '$lib/toast'
import Popup from '../common/popup/Popup.svelte'
import { copilotInfo } from '$lib/stores'
@@ -25,7 +25,6 @@
savePrompt()
genLoading = true
abortController = new AbortController()
const aiProvider = $copilotInfo.ai_provider
try {
const res = await getNonStreamingCompletion(
[
@@ -39,8 +38,7 @@
content: funcDesc
}
],
abortController,
aiProvider as AiProviderTypes
abortController
)
dispatch('gen', { res: res, prompt: funcDesc })
funcDesc = ''
@@ -116,7 +114,7 @@
/>
</svelte:fragment>
<div class="block text-primary z-[2000]">
{#if $copilotInfo.exists_ai_resource}
{#if $copilotInfo.exists_openai_resource_path}
<div class="flex flex-col gap-4">
<div class="flex w-96">
<input
@@ -169,7 +167,7 @@
{:else}
<p class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
href="{base}/workspace_settings?tab=openai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
@@ -2,7 +2,7 @@
import { base } from '$lib/base'
import { Button } from '../common'
import { SUPPORTED_LANGUAGES, copilot, type AiProviderTypes } from './lib'
import { SUPPORTED_LANGUAGES, copilot } from './lib'
import type { SupportedLanguage } from '$lib/common'
import { sendUserToast } from '$lib/toast'
import type Editor from '../Editor.svelte'
@@ -40,7 +40,6 @@
try {
genLoading = true
abortController = new AbortController()
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
await copilot(
{
language: lang,
@@ -52,8 +51,7 @@
},
generatedCode,
abortController,
aiProvider,
generatedExplanation,
generatedExplanation
)
setupDiff()
diffEditor?.setModified($generatedCode)
@@ -188,12 +186,12 @@
</Button>
</svelte:fragment>
{@const fixAction = (_) => {
if ($copilotInfo.exists_ai_resource) {
if ($copilotInfo.exists_openai_resource_path) {
onFix(() => close(null))
}
}}
<div use:fixAction>
{#if $copilotInfo.exists_ai_resource}
{#if $copilotInfo.exists_openai_resource_path}
<div class="w-[42rem] min-h-[3rem] max-h-[34rem] overflow-y-scroll">
{#if $generatedCode.length > 0}
<div class="overflow-x-scroll">
@@ -213,7 +211,7 @@
<p class="text-sm"
>Enable Windmill AI in the <a
class="inline-flex flex-row items-center gap-1"
href="{base}/workspace_settings?tab=ai"
href="{base}/workspace_settings?tab=openai"
target="_blank">workspace settings</a
></p
></div
@@ -2,13 +2,7 @@
import { base } from '$lib/base'
import { Button } from '../common'
import {
MAX_SCHEMA_LENGTH,
SUPPORTED_LANGUAGES,
addThousandsSeparator,
copilot,
type AiProviderTypes
} from './lib'
import { MAX_SCHEMA_LENGTH, SUPPORTED_LANGUAGES, addThousandsSeparator, copilot } from './lib'
import type { SupportedLanguage } from '$lib/common'
import { sendUserToast } from '$lib/toast'
import type Editor from '../Editor.svelte'
@@ -74,7 +68,6 @@
return
}
savePrompt()
const aiProvider = $copilotInfo.ai_provider as AiProviderTypes
try {
genLoading = true
blockPopupOpen = true
@@ -91,8 +84,7 @@
workspace: $workspaceStore!
},
generatedCode,
abortController,
aiProvider
abortController
)
} else {
await copilot(
@@ -105,8 +97,7 @@
workspace: $workspaceStore!
},
generatedCode,
abortController,
aiProvider
abortController
)
}
setupDiff()
@@ -397,7 +388,7 @@
<LoadingIcon />
{/if}
</div>
{:else if $copilotInfo.exists_ai_resource}
{:else if $copilotInfo.exists_openai_resource_path}
<div class="flex flex-col gap-4">
<div class="flex flex-row justify-between items-center">
<ToggleButtonGroup class="w-auto shrink-0" bind:selected={mode}>
@@ -406,14 +397,7 @@
</ToggleButtonGroup>
<div class="text-[0.6rem] text-secondary opacity-60 flex flex-row items-center gap-0.5">
{#if $copilotInfo.ai_provider === 'openai'}
GPT-4o
{:else if $copilotInfo.ai_provider === 'anthropic'}
Claude-3.5
{:else}
Codestral
{/if}
<Bot size={14} />
GPT-4o<Bot size={14} />
</div>
</div>
<div class="flex w-96 items-start">
@@ -509,7 +493,7 @@
{:else}
<p class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
href="{base}/workspace_settings?tab=openai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
@@ -70,7 +70,7 @@
}
async function onGenerate() {
if (!selectedCompletion && !$copilotInfo.exists_ai_resource) {
if (!selectedCompletion && !$copilotInfo.exists_openai_resource_path) {
sendUserToast(
'Windmill AI is not enabled, you can activate it in the workspace settings',
true
@@ -1,7 +1,7 @@
<script lang="ts">
import { Check, Loader2, Wand2 } from 'lucide-svelte'
import Button from '../common/button/Button.svelte'
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
import { getNonStreamingCompletion } from './lib'
import { sendUserToast } from '$lib/toast'
import type { Flow, InputTransform } from '$lib/gen'
import ManualPopover from '../ManualPopover.svelte'
@@ -107,7 +107,7 @@ If none of the available results are appropriate, are already used or are more a
Reply with the most probable answer, do not explain or discuss.
Use javascript object dot notation to access the properties.
Only return the expression without any wrapper.`
const aiProvider = $copilotInfo.ai_provider
generatedContent = await getNonStreamingCompletion(
[
{
@@ -115,8 +115,7 @@ Only return the expression without any wrapper.`
content: user
}
],
abortController,
aiProvider as AiProviderTypes
abortController
)
if (
@@ -139,7 +138,7 @@ Only return the expression without any wrapper.`
}
export function onKeyUp(event: KeyboardEvent) {
if (!$copilotInfo.exists_ai_resource || !$stepInputCompletionEnabled) {
if (!$copilotInfo.exists_openai_resource_path || !$stepInputCompletionEnabled) {
return
}
if (event.key === 'Tab') {
@@ -182,7 +181,7 @@ Only return the expression without any wrapper.`
cancelOnOutOfFocus()
}
$: if ($copilotInfo.exists_ai_resource && $stepInputCompletionEnabled && focused) {
$: if ($copilotInfo.exists_openai_resource_path && $stepInputCompletionEnabled && focused) {
automaticGeneration()
}
@@ -194,7 +193,7 @@ Only return the expression without any wrapper.`
let openInputsModal = false
</script>
{#if $copilotInfo.exists_ai_resource && $stepInputCompletionEnabled}
{#if $copilotInfo.exists_openai_resource_path && $stepInputCompletionEnabled}
<FlowCopilotInputsModal
on:confirmed={async () => {
createFlowInput()
@@ -7,7 +7,7 @@
import type { FlowEditorContext } from '../flows/types'
import type { PickableProperties } from '../flows/previousResults'
import { getContext } from 'svelte'
import { getNonStreamingCompletion, type AiProviderTypes } from './lib'
import { getNonStreamingCompletion } from './lib'
import { sendUserToast } from '$lib/toast'
import Button from '../common/button/Button.svelte'
import type { FlowCopilotContext } from './flow'
@@ -83,7 +83,7 @@ Your answer has to be in the following format (one line per input):
input_name1: expression1
input_name2: expression2
...`
const aiProvider = $copilotInfo.ai_provider
generatedContent = await getNonStreamingCompletion(
[
{
@@ -91,8 +91,7 @@ input_name2: expression2
content: user
}
],
abortController,
aiProvider as AiProviderTypes
abortController
)
parsedInputs = generatedContent.split('\n').map((x) => x.split(': '))
@@ -169,7 +168,7 @@ input_name2: expression2
</script>
<div class="flex flex-row justify-end">
{#if $copilotInfo.exists_ai_resource && $stepInputCompletionEnabled}
{#if $copilotInfo.exists_openai_resource_path && $stepInputCompletionEnabled}
<FlowCopilotInputsModal
on:confirmed={async () => {
createFlowInputs()
@@ -237,10 +236,10 @@ input_name2: expression2
</Button>
</svelte:fragment>
<p class="text-sm">
{#if !$copilotInfo.exists_ai_resource}
{#if !$copilotInfo.exists_openai_resource_path}
Enable Windmill AI in the{' '}
<a
href="{base}/workspace_settings?tab=ai"
href="{base}/workspace_settings?tab=openai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>

Some files were not shown because too many files have changed in this diff Show More