mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
feat: add workspace free-tier usage (#3489)
* feat: separate workspace and user usage * fix: sqlx build
This commit is contained in:
-6
@@ -27,11 +27,6 @@
|
||||
"ordinal": 4,
|
||||
"name": "premium",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "is_overquota",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -45,7 +40,6 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(id) FROM queue WHERE running = true AND workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "12a0fd7d8d99fb73b01bc24774fe9a8da57b5204bb6b1207aed47143c17a20bc"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "usage",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2e9b3e718440f3c5269e9217a13076c565f3add98b6768b5476bd3afed11ea31"
|
||||
}
|
||||
+3
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, $2, 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",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage)\n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 1)\n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -11,13 +11,12 @@
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Bool"
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bd086c56ad3a58163619122641b93b941ef75880eeeab0756d40f68f87760d5b"
|
||||
"hash": "621e9a2a53187dac3ebed62f0d645b692815f1594bf302dbebd5f80d5d22b98e"
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT premium, is_overquota FROM workspace WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "premium",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "is_overquota",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6e816fdba0d281d5147ddc5d4d438a3209b811324dba4ac661938e0523210682"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT usage.usage + 1 FROM usage \n WHERE is_workspace IS FALSE AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "7d93eb90163516718c85f28f8f05093133c5cbc96414000ac364583114d0ce77"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT usage.usage FROM usage \n WHERE is_workspace = true \n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "usage",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "82b16e771b6e21c4587b5ebf059e312f43b3e5a48f7599133831dbd65886f5d8"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium, is_overquota FROM workspace WHERE id = $3",
|
||||
"query": "INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -12,5 +12,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6d89ac43c4ae3a17167a44c802df72bc506803b79fe93ab3b203fb0690b1bef1"
|
||||
"hash": "8f0031533f1bf407bd5d8af4d364eaf00d4c38ee7ba75141b40fc9fcd2ffc0b8"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT usage.usage + 1 FROM usage \n WHERE is_workspace IS TRUE AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a405e637f5f3b3203de6d65dfcb0ba1be406ee5167f7b8aa90213ef52c97441f"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ca3ba808e020c8c7a35eaef842b20cfeee64fd47ded72fce55cc75e0bbb291a8"
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT usage.usage + 1 FROM usage \n WHERE is_workspace = false AND\n month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "d918e1d3a1ccc36ca8b4b25aeeec1cb55aadaf1e6609a1aad09b0515c93a667a"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e38240e6d50bfe60e1c2b649588eb41dcef121ed161db04b2568ac2d990aed7c"
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, $2, 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 + $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e7418515b88d14d1fd79078342963e11422eed9a60e0472c68971f4e8f8735d9"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(id) FROM queue WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e7a1c2b5d79e72f557181782419a9d8d1a502796842f185374d2d0f69043086b"
|
||||
}
|
||||
-6
@@ -27,11 +27,6 @@
|
||||
"ordinal": 4,
|
||||
"name": "premium",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "is_overquota",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -44,7 +39,6 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
alter table workspace drop column is_overquota;
|
||||
@@ -1955,6 +1955,22 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/LargeFileStorage"
|
||||
|
||||
/w/{workspace}/workspaces/usage:
|
||||
get:
|
||||
summary: get usage
|
||||
operationId: getWorkspaceUsage
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
responses:
|
||||
"200":
|
||||
description: usage
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: number
|
||||
|
||||
/w/{workspace}/users/list:
|
||||
get:
|
||||
summary: list users
|
||||
|
||||
@@ -112,7 +112,8 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/leave", post(leave_workspace))
|
||||
.route("/get_workspace_name", get(get_workspace_name))
|
||||
.route("/change_workspace_name", post(change_workspace_name))
|
||||
.route("/change_workspace_id", post(change_workspace_id));
|
||||
.route("/change_workspace_id", post(change_workspace_id))
|
||||
.route("/usage", get(get_usage));
|
||||
|
||||
#[cfg(feature = "stripe")]
|
||||
{
|
||||
@@ -146,7 +147,6 @@ struct Workspace {
|
||||
owner: String,
|
||||
deleted: bool,
|
||||
premium: bool,
|
||||
is_overquota: bool,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
@@ -2801,7 +2801,7 @@ async fn change_workspace_id(
|
||||
|
||||
// duplicate workspace with new id name
|
||||
sqlx::query!(
|
||||
"INSERT INTO workspace SELECT $1, $2, owner, deleted, premium, is_overquota FROM workspace WHERE id = $3",
|
||||
"INSERT INTO workspace SELECT $1, $2, owner, deleted, premium FROM workspace WHERE id = $3",
|
||||
&rw.new_id,
|
||||
&rw.new_name,
|
||||
&old_id
|
||||
@@ -3082,3 +3082,21 @@ async fn change_workspace_id(
|
||||
&old_id, &rw.new_id
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_usage(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
) -> Result<String> {
|
||||
let usage = sqlx::query_scalar!(
|
||||
"
|
||||
SELECT usage.usage FROM usage
|
||||
WHERE is_workspace = true
|
||||
AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)
|
||||
AND id = $1",
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
Ok(usage.to_string())
|
||||
}
|
||||
@@ -701,14 +701,25 @@ pub async fn add_completed_job<
|
||||
.map_err(|e| Error::InternalErr(format!("fetching if {w_id} is premium: {e}")))?;
|
||||
let _ = sqlx::query!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
VALUES ($1, $2, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $3",
|
||||
if premium_workspace { w_id } else { &queued_job.email },
|
||||
premium_workspace,
|
||||
VALUES ($1, TRUE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
w_id,
|
||||
additional_usage as i32)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")));
|
||||
|
||||
if !premium_workspace {
|
||||
let _ = sqlx::query!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
VALUES ($1, FALSE, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), $2)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + $2",
|
||||
queued_job.email,
|
||||
additional_usage as i32)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")));
|
||||
}
|
||||
}
|
||||
|
||||
if !skip_downstream_error_handlers
|
||||
@@ -2579,8 +2590,8 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
) -> Result<(Uuid, QueueTransaction<'c, R>), Error> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
if *CLOUD_HOSTED {
|
||||
let row = sqlx::query!(
|
||||
"SELECT premium, is_overquota FROM workspace WHERE id = $1",
|
||||
let premium_workspace = sqlx::query_scalar!(
|
||||
"SELECT premium FROM workspace WHERE id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
@@ -2590,43 +2601,43 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
"fetching if {workspace_id} is premium and overquota: {e}"
|
||||
))
|
||||
})?;
|
||||
let premium_workspace = row.premium;
|
||||
let is_overquota = premium_workspace && row.is_overquota;
|
||||
|
||||
// we track only non flow steps
|
||||
let usage = if !matches!(
|
||||
let (workspace_usage, user_usage) = if !matches!(
|
||||
job_payload,
|
||||
JobPayload::Flow { .. } | JobPayload::RawFlow { .. }
|
||||
) {
|
||||
sqlx::query_scalar!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
VALUES ($1, $2, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0)
|
||||
let workspace_usage = 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), 1)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1
|
||||
RETURNING usage.usage",
|
||||
if premium_workspace { workspace_id } else { email },
|
||||
premium_workspace
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?
|
||||
} else if !premium_workspace {
|
||||
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)
|
||||
AND id = $1",
|
||||
email
|
||||
)
|
||||
.fetch_optional(_db)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or(0)
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?;
|
||||
|
||||
let user_usage = if !premium_workspace {
|
||||
Some(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), 1)
|
||||
ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1
|
||||
RETURNING usage.usage",
|
||||
email
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(Some(workspace_usage), user_usage)
|
||||
} else {
|
||||
0
|
||||
(None, None)
|
||||
};
|
||||
|
||||
if is_overquota || !premium_workspace {
|
||||
if !premium_workspace {
|
||||
let is_super_admin =
|
||||
sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)
|
||||
.fetch_optional(_db)
|
||||
@@ -2634,12 +2645,39 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_super_admin {
|
||||
if is_overquota {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"Workspace {workspace_id} is overquota. Please update your subscription in the customer portal to continue using Windmill."
|
||||
)));
|
||||
}
|
||||
if usage > MAX_FREE_EXECS
|
||||
let user_usage = if let Some(user_usage) = user_usage {
|
||||
user_usage
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT usage.usage + 1 FROM usage
|
||||
WHERE is_workspace IS FALSE AND
|
||||
month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)
|
||||
AND id = $1",
|
||||
email
|
||||
)
|
||||
.fetch_optional(_db)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or(1)
|
||||
};
|
||||
|
||||
let workspace_usage = if let Some(workspace_usage) = workspace_usage {
|
||||
workspace_usage
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT usage.usage + 1 FROM usage
|
||||
WHERE is_workspace IS TRUE AND
|
||||
month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)
|
||||
AND id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_optional(_db)
|
||||
.await?
|
||||
.flatten()
|
||||
.unwrap_or(1)
|
||||
};
|
||||
|
||||
if user_usage > MAX_FREE_EXECS
|
||||
&& !matches!(job_payload, JobPayload::Dependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::FlowDependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::AppDependencies { .. })
|
||||
@@ -2648,6 +2686,15 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
if workspace_usage > MAX_FREE_EXECS
|
||||
&& !matches!(job_payload, JobPayload::Dependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::FlowDependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::AppDependencies { .. })
|
||||
{
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"Workspace {workspace_id} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
let in_queue =
|
||||
sqlx::query_scalar!("SELECT COUNT(id) FROM queue WHERE email = $1", email)
|
||||
.fetch_one(_db)
|
||||
@@ -2660,6 +2707,18 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
)));
|
||||
}
|
||||
|
||||
let in_queue_workspace =
|
||||
sqlx::query_scalar!("SELECT COUNT(id) FROM queue WHERE workspace_id = $1", workspace_id)
|
||||
.fetch_one(_db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
if in_queue_workspace > MAX_FREE_EXECS.into() {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
|
||||
let concurrent_runs = sqlx::query_scalar!(
|
||||
"SELECT COUNT(id) FROM queue WHERE running = true AND email = $1",
|
||||
email
|
||||
@@ -2673,6 +2732,20 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
"User {email} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
|
||||
let concurrent_runs_workspace = sqlx::query_scalar!(
|
||||
"SELECT COUNT(id) FROM queue WHERE running = true AND workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS.into() {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { logout } from '$lib/logout'
|
||||
import { userStore, usageStore, isPremiumStore } from '$lib/stores'
|
||||
import { userStore, usageStore, workspaceUsageStore, isPremiumStore } from '$lib/stores'
|
||||
import Menu from '../common/menu/MenuV2.svelte'
|
||||
import { USER_SETTINGS_HASH } from './settings'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
@@ -100,11 +100,20 @@
|
||||
{#if !$isPremiumStore}
|
||||
<div class="py-1" role="none">
|
||||
<span class="text-secondary block w-full text-left px-4 py-2 text-sm"
|
||||
>{$usageStore}/1000 free-tier executions</span
|
||||
>{$usageStore}/1000 user execs</span
|
||||
>
|
||||
<div class="w-full bg-gray-200 h-1">
|
||||
<div class="bg-blue-400 h-1" style="width: {Math.min($usageStore, 1000) / 10}%" />
|
||||
</div>
|
||||
<span class="text-secondary block w-full text-left px-4 py-2 text-sm"
|
||||
>{$workspaceUsageStore}/1000 workspace execs</span
|
||||
>
|
||||
<div class="w-full bg-gray-200 h-1">
|
||||
<div
|
||||
class="bg-blue-400 h-1"
|
||||
style="width: {Math.min($workspaceUsageStore, 1000) / 10}%"
|
||||
/>
|
||||
</div>
|
||||
{#if $userStore?.is_admin}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -33,6 +33,7 @@ export const awarenessStore = writable<Record<string, string>>(undefined)
|
||||
export const enterpriseLicense = writable<string | undefined>(undefined)
|
||||
export const workerTags = writable<string[] | undefined>(undefined)
|
||||
export const usageStore = writable<number>(0)
|
||||
export const workspaceUsageStore = writable<number>(0)
|
||||
export const runFormStore = writable<any>()
|
||||
export const oauthStore = writable<TokenResponse | undefined>(undefined)
|
||||
export const userStore = writable<UserExt | undefined>(undefined)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
starStore,
|
||||
superadmin,
|
||||
usageStore,
|
||||
workspaceUsageStore,
|
||||
userStore,
|
||||
workspaceStore,
|
||||
type UserExt,
|
||||
@@ -117,6 +118,9 @@
|
||||
async function loadUsage() {
|
||||
if (isCloudHosted()) {
|
||||
$usageStore = await UserService.getUsage()
|
||||
$workspaceUsageStore = await WorkspaceService.getWorkspaceUsage({
|
||||
workspace: $workspaceStore!
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user