Live Connect: Cluster Slots $0.06/hr + Rill Slots $0.15/hr
+
+
+
+
+ {#if $allStatus.isError || fetchError}
+
+ {#if fetchError}
+
{fetchError}
+ {/if}
+ {#each $allStatus.errors as e}
+
{e}
+ {/each}
+
+ {/if}
+
+
+ (open = false)}>Close
+
+ {buttonText}
+
+
+
+
diff --git a/web-admin/src/features/billing/plans/types.ts b/web-admin/src/features/billing/plans/types.ts
index ebd95149f8c..3992ca1522b 100644
--- a/web-admin/src/features/billing/plans/types.ts
+++ b/web-admin/src/features/billing/plans/types.ts
@@ -13,3 +13,16 @@ export type TeamPlanDialogTypes =
| "proj"
| "renew"
| "trial-expired";
+
+/**
+ * Growth plan dialog variants (PRD v10):
+ * 1. base - Free → Growth upgrade
+ * 2. credit-low - Credit warning (80% used) upgrade prompt
+ * 3. credit-exhausted - Credit exhausted, projects hibernated
+ * 4. renew - Cancelled Growth plan renewal
+ */
+export type GrowthPlanDialogTypes =
+ | "base"
+ | "credit-low"
+ | "credit-exhausted"
+ | "renew";
diff --git a/web-admin/src/features/billing/plans/utils.ts b/web-admin/src/features/billing/plans/utils.ts
index c2ce8ffee05..96c67dfd58b 100644
--- a/web-admin/src/features/billing/plans/utils.ts
+++ b/web-admin/src/features/billing/plans/utils.ts
@@ -34,19 +34,11 @@ export function isManagedPlan(planName: string) {
}
export function isFreePlan(planName: string) {
- return (
- planName === "free" ||
- planName === "free_managed" ||
- planName === "free_live_connect"
- );
+ return planName === "free-plan";
}
export function isGrowthPlan(planName: string) {
- return (
- planName === "growth" ||
- planName === "growth_managed" ||
- planName === "growth_live_connect"
- );
+ return planName === "growth-plan";
}
export function isEnterprisePlan(planName: string) {
diff --git a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte
index 049d3da7da4..306d6f20c04 100644
--- a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte
+++ b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte
@@ -31,6 +31,7 @@
import ClickHouseCloudDetailsModal from "./ClickHouseCloudDetailsModal.svelte";
import OverviewCard from "./OverviewCard.svelte";
import { onMount } from "svelte";
+ import { page } from "$app/stores";
export let organization: string;
export let project: string;
@@ -97,13 +98,21 @@
$: isFree = isFreePlan(planName);
$: isGrowth = isGrowthPlan(planName);
$: isEnterprise = planName !== "" && isEnterprisePlan(planName);
- // New pricing applies to Free and Growth plans
- $: useNewPricing = isFree || isGrowth;
-
- // Cluster Slots: derived at runtime from RillMinSlots (set by CHC sync from OLAP connector)
- $: clusterSlots = Number(projectData?.rillMinSlots) || 0;
- // For new pricing Live Connect, Rill Slots = prodSlots (user-controlled, starts at 0)
- $: rillSlots = useNewPricing && !isRillManaged ? currentSlots : 0;
+ // New pricing applies to Free and Growth plans; ?newPricing=true forces it for testing
+ $: devForceNewPricing = $page.url.searchParams.get("newPricing") === "true";
+ $: useNewPricing = isFree || isGrowth || devForceNewPricing;
+
+ // Cluster Slots: derived at runtime from RillMinSlots (set by CHC sync from OLAP connector).
+ // Only applies to Live Connect (not Rill-managed).
+ $: clusterSlots = !isRillManaged
+ ? Number(projectData?.rillMinSlots) || (devForceNewPricing ? 8 : 0)
+ : 0;
+ // For new pricing Live Connect, Rill Slots = prodSlots - clusterSlots (rill_min_slots).
+ // Cluster slots already include the hidden 4-slot infra minimum.
+ $: rillSlots =
+ useNewPricing && !isRillManaged
+ ? Math.max(0, currentSlots - clusterSlots)
+ : 0;
// Slot usage breakdown (dev edit modes coming soon; each consumes 1 slot)
$: prodSlots = currentSlots; // today all slots go to prod
diff --git a/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte b/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte
index 794aed2af36..eb0e4f3cdfd 100644
--- a/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte
+++ b/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte
@@ -126,8 +126,12 @@
async function applySlotChange() {
try {
+ // For new pricing Live Connect: Rill Slots are additional slots on top of
+ // cluster slots (rill_min_slots). prodSlots stored = rillSlots + clusterSlots.
const newSlots =
- useNewPricing && !isRillManaged ? selectedRillSlots : selectedSlots;
+ useNewPricing && !isRillManaged
+ ? selectedRillSlots + clusterSlots
+ : selectedSlots;
await $updateProject.mutateAsync({
org: organization,
project,
@@ -137,7 +141,10 @@
queryKey: getAdminServiceGetProjectQueryKey(organization, project),
});
eventBus.emit("notification", {
- message: `Slots updated to ${newSlots}`,
+ message:
+ useNewPricing && !isRillManaged
+ ? `Rill Slots updated to ${selectedRillSlots}`
+ : `Slots updated to ${newSlots}`,
});
open = false;
} catch (err) {
diff --git a/web-common/src/features/billing/PricingDetails.svelte b/web-common/src/features/billing/PricingDetails.svelte
index 34b32d3708e..4244e2fa76e 100644
--- a/web-common/src/features/billing/PricingDetails.svelte
+++ b/web-common/src/features/billing/PricingDetails.svelte
@@ -1,9 +1,18 @@
- {extraText} Pricing is based on amount of data ingested (and compressed) into Rill.
+ {extraText}
+ {#if mode === "legacy"}
+ Pricing is based on amount of data ingested (and compressed) into Rill.
+ {:else if mode === "managed"}
+ Pure usage-based billing with no base fee.
+ {:else}
+ Cluster Slots are auto-calculated from your OLAP cluster. Add Rill Slots for extra performance.
+ {/if}
{/if}
-
diff --git a/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte b/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte
index 8bd2222284e..4efafb091bf 100644
--- a/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte
+++ b/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte
@@ -12,12 +12,10 @@
import type { AxiosError } from "axios";
import {
LIVE_CONNECT_TIERS,
- MANAGED_SLOT_TIERS,
RILL_SLOT_TIERS,
POPULAR_SLOTS,
ALL_SLOTS,
MANAGED_SLOT_RATE_PER_HR,
- CLUSTER_SLOT_RATE_PER_HR,
RILL_SLOT_RATE_PER_HR,
HOURS_PER_MONTH,
STORAGE_RATE_PER_GB_PER_MONTH,
@@ -31,9 +29,6 @@
export let isRillManaged: boolean;
// Auto-detected slot count from the OLAP cluster (SQL-based detection).
export let detectedSlots: number | undefined = undefined;
- // When true, the user can only view the detected tier and apply it (no selection).
- // required mode is no longer triggered (kept for template compatibility)
- export let required = false;
export let viewOnly = false;
// When true, uses new PRD v10 pricing (Free/Growth plans). False for legacy Team plans.
export let useNewPricing = false;
@@ -41,13 +36,10 @@
export let clusterSlots = 0;
// Current Rill Slots (user-controlled). Only for Live Connect + new pricing.
export let currentRillSlots = 0;
- // (infraSlots prop removed — no longer shown in the status UI)
-
const POPULAR_RILL_MANAGED = POPULAR_SLOTS.map((s) => ({ slots: s }));
const ALL_RILL_MANAGED = ALL_SLOTS.map((s) => ({ slots: s }));
- // All plans use the $0.15/slot/hr rate
- $: managedRate = MANAGED_SLOT_RATE_PER_HR;
+ const managedRate = MANAGED_SLOT_RATE_PER_HR;
// For new pricing Live Connect: Rill Slots selection
let selectedRillSlots = currentRillSlots;
@@ -72,8 +64,6 @@
$: if (open) {
if (viewOnly) {
selectedSlots = detectedTierSlots ?? minimumTierSlots;
- } else if (required && currentSlots === 0) {
- selectedSlots = detectedTierSlots ?? minimumTierSlots;
} else {
selectedSlots = currentSlots;
}
@@ -154,13 +144,10 @@
{
- if (required && !isOpen) return; // prevent closing when required
open = isOpen;
}}
- closeOnEscape={!required}
- closeOnOutsideClick={!required}
>
-
+ Manage Slots
@@ -168,15 +155,15 @@
Based on your OLAP cluster, we recommend the following slot
configuration. Start a Team planUpgrade to Growth to customize your slot allocation.
{:else if isRillManaged}
Rill-managed projects are billed at ${managedRate}/slot/hr.{#if useNewPricing} Storage is ${STORAGE_RATE_PER_GB_PER_MONTH}/GB/month above {INCLUDED_STORAGE_GB}GB included.{:else} Data
storage is charged separately based on usage.{/if} Monthly estimates assume
~{HOURS_PER_MONTH} hours/month.
{:else if useNewPricing}
- Cluster Slots are auto-calculated from your OLAP cluster at ${CLUSTER_SLOT_RATE_PER_HR}/slot/hr.
Add Rill Slots at ${RILL_SLOT_RATE_PER_HR}/slot/hr for extra performance or dev environments.
+ Cluster Slots are auto-detected from your OLAP cluster and shown on the Deployments page.
{:else}
Choose the slot tier that matches your OLAP cluster's resources. We
auto-detect the minimum tier from your cluster configuration. You can
@@ -240,86 +227,55 @@
{/if}
{:else if useNewPricing}
-
-
-
-
- Cluster Slots
- Auto-calculated from your OLAP cluster · read-only
-