mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 00:00:33 +00:00
rework premium workspace pricing
This commit is contained in:
+112
-135
@@ -177,26 +177,6 @@
|
||||
},
|
||||
"query": "SELECT * from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')"
|
||||
},
|
||||
"0473482de2910768dbe1cd4569cf157c91939150ea176f44bcce68eb68dbdeb9": {
|
||||
"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"
|
||||
},
|
||||
"04a15674cf66f2822085e65ca33ee42b8e7f3f9d63ffac0585a12368d0322252": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -801,75 +781,6 @@
|
||||
},
|
||||
"query": "DELETE FROM queue WHERE schedule_path = $1 AND running = false AND job_kind = $2"
|
||||
},
|
||||
"1baf08ea78b84c9836c55975044d072c9d92400a1b1248a61d2d0247ac872fe4": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Numeric",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int4",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n , mem_peak\n , tag\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28, $29, $30)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)"
|
||||
},
|
||||
"1bf2ca894246bd716875635b2d0c294a1ce2ed21916097ea165df240f7421a1e": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -3721,26 +3632,6 @@
|
||||
},
|
||||
"query": "INSERT INTO resource\n (workspace_id, path, value, description, resource_type)\n VALUES ($1, $2, $3, $4, $5)"
|
||||
},
|
||||
"8af34c8fd96ccc3c007e211982e60306fee109944c2a053e8c38632992c39983": {
|
||||
"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"
|
||||
},
|
||||
"8c0131a9cc61f2daa258d49767242bcaab6bb34a977ff7fb0c18aa9202d11f47": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -5351,6 +5242,27 @@
|
||||
},
|
||||
"query": "SELECT null FROM queue WHERE id = $1 FOR UPDATE"
|
||||
},
|
||||
"bd086c56ad3a58163619122641b93b941ef75880eeeab0756d40f68f87760d5b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "usage",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"be1de9116b18a40681cb8c5ec7578fd7c5f7ce77f7af63f766c2899d85fe0bef": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -5762,6 +5674,83 @@
|
||||
},
|
||||
"query": "\n SELECT id, flow_status, suspend, script_path\n FROM queue\n WHERE id = ( SELECT parent_job FROM queue WHERE id = $1 UNION ALL SELECT parent_job FROM completed_job WHERE id = $1)\n FOR UPDATE\n "
|
||||
},
|
||||
"c8467b5a1a9c915384b7671990111ec548e399a44df6773f48a81d8956aab683": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "duration_ms",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Numeric",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int4",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n , mem_peak\n , tag\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28, $29, $30)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12) RETURNING duration_ms"
|
||||
},
|
||||
"c9d97800eb0ec87df8e8959b283dacb2c6cce422365ed394375641488ceb6b65": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -6202,19 +6191,6 @@
|
||||
},
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM workspace WHERE id = $1)"
|
||||
},
|
||||
"de4c8f42ff846599d6b2b26615a34f0bcbad974edc5f00fb0491475017eebaeb": {
|
||||
"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"
|
||||
},
|
||||
"df8f9b2e601b0157759e9507308ea7df562a7a42516be0fcb465b8e34de0f438": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -6392,6 +6368,20 @@
|
||||
},
|
||||
"query": "DELETE FROM completed_job WHERE workspace_id = $1"
|
||||
},
|
||||
"e7418515b88d14d1fd79078342963e11422eed9a60e0472c68971f4e8f8735d9": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"e838b15953b567af2a2da6a4e3a04add5ec2cacabd93171a97c5fc978a64f8aa": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -6839,19 +6829,6 @@
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"f7f2598ac824e9b5719b96b1d1873ad728f7f786c7f447df6cac3e65d5a53bf1": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
||||
@@ -259,37 +259,15 @@ async fn stripe_checkout(
|
||||
"team" => Some(vec![
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: Some(1),
|
||||
price: Some("price_1MUlrWGU3NdFi9eLE9GBZhoY".to_string()),
|
||||
price: Some("price_1NCNOgGU3NdFi9eLuG4fZuEP".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: None,
|
||||
price: Some("price_1MUlreGU3NdFi9eLi6sOyvVa".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: None,
|
||||
price: Some("price_1MUlrlGU3NdFi9eLFLggSXZV".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: None,
|
||||
price: Some("price_1MUlr3GU3NdFi9eLbZYFjR9p".to_string()),
|
||||
price: Some("price_1NCNCpGU3NdFi9eLbiE6Ca42".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
]),
|
||||
// "enterprise" => Some(vec![
|
||||
// stripe::CreateCheckoutSessionLineItems {
|
||||
// quantity: None,
|
||||
// price: Some("price_1MSdf6GU3NdFi9eLJFRkntlx".to_string()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// stripe::CreateCheckoutSessionLineItems {
|
||||
// quantity: None,
|
||||
// price: Some("price_1MShsNGU3NdFi9eLJMEZUW8b".to_string()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ]),
|
||||
_ => Err(Error::BadRequest("invalid plan".to_string()))?,
|
||||
};
|
||||
params.customer_email = Some(&authed.email);
|
||||
|
||||
@@ -351,7 +351,8 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
Ulid::new().into()
|
||||
};
|
||||
|
||||
if cfg!(feature = "enterprise") {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
let premium_workspace = *CLOUD_HOSTED
|
||||
&& sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", workspace_id)
|
||||
.fetch_one(&mut tx)
|
||||
@@ -365,27 +366,17 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
job_payload,
|
||||
JobPayload::Flow(_) | JobPayload::RawFlow { .. }
|
||||
) {
|
||||
if !premium_workspace {
|
||||
sqlx::query_scalar!(
|
||||
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)
|
||||
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 + 1
|
||||
RETURNING usage.usage",
|
||||
email)
|
||||
if premium_workspace { workspace_id } else { email },
|
||||
premium_workspace
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")))?
|
||||
} 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!("updating usage: {e}")))?
|
||||
}
|
||||
} else if *CLOUD_HOSTED && !premium_workspace {
|
||||
sqlx::query_scalar!(
|
||||
"
|
||||
|
||||
@@ -67,29 +67,30 @@ pub async fn add_completed_job<R: rsmq_async::RsmqConnection + Clone + Send>(
|
||||
logs: String,
|
||||
rsmq: Option<R>,
|
||||
) -> Result<Uuid, Error> {
|
||||
let duration =
|
||||
if queued_job.job_kind == JobKind::Flow || queued_job.job_kind == JobKind::FlowPreview {
|
||||
let jobs = queued_job.parse_flow_status().map(|s| {
|
||||
let mut modules = s.modules;
|
||||
modules.extend([s.failure_module.module_status]);
|
||||
flatten_jobs(modules)
|
||||
});
|
||||
if let Some(jobs) = jobs {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT SUM(duration_ms) as duration FROM completed_job WHERE id = ANY($1)",
|
||||
jobs.as_slice()
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
} else {
|
||||
tracing::warn!("Could not parse flow status");
|
||||
None
|
||||
}
|
||||
let is_flow =
|
||||
queued_job.job_kind == JobKind::Flow || queued_job.job_kind == JobKind::FlowPreview;
|
||||
let duration = if is_flow {
|
||||
let jobs = queued_job.parse_flow_status().map(|s| {
|
||||
let mut modules = s.modules;
|
||||
modules.extend([s.failure_module.module_status]);
|
||||
flatten_jobs(modules)
|
||||
});
|
||||
if let Some(jobs) = jobs {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT SUM(duration_ms) as duration FROM completed_job WHERE id = ANY($1)",
|
||||
jobs.as_slice()
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
} else {
|
||||
tracing::warn!("Could not parse flow status");
|
||||
None
|
||||
};
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mem_peak = sqlx::query_scalar!("SELECT mem_peak FROM queue WHERE id = $1", &queued_job.id)
|
||||
.fetch_optional(db)
|
||||
@@ -99,7 +100,7 @@ pub async fn add_completed_job<R: rsmq_async::RsmqConnection + Clone + Send>(
|
||||
.flatten();
|
||||
let mut tx: QueueTransaction<'_, R> = (rsmq, db.begin().await?).into();
|
||||
let job_id = queued_job.id.clone();
|
||||
sqlx::query!(
|
||||
let duration = sqlx::query_scalar!(
|
||||
"INSERT INTO completed_job AS cj
|
||||
( workspace_id
|
||||
, id
|
||||
@@ -134,7 +135,7 @@ pub async fn add_completed_job<R: rsmq_async::RsmqConnection + Clone + Send>(
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,\
|
||||
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28, $29, $30)
|
||||
ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)",
|
||||
ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12) RETURNING duration_ms",
|
||||
queued_job.workspace_id,
|
||||
queued_job.id,
|
||||
queued_job.parent_job,
|
||||
@@ -166,7 +167,7 @@ pub async fn add_completed_job<R: rsmq_async::RsmqConnection + Clone + Send>(
|
||||
mem_peak,
|
||||
queued_job.tag,
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("Could not add completed job {job_id}: {e}")))?;
|
||||
|
||||
@@ -190,36 +191,25 @@ pub async fn add_completed_job<R: rsmq_async::RsmqConnection + Clone + Send>(
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
if cfg!(enterprise) && duration.unwrap_or(0) > 1000 {
|
||||
let additional_usage = duration.unwrap() as i32 / 1000;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
if !is_flow && duration > 1000 {
|
||||
let additional_usage = duration / 1000;
|
||||
let w_id = &queued_job.workspace_id;
|
||||
let premium_workspace = *CLOUD_HOSTED
|
||||
&& 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!(
|
||||
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,
|
||||
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,
|
||||
additional_usage)
|
||||
.execute(db)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("updating usage: {e}")));
|
||||
} else {
|
||||
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}")))?;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("Added completed job {}", queued_job.id);
|
||||
|
||||
@@ -169,26 +169,18 @@
|
||||
'<b>1 000</b> free global executions per-user per month'
|
||||
],
|
||||
Team: [
|
||||
`<b>$10/mo</b>, includes 1 user/author + 10k computations`,
|
||||
`<b>+ $8/mo</b> per extra user/author in the workspace (1 included)`,
|
||||
`<b>+ $4/mo</b> per extra operators in the workspace.`,
|
||||
`<b>$0.001</b> per additional computation (10k included)`,
|
||||
`Google/Github/Microsoft/Gitlab <b>SSO</b>`,
|
||||
`<b>Unlimited</b> variables/resources/scripts/apps/flows`,
|
||||
`<b>Support 24/7 with 48h response time</b>`
|
||||
`<b>$10/mo</b> per seat`,
|
||||
`Every seat includes <b>10 000</b> executions`,
|
||||
`Every seat includes either 1 user OR 2 operators`
|
||||
],
|
||||
Enterprise: [
|
||||
`<b>$200/mo</b>, includes 1 user/author + 10k computations`,
|
||||
`<b>+ $32/mo</b> per extra user/author in the workspace (1 included)`,
|
||||
`<b>+ $16/mo</b> per extra operators in the workspace.`,
|
||||
`<b>$0.004</b> per additional computation (10k included)`,
|
||||
`<b>Dedicated</b> and isolated database and workers available for <b>+400$/mo</b> (EU/US)`,
|
||||
`<b>Dedicated</b> entire cluster available for <b>+4000$/mo</b> (EU/US)`,
|
||||
`<b>SAML</b> support`,
|
||||
`<b>Dedicated</b> and isolated database and workers available (EU/US/Asia)`,
|
||||
`<b>Dedicated</b> entire cluster available for (EU/US/Asia)`,
|
||||
`<b>SAML</b> support with group syncing`,
|
||||
`<b>SLA</b>`,
|
||||
`<b>Priority Support 24/7 with 3h response time and automation engineer assistance</b>`,
|
||||
`<b>Design partners for Roadmap</b>`,
|
||||
`<div class="mt-4">(Self-hosted enterprise licenses also available at 50% discount)</div>`
|
||||
`<div class="mt-4">Self-hosted licenses also available</div>`
|
||||
]
|
||||
}
|
||||
</script>
|
||||
@@ -485,29 +477,33 @@
|
||||
{/if}
|
||||
|
||||
{#if plan}
|
||||
{@const team_factor = plan == 'team' ? 8 : 32}
|
||||
{@const user_nb = Math.max(0, (users?.filter((x) => !x.operator).length ?? 0) - 1)}
|
||||
{@const team_factor = plan == 'team' ? 10 : 40}
|
||||
{@const user_nb = users?.filter((x) => !x.operator)?.length ?? 0}
|
||||
{@const operator_nb = users?.filter((x) => x.operator)?.length ?? 0}
|
||||
{@const seats_from_users = Math.ceil(user_nb + operator_nb / 2)}
|
||||
{@const seats_from_comps = Math.ceil((premium_info?.usage ?? 0) / 10000)}
|
||||
|
||||
<div>
|
||||
Extra seats:
|
||||
<div class="inline text-2xl font-bold float-right"
|
||||
>{user_nb} * ${team_factor}
|
||||
= ${(users?.length ?? 0) * team_factor}/mo</div
|
||||
>
|
||||
Authors:
|
||||
<div class="inline text-2xl font-bold float-right">{user_nb}</div>
|
||||
<Tooltip
|
||||
>Actual pricing is calculated on the MAXIMUM number of users in a given billing
|
||||
period, see the customer portal for more info.</Tooltip
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
Extra operators:
|
||||
<div class="inline text-2xl font-bold float-right"
|
||||
>{Math.max(0, (users?.filter((x) => x.operator).length ?? 0) - 1)} * ${team_factor /
|
||||
2} = ${((users?.length ?? 0) * team_factor) / 2}/mo</div
|
||||
>
|
||||
Operators:
|
||||
<div class="inline text-2xl font-bold float-right">{operator_nb}</div>
|
||||
<Tooltip
|
||||
>Actual pricing is calculated on the MAXIMUM number of users in a given billing
|
||||
period, see the customer portal for more info.</Tooltip
|
||||
>Actual pricing is calculated on the MAXIMUM number of operators in a given
|
||||
billing period, see the customer portal for more info.</Tooltip
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Seats from authors + operators:
|
||||
<div class="inline text-2xl font-bold float-right mb-8"
|
||||
>ceil({user_nb} + {operator_nb}/2) = {seats_from_users}</div
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
@@ -517,12 +513,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Extra computations (above the 10k included) this month:
|
||||
<div class=" inline text-2xl font-bold float-right"
|
||||
>{Math.max(0, (premium_info?.usage ?? 0) - 10000)} x {plan == 'team'
|
||||
? '0.001'
|
||||
: '0.004'} = ${Math.max(0, (premium_info?.usage ?? 0) - 10000) *
|
||||
(plan == 'team' ? 0.001 : 0.004)}
|
||||
Seats from computations:
|
||||
<div class="inline text-2xl font-bold float-right mb-8"
|
||||
>ceil({premium_info?.usage ?? 0} / 10 000) = {seats_from_comps}</div
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Total seats:
|
||||
<div class=" inline text-2xl font-bold float-right">
|
||||
max({seats_from_comps}, {seats_from_users}) * {team_factor} = ${Math.max(
|
||||
seats_from_comps,
|
||||
seats_from_users
|
||||
) * team_factor}/mo
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -534,7 +537,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<div class="flex flex-col gap-1 mb-4">
|
||||
<Slider text="What is a computation ?">
|
||||
<Alert type="info" title="A computation is 1s of execution">
|
||||
The single credit-unit is called a "computation". A computation corresponds to a
|
||||
@@ -556,7 +559,7 @@
|
||||
{#each Object.entries(plans) as [planTitle, planDesc]}
|
||||
<div class="box p-4 text-sm flex flex-col h-full overflow-hidden">
|
||||
<h2 class="mb-4">{planTitle}</h2>
|
||||
<ul class="list-disc p-4">
|
||||
<ul class="list-disc text-lg p-4">
|
||||
{#each planDesc as item}
|
||||
<li class="mt-2">{@html item}</li>
|
||||
{/each}
|
||||
@@ -566,7 +569,7 @@
|
||||
{#if planTitle == 'Team'}
|
||||
{#if plan != 'team'}
|
||||
<div class="mt-4 mx-auto">
|
||||
<Button href="/api/w/{$workspaceStore}/workspaces/checkout?plan=team"
|
||||
<Button size="lg" href="/api/w/{$workspaceStore}/workspaces/checkout?plan=team"
|
||||
>Upgrade to the Team plan</Button
|
||||
>
|
||||
</div>
|
||||
@@ -576,11 +579,8 @@
|
||||
{:else if planTitle == 'Enterprise'}
|
||||
{#if plan != 'enterprise'}
|
||||
<div class="mt-4 mx-auto">
|
||||
<Button
|
||||
on:click={() =>
|
||||
sendUserToast(
|
||||
'Contact contact@windmill.dev to have your dedicated instance provisioned'
|
||||
)}>Upgrade to the Enterprise plan</Button
|
||||
<Button size="lg" href="https://www.windmill.dev/pricing" target="_blank"
|
||||
>See more</Button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user