mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
prevent past due team plans to overuse (#6487)
* prevent past due team plans to overuse * nit * update ee ref
This commit is contained in:
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0156016836adeb2714d99811e4ad2ea9245f05e772f3601c99312c13ed65ae1a"
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO script\n (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets) \n\n SELECT workspace_id, $1, path, array_prepend($2::bigint, COALESCE(parent_hashes, '{}'::bigint[])), summary, description, content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, draft_only, envs, concurrent_limit, concurrency_time_window_s, cache_ttl, dedicated_worker, ws_error_handler_muted, priority, restart_unless_cancelled, delete_after_use, timeout, concurrency_key, visible_to_runner_only, no_main_func, codebase, has_preprocessor, on_behalf_of_email, schema_validation, assets \n\n FROM script WHERE hash = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "03e213d2934991c57af64b5ae94a6aa59599f852cc17b16323fc627b6ad8671e"
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n w.premium,\n COALESCE(cw.is_past_due, false) as \"is_past_due!\",\n cw.max_tolerated_executions\n FROM\n workspace w\n LEFT JOIN cloud_workspace_settings cw ON cw.workspace_id = w.id\n WHERE\n w.id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "premium",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "is_past_due!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "max_tolerated_executions",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "124e67b0cee1baa6295846db4ad6242a39dd40186f1dbb48ad3018bd9f6913ec"
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT owner, premium, usage.usage as \"usage?\", workspace_settings.customer_id, workspace_settings.plan FROM workspace LEFT JOIN workspace_settings ON workspace_settings.workspace_id = $1 LEFT JOIN usage ON usage.id = $1 AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND usage.is_workspace IS true WHERE workspace.id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "owner",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "premium",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "usage?",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "customer_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "plan",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "4b8132b04e454eddfe6724c6cc3a2e60c9c24decb2a6b41125247bbf741e9c25"
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT \n owner, \n premium, \n usage.usage as \"usage?\", \n workspace_settings.customer_id, \n workspace_settings.plan, \n COALESCE(cw.is_past_due, false) as \"is_past_due!\", \n cw.max_tolerated_executions\n FROM workspace\n LEFT JOIN workspace_settings \n ON workspace_settings.workspace_id = $1\n LEFT JOIN usage \n ON usage.id = $1\n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND usage.is_workspace IS true\n LEFT JOIN cloud_workspace_settings cw\n ON cw.workspace_id = $1\n WHERE workspace.id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "owner",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "premium",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "usage?",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "customer_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "plan",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "is_past_due!",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "max_tolerated_executions",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "ac4a375035049304db7ae084b2d1789afb8250adde6f38f458b115e787ed876f"
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT premium FROM workspace WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "premium",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d768bbc46f8a9c4289b918c88ca50aa180b29bbe931d948f6e61976f71b7cdb9"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
6396854336ae27fb14ccb792d80c31ff614b2afa
|
||||
d16e52d570f10dfdabb04a9061fe7ebdbf5a2aa1
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Add down migration script here
|
||||
DROP FUNCTION notify_team_plan_status_change;
|
||||
DROP TRIGGER notify_team_plan_status_change ON cloud_workspace_settings;
|
||||
|
||||
ALTER TABLE cloud_workspace_settings
|
||||
DROP COLUMN is_past_due,
|
||||
DROP COLUMN max_tolerated_executions;
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE cloud_workspace_settings
|
||||
ADD COLUMN is_past_due BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN max_tolerated_executions INTEGER;
|
||||
|
||||
CREATE OR REPLACE FUNCTION notify_team_plan_status_change()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('notify_workspace_premium_change', NEW.workspace_id); -- reuse the same channel as the one used for workspace premium change => clear cache
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
CREATE TRIGGER team_plan_status_change_trigger
|
||||
AFTER UPDATE OF is_past_due, max_tolerated_executions ON cloud_workspace_settings
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_team_plan_status_change();
|
||||
+1
-1
@@ -857,7 +857,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
"notify_workspace_premium_change" => {
|
||||
let workspace_id = n.payload();
|
||||
tracing::info!("Workspace premium change detected, invalidating workspace premium cache: {}", workspace_id);
|
||||
windmill_common::workspaces::IS_PREMIUM_CACHE.remove(workspace_id);
|
||||
windmill_common::workspaces::TEAM_PLAN_CACHE.remove(workspace_id);
|
||||
},
|
||||
"notify_runnable_version_change" => {
|
||||
let payload = n.payload();
|
||||
|
||||
@@ -2034,6 +2034,11 @@ paths:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: skip_subscription_fetch
|
||||
in: query
|
||||
description: skip fetching subscription status from stripe
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
responses:
|
||||
"200":
|
||||
@@ -2051,9 +2056,14 @@ paths:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
is_past_due:
|
||||
type: boolean
|
||||
max_tolerated_executions:
|
||||
type: number
|
||||
required:
|
||||
- premium
|
||||
- owner
|
||||
- is_past_due
|
||||
|
||||
/w/{workspace}/workspaces/threshold_alert:
|
||||
get:
|
||||
|
||||
@@ -415,7 +415,9 @@ async fn is_premium(
|
||||
) -> JsonResult<bool> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
#[cfg(feature = "cloud")]
|
||||
let premium = windmill_common::workspaces::is_premium_workspace(&_db, &_w_id).await;
|
||||
let premium = windmill_common::workspaces::get_team_plan_status(&_db, &_w_id)
|
||||
.await
|
||||
.premium;
|
||||
#[cfg(not(feature = "cloud"))]
|
||||
let premium = false;
|
||||
Ok(Json(premium))
|
||||
|
||||
@@ -86,22 +86,47 @@ impl Default for GitSyncSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TeamPlanStatus {
|
||||
pub premium: bool,
|
||||
pub is_past_due: bool,
|
||||
pub max_tolerated_executions: Option<i32>,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref IS_PREMIUM_CACHE: Cache<String, bool> = Cache::new(5000);
|
||||
pub static ref TEAM_PLAN_CACHE: Cache<String, TeamPlanStatus> = Cache::new(5000);
|
||||
}
|
||||
|
||||
#[cfg(feature = "cloud")]
|
||||
pub async fn is_premium_workspace(_db: &crate::DB, _w_id: &str) -> bool {
|
||||
let cached = IS_PREMIUM_CACHE.get(_w_id);
|
||||
pub async fn get_team_plan_status(_db: &crate::DB, _w_id: &str) -> TeamPlanStatus {
|
||||
let cached = TEAM_PLAN_CACHE.get(_w_id);
|
||||
if let Some(cached) = cached {
|
||||
return cached;
|
||||
}
|
||||
let premium = sqlx::query_scalar!("SELECT premium FROM workspace WHERE id = $1", _w_id)
|
||||
.fetch_one(_db)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
IS_PREMIUM_CACHE.insert(_w_id.to_string(), premium);
|
||||
premium
|
||||
let team_plan_info = sqlx::query_as!(
|
||||
TeamPlanStatus,
|
||||
r#"
|
||||
SELECT
|
||||
w.premium,
|
||||
COALESCE(cw.is_past_due, false) as "is_past_due!",
|
||||
cw.max_tolerated_executions
|
||||
FROM
|
||||
workspace w
|
||||
LEFT JOIN cloud_workspace_settings cw ON cw.workspace_id = w.id
|
||||
WHERE
|
||||
w.id = $1
|
||||
"#,
|
||||
_w_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await
|
||||
.unwrap_or_else(|_| TeamPlanStatus {
|
||||
premium: false,
|
||||
is_past_due: false,
|
||||
max_tolerated_executions: None,
|
||||
});
|
||||
TEAM_PLAN_CACHE.insert(_w_id.to_string(), team_plan_info.clone());
|
||||
team_plan_info
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
|
||||
@@ -1419,8 +1419,9 @@ fn apply_completed_job_cloud_usage(
|
||||
let email2 = email.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let additional_usage = _duration / 1000;
|
||||
let premium_workspace =
|
||||
windmill_common::workspaces::is_premium_workspace(&db, &w_id).await;
|
||||
let premium_workspace = windmill_common::workspaces::get_team_plan_status(&db, &w_id)
|
||||
.await
|
||||
.premium;
|
||||
tokio::time::timeout(std::time::Duration::from_secs(10), async move {
|
||||
let _ = sqlx::query!(
|
||||
"INSERT INTO usage (id, is_workspace, month_, usage)
|
||||
@@ -3636,8 +3637,8 @@ pub async fn push<'c, 'd>(
|
||||
) -> Result<(Uuid, Transaction<'c, Postgres>), Error> {
|
||||
#[cfg(feature = "cloud")]
|
||||
if *CLOUD_HOSTED {
|
||||
let premium_workspace =
|
||||
windmill_common::workspaces::is_premium_workspace(_db, workspace_id).await;
|
||||
let team_plan_status =
|
||||
windmill_common::workspaces::get_team_plan_status(_db, workspace_id).await;
|
||||
// we track only non flow steps
|
||||
let (workspace_usage, user_usage) = if !matches!(
|
||||
job_payload,
|
||||
@@ -3655,7 +3656,7 @@ pub async fn push<'c, 'd>(
|
||||
.await
|
||||
.map_err(|e| Error::internal_err(format!("updating usage: {e:#}")))?;
|
||||
|
||||
let user_usage = if !premium_workspace {
|
||||
let user_usage = if !team_plan_status.premium {
|
||||
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)
|
||||
@@ -3678,7 +3679,7 @@ pub async fn push<'c, 'd>(
|
||||
Ok((None, None))
|
||||
}?;
|
||||
|
||||
if !premium_workspace {
|
||||
if !team_plan_status.premium || team_plan_status.is_past_due {
|
||||
let is_super_admin =
|
||||
sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)
|
||||
.fetch_optional(_db)
|
||||
@@ -3686,7 +3687,8 @@ pub async fn push<'c, 'd>(
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_super_admin {
|
||||
if email != ERROR_HANDLER_USER_EMAIL
|
||||
if !team_plan_status.premium
|
||||
&& email != ERROR_HANDLER_USER_EMAIL
|
||||
&& email != SCHEDULE_ERROR_HANDLER_USER_EMAIL
|
||||
&& email != SCHEDULE_RECOVERY_HANDLER_USER_EMAIL
|
||||
&& email != "worker@windmill.dev"
|
||||
@@ -3765,43 +3767,53 @@ pub async fn push<'c, 'd>(
|
||||
.flatten()
|
||||
.unwrap_or(1)
|
||||
};
|
||||
if team_plan_status.premium {
|
||||
// team plan is premium but past due, we check if the workspace has exceeded the max tolerated executions
|
||||
if team_plan_status.max_tolerated_executions.is_none()
|
||||
|| workspace_usage > team_plan_status.max_tolerated_executions.unwrap()
|
||||
{
|
||||
return Err(error::Error::QuotaExceeded(format!(
|
||||
"Workspace {workspace_id} team plan is past due and isn't allowed to run any more jobs. Please fix your payment method in the workspace settings."
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
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::QuotaExceeded(format!(
|
||||
"Workspace {workspace_id} 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::QuotaExceeded(format!(
|
||||
"Workspace {workspace_id} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
let in_queue_workspace = sqlx::query_scalar!(
|
||||
"SELECT COUNT(id) FROM v2_job_queue WHERE workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
let in_queue_workspace = sqlx::query_scalar!(
|
||||
"SELECT COUNT(id) FROM v2_job_queue WHERE workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
if in_queue_workspace > MAX_FREE_EXECS as i64 {
|
||||
return Err(error::Error::QuotaExceeded(format!(
|
||||
"Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
|
||||
if in_queue_workspace > MAX_FREE_EXECS as i64 {
|
||||
return Err(error::Error::QuotaExceeded(format!(
|
||||
"Workspace {workspace_id} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
|
||||
let concurrent_runs_workspace = sqlx::query_scalar!(
|
||||
let concurrent_runs_workspace = sqlx::query_scalar!(
|
||||
"SELECT COUNT(id) FROM v2_job_queue WHERE running = true AND workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(_db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS as i64 {
|
||||
return Err(error::Error::QuotaExceeded(format!(
|
||||
"Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces."
|
||||
)));
|
||||
if concurrent_runs_workspace > MAX_FREE_CONCURRENT_RUNS as i64 {
|
||||
return Err(error::Error::QuotaExceeded(format!(
|
||||
"Workspace {workspace_id} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -680,11 +680,12 @@ pub async fn resolve_job_timeout(
|
||||
let mut warn_msg: Option<String> = None;
|
||||
#[cfg(feature = "cloud")]
|
||||
let cloud_premium_workspace = *CLOUD_HOSTED
|
||||
&& windmill_common::workspaces::is_premium_workspace(
|
||||
&& windmill_common::workspaces::get_team_plan_status(
|
||||
_conn.as_sql().expect("cloud cannot use http connection"),
|
||||
_w_id,
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
.premium;
|
||||
#[cfg(not(feature = "cloud"))]
|
||||
let cloud_premium_workspace = false;
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
seatsFromExtraComps: number
|
||||
usedSeats: number
|
||||
owner: string
|
||||
is_past_due: boolean
|
||||
max_tolerated_executions?: number
|
||||
}
|
||||
| undefined = undefined
|
||||
const plans = {
|
||||
@@ -157,8 +159,13 @@
|
||||
</div>
|
||||
{#if premiumInfo?.status === 'past_due'}
|
||||
<p class="text-red-500 text-base">
|
||||
Your last invoice is unpaid. Please update your payment method in the customer portal to
|
||||
prevent account downgrade and the interruption of your job executions.
|
||||
{#if premiumInfo.max_tolerated_executions === undefined || premiumInfo.usage > premiumInfo.max_tolerated_executions}
|
||||
Your last invoice is unpaid, you cannot run any more jobs. Please update your payment
|
||||
method in the customer portal to continue running jobs.
|
||||
{:else}
|
||||
Your last invoice is unpaid. Please update your payment method in the customer portal to
|
||||
prevent the interruption of your job executions.
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
UserService,
|
||||
WorkspaceService
|
||||
} from '$lib/gen'
|
||||
import { capitalize, classNames, getModifierKey } from '$lib/utils'
|
||||
import { capitalize, classNames, getModifierKey, sendUserToast } from '$lib/utils'
|
||||
import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte'
|
||||
import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte'
|
||||
import CriticalAlertModal from '$lib/components/sidebar/CriticalAlertModal.svelte'
|
||||
@@ -305,6 +305,30 @@
|
||||
|
||||
muteSettings = { global: g_muted, workspace: ws_muted }
|
||||
}
|
||||
|
||||
async function checkTeamPlanStatus(workspace: string) {
|
||||
const premiumInfo = await WorkspaceService.getPremiumInfo({
|
||||
workspace,
|
||||
skipSubscriptionFetch: true // won't load subscription status from stripe but only the past due status from db
|
||||
})
|
||||
if (premiumInfo.is_past_due) {
|
||||
if (
|
||||
premiumInfo.max_tolerated_executions === undefined ||
|
||||
(premiumInfo.usage ?? 0) > premiumInfo.max_tolerated_executions
|
||||
) {
|
||||
sendUserToast(
|
||||
'Your last invoice is unpaid, you cannot run any more jobs. Please update your payment method in the workspace settings to continue running jobs.',
|
||||
true
|
||||
)
|
||||
} else {
|
||||
sendUserToast(
|
||||
'Your last invoice is unpaid. Please update your payment method in the workspace settings to prevent the interruption of your job executions.',
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
$page.url && userSettings != undefined && untrack(() => onQueryChangeUserSettings())
|
||||
})
|
||||
@@ -350,6 +374,15 @@
|
||||
mountModal = false
|
||||
}
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (isCloudHosted()) {
|
||||
const workspace = $workspaceStore
|
||||
if (workspace) {
|
||||
checkTeamPlanStatus(workspace)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth />
|
||||
|
||||
Reference in New Issue
Block a user