Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kiloclaw/worker-configuration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ declare namespace Cloudflare {
FLY_REGISTRY_APP: "kiloclaw-machines";
FLY_ORG_SLUG: "kilo-679";
FLY_IMAGE_TAG: "latest";
FLY_REGION: "dfw,ewr,lax,sjc,eu";
FLY_REGION: "eu,us";
OPENCLAW_ALLOWED_ORIGINS: "https://claw.kilosessions.ai,https://kilo.ai,https://www.kilo.ai";
REQUIRE_PROXY_TOKEN: "true";
PROACTIVE_REFRESH_THRESHOLD_HOURS: "72";
Expand Down
38 changes: 31 additions & 7 deletions src/app/admin/components/UserAdmin/UserAdminKiloClaw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { useEffect, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import Link from 'next/link';
import { ExternalLink } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
Expand Down Expand Up @@ -139,8 +141,20 @@ export function UserAdminKiloClaw({ userId }: { userId: string }) {
return (
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle>KiloClaw</CardTitle>
<CardDescription>No KiloClaw subscription</CardDescription>
<div className="flex items-start justify-between gap-3">
<div>
<CardTitle>KiloClaw</CardTitle>
<CardDescription>n/a</CardDescription>
</div>
{data?.activeInstanceId && (
<Button variant="outline" size="sm" asChild>
<Link href={`/admin/kiloclaw/${data.activeInstanceId}`}>
<ExternalLink className="mr-1 h-3 w-3" />
View KiloClaw
</Link>
</Button>
)}
</div>
</CardHeader>
<CardContent className="space-y-3">
{data?.earlybird ? (
Expand Down Expand Up @@ -185,11 +199,21 @@ export function UserAdminKiloClaw({ userId }: { userId: string }) {
<CardTitle>KiloClaw</CardTitle>
<CardDescription>KiloClaw subscription and trial status</CardDescription>
</div>
{canEditTrialEnd && (
<Button variant="outline" size="sm" onClick={() => setDialogOpen(true)}>
{isTrialReset ? 'Reset Trial' : 'Edit Trial End'}
</Button>
)}
<div className="flex gap-2">
{data.activeInstanceId && (
<Button variant="outline" size="sm" asChild>
<Link href={`/admin/kiloclaw/${data.activeInstanceId}`}>
<ExternalLink className="mr-1 h-3 w-3" />
View KiloClaw
</Link>
</Button>
)}
{canEditTrialEnd && (
<Button variant="outline" size="sm" onClick={() => setDialogOpen(true)}>
{isTrialReset ? 'Reset Trial' : 'Edit Trial End'}
</Button>
)}
</div>
</div>
</CardHeader>
<CardContent className="space-y-6">
Expand Down
30 changes: 30 additions & 0 deletions src/routers/admin-kiloclaw-user-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { insertTestUser } from '@/tests/helpers/user.helper';
import {
kiloclaw_admin_audit_logs,
kiloclaw_earlybird_purchases,
kiloclaw_instances,
kiloclaw_subscriptions,
} from '@kilocode/db/schema';
import { eq } from 'drizzle-orm';
Expand Down Expand Up @@ -53,6 +54,7 @@ describe('admin.users.getKiloClawState', () => {
hasAccess: expectedAccessWithoutEntitlement,
accessReason: null,
earlybird: null,
activeInstanceId: null,
});
});

Expand Down Expand Up @@ -133,6 +135,34 @@ describe('admin.users.getKiloClawState', () => {
})
);
});

it('returns activeInstanceId when the user has an active instance', async () => {
const [instance] = await db
.insert(kiloclaw_instances)
.values({
user_id: targetUser.id,
sandbox_id: 'sandbox-test-active',
})
.returning({ id: kiloclaw_instances.id });

const caller = await createCallerForUser(adminUser.id);
const result = await caller.admin.users.getKiloClawState({ userId: targetUser.id });

expect(result.activeInstanceId).toBe(instance.id);
});

it('returns null activeInstanceId when the user only has destroyed instances', async () => {
await db.insert(kiloclaw_instances).values({
user_id: targetUser.id,
sandbox_id: 'sandbox-test-destroyed',
destroyed_at: new Date().toISOString(),
});

const caller = await createCallerForUser(adminUser.id);
const result = await caller.admin.users.getKiloClawState({ userId: targetUser.id });

expect(result.activeInstanceId).toBeNull();
});
});

describe('admin.users.updateKiloClawTrialEndAt', () => {
Expand Down
10 changes: 9 additions & 1 deletion src/routers/admin-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,14 +469,21 @@ export const adminRouter = createTRPCRouter({
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
}

const [subscription, earlybird] = await Promise.all([
const [subscription, earlybird, activeInstance] = await Promise.all([
db.query.kiloclaw_subscriptions.findFirst({
where: eq(kiloclaw_subscriptions.user_id, input.userId),
}),
db.query.kiloclaw_earlybird_purchases.findFirst({
columns: { id: true },
where: eq(kiloclaw_earlybird_purchases.user_id, input.userId),
}),
db.query.kiloclaw_instances.findFirst({
columns: { id: true },
where: and(
eq(kiloclaw_instances.user_id, input.userId),
isNull(kiloclaw_instances.destroyed_at)
),
}),
]);

const now = new Date();
Expand Down Expand Up @@ -518,6 +525,7 @@ export const adminRouter = createTRPCRouter({
daysRemaining: earlybirdDaysRemaining,
}
: null,
activeInstanceId: activeInstance?.id ?? null,
};
}),

Expand Down
Loading