Skip to content

Commit 787cf90

Browse files
authored
chore: set trial api usage to 0 and show ui (#8664)
1 parent 15fe47a commit 787cf90

File tree

5 files changed

+244
-172
lines changed

5 files changed

+244
-172
lines changed

backend/shared_configs/configs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,12 @@ async def async_return_default_schema(*args: Any, **kwargs: Any) -> str: # noqa
243243
)
244244

245245
# Per-week API calls using API keys or Personal Access Tokens
246-
USAGE_LIMIT_API_CALLS_TRIAL = int(os.environ.get("USAGE_LIMIT_API_CALLS_TRIAL", "400"))
246+
USAGE_LIMIT_API_CALLS_TRIAL = int(os.environ.get("USAGE_LIMIT_API_CALLS_TRIAL", "0"))
247247
USAGE_LIMIT_API_CALLS_PAID = int(os.environ.get("USAGE_LIMIT_API_CALLS_PAID", "40000"))
248248

249249
# Per-week non-streaming API calls (more expensive, so lower limits)
250250
USAGE_LIMIT_NON_STREAMING_CALLS_TRIAL = int(
251-
os.environ.get("USAGE_LIMIT_NON_STREAMING_CALLS_TRIAL", "80")
251+
os.environ.get("USAGE_LIMIT_NON_STREAMING_CALLS_TRIAL", "0")
252252
)
253253
USAGE_LIMIT_NON_STREAMING_CALLS_PAID = int(
254254
os.environ.get("USAGE_LIMIT_NON_STREAMING_CALLS_PAID", "160")

web/src/app/admin/api-key/page.tsx

Lines changed: 108 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import Button from "@/refresh-components/buttons/Button";
3131
import CopyIconButton from "@/refresh-components/buttons/CopyIconButton";
3232
import Text from "@/refresh-components/texts/Text";
3333
import { SvgEdit, SvgKey, SvgRefreshCw } from "@opal/icons";
34+
import { useCloudSubscription } from "@/hooks/useCloudSubscription";
3435

3536
function Main() {
3637
const {
@@ -39,6 +40,8 @@ function Main() {
3940
error,
4041
} = useSWR<APIKey[]>("/api/admin/api-key", errorHandlingFetcher);
4142

43+
const canCreateKeys = useCloudSubscription();
44+
4245
const [fullApiKey, setFullApiKey] = useState<string | null>(null);
4346
const [keyIsGenerating, setKeyIsGenerating] = useState(false);
4447
const [showCreateUpdateForm, setShowCreateUpdateForm] = useState(false);
@@ -70,12 +73,23 @@ function Main() {
7073
const introSection = (
7174
<div className="flex flex-col items-start gap-4">
7275
<Text as="p">
73-
API Keys allow you to access Onyx APIs programmatically. Click the
74-
button below to generate a new API Key.
76+
API Keys allow you to access Onyx APIs programmatically.
77+
{canCreateKeys
78+
? " Click the button below to generate a new API Key."
79+
: ""}
7580
</Text>
76-
<CreateButton onClick={() => setShowCreateUpdateForm(true)}>
77-
Create API Key
78-
</CreateButton>
81+
{canCreateKeys ? (
82+
<CreateButton onClick={() => setShowCreateUpdateForm(true)}>
83+
Create API Key
84+
</CreateButton>
85+
) : (
86+
<div className="flex flex-col gap-2 rounded-lg bg-background-tint-02 p-4">
87+
<Text as="p" text04>
88+
This feature requires an active paid subscription.
89+
</Text>
90+
<Button href="/admin/billing">Upgrade Plan</Button>
91+
</div>
92+
)}
7993
</div>
8094
);
8195

@@ -109,7 +123,7 @@ function Main() {
109123
title="New API Key"
110124
icon={SvgKey}
111125
onClose={() => setFullApiKey(null)}
112-
description="Make sure you copy your new API key. You wont be able to see this key again."
126+
description="Make sure you copy your new API key. You won't be able to see this key again."
113127
/>
114128
<Modal.Body>
115129
<Text as="p" className="break-all flex-1">
@@ -124,88 +138,94 @@ function Main() {
124138

125139
{introSection}
126140

127-
<Separator />
128-
129-
<Title className="mt-6">Existing API Keys</Title>
130-
<Table className="overflow-visible">
131-
<TableHeader>
132-
<TableRow>
133-
<TableHead>Name</TableHead>
134-
<TableHead>API Key</TableHead>
135-
<TableHead>Role</TableHead>
136-
<TableHead>Regenerate</TableHead>
137-
<TableHead>Delete</TableHead>
138-
</TableRow>
139-
</TableHeader>
140-
<TableBody>
141-
{filteredApiKeys.map((apiKey) => (
142-
<TableRow key={apiKey.api_key_id}>
143-
<TableCell>
144-
<Button
145-
internal
146-
onClick={() => handleEdit(apiKey)}
147-
leftIcon={SvgEdit}
148-
>
149-
{apiKey.api_key_name || <i>null</i>}
150-
</Button>
151-
</TableCell>
152-
<TableCell className="max-w-64">
153-
{apiKey.api_key_display}
154-
</TableCell>
155-
<TableCell className="max-w-64">
156-
{apiKey.api_key_role.toUpperCase()}
157-
</TableCell>
158-
<TableCell>
159-
<Button
160-
internal
161-
leftIcon={SvgRefreshCw}
162-
onClick={async () => {
163-
setKeyIsGenerating(true);
164-
const response = await regenerateApiKey(apiKey);
165-
setKeyIsGenerating(false);
166-
if (!response.ok) {
167-
const errorMsg = await response.text();
168-
toast.error(`Failed to regenerate API Key: ${errorMsg}`);
169-
return;
170-
}
171-
const newKey = (await response.json()) as APIKey;
172-
setFullApiKey(newKey.api_key);
173-
mutate("/api/admin/api-key");
174-
}}
175-
>
176-
Refresh
177-
</Button>
178-
</TableCell>
179-
<TableCell>
180-
<DeleteButton
181-
onClick={async () => {
182-
const response = await deleteApiKey(apiKey.api_key_id);
183-
if (!response.ok) {
184-
const errorMsg = await response.text();
185-
toast.error(`Failed to delete API Key: ${errorMsg}`);
186-
return;
187-
}
188-
mutate("/api/admin/api-key");
189-
}}
190-
/>
191-
</TableCell>
192-
</TableRow>
193-
))}
194-
</TableBody>
195-
</Table>
196-
197-
{showCreateUpdateForm && (
198-
<OnyxApiKeyForm
199-
onCreateApiKey={(apiKey) => {
200-
setFullApiKey(apiKey.api_key);
201-
}}
202-
onClose={() => {
203-
setShowCreateUpdateForm(false);
204-
setSelectedApiKey(undefined);
205-
mutate("/api/admin/api-key");
206-
}}
207-
apiKey={selectedApiKey}
208-
/>
141+
{canCreateKeys && (
142+
<>
143+
<Separator />
144+
145+
<Title className="mt-6">Existing API Keys</Title>
146+
<Table className="overflow-visible">
147+
<TableHeader>
148+
<TableRow>
149+
<TableHead>Name</TableHead>
150+
<TableHead>API Key</TableHead>
151+
<TableHead>Role</TableHead>
152+
<TableHead>Regenerate</TableHead>
153+
<TableHead>Delete</TableHead>
154+
</TableRow>
155+
</TableHeader>
156+
<TableBody>
157+
{filteredApiKeys.map((apiKey) => (
158+
<TableRow key={apiKey.api_key_id}>
159+
<TableCell>
160+
<Button
161+
internal
162+
onClick={() => handleEdit(apiKey)}
163+
leftIcon={SvgEdit}
164+
>
165+
{apiKey.api_key_name || <i>null</i>}
166+
</Button>
167+
</TableCell>
168+
<TableCell className="max-w-64">
169+
{apiKey.api_key_display}
170+
</TableCell>
171+
<TableCell className="max-w-64">
172+
{apiKey.api_key_role.toUpperCase()}
173+
</TableCell>
174+
<TableCell>
175+
<Button
176+
internal
177+
leftIcon={SvgRefreshCw}
178+
onClick={async () => {
179+
setKeyIsGenerating(true);
180+
const response = await regenerateApiKey(apiKey);
181+
setKeyIsGenerating(false);
182+
if (!response.ok) {
183+
const errorMsg = await response.text();
184+
toast.error(
185+
`Failed to regenerate API Key: ${errorMsg}`
186+
);
187+
return;
188+
}
189+
const newKey = (await response.json()) as APIKey;
190+
setFullApiKey(newKey.api_key);
191+
mutate("/api/admin/api-key");
192+
}}
193+
>
194+
Refresh
195+
</Button>
196+
</TableCell>
197+
<TableCell>
198+
<DeleteButton
199+
onClick={async () => {
200+
const response = await deleteApiKey(apiKey.api_key_id);
201+
if (!response.ok) {
202+
const errorMsg = await response.text();
203+
toast.error(`Failed to delete API Key: ${errorMsg}`);
204+
return;
205+
}
206+
mutate("/api/admin/api-key");
207+
}}
208+
/>
209+
</TableCell>
210+
</TableRow>
211+
))}
212+
</TableBody>
213+
</Table>
214+
215+
{showCreateUpdateForm && (
216+
<OnyxApiKeyForm
217+
onCreateApiKey={(apiKey) => {
218+
setFullApiKey(apiKey.api_key);
219+
}}
220+
onClose={() => {
221+
setShowCreateUpdateForm(false);
222+
setSelectedApiKey(undefined);
223+
mutate("/api/admin/api-key");
224+
}}
225+
apiKey={selectedApiKey}
226+
/>
227+
)}
228+
</>
209229
)}
210230
</>
211231
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { NEXT_PUBLIC_CLOUD_ENABLED } from "@/lib/constants";
2+
import { hasPaidSubscription } from "@/lib/billing/interfaces";
3+
import { useBillingInformation } from "@/hooks/useBillingInformation";
4+
5+
/**
6+
* Returns whether the current tenant has an active paid subscription on cloud.
7+
*
8+
* Self-hosted deployments always return true (no billing gate).
9+
* Cloud deployments check billing status via the billing API.
10+
* Returns true while loading to avoid flashing the upgrade prompt.
11+
*/
12+
export function useCloudSubscription(): boolean {
13+
const { data: billingData, isLoading } = useBillingInformation();
14+
15+
if (!NEXT_PUBLIC_CLOUD_ENABLED) {
16+
return true;
17+
}
18+
19+
// Treat loading as subscribed to avoid UI flash
20+
if (isLoading || billingData == null) {
21+
return true;
22+
}
23+
24+
return hasPaidSubscription(billingData);
25+
}

web/src/lib/billing/interfaces.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,19 @@ export function hasActiveSubscription(
133133
return data.status !== null;
134134
}
135135

136+
/**
137+
* Check if the response indicates an active *paid* subscription.
138+
* Returns true only for status === "active" (excludes trialing, past_due, etc.).
139+
*/
140+
export function hasPaidSubscription(
141+
data: BillingInformation | SubscriptionStatus
142+
): data is BillingInformation {
143+
if ("subscribed" in data) {
144+
return false;
145+
}
146+
return data.status === BillingStatus.ACTIVE;
147+
}
148+
136149
/**
137150
* Check if a license is valid and active.
138151
*/

0 commit comments

Comments
 (0)