diff --git a/admin/server/projects.go b/admin/server/projects.go index d4181312ddd..2139a63cde3 100644 --- a/admin/server/projects.go +++ b/admin/server/projects.go @@ -2218,6 +2218,7 @@ func (s *Server) projToDTO(p *database.Project, orgName string) *adminv1.Project Provisioner: p.Provisioner, ProdVersion: p.ProdVersion, ProdSlots: int64(p.ProdSlots), + DevSlots: int64(p.DevSlots), PrimaryBranch: p.PrimaryBranch, Subpath: p.Subpath, GitRemote: safeStr(p.GitRemote), diff --git a/web-admin/src/components/layout/ContentContainer.svelte b/web-admin/src/components/layout/ContentContainer.svelte deleted file mode 100644 index ecc1cfbcba7..00000000000 --- a/web-admin/src/components/layout/ContentContainer.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -
-
- {#if title && showTitle} -

- {title} -

- {/if} - - -
-
diff --git a/web-admin/src/features/branches/BranchesSection.svelte b/web-admin/src/features/branches/BranchesSection.svelte index 192c1344298..1a7edb26bb0 100644 --- a/web-admin/src/features/branches/BranchesSection.svelte +++ b/web-admin/src/features/branches/BranchesSection.svelte @@ -38,10 +38,12 @@ EyeIcon, GitBranchIcon, PlayIcon, + SlidersHorizontalIcon, StopCircleIcon, Trash2Icon, } from "lucide-svelte"; import { eventBus } from "@rilldata/web-common/lib/event-bus/event-bus"; + import ManageSlotsModal from "@rilldata/web-admin/features/projects/status/overview/ManageSlotsModal.svelte"; let { organization, project }: { organization: string; project: string } = $props(); @@ -89,6 +91,9 @@ ), ); + let canManage = $derived( + $projectQuery.data?.projectPermissions?.manageProject ?? false, + ); let prodSlots = $derived( $projectQuery.data?.project?.prodSlots != null ? parseInt($projectQuery.data.project.prodSlots, 10) @@ -142,6 +147,8 @@ let pendingId = $state(""); let deleteDialogOpen = $state(false); let pendingDelete = $state<{ id: string; branch: string } | null>(null); + let prodSlotsModalOpen = $state(false); + let devSlotsModalOpen = $state(false); async function mutateDeployment( deploymentId: string, @@ -202,9 +209,7 @@ } -
-

Branches

- +
{#if $allDeployments.isLoading}
@@ -221,22 +226,12 @@ {:else}
-
- Branch -
-
- Author -
-
- Status -
-
- Slots -
-
- Last updated -
-
+
Branch
+
Author
+
Status
+
Units
+
Last updated
+
{#each visibleDeployments as deployment, i (deployment.id ?? i)} {@const prod = isProdDeployment(deployment)} @@ -308,6 +303,34 @@ {prod ? "View" : "Preview"}
+ {#if prod && canManage && isActiveDeployment(deployment)} + { + openDropdownId = ""; + prodSlotsModalOpen = true; + }} + > +
+ + Manage units +
+
+ {/if} + {#if !prod && canManage && isActiveDeployment(deployment)} + { + openDropdownId = ""; + devSlotsModalOpen = true; + }} + > +
+ + Manage units +
+
+ {/if} {#if canStart} + + + + diff --git a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte index c6f3c752d5b..9e71fdca420 100644 --- a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte +++ b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte @@ -2,31 +2,18 @@ import { page } from "$app/stores"; import { createAdminServiceGetProject, - createAdminServiceGetBillingSubscription, V1DeploymentStatus, } from "@rilldata/web-admin/client"; - import { - isFreePlan, - isProPlan, - isTrialPlan, - } from "@rilldata/web-admin/features/billing/plans/utils"; import { extractBranchFromPath } from "@rilldata/web-admin/features/branches/branch-utils"; import { useDashboardsLastUpdated } from "@rilldata/web-admin/features/dashboards/listing/selectors"; import { useGithubLastSynced } from "@rilldata/web-admin/features/projects/selectors"; - import { createRuntimeServiceGetInstance } from "@rilldata/web-common/runtime-client"; import { createQueryServiceProjectStorage } from "@rilldata/web-common/runtime-client/v2/gen/query-service"; import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2"; import { formatMemorySize } from "@rilldata/web-common/lib/number-formatting/memory-size"; - import { - useParserReconcileError, - useProjectDeployment, - useRuntimeVersion, - } from "../selectors"; + import { useParserReconcileError, useProjectDeployment } from "../selectors"; import { formatEnvironmentName, - formatConnectorName, - getOlapEngineLabel, getStatusDotClass, getStatusLabel, isTransitoryStatus, @@ -72,16 +59,6 @@ ); $: lastUpdated = $githubLastSynced.data ?? $dashboardsLastUpdated; - // Runtime - $: runtimeVersionQuery = useRuntimeVersion(runtimeClient); - $: version = $runtimeVersionQuery.data?.version?.match(/v[\d.]+/)?.[0] ?? ""; - - // Connectors — sensitive: true is needed to read projectConnectors (OLAP/AI connector types) - $: instanceQuery = createRuntimeServiceGetInstance(runtimeClient, { - sensitive: true, - }); - $: instance = $instanceQuery.data?.instance; - // Project storage (OLAP connector data size) $: storageQuery = createQueryServiceProjectStorage(runtimeClient, {}); $: defaultOlapEntry = $storageQuery.data?.entries?.find( @@ -105,29 +82,15 @@ $: isGithubConnected = !!projectData?.gitRemote && !projectData?.managedGitId && !!githubUrl; - $: olapConnector = instance?.projectConnectors?.find( - (c) => c.name === instance?.olapConnector, - ); - $: olapEngineLabel = getOlapEngineLabel(olapConnector); - $: aiConnector = instance?.projectConnectors?.find( - (c) => c.name === instance?.aiConnector, - ); - // Slots - $: currentSlots = Number(projectData?.prodSlots) || 0; - - // Billing plan detection - $: subscriptionQuery = createAdminServiceGetBillingSubscription(organization); - $: planName = $subscriptionQuery?.data?.subscription?.plan?.name ?? ""; - $: showSlots = - isTrialPlan(planName) || isFreePlan(planName) || isProPlan(planName); + $: currentSlots = + deployment?.environment === "dev" + ? Number(projectData?.devSlots) || 0 + : Number(projectData?.prodSlots) || 0;
-
- {#if !$subscriptionQuery?.isLoading && showSlots} -
- Cluster Size - - - -
- {/if} +
+ Cluster Size + + + +
{#if isGithubConnected}
@@ -176,7 +137,7 @@ rel="noopener noreferrer" class="repo-link" > - {githubUrl.replace("https://github.com/", "")} + {githubUrl?.replace("https://github.com/", "")}
@@ -189,53 +150,17 @@
{/if} - {#if parserReconcileError} - -
- - {parserReconcileError} - -
- {:else} - {#if lastUpdated} -
- Last synced - - {lastUpdated.toLocaleString(undefined, { - year: "numeric", - month: "short", - day: "numeric", - hour: "numeric", - minute: "numeric", - })} - -
- {/if} - - {#if version} -
- Runtime - {version} -
- {/if} - + {#if lastUpdated}
- OLAP Engine - {olapEngineLabel} -
- -
- AI Connector + Last synced - {#if aiConnector && aiConnector.name !== "admin"} - {formatConnectorName(aiConnector.type)} - ({aiConnector.name}) - {:else} - Rill Managed - {/if} + {lastUpdated.toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "numeric", + })}
@@ -253,6 +178,16 @@ {/if} {/if} + + {#if parserReconcileError && isGithubConnected} + +
+ + {parserReconcileError} + +
+ {/if} diff --git a/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte b/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte new file mode 100644 index 00000000000..f688428d6c9 --- /dev/null +++ b/web-admin/src/features/projects/status/overview/ManageSlotsModal.svelte @@ -0,0 +1,265 @@ + + + + + + {title} + + Choose the vCPU and memory allocation for your deployment. Monthly + estimates assume ~{HOURS_PER_MONTH} hours at ${SLOT_RATE_PER_HR}/unit/hr.{#if minSlots > 0} + Minimum {minSlots * 4}GiB / {minSlots}vCPU.{/if} + + + + +
+
+ Cluster Size + Est. $/mo +
+
+ {#each visibleTiers as tier} + + {/each} +
+
+ + + +

+ Want to stop billing entirely? + (open = false)} + > + Hibernate this project + + from the project settings page. +

+ + +
+
+ + diff --git a/web-admin/src/features/projects/status/overview/slots-utils.test.ts b/web-admin/src/features/projects/status/overview/slots-utils.test.ts index ab47d78b807..3f37f440d43 100644 --- a/web-admin/src/features/projects/status/overview/slots-utils.test.ts +++ b/web-admin/src/features/projects/status/overview/slots-utils.test.ts @@ -13,7 +13,7 @@ describe("slots-utils", () => { }); it("popular slots list has expected entries", () => { - expect(POPULAR_SLOTS).toHaveLength(6); + expect(POPULAR_SLOTS).toHaveLength(7); }); it("managed default is 2 slots", () => { @@ -24,15 +24,15 @@ describe("slots-utils", () => { expect(DEFAULT_SELF_MANAGED_SLOTS).toBe(4); }); - it("all slot values are at least managed minimum", () => { + it("all slot values are at least 1", () => { for (const s of ALL_SLOTS) { - expect(s).toBeGreaterThanOrEqual(DEFAULT_MANAGED_SLOTS); + expect(s).toBeGreaterThanOrEqual(1); } }); it("tiers have correct bill calculations", () => { - const tier = SLOT_TIERS[0]; // 2 slots - expect(tier.slots).toBe(2); - expect(tier.rillBill).toBe(Math.round(2 * 0.15 * 730)); + const tier = SLOT_TIERS[0]; // 1 slot + expect(tier.slots).toBe(1); + expect(tier.rillBill).toBe(Math.round(1 * 0.15 * 730)); }); }); diff --git a/web-admin/src/features/projects/status/overview/slots-utils.ts b/web-admin/src/features/projects/status/overview/slots-utils.ts index d8cc8b90c3d..d9aae822403 100644 --- a/web-admin/src/features/projects/status/overview/slots-utils.ts +++ b/web-admin/src/features/projects/status/overview/slots-utils.ts @@ -20,9 +20,11 @@ function tier(slots: number, rate = SLOT_RATE_PER_HR): SlotTier { } // Popular slot values shown by default -export const POPULAR_SLOTS = [2, 3, 4, 8, 16, 30]; +export const POPULAR_SLOTS = [1, 2, 3, 4, 8, 16, 30]; // All available slot values including intermediate sizes -export const ALL_SLOTS = [2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 30]; +export const ALL_SLOTS = [ + 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 30, +]; export const SLOT_TIERS: SlotTier[] = ALL_SLOTS.map((s) => tier(s)); diff --git a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte index be33a56dc01..5ca75d84c1e 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/status/+layout.svelte @@ -4,19 +4,22 @@ import { page } from "$app/stores"; import ContentContainer from "@rilldata/web-common/components/layout/ContentContainer.svelte"; import LeftNav from "@rilldata/web-admin/components/nav/LeftNav.svelte"; + import { extractBranchFromPath } from "@rilldata/web-admin/features/branches/branch-utils"; - $: basePage = `/${$page.params.organization}/${$page.params.project}/-/status`; + $: organization = $page.params.organization; + $: basePage = `/${organization}/${$page.params.project}/-/status`; + $: onBranch = !!extractBranchFromPath($page.url.pathname); - const navItems = [ + $: navItems = [ { label: "Overview", route: "", hasPermission: true, }, { - label: "Branches", + label: "Deployments", route: "/branches", - hasPermission: true, + hasPermission: !onBranch, }, { label: "Resources", diff --git a/web-admin/src/routes/[organization]/[project]/-/status/branches/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/status/branches/+page.svelte index 7d10d5dbc57..7a099bf6014 100644 --- a/web-admin/src/routes/[organization]/[project]/-/status/branches/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/-/status/branches/+page.svelte @@ -1,11 +1,30 @@ -
- -
+{#if !activeBranch} +
+ + +
+{/if}