diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index 5f617412a0c04..4cc6f52523edf 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -85,6 +85,9 @@ const LicensesSettingsPage: FC = () => { isRemovingLicense={isRemovingLicense} removeLicense={(licenseId: number) => removeLicenseApi(licenseId)} activeUsers={userStatusCount?.active} + managedAgentFeature={ + entitlementsQuery.data?.features.managed_agent_limit + } refreshEntitlements={async () => { try { await refreshEntitlementsMutation.mutateAsync(); diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index eb60361883b72..c631ed178b9a3 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -4,7 +4,7 @@ import MuiLink from "@mui/material/Link"; import Skeleton from "@mui/material/Skeleton"; import Tooltip from "@mui/material/Tooltip"; import type { GetLicensesResponse } from "api/api"; -import type { UserStatusChangeCount } from "api/typesGenerated"; +import type { Feature, UserStatusChangeCount } from "api/typesGenerated"; import { Button } from "components/Button/Button"; import { SettingsHeader, @@ -20,6 +20,7 @@ import Confetti from "react-confetti"; import { Link } from "react-router-dom"; import { LicenseCard } from "./LicenseCard"; import { LicenseSeatConsumptionChart } from "./LicenseSeatConsumptionChart"; +import { ManagedAgentsConsumption } from "./ManagedAgentsConsumption"; type Props = { showConfetti: boolean; @@ -32,6 +33,7 @@ type Props = { removeLicense: (licenseId: number) => void; refreshEntitlements: () => void; activeUsers: UserStatusChangeCount[] | undefined; + managedAgentFeature?: Feature; }; const LicensesSettingsPageView: FC = ({ @@ -45,6 +47,7 @@ const LicensesSettingsPageView: FC = ({ removeLicense, refreshEntitlements, activeUsers, + managedAgentFeature, }) => { const theme = useTheme(); const { width, height } = useWindowSize(); @@ -151,6 +154,10 @@ const LicensesSettingsPageView: FC = ({ }))} /> )} + + {licenses && licenses.length > 0 && ( + + )} ); diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.stories.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.stories.tsx new file mode 100644 index 0000000000000..8b526914edd50 --- /dev/null +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.stories.tsx @@ -0,0 +1,196 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { ManagedAgentsConsumption } from "./ManagedAgentsConsumption"; + +const meta: Meta = { + title: + "pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption", + component: ManagedAgentsConsumption, + args: { + managedAgentFeature: { + enabled: true, + actual: 50000, + soft_limit: 60000, + limit: 120000, + usage_period: { + start: "February 27, 2025", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const NearLimit: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: 115000, + soft_limit: 60000, + limit: 120000, + usage_period: { + start: "February 27, 2025", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export const OverIncluded: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: 80000, + soft_limit: 60000, + limit: 120000, + usage_period: { + start: "February 27, 2025", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export const LowUsage: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: 25000, + soft_limit: 60000, + limit: 120000, + usage_period: { + start: "February 27, 2025", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export const IncludedAtLimit: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: 25000, + soft_limit: 30500, + limit: 30500, + usage_period: { + start: "February 27, 2025", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export const Disabled: Story = { + args: { + managedAgentFeature: { + enabled: false, + actual: undefined, + soft_limit: undefined, + limit: undefined, + usage_period: undefined, + entitlement: "not_entitled", + }, + }, +}; + +export const NoFeature: Story = { + args: { + managedAgentFeature: undefined, + }, +}; + +// Error States for Validation +export const ErrorMissingData: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: undefined, + soft_limit: undefined, + limit: undefined, + usage_period: undefined, + entitlement: "entitled", + }, + }, +}; + +export const ErrorNegativeValues: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: -100, + soft_limit: 60000, + limit: 120000, + usage_period: { + start: "February 27, 2025", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export const ErrorSoftLimitExceedsLimit: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: 50000, + soft_limit: 150000, + limit: 120000, + usage_period: { + start: "February 27, 2025", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export const ErrorInvalidDates: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: 50000, + soft_limit: 60000, + limit: 120000, + usage_period: { + start: "invalid-date", + end: "February 27, 2026", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; + +export const ErrorEndBeforeStart: Story = { + args: { + managedAgentFeature: { + enabled: true, + actual: 50000, + soft_limit: 60000, + limit: 120000, + usage_period: { + start: "February 27, 2026", + end: "February 27, 2025", + issued_at: "February 27, 2025", + }, + entitlement: "entitled", + }, + }, +}; diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.tsx new file mode 100644 index 0000000000000..e96d86b5a4c92 --- /dev/null +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/ManagedAgentsConsumption.tsx @@ -0,0 +1,202 @@ +import MuiLink from "@mui/material/Link"; +import type { Feature } from "api/typesGenerated"; +import { ErrorAlert } from "components/Alert/ErrorAlert"; +import { Button } from "components/Button/Button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "components/Collapsible/Collapsible"; +import { Stack } from "components/Stack/Stack"; +import dayjs from "dayjs"; +import { ChevronRightIcon } from "lucide-react"; +import type { FC } from "react"; +import { docs } from "utils/docs"; + +interface ManagedAgentsConsumptionProps { + managedAgentFeature?: Feature; +} + +export const ManagedAgentsConsumption: FC = ({ + managedAgentFeature, +}) => { + // If no feature is provided or it's disabled, show disabled state + if (!managedAgentFeature?.enabled) { + return ( +
+ + + Managed AI Agents Disabled + + Managed AI agents are not included in your current license. + Contact sales to + upgrade your license and unlock this feature. + + + +
+ ); + } + + const usage = managedAgentFeature.actual; + const included = managedAgentFeature.soft_limit; + const limit = managedAgentFeature.limit; + const startDate = managedAgentFeature.usage_period?.start; + const endDate = managedAgentFeature.usage_period?.end; + + if (!usage || usage < 0) { + return ; + } + + if (!included || included < 0 || !limit || limit < 0) { + return ; + } + + if (!startDate || !endDate) { + return ; + } + + const start = dayjs(startDate); + const end = dayjs(endDate); + if (!start.isValid() || !end.isValid() || !start.isBefore(end)) { + return ; + } + + const usagePercentage = Math.min((usage / limit) * 100, 100); + const includedPercentage = Math.min((included / limit) * 100, 100); + const remainingPercentage = Math.max(100 - includedPercentage, 0); + + return ( +
+
+ +
+

Managed AI Agents Usage

+ + + + +
+ + +

+ + Coder Tasks + {" "} + and upcoming managed AI features are included in Coder Premium + licenses during beta. Usage limits and pricing subject to change. +

+
    +
  • +
    + Amount of started workspaces with an AI agent. +
  • +
  • +
    + Included allowance from your current license plan. +
  • +
  • +
    +
    +
    + Total limit after which further AI workspace builds will be + blocked. +
  • +
+
+
+
+ +
+
+ + {startDate ? dayjs(startDate).format("MMMM D, YYYY") : ""} + + {endDate ? dayjs(endDate).format("MMMM D, YYYY") : ""} +
+ +
+
+ +
+
+ +
+
+ Actual: + {usage.toLocaleString()} +
+ +
+ Included: + {included.toLocaleString()} +
+ +
+ Limit: + {limit.toLocaleString()} +
+
+ +
+
+
+ Actual: + {usage.toLocaleString()} +
+
+ Included: + {included.toLocaleString()} +
+
+ Limit: + {limit.toLocaleString()} +
+
+
+
+
+ ); +}; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy