feat: implement usage tracker + quotas
This commit is contained in:
1
backend/migrations/20221210194626_usage_tracker.down.sql
Normal file
1
backend/migrations/20221210194626_usage_tracker.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
8
backend/migrations/20221210194626_usage_tracker.up.sql
Normal file
8
backend/migrations/20221210194626_usage_tracker.up.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Add up migration script here
|
||||
CREATE TABLE usage (
|
||||
id VARCHAR(50) NOT NULL,
|
||||
is_workspace BOOLEAN NOT NULL,
|
||||
month_ INTEGER NOT NULL,
|
||||
usage INTEGER NOT NULL,
|
||||
PRIMARY KEY (id, is_workspace, month_)
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE queue DROP COLUMN email;
|
||||
ALTER TABLE workspace_settings DROP COLUMN slack_email;
|
||||
ALTER TABLE schedule DROP COLUMN email;
|
||||
ALTER TABLE schedule DROP COLUMN error;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE queue ADD COLUMN email VARCHAR(50) NOT NULL DEFAULT 'missing@email.xyz';
|
||||
ALTER TABLE workspace_settings ADD COLUMN slack_email VARCHAR(50) NOT NULL DEFAULT 'missing@email.xyz';;
|
||||
ALTER TABLE schedule ADD COLUMN email VARCHAR(50) NOT NULL DEFAULT 'missing@email.xyz';
|
||||
ALTER TABLE schedule ADD COLUMN error TEXT;
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
{
|
||||
"db": "PostgreSQL",
|
||||
"02cf26c6e81ce21fa84fdc2a431663aa2db7efea3596d7eec984dd707b8ad010": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "expiration",
|
||||
"ordinal": 0,
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6) RETURNING expiration"
|
||||
},
|
||||
"0355b53b1d45955ca56b2829372ce9c656d7f0ad7b8d0709161047f0d8cdc4f4": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -258,73 +233,6 @@
|
||||
},
|
||||
"query": "DELETE FROM account WHERE id = $1 AND workspace_id = $2"
|
||||
},
|
||||
"0eea65f84168d4425d20d24b321cdee98b69dbf1e8a75e916af6d37abc4aee92": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Bool",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO queue\n (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language, started_at, same_worker, pre_run_error)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20) RETURNING id"
|
||||
},
|
||||
"11b1586acdfc180c5a077861ee1f7201fcbcec9d0ebada464f9d952c9c3e400d": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -472,13 +380,19 @@
|
||||
"name": "slack_command_script",
|
||||
"ordinal": 3,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "slack_email",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -594,6 +508,74 @@
|
||||
},
|
||||
"query": "UPDATE queue SET suspend = $1 WHERE id = $2"
|
||||
},
|
||||
"207b9cca0240dd6eea3f3b95c093ead9d7bb48fae2484e1a4086636396c0dcb7": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Bool",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Bool",
|
||||
"Text",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO queue\n (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21) RETURNING id"
|
||||
},
|
||||
"20e8a8343adc4a3a28a1c4908a13047cddafc4480cf028d3455fd17347cace73": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -754,20 +736,6 @@
|
||||
},
|
||||
"query": "SELECT now()"
|
||||
},
|
||||
"282655dc568d000379196fae0dee32438a824d93f4ad2fcc155269e76f4ca20d": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name)\n VALUES ($1, $2, $3) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, slack_name = $3"
|
||||
},
|
||||
"28c042adef65c3055edc324fbbd2f267285d3566cbec58404983323d410ace27": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -918,6 +886,20 @@
|
||||
},
|
||||
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth)\n VALUES ($1, $2, $3, $4, $5, $6, $7)"
|
||||
},
|
||||
"2ee6d24b95cdda151585dcff19f8e7c931785fc21f7bbe9c3a82671943ced0ea": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE schedule SET enabled = false, error = $1 WHERE workspace_id = $2 AND path = $3"
|
||||
},
|
||||
"355dcb2cbebd13f0e3bdd4929b9e431b0e6d72716d1c4f9ab6af6adce5b5e4b3": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1050,6 +1032,26 @@
|
||||
},
|
||||
"query": "UPDATE app SET versions = array_append(versions, $1) WHERE id = $2"
|
||||
},
|
||||
"3e785610aa9f63241c16affc1f4a20c52f2aeb2aed9fc9e3b8c24874c87b14dc": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "usage",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, true, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage"
|
||||
},
|
||||
"3fabb3857c9cf2d057814b54ee54a95d01b6a7d9e89bea239b832a9d70f0044b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -1251,6 +1253,16 @@
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -1264,7 +1276,9 @@
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -1360,6 +1374,23 @@
|
||||
},
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2),\n ARRAY['step'], $3)\n WHERE id = $4\n "
|
||||
},
|
||||
"53f2836a652d34aafe6ffb266a092d7390212bc5532dcc226529a61a53deec42": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)"
|
||||
},
|
||||
"541ebd3bac65431237cf3b882dfdcd61ca97c253d9754d05bba59fda89841067": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1414,13 +1445,19 @@
|
||||
"name": "slack_command_script",
|
||||
"ordinal": 3,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "slack_email",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -1515,6 +1552,19 @@
|
||||
},
|
||||
"query": "SELECT app.id, app.path, app.summary, app.versions, app.policy,\n app.extra_perms, app_version.value, \n app_version.created_at, app_version.created_by from app, app_version \n WHERE app_version.id = $1 AND app.id = app_version.flow_id AND app.workspace_id = $2"
|
||||
},
|
||||
"576b00c515ee7cbf628b1881596d8a03c1c506b309e39a0048a3a8fe18f37070": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE schedule SET error = NULL WHERE workspace_id = $1 AND path = $2"
|
||||
},
|
||||
"5850e0c1f7dee98455027a4c0db6ccea0af2c58aee656ef43964b515de054888": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1575,27 +1625,6 @@
|
||||
},
|
||||
"query": "SELECT * from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'starter')"
|
||||
},
|
||||
"5b7a1d16d8109a65479ab33d411c60d14ea91d870fdff8606d7aa4ad39f0ba00": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT email FROM usr WHERE username = $1 AND workspace_id = $2"
|
||||
},
|
||||
"5b9b58612ca0f703a5d154a76fab82ac2329aef965fa937bfab2810b6e1336a4": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -1876,26 +1905,6 @@
|
||||
},
|
||||
"query": "SELECT policy from app WHERE path = $1 AND workspace_id = $2"
|
||||
},
|
||||
"6eacfedfc1ab2431c318996d2ff480a65d55fc43d8fa95aa4e2e8430722dc82d": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT email FROM usr WHERE username = $1"
|
||||
},
|
||||
"6fc2cfae9df83eb24ea33e4c9567740100f4dd2285afc3ef474fc70041b0567b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -2461,6 +2470,16 @@
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -2474,7 +2493,9 @@
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -2721,27 +2742,6 @@
|
||||
},
|
||||
"query": "INSERT INTO app_version\n (flow_id, value, created_by)\n VALUES ($1, $2, $3) RETURNING id"
|
||||
},
|
||||
"92ee6274b2287bc4d368bfdb47d6452df75bd265cdd891f55112df2795294dac": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT COUNT(id) FROM queue WHERE permissioned_as = $1 AND workspace_id = $2"
|
||||
},
|
||||
"9490a4388f43e45e32911f1129e623f2d73ce2da7a948fe134bea1c87cdbefd1": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -2784,6 +2784,101 @@
|
||||
},
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'branchall', 'branch'], ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'branchall'->>'branch')::int\n "
|
||||
},
|
||||
"96dc1cd308f3c21cfb50b88048054dc03f93e261d25969d66aa48e9d0502960f": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE schedule SET enabled = $1, email = $2 WHERE path = $3 AND workspace_id = $4 RETURNING *"
|
||||
},
|
||||
"97e364c703bdcdfb5345810659cbe0477a28b8199ef0b297f9a22c88a43b6b5c": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -3008,6 +3103,16 @@
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -3021,7 +3126,9 @@
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -3032,6 +3139,19 @@
|
||||
},
|
||||
"query": "SELECT * FROM schedule WHERE path = $1 AND workspace_id = $2"
|
||||
},
|
||||
"ab690bfb3fd84eb0a833818352501e1560d22fd47b9a676571ea6efdad1ad300": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, false, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2"
|
||||
},
|
||||
"abc9f034e62ac224894173356aa69e09f3647a45d176e253e4fc8f7206f6a18d": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -3181,6 +3301,26 @@
|
||||
},
|
||||
"query": "UPDATE usr SET is_admin = $1 WHERE username = $2 AND workspace_id = $3"
|
||||
},
|
||||
"b2e14f4fe46b82fe333eb76cd75fdac0ab205a14999becb394b09c6e51af284f": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "usage",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, false, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage"
|
||||
},
|
||||
"b3b80de52d0931a2fdb5d38b7603a2d69cc25ab1cda413228c363a5ffd777113": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -3464,88 +3604,6 @@
|
||||
},
|
||||
"query": "UPDATE flow SET path = $1, summary = $2, description = $3, value = $4, edited_by = $5, edited_at = now(), schema = $6, dependency_job = NULL WHERE path = $7 AND workspace_id = $8"
|
||||
},
|
||||
"c213427a32e8903fff649a3e7aa8392d2215665ed04f5a8f2178861e9dba298a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE schedule SET enabled = $1 WHERE path = $2 AND workspace_id = $3 RETURNING *"
|
||||
},
|
||||
"c2849e67b9fea0dc46e6d7000f5a0c9dab89ae80a183d9255f8fdb356b4bc61c": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -4107,26 +4165,106 @@
|
||||
},
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int\n "
|
||||
},
|
||||
"e69142610bd94f6c65022cca5ccf7458b7c5bb77b3ed0278715222dd920d8644": {
|
||||
"e5ba82116000d2ae71e2099f417b39121178349fda9f8438bdd5556ff632ceb2": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "sum",
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT SUM(duration_ms)\n FROM completed_job\n WHERE permissioned_as = $1\n AND created_at > NOW() - INTERVAL '1200 seconds'\n AND workspace_id = $2"
|
||||
"query": "INSERT INTO schedule (workspace_id, path, schedule, offset_, edited_by, script_path, is_flow, args, enabled, email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *"
|
||||
},
|
||||
"e838b15953b567af2a2da6a4e3a04add5ec2cacabd93171a97c5fc978a64f8aa": {
|
||||
"describe": {
|
||||
@@ -4156,6 +4294,21 @@
|
||||
},
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3"
|
||||
},
|
||||
"eaa6e9dc4c0d3d6cc8152515019befa880bb3b69ff340d337edd14b65e74e2a3": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name, slack_email)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, slack_name = $3, slack_email = $4"
|
||||
},
|
||||
"ed400dbf4a1fa597bc984240cfe9069369a8a265b127116187d78c0ba93ae076": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -4296,94 +4449,6 @@
|
||||
},
|
||||
"query": "\n SELECT payload\n FROM capture\n WHERE workspace_id = $1\n AND path = $2\n "
|
||||
},
|
||||
"f4a1bb4dfeafa3f28385c19d10730f2f49b4ebe5bba631cee46c760cee2e6949": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO schedule (workspace_id, path, schedule, offset_, edited_by, script_path, is_flow, args, enabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *"
|
||||
},
|
||||
"f516ca558816c2cbab3c8ae865ef8f764aa4686e7df9c53d30d49a3dbbf36af4": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -4404,6 +4469,19 @@
|
||||
},
|
||||
"query": "SELECT SUM(duration_ms) as duration FROM completed_job WHERE id = ANY($1)"
|
||||
},
|
||||
"f67ba6ca3ed802ca41e49a310a4161ea8d81f79021f11f3a9f2b65e93ce6312d": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, true, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2"
|
||||
},
|
||||
"f7906298e4204ad55ec84021bb2461f369386493519637279f1188227230c580": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -4571,6 +4649,24 @@
|
||||
},
|
||||
"query": "SELECT * FROM resource WHERE workspace_id = $1"
|
||||
},
|
||||
"fa32fb9e4676dd91e20226b2a88672aed65ef33c39be7cdf63c733020a46e212": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "?column?",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "\n SELECT usage.usage + 1 FROM usage \n WHERE is_workspace = false AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)"
|
||||
},
|
||||
"fa567c205929f41799a64aefac97c4504c7993b22478530e9345b9bc117e92e9": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
||||
@@ -823,6 +823,7 @@ impl RunJob {
|
||||
payload,
|
||||
args,
|
||||
/* user */ "test-user",
|
||||
/* email */ "test@windmill.dev",
|
||||
/* permissioned_as */ "u/admin".to_string(),
|
||||
/* scheduled_for_o */ None,
|
||||
/* schedule_path */ None,
|
||||
|
||||
@@ -4041,6 +4041,8 @@ components:
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno, go, bash]
|
||||
email:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- running
|
||||
@@ -4048,6 +4050,7 @@ components:
|
||||
- job_kind
|
||||
- permissioned_as
|
||||
- is_flow_step
|
||||
- email
|
||||
|
||||
CompletedJob:
|
||||
type: object
|
||||
@@ -4633,6 +4636,10 @@ components:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
email:
|
||||
type: string
|
||||
error:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
- edited_by
|
||||
@@ -4643,6 +4650,7 @@ components:
|
||||
- extra_perms
|
||||
- is_flow
|
||||
- enabled
|
||||
- email
|
||||
|
||||
NewSchedule:
|
||||
type: object
|
||||
|
||||
@@ -94,6 +94,7 @@ pub enum ExecutionMode {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Policy {
|
||||
pub on_behalf_of: Option<String>,
|
||||
pub on_behalf_of_email: Option<String>,
|
||||
//paths:
|
||||
// - script/<path>
|
||||
// - flow/<path>
|
||||
@@ -457,49 +458,40 @@ async fn execute_component(
|
||||
static_fields,
|
||||
);
|
||||
}
|
||||
Policy { execution_mode: ExecutionMode::Viewer, triggerables: hm, on_behalf_of: None }
|
||||
Policy {
|
||||
execution_mode: ExecutionMode::Viewer,
|
||||
triggerables: hm,
|
||||
on_behalf_of: None,
|
||||
on_behalf_of_email: None,
|
||||
}
|
||||
} else {
|
||||
policy
|
||||
};
|
||||
|
||||
let (username, permissioned_as) = match policy.execution_mode {
|
||||
let (username, permissioned_as, email) = match policy.execution_mode {
|
||||
ExecutionMode::Anonymous => {
|
||||
let username = opt_authed
|
||||
.map(|a| a.username)
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
let permissioned_as = policy
|
||||
.on_behalf_of
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"on_behalf_of is missing in the app policy and is required for anonymous execution"
|
||||
.to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
(username, permissioned_as)
|
||||
let (permissioned_as, email) = get_on_behalf_of(&policy)?;
|
||||
(username, permissioned_as, email)
|
||||
}
|
||||
ExecutionMode::Publisher => {
|
||||
let username = opt_authed.map(|a| a.username).ok_or_else(|| {
|
||||
Error::BadRequest("publisher execution mode requires authentication".to_string())
|
||||
})?;
|
||||
let permissioned_as = policy
|
||||
.on_behalf_of
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"on_behalf_of is missing in the app policy and is required for publisher execution"
|
||||
.to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
(username, permissioned_as)
|
||||
let (permissioned_as, email) = get_on_behalf_of(&policy)?;
|
||||
(username, permissioned_as, email)
|
||||
}
|
||||
ExecutionMode::Viewer => {
|
||||
let username = opt_authed
|
||||
.map(|a| a.username)
|
||||
.ok_or_else(|| Error::BadRequest("".to_string()))?;
|
||||
(username.clone(), owner_to_token_owner(&username, false))
|
||||
let (username, email) = opt_authed.map(|a| (a.username, a.email)).ok_or_else(|| {
|
||||
Error::BadRequest("Required to be authed in viewer mode".to_string())
|
||||
})?;
|
||||
(
|
||||
username.clone(),
|
||||
owner_to_token_owner(&username, false),
|
||||
email,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -535,6 +527,7 @@ async fn execute_component(
|
||||
job_payload,
|
||||
args,
|
||||
&username,
|
||||
&email,
|
||||
permissioned_as,
|
||||
None,
|
||||
None,
|
||||
@@ -549,6 +542,30 @@ async fn execute_component(
|
||||
Ok(uuid.to_string())
|
||||
}
|
||||
|
||||
fn get_on_behalf_of(policy: &Policy) -> Result<(String, String)> {
|
||||
let permissioned_as = policy
|
||||
.on_behalf_of
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"on_behalf_of is missing in the app policy and is required for anonymous execution"
|
||||
.to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
let email = policy
|
||||
.on_behalf_of_email
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
Error::BadRequest(
|
||||
"on_behalf_of is missing in the app policy and is required for anonymous execution"
|
||||
.to_string(),
|
||||
)
|
||||
})?
|
||||
.to_string();
|
||||
Ok((permissioned_as, email))
|
||||
}
|
||||
|
||||
async fn exists_app(
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
|
||||
@@ -11,7 +11,7 @@ use reqwest::Client;
|
||||
use sql_builder::prelude::*;
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Host, Path, Query},
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
@@ -108,33 +108,27 @@ async fn list_flows(
|
||||
}
|
||||
|
||||
async fn list_hub_flows(
|
||||
Authed { email, username, .. }: Authed,
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Host(host): Host,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let flows = list_elems_from_hub(
|
||||
http_client,
|
||||
"https://hub.windmill.dev/searchFlowData?approved=true",
|
||||
email,
|
||||
username,
|
||||
host,
|
||||
&email,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(flows))
|
||||
}
|
||||
|
||||
pub async fn get_hub_flow_by_id(
|
||||
Authed { email, username, .. }: Authed,
|
||||
Authed { email, .. }: Authed,
|
||||
Path(id): Path<i32>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Host(host): Host,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let value = http_get_from_hub(
|
||||
http_client,
|
||||
&format!("https://hub.windmill.dev/flows/{id}/json"),
|
||||
email,
|
||||
username,
|
||||
host,
|
||||
&email,
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
@@ -194,6 +188,7 @@ async fn create_flow(
|
||||
JobPayload::FlowDependencies { path: nf.path.clone() },
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
windmill_common::users::owner_to_token_owner(&authed.username, false),
|
||||
None,
|
||||
None,
|
||||
@@ -299,6 +294,7 @@ async fn update_flow(
|
||||
JobPayload::FlowDependencies { path: nf.path.clone() },
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
windmill_common::users::owner_to_token_owner(&authed.username, false),
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -868,6 +868,7 @@ impl From<UnifiedJob> for Job {
|
||||
language: uj.language,
|
||||
same_worker: false,
|
||||
pre_run_error: None,
|
||||
email: "".to_string(),
|
||||
}),
|
||||
t => panic!("job type {} not valid", t),
|
||||
}
|
||||
@@ -957,6 +958,7 @@ pub async fn run_flow_by_path(
|
||||
JobPayload::Flow(flow_path.to_string()),
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
scheduled_for,
|
||||
None,
|
||||
@@ -990,6 +992,7 @@ pub async fn run_job_by_path(
|
||||
job_payload,
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
scheduled_for,
|
||||
None,
|
||||
@@ -1056,6 +1059,7 @@ pub async fn run_wait_result_job_by_path(
|
||||
job_payload,
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
scheduled_for,
|
||||
None,
|
||||
@@ -1090,6 +1094,7 @@ pub async fn run_wait_result_job_by_hash(
|
||||
JobPayload::ScriptHash { hash: ScriptHash(hash), path },
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
scheduled_for,
|
||||
None,
|
||||
@@ -1142,6 +1147,7 @@ async fn run_preview_job(
|
||||
}),
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
scheduled_for,
|
||||
None,
|
||||
@@ -1173,6 +1179,7 @@ async fn run_preview_flow_job(
|
||||
JobPayload::RawFlow { value: raw_flow.value, path: raw_flow.path },
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
scheduled_for,
|
||||
None,
|
||||
@@ -1206,6 +1213,7 @@ pub async fn run_job_by_hash(
|
||||
JobPayload::ScriptHash { hash: ScriptHash(hash), path },
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
scheduled_for,
|
||||
None,
|
||||
|
||||
@@ -644,12 +644,13 @@ async fn connect_slack_callback(
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace_settings
|
||||
(workspace_id, slack_team_id, slack_name)
|
||||
VALUES ($1, $2, $3) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, \
|
||||
slack_name = $3",
|
||||
(workspace_id, slack_team_id, slack_name, slack_email)
|
||||
VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, \
|
||||
slack_name = $3, slack_email = $4",
|
||||
&w_id,
|
||||
token.team_id,
|
||||
token.team_name
|
||||
token.team_name,
|
||||
authed.email
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
@@ -796,6 +797,7 @@ async fn slack_command(
|
||||
payload,
|
||||
map,
|
||||
&form.user_name,
|
||||
&settings.slack_email,
|
||||
"g/slack".to_string(),
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -47,7 +47,9 @@ async fn create_schedule(
|
||||
Json(ns): Json<NewSchedule>,
|
||||
) -> Result<String> {
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let res = windmill_queue::schedule::create_schedule(tx, w_id, ns, &authed.username).await?;
|
||||
let res =
|
||||
windmill_queue::schedule::create_schedule(tx, w_id, ns, &authed.username, &authed.email)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -110,8 +112,15 @@ pub async fn set_enabled(
|
||||
Json(payload): Json<SetEnabled>,
|
||||
) -> Result<String> {
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let res =
|
||||
windmill_queue::schedule::set_enabled(tx, w_id, path, payload, &authed.username).await?;
|
||||
let res = windmill_queue::schedule::set_enabled(
|
||||
tx,
|
||||
w_id,
|
||||
path,
|
||||
payload,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ use windmill_audit::{audit_log, ActionKind};
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::{truncate_token, Authed, Tokened},
|
||||
users::Authed,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Host, Path, Query},
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
@@ -160,16 +160,13 @@ async fn list_scripts(
|
||||
}
|
||||
|
||||
async fn list_hub_scripts(
|
||||
Authed { email, username, .. }: Authed,
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Host(host): Host,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let asks = list_elems_from_hub(
|
||||
http_client,
|
||||
"https://hub.windmill.dev/searchData?approved=true",
|
||||
email,
|
||||
username,
|
||||
host,
|
||||
&email,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(asks))
|
||||
@@ -182,7 +179,6 @@ fn hash_script(ns: &NewScript) -> i64 {
|
||||
}
|
||||
async fn create_script(
|
||||
authed: Authed,
|
||||
Tokened { token }: Tokened,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(ns): Json<NewScript>,
|
||||
@@ -293,7 +289,7 @@ async fn create_script(
|
||||
.map(|v| v.1.clone())
|
||||
.unwrap_or(json!({}));
|
||||
|
||||
let lock = if ns.language == ScriptLang::Bash {
|
||||
let lock = if ns.language == ScriptLang::Bash || ns.language == ScriptLang::Deno {
|
||||
Some(String::new())
|
||||
} else {
|
||||
ns.lock
|
||||
@@ -339,6 +335,7 @@ async fn create_script(
|
||||
windmill_queue::JobPayload::Dependencies { hash, dependencies, language: ns.language },
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
None,
|
||||
None,
|
||||
@@ -361,13 +358,7 @@ async fn create_script(
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&ns.path),
|
||||
Some(
|
||||
[
|
||||
("hash", hash.to_string().as_str()),
|
||||
("token", &truncate_token(&token)),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
Some([("hash", hash.to_string().as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
@@ -382,7 +373,6 @@ async fn create_script(
|
||||
[
|
||||
("workspace", w_id.as_str()),
|
||||
("hash", hash.to_string().as_str()),
|
||||
("token", &truncate_token(&token)),
|
||||
]
|
||||
.into(),
|
||||
),
|
||||
@@ -399,33 +389,17 @@ pub async fn get_hub_script_by_path(
|
||||
authed: Authed,
|
||||
Path(path): Path<StripPath>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Host(host): Host,
|
||||
) -> Result<String> {
|
||||
windmill_common::scripts::get_hub_script_by_path(
|
||||
authed.email,
|
||||
authed.username,
|
||||
path,
|
||||
http_client,
|
||||
host,
|
||||
)
|
||||
.await
|
||||
windmill_common::scripts::get_hub_script_by_path(&authed.email, path, http_client).await
|
||||
}
|
||||
|
||||
pub async fn get_full_hub_script_by_path(
|
||||
Authed { username, email, .. }: Authed,
|
||||
Authed { email, .. }: Authed,
|
||||
Path(path): Path<StripPath>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Host(host): Host,
|
||||
) -> JsonResult<HubScript> {
|
||||
Ok(Json(
|
||||
windmill_common::scripts::get_full_hub_script_by_path(
|
||||
email,
|
||||
username,
|
||||
path,
|
||||
http_client,
|
||||
host,
|
||||
)
|
||||
.await?,
|
||||
windmill_common::scripts::get_full_hub_script_by_path(&email, path, http_client).await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,12 @@ use windmill_common::error::{self, Error};
|
||||
|
||||
pub async fn require_super_admin<'c>(
|
||||
db: &mut Transaction<'c, Postgres>,
|
||||
email: Option<String>,
|
||||
email: &str,
|
||||
) -> error::Result<()> {
|
||||
let is_admin = sqlx::query_scalar!(
|
||||
"SELECT super_admin FROM password WHERE email = $1",
|
||||
email.as_ref()
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("fetching super admin: {e}")))?;
|
||||
let is_admin = sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("fetching super admin: {e}")))?;
|
||||
if !is_admin {
|
||||
Err(Error::NotAuthorized(
|
||||
"This endpoint require caller to be a super admin".to_owned(),
|
||||
|
||||
@@ -60,7 +60,7 @@ async fn list_contextual_variables(
|
||||
get_reserved_variables(
|
||||
&w_id,
|
||||
"q1A0qcPuO00yxioll7iph76N9CJDqn",
|
||||
&email.unwrap_or_else(|| "no email".to_string()),
|
||||
&email,
|
||||
&username,
|
||||
"017e0ad5-f499-73b6-5488-92a61c5196dd",
|
||||
format!("u/{username}").as_str(),
|
||||
|
||||
@@ -46,6 +46,7 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/get_settings", get(get_settings))
|
||||
.route("/edit_slack_command", post(edit_slack_command))
|
||||
.route("/tarball", get(tarball_workspace))
|
||||
.route("/usage", get(get_usage))
|
||||
}
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
@@ -74,6 +75,16 @@ pub struct WorkspaceSettings {
|
||||
pub slack_team_id: Option<String>,
|
||||
pub slack_name: Option<String>,
|
||||
pub slack_command_script: Option<String>,
|
||||
pub slack_email: String,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
pub struct Usage {
|
||||
pub workspace_id: String,
|
||||
pub slack_team_id: Option<String>,
|
||||
pub slack_name: Option<String>,
|
||||
pub slack_command_script: Option<String>,
|
||||
pub slack_email: String,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Debug)]
|
||||
@@ -175,7 +186,7 @@ async fn list_workspaces(
|
||||
Workspace,
|
||||
"SELECT workspace.* FROM workspace, usr WHERE usr.workspace_id = workspace.id AND \
|
||||
usr.email = $1 AND deleted = false",
|
||||
authed.email.as_ref()
|
||||
authed.email
|
||||
)
|
||||
.fetch_all(&mut tx)
|
||||
.await?;
|
||||
@@ -202,6 +213,25 @@ async fn get_settings(
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
async fn get_usage(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
) -> JsonResult<WorkspaceSettings> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let settings = sqlx::query_as!(
|
||||
WorkspaceSettings,
|
||||
"SELECT * FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("getting settings: {e}")))?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
async fn edit_slack_command(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -225,7 +255,7 @@ async fn edit_slack_command(
|
||||
"workspaces.edit_command_script",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email.unwrap()),
|
||||
Some(&authed.email),
|
||||
Some(
|
||||
[(
|
||||
"script",
|
||||
@@ -249,7 +279,7 @@ async fn list_workspaces_as_super_admin(
|
||||
Authed { email, .. }: Authed,
|
||||
) -> JsonResult<Vec<Workspace>> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
require_super_admin(&mut tx, email).await?;
|
||||
require_super_admin(&mut tx, &email).await?;
|
||||
let (per_page, offset) = paginate(pagination);
|
||||
|
||||
let workspaces = sqlx::query_as!(
|
||||
@@ -268,9 +298,6 @@ async fn user_workspaces(
|
||||
Extension(db): Extension<DB>,
|
||||
Authed { email, .. }: Authed,
|
||||
) -> JsonResult<WorkspaceList> {
|
||||
let email = email
|
||||
.ok_or("not a personal token")
|
||||
.map_err(|x| Error::NotAuthorized(x.to_string()))?;
|
||||
let mut tx = db.begin().await?;
|
||||
let workspaces = sqlx::query_as!(
|
||||
UserWorkspace,
|
||||
@@ -406,7 +433,7 @@ async fn edit_workspace(
|
||||
"workspaces.update",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email.unwrap()),
|
||||
Some(&authed.email),
|
||||
Some(
|
||||
[(
|
||||
"domain",
|
||||
@@ -447,7 +474,7 @@ async fn delete_workspace(
|
||||
"workspaces.delete",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&email.unwrap_or("noemail".to_string())),
|
||||
Some(&email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -208,11 +208,9 @@ pub fn to_hex_string(i: &i64) -> String {
|
||||
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub async fn get_hub_script_by_path(
|
||||
email: Option<String>,
|
||||
username: String,
|
||||
email: &str,
|
||||
path: StripPath,
|
||||
http_client: reqwest::Client,
|
||||
host: String,
|
||||
) -> crate::error::Result<String> {
|
||||
use crate::{
|
||||
error::{to_anyhow, Error},
|
||||
@@ -228,8 +226,6 @@ pub async fn get_hub_script_by_path(
|
||||
http_client,
|
||||
&format!("https://hub.windmill.dev/raw/{path}.ts"),
|
||||
email,
|
||||
username,
|
||||
host,
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
@@ -241,11 +237,9 @@ pub async fn get_hub_script_by_path(
|
||||
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub async fn get_full_hub_script_by_path(
|
||||
email: Option<String>,
|
||||
username: String,
|
||||
email: &str,
|
||||
path: StripPath,
|
||||
http_client: reqwest::Client,
|
||||
host: String,
|
||||
) -> crate::error::Result<HubScript> {
|
||||
use crate::{
|
||||
error::{to_anyhow, Error},
|
||||
@@ -261,8 +255,6 @@ pub async fn get_full_hub_script_by_path(
|
||||
http_client,
|
||||
&format!("https://hub.windmill.dev/raw2/{path}"),
|
||||
email,
|
||||
username,
|
||||
host,
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
|
||||
@@ -80,11 +80,9 @@ pub fn get_owner_from_path(path: &str) -> String {
|
||||
pub async fn list_elems_from_hub(
|
||||
http_client: reqwest::Client,
|
||||
url: &str,
|
||||
email: Option<String>,
|
||||
username: String,
|
||||
host: String,
|
||||
email: &str,
|
||||
) -> Result<serde_json::Value> {
|
||||
let rows = http_get_from_hub(http_client, url, email, username, host, false)
|
||||
let rows = http_get_from_hub(http_client, url, email, false)
|
||||
.await?
|
||||
.json::<serde_json::Value>()
|
||||
.await
|
||||
@@ -96,9 +94,7 @@ pub async fn list_elems_from_hub(
|
||||
pub async fn http_get_from_hub(
|
||||
http_client: reqwest::Client,
|
||||
url: &str,
|
||||
email: Option<String>,
|
||||
username: String,
|
||||
host: String,
|
||||
email: &str,
|
||||
plain: bool,
|
||||
) -> Result<reqwest::Response> {
|
||||
let response = http_client
|
||||
@@ -111,9 +107,7 @@ pub async fn http_get_from_hub(
|
||||
"application/json"
|
||||
},
|
||||
)
|
||||
.header("X-email", email.unwrap_or_else(|| "".to_string()))
|
||||
.header("X-username", username)
|
||||
.header("X-hostname", host)
|
||||
.header("X-email", email)
|
||||
.send()
|
||||
.await
|
||||
.map_err(crate::error::to_anyhow)?;
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Pool, Postgres, Transaction};
|
||||
use tracing::instrument;
|
||||
use tracing::{instrument, Instrument};
|
||||
use ulid::Ulid;
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
@@ -39,10 +39,11 @@ lazy_static::lazy_static! {
|
||||
"Total number of jobs pulled from the queue."
|
||||
)
|
||||
.unwrap();
|
||||
pub static ref CLOUD_HOSTED: bool = std::env::var("CLOUD_HOSTED").is_ok();
|
||||
|
||||
}
|
||||
|
||||
const MAX_NB_OF_JOBS_IN_Q_PER_USER: i64 = 10;
|
||||
const MAX_DURATION_LAST_1200: std::time::Duration = std::time::Duration::from_secs(900);
|
||||
const MAX_FREE_EXECS: i32 = 1000;
|
||||
|
||||
pub async fn cancel_job<'c>(
|
||||
username: &str,
|
||||
@@ -245,6 +246,7 @@ pub async fn push<'c>(
|
||||
job_payload: JobPayload,
|
||||
args: serde_json::Map<String, serde_json::Value>,
|
||||
user: &str,
|
||||
email: &str,
|
||||
permissioned_as: String,
|
||||
scheduled_for_o: Option<chrono::DateTime<chrono::Utc>>,
|
||||
schedule_path: Option<String>,
|
||||
@@ -257,54 +259,60 @@ pub async fn push<'c>(
|
||||
let args_json = serde_json::Value::Object(args);
|
||||
let job_id: Uuid = Ulid::new().into();
|
||||
|
||||
let premium_workspace =
|
||||
sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", workspace_id)
|
||||
// we track only non flow steps
|
||||
let usage = if !matches!(
|
||||
job_payload,
|
||||
JobPayload::Flow(_) | JobPayload::RawFlow { .. }
|
||||
) {
|
||||
sqlx::query_scalar!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
VALUES ($1, false, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1
|
||||
RETURNING usage.usage",
|
||||
email)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?
|
||||
} else {
|
||||
sqlx::query_scalar!("
|
||||
SELECT usage.usage + 1 FROM usage
|
||||
WHERE is_workspace = false AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)")
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
if *CLOUD_HOSTED {
|
||||
let premium_workspace =
|
||||
sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", workspace_id)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!("fetching if {workspace_id} is premium: {e}"))
|
||||
})?;
|
||||
|
||||
if !premium_workspace {
|
||||
let is_super_admin =
|
||||
sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !is_super_admin && usage > MAX_FREE_EXECS {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
VALUES ($1, true, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1
|
||||
RETURNING usage.usage",
|
||||
workspace_id)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!("fetching if {workspace_id} is premium: {e}"))
|
||||
})?;
|
||||
|
||||
if !premium_workspace && std::env::var("CLOUD_HOSTED").is_ok() {
|
||||
let rate_limiting_queue = sqlx::query_scalar!(
|
||||
"SELECT COUNT(id) FROM queue WHERE permissioned_as = $1 AND workspace_id = $2",
|
||||
permissioned_as,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some(nb_jobs) = rate_limiting_queue {
|
||||
if nb_jobs > MAX_NB_OF_JOBS_IN_Q_PER_USER {
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"You have exceeded the number of authorized elements of queue at any given \
|
||||
time: {}",
|
||||
MAX_NB_OF_JOBS_IN_Q_PER_USER
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let rate_limiting_duration_ms = sqlx::query_scalar!(
|
||||
"
|
||||
SELECT SUM(duration_ms)
|
||||
FROM completed_job
|
||||
WHERE permissioned_as = $1
|
||||
AND created_at > NOW() - INTERVAL '1200 seconds'
|
||||
AND workspace_id = $2",
|
||||
permissioned_as,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some(sum_duration_ms) = rate_limiting_duration_ms {
|
||||
if sum_duration_ms as u128 > MAX_DURATION_LAST_1200.as_millis() {
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"You have exceeded the scripts cumulative duration limit over the last 20m \
|
||||
which is: {} seconds",
|
||||
MAX_DURATION_LAST_1200.as_secs()
|
||||
)));
|
||||
}
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,14 +342,7 @@ pub async fn push<'c>(
|
||||
)
|
||||
}
|
||||
JobPayload::ScriptHub { path } => {
|
||||
let email = sqlx::query_scalar!(
|
||||
"SELECT email FROM usr WHERE username = $1 AND workspace_id = $2",
|
||||
user,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
let script = get_hub_script(path.clone(), email, user).await?;
|
||||
let script = get_hub_script(path.clone(), email).await?;
|
||||
(
|
||||
None,
|
||||
Some(path),
|
||||
@@ -466,8 +467,8 @@ pub async fn push<'c>(
|
||||
"INSERT INTO queue
|
||||
(workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for,
|
||||
script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, \
|
||||
flow_status, is_flow_step, language, started_at, same_worker, pre_run_error)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20) \
|
||||
flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21) \
|
||||
RETURNING id",
|
||||
workspace_id,
|
||||
job_id,
|
||||
@@ -488,7 +489,8 @@ pub async fn push<'c>(
|
||||
is_flow_step,
|
||||
language: ScriptLang,
|
||||
same_worker,
|
||||
pre_run_error.map(|e| e.to_string())
|
||||
pre_run_error.map(|e| e.to_string()),
|
||||
email
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
@@ -526,6 +528,7 @@ pub async fn push<'c>(
|
||||
script_path.as_ref().map(|x| x.as_str()),
|
||||
Some(hm),
|
||||
)
|
||||
.instrument(tracing::info_span!("job_run", email = &email))
|
||||
.await?;
|
||||
}
|
||||
Ok((uuid, tx))
|
||||
@@ -540,20 +543,14 @@ pub fn canceled_job_to_result(job: &QueuedJob) -> String {
|
||||
format!("Job canceled: {reason} by {canceler}")
|
||||
}
|
||||
|
||||
pub async fn get_hub_script(
|
||||
path: String,
|
||||
email: Option<String>,
|
||||
user: &str,
|
||||
) -> error::Result<HubScript> {
|
||||
pub async fn get_hub_script(path: String, email: &str) -> error::Result<HubScript> {
|
||||
get_full_hub_script_by_path(
|
||||
email,
|
||||
user.to_string(),
|
||||
StripPath(path),
|
||||
reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?,
|
||||
std::env::var("BASE_URL").unwrap_or_else(|_| "".to_string()),
|
||||
)
|
||||
.await
|
||||
.map(|e| e)
|
||||
@@ -588,6 +585,7 @@ pub struct QueuedJob {
|
||||
pub language: Option<ScriptLang>,
|
||||
pub same_worker: bool,
|
||||
pub pre_run_error: Option<String>,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
impl QueuedJob {
|
||||
|
||||
@@ -32,6 +32,8 @@ pub struct Schedule {
|
||||
pub is_flow: bool,
|
||||
pub args: Option<serde_json::Value>,
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub email: String,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -100,21 +102,29 @@ pub async fn push_scheduled_job<'c>(
|
||||
}
|
||||
};
|
||||
|
||||
let (_, tx) = push(
|
||||
let (_, mut tx) = push(
|
||||
tx,
|
||||
&schedule.workspace_id,
|
||||
payload,
|
||||
args,
|
||||
&schedule_to_user(&schedule.path),
|
||||
&schedule.email,
|
||||
get_owner_from_path(&schedule.path),
|
||||
Some(next),
|
||||
Some(schedule.path),
|
||||
Some(schedule.path.clone()),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"UPDATE schedule SET error = NULL WHERE workspace_id = $1 AND path = $2",
|
||||
&schedule.workspace_id,
|
||||
&schedule.path
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
@@ -123,6 +133,7 @@ pub async fn create_schedule(
|
||||
w_id: String,
|
||||
ns: NewSchedule,
|
||||
username: &str,
|
||||
email: &str,
|
||||
) -> Result<String> {
|
||||
cron::Schedule::from_str(&ns.schedule).map_err(|e| error::Error::BadRequest(e.to_string()))?;
|
||||
check_flow_conflict(&mut tx, &w_id, &ns.path, ns.is_flow, &ns.script_path).await?;
|
||||
@@ -130,7 +141,7 @@ pub async fn create_schedule(
|
||||
let schedule = sqlx::query_as!(
|
||||
Schedule,
|
||||
"INSERT INTO schedule (workspace_id, path, schedule, offset_, edited_by, script_path, \
|
||||
is_flow, args, enabled) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *",
|
||||
is_flow, args, enabled, email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *",
|
||||
w_id,
|
||||
ns.path,
|
||||
ns.schedule,
|
||||
@@ -140,6 +151,7 @@ pub async fn create_schedule(
|
||||
ns.is_flow,
|
||||
ns.args,
|
||||
ns.enabled.unwrap_or(false),
|
||||
email
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
@@ -360,12 +372,14 @@ pub async fn set_enabled(
|
||||
path: StripPath,
|
||||
SetEnabled { enabled }: SetEnabled,
|
||||
username: &str,
|
||||
email: &str,
|
||||
) -> Result<String> {
|
||||
let path = path.to_path();
|
||||
let schedule_o = sqlx::query_as!(
|
||||
Schedule,
|
||||
"UPDATE schedule SET enabled = $1 WHERE path = $2 AND workspace_id = $3 RETURNING *",
|
||||
"UPDATE schedule SET enabled = $1, email = $2 WHERE path = $3 AND workspace_id = $4 RETURNING *",
|
||||
enabled,
|
||||
email,
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
*/
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
use sqlx::{Pool, Postgres, Transaction};
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tracing::instrument;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::{error::Error, flow_status::FlowStatusModule};
|
||||
use windmill_queue::{delete_job, schedule::get_schedule_opt, JobKind, QueuedJob};
|
||||
use windmill_queue::{delete_job, schedule::get_schedule_opt, JobKind, QueuedJob, CLOUD_HOSTED};
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub async fn add_completed_job_error<E: ToString + std::fmt::Debug>(
|
||||
@@ -46,6 +46,24 @@ pub fn error_to_result<E: ToString + std::fmt::Debug>(
|
||||
);
|
||||
}
|
||||
|
||||
fn flatten_jobs(modules: Vec<FlowStatusModule>) -> Vec<Uuid> {
|
||||
modules
|
||||
.into_iter()
|
||||
.filter_map(|m| match m {
|
||||
FlowStatusModule::Success { job, flow_jobs, .. }
|
||||
| FlowStatusModule::Failure { job, flow_jobs, .. } => {
|
||||
if let Some(flow_jobs) = flow_jobs {
|
||||
Some(flow_jobs)
|
||||
} else {
|
||||
Some(vec![job])
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub async fn add_completed_job(
|
||||
db: &Pool<Postgres>,
|
||||
@@ -60,14 +78,7 @@ pub async fn add_completed_job(
|
||||
let jobs = queued_job.parse_flow_status().map(|s| {
|
||||
let mut modules = s.modules;
|
||||
modules.extend([s.failure_module.module_status]);
|
||||
modules
|
||||
.into_iter()
|
||||
.filter_map(|m| match m {
|
||||
FlowStatusModule::Success { job, .. }
|
||||
| FlowStatusModule::Failure { job, .. } => Some(job),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
flatten_jobs(modules)
|
||||
});
|
||||
if let Some(jobs) = jobs {
|
||||
sqlx::query_scalar!(
|
||||
@@ -85,6 +96,7 @@ pub async fn add_completed_job(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
let job_id = queued_job.id.clone();
|
||||
sqlx::query!(
|
||||
@@ -149,32 +161,69 @@ pub async fn add_completed_job(
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("Could not add completed job {job_id}: {e}")))?;
|
||||
let _ = delete_job(db, &queued_job.workspace_id, job_id).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
if duration.unwrap_or(0) > 1000 {
|
||||
let additional_usage = (duration.unwrap() as i32 / 1000) - 1;
|
||||
sqlx::query!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
VALUES ($1, false, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
queued_job.email,
|
||||
additional_usage)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?;
|
||||
|
||||
if *CLOUD_HOSTED {
|
||||
let w_id = &queued_job.workspace_id;
|
||||
let premium_workspace =
|
||||
sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", w_id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!("fetching if {w_id} is premium: {e}"))
|
||||
})?;
|
||||
if premium_workspace {
|
||||
let _ = sqlx::query!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
VALUES ($1, true, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
w_id,
|
||||
additional_usage)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !queued_job.is_flow_step
|
||||
&& queued_job.job_kind != JobKind::Flow
|
||||
&& queued_job.job_kind != JobKind::FlowPreview
|
||||
&& queued_job.schedule_path.is_some()
|
||||
&& queued_job.script_path.is_some()
|
||||
{
|
||||
tx = schedule_again_if_scheduled(
|
||||
tx,
|
||||
schedule_again_if_scheduled(
|
||||
db,
|
||||
queued_job.schedule_path.as_ref().unwrap(),
|
||||
queued_job.script_path.as_ref().unwrap(),
|
||||
&queued_job.workspace_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
tracing::debug!("Added completed job {}", queued_job.id);
|
||||
Ok(queued_job.id)
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub async fn schedule_again_if_scheduled<'c>(
|
||||
mut tx: Transaction<'c, Postgres>,
|
||||
pub async fn schedule_again_if_scheduled(
|
||||
db: &Pool<Postgres>,
|
||||
schedule_path: &str,
|
||||
script_path: &str,
|
||||
w_id: &str,
|
||||
) -> windmill_common::error::Result<Transaction<'c, Postgres>> {
|
||||
) -> windmill_common::error::Result<()> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let schedule = get_schedule_opt(&mut tx, w_id, schedule_path)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
@@ -184,11 +233,11 @@ pub async fn schedule_again_if_scheduled<'c>(
|
||||
))
|
||||
})?;
|
||||
if schedule.enabled && script_path == schedule.script_path {
|
||||
tx = windmill_queue::schedule::push_scheduled_job(
|
||||
let res = windmill_queue::schedule::push_scheduled_job(
|
||||
tx,
|
||||
windmill_queue::schedule::Schedule {
|
||||
workspace_id: w_id.to_owned(),
|
||||
path: schedule.path,
|
||||
path: schedule.path.clone(),
|
||||
edited_by: schedule.edited_by,
|
||||
edited_at: schedule.edited_at,
|
||||
schedule: schedule.schedule,
|
||||
@@ -200,10 +249,25 @@ pub async fn schedule_again_if_scheduled<'c>(
|
||||
.args
|
||||
.and_then(|e| serde_json::to_value(e).map_or(None, |v| Some(v))),
|
||||
extra_perms: serde_json::to_value(schedule.extra_perms).expect("hashmap -> json"),
|
||||
email: schedule.email,
|
||||
error: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
match res {
|
||||
Ok(tx) => tx.commit().await?,
|
||||
Err(e) => {
|
||||
sqlx::query!(
|
||||
"UPDATE schedule SET enabled = false, error = $1 WHERE workspace_id = $2 AND path = $3",
|
||||
e.to_string(),
|
||||
&schedule.workspace_id,
|
||||
&schedule.path
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
tracing::warn!("Could not schedule job for {}: {}", schedule_path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tx)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -242,10 +242,10 @@ pub async fn create_token_for_owner<'c>(
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
let expiration = sqlx::query_scalar!(
|
||||
sqlx::query_scalar!(
|
||||
"INSERT INTO token
|
||||
(workspace_id, token, owner, label, expiration, super_admin)
|
||||
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6) RETURNING expiration",
|
||||
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)",
|
||||
&w_id,
|
||||
token,
|
||||
owner,
|
||||
@@ -253,32 +253,7 @@ pub async fn create_token_for_owner<'c>(
|
||||
expires_in.to_string(),
|
||||
is_super_admin
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
let mut truncated_token = token[..10].to_owned();
|
||||
truncated_token.push_str("*****");
|
||||
|
||||
windmill_audit::audit_log(
|
||||
&mut tx,
|
||||
&username,
|
||||
"users.token.create",
|
||||
windmill_audit::ActionKind::Create,
|
||||
w_id,
|
||||
Some(&truncated_token),
|
||||
Some(
|
||||
[
|
||||
Some(("label", label)),
|
||||
expiration
|
||||
.map(|x| x.to_string())
|
||||
.as_ref()
|
||||
.map(|exp| ("expiration", &exp[..])),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect(),
|
||||
),
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
Ok((tx, token))
|
||||
}
|
||||
@@ -646,6 +621,17 @@ async fn handle_job_error(
|
||||
keep_job_dir: bool,
|
||||
base_internal_url: &str,
|
||||
) {
|
||||
add_completed_job_error(
|
||||
db,
|
||||
&job,
|
||||
format!("Unexpected error during job execution:\n{err}"),
|
||||
&err,
|
||||
metrics.clone(),
|
||||
)
|
||||
.await
|
||||
.map(|(_, m)| m)
|
||||
.unwrap_or_else(|_| Map::new());
|
||||
|
||||
if job.is_flow_step || job.job_kind == JobKind::FlowPreview || job.job_kind == JobKind::Flow {
|
||||
let (flow, job_status_to_update) = if let Some(parent_job_id) = job.parent_job {
|
||||
(parent_job_id, job.id)
|
||||
@@ -693,17 +679,6 @@ async fn handle_job_error(
|
||||
}
|
||||
}
|
||||
}
|
||||
add_completed_job_error(
|
||||
db,
|
||||
&job,
|
||||
format!("Unexpected error during job execution:\n{err}"),
|
||||
&err,
|
||||
metrics,
|
||||
)
|
||||
.await
|
||||
.map(|(_, m)| m)
|
||||
.unwrap_or_else(|_| Map::new());
|
||||
|
||||
tracing::error!(job_id = %job.id, err = err.alt(), "error handling job: {} {} {}", job.id, job.workspace_id, job.created_by);
|
||||
}
|
||||
|
||||
@@ -787,7 +762,7 @@ async fn handle_queued_job(
|
||||
.await?;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
tracing::debug!(
|
||||
worker = %worker_name,
|
||||
job_id = %job.id,
|
||||
workspace_id = %job.workspace_id,
|
||||
@@ -1005,7 +980,7 @@ async fn handle_code_execution_job(
|
||||
.map(|x| format!("{x:?}"))
|
||||
.unwrap_or_else(|| "NO_LANG".to_string());
|
||||
|
||||
tracing::info!(
|
||||
tracing::debug!(
|
||||
worker_name = %worker_name,
|
||||
job_id = %job.id,
|
||||
workspace_id = %job.workspace_id,
|
||||
@@ -2103,17 +2078,6 @@ async fn gen_go_mymod(code: &str, job_dir: &str) -> error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: this really shouldn't be here
|
||||
pub async fn get_email_from_username(
|
||||
username: &String,
|
||||
db: &Pool<Postgres>,
|
||||
) -> error::Result<Option<String>> {
|
||||
let email = sqlx::query_scalar!("SELECT email FROM usr WHERE username = $1", username)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
Ok(email)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn get_reserved_variables(
|
||||
job: &QueuedJob,
|
||||
@@ -2133,9 +2097,7 @@ async fn get_reserved_variables(
|
||||
let variables = variables::get_reserved_variables(
|
||||
&job.workspace_id,
|
||||
token,
|
||||
&get_email_from_username(&job.created_by, db)
|
||||
.await?
|
||||
.unwrap_or_else(|| "nosuitable@email.xyz".to_string()),
|
||||
&job.email,
|
||||
&job.created_by,
|
||||
&job.id.to_string(),
|
||||
&job.permissioned_as,
|
||||
|
||||
@@ -407,13 +407,15 @@ pub async fn update_flow_status_after_job_completion(
|
||||
false => false,
|
||||
};
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
if old_status.step == 0
|
||||
&& !flow_job.is_flow_step
|
||||
&& flow_job.schedule_path.is_some()
|
||||
&& flow_job.script_path.is_some()
|
||||
{
|
||||
tx = schedule_again_if_scheduled(
|
||||
tx,
|
||||
schedule_again_if_scheduled(
|
||||
db,
|
||||
flow_job.schedule_path.as_ref().unwrap(),
|
||||
flow_job.script_path.as_ref().unwrap(),
|
||||
&w_id,
|
||||
@@ -421,8 +423,6 @@ pub async fn update_flow_status_after_job_completion(
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
let done = if !should_continue_flow {
|
||||
let logs = if flow_job.canceled {
|
||||
"Flow job canceled".to_string()
|
||||
@@ -1233,6 +1233,7 @@ async fn push_next_flow_job(
|
||||
payload,
|
||||
ok.unwrap_or_else(|| Map::new()),
|
||||
&flow_job.created_by,
|
||||
&flow_job.email,
|
||||
flow_job.permissioned_as.to_owned(),
|
||||
scheduled_for_o,
|
||||
flow_job.schedule_path.clone(),
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<div class="flex flex-row justify-between">The next runs will be scheduled at:</div>
|
||||
<ul class="list-disc mx-12">
|
||||
{#each preview.slice(0, limit) as p}
|
||||
<li class="mx-2 text-gray-700 text-sm">{displayDate(p)}</li>
|
||||
<li class="mx-2 text-gray-700 text-sm">{displayDate(p, true)}</li>
|
||||
{/each}
|
||||
<li class="text-sm mx-2">...</li>
|
||||
{#if limit != 10}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
dispatch('click', { item: item?.eventName })
|
||||
}
|
||||
}}
|
||||
class="block whitespace-nowrap hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30 px-4 py-2 text-sm text-gray-700 text-left{item.separatorTop
|
||||
class="block w-full whitespace-nowrap hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30 px-4 py-2 text-sm text-gray-700 text-left {item.separatorTop
|
||||
? 'border-t'
|
||||
: ''} {item.separatorBottom ? 'border-b' : ''} {item.type == 'delete'
|
||||
? 'text-red-500'
|
||||
@@ -64,7 +64,7 @@
|
||||
close()
|
||||
}
|
||||
}}
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30"
|
||||
class="block w-full px-4 py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-{name}-{i}}"
|
||||
@@ -81,7 +81,7 @@
|
||||
</a>
|
||||
{:else}
|
||||
<span
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-{name}-{i}}"
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
fill: #ffffff;
|
||||
}
|
||||
.st2 {
|
||||
fill: #999;
|
||||
fill: #bcd4fc;
|
||||
}
|
||||
.st3 {
|
||||
fill: black;
|
||||
fill: #3b82f6;
|
||||
}
|
||||
.st4 {
|
||||
fill: #999;
|
||||
fill: #b3b3b3;
|
||||
}
|
||||
.st5 {
|
||||
fill: url(#SVGID_1_);
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface UserExt {
|
||||
email: string;
|
||||
username: string;
|
||||
is_admin: boolean;
|
||||
is_super_admin: boolean;
|
||||
created_at: string;
|
||||
groups: string[];
|
||||
pgroups: string[];
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FlowService, Script, ScriptService, type Flow, type FlowModule, type Us
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import { get } from 'svelte/store'
|
||||
import type { Schema, SupportedLanguage } from './common'
|
||||
import { hubScripts, superadmin, workspaceStore, type UserExt } from './stores'
|
||||
import { hubScripts, workspaceStore, type UserExt } from './stores'
|
||||
|
||||
export function validateUsername(username: string): string {
|
||||
if (username != '' && !/^\w+$/.test(username)) {
|
||||
@@ -179,7 +179,7 @@ export function canWrite(
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
if (user.is_admin || get(superadmin)) {
|
||||
if (user.is_admin || user.is_super_admin) {
|
||||
return true
|
||||
}
|
||||
let userOwner = `u/${user.username}`
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script context="module">
|
||||
export function load({ params }) {
|
||||
return {
|
||||
stuff: { title: `Script ${params.hash}` }
|
||||
stuff: { title: `App ${params.path}` }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import AppEditor from '$lib/components/apps/editor/AppEditor.svelte'
|
||||
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
|
||||
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import {
|
||||
faCircle,
|
||||
faEdit,
|
||||
faPlus,
|
||||
faShare,
|
||||
@@ -28,6 +29,8 @@
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
|
||||
type ScheduleW = Schedule & { canWrite: boolean }
|
||||
|
||||
@@ -51,7 +54,7 @@
|
||||
})
|
||||
loadSchedules()
|
||||
} catch (err) {
|
||||
sendUserToast(`Cannot ` + (enabled ? 'disable' : 'enable') + ` schedule: ${err}`, true)
|
||||
sendUserToast(`Cannot ` + (enabled ? 'enable' : 'disable') + ` schedule: ${err.body}`, true)
|
||||
loadSchedules()
|
||||
}
|
||||
}
|
||||
@@ -79,13 +82,14 @@
|
||||
<th>Schedule</th>
|
||||
<th>Script or Flow</th>
|
||||
<th>schedule</th>
|
||||
<th />
|
||||
<th>off/on</th>
|
||||
<th>timezone</th>
|
||||
<th>last edit</th>
|
||||
<th />
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each schedules as { path, edited_by, edited_at, schedule, offset_, enabled, script_path, is_flow, extra_perms, canWrite }}
|
||||
{#each schedules as { path, error, edited_by, edited_at, schedule, offset_, enabled, script_path, is_flow, extra_perms, canWrite }}
|
||||
<tr class={enabled ? '' : 'bg-gray-50'}>
|
||||
<td class="max-w-sm"
|
||||
><a class="break-words text-sm" href="/schedule/add?edit={path}&isFlow={is_flow}"
|
||||
@@ -101,6 +105,32 @@
|
||||
></div
|
||||
>
|
||||
</td><td><Badge color="blue">{schedule}</Badge></td>
|
||||
<td>
|
||||
<div class="w-10">
|
||||
{#if error}
|
||||
<Popover notClickable>
|
||||
<span class="flex h-4 w-4">
|
||||
<Icon
|
||||
class="text-red-600 animate-ping absolute inline-flex "
|
||||
data={faCircle}
|
||||
scale={0.7}
|
||||
label="Error during last job scheduling"
|
||||
/>
|
||||
<Icon
|
||||
class="text-red-600 relative inline-flex"
|
||||
data={faCircle}
|
||||
scale={0.7}
|
||||
label="Error during last job scheduling"
|
||||
/>
|
||||
</span>
|
||||
<div slot="text">
|
||||
The schedule disabled itself because there was an error scheduling the next
|
||||
job: {error}
|
||||
</div>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div></td
|
||||
>
|
||||
<td>
|
||||
<Toggle
|
||||
checked={enabled}
|
||||
@@ -117,8 +147,8 @@
|
||||
<td
|
||||
><span class="text-2xs">By {edited_by} <br />the {displayDate(edited_at)}</span></td
|
||||
>
|
||||
<td
|
||||
><Dropdown
|
||||
<td>
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
dropdownItems={[
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
sendUserToast(`Job <a href='/run/${run}?workspace=${$workspaceStore}'>${run}</a> started`)
|
||||
goto('/run/' + run + '?workspace=' + $workspaceStore)
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not create job: ${err}`, true)
|
||||
sendUserToast(`Could not create job: ${err.body}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,12 +229,20 @@
|
||||
<div class="w-10">
|
||||
{#if refresh_error}
|
||||
<Popover notClickable>
|
||||
<Icon
|
||||
class="text-red-600 animate-[pulse_5s_linear_infinite]"
|
||||
data={faCircle}
|
||||
scale={0.7}
|
||||
label="Error during exchange of the refresh token"
|
||||
/>
|
||||
<span class="flex h-4 w-4">
|
||||
<Icon
|
||||
class="text-red-600 animate-ping absolute inline-flex "
|
||||
data={faCircle}
|
||||
scale={0.7}
|
||||
label="Error during exchange of the refresh token"
|
||||
/>
|
||||
<Icon
|
||||
class="text-red-600 relative inline-flex"
|
||||
data={faCircle}
|
||||
scale={0.7}
|
||||
label="Error during exchange of the refresh token"
|
||||
/>
|
||||
</span>
|
||||
<div slot="text">
|
||||
Latest exchange of the refresh token did not succeed. Error: {refresh_error}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user