@ailog/cli 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +1 -1
- package/dist/standalone/.next/BUILD_ID +1 -1
- package/dist/standalone/.next/build-manifest.json +2 -2
- package/dist/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/api/[[...route]]/route.js.nft.json +1 -1
- package/dist/standalone/.next/server/app/index.html +1 -1
- package/dist/standalone/.next/server/app/index.rsc +1 -1
- package/dist/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/standalone/.next/server/chunks/[root-of-the-server]__a0f47697._.js +2 -2
- package/dist/standalone/.next/server/chunks/ssr/node_modules__bun_4bd60410._.js +1 -1
- package/dist/standalone/.next/server/pages/404.html +1 -1
- package/dist/standalone/.next/server/pages/500.html +2 -2
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/layout.tsx +3 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/page.tsx +238 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/settings/page.tsx +224 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/threads/[threadId]/page.tsx +283 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components/apps-table.tsx +274 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components.json +24 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/layout.tsx +3 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/page.tsx +238 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/settings/page.tsx +224 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/threads/[threadId]/page.tsx +283 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components/apps-table.tsx +274 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components.json +24 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +66 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/tsconfig.json +34 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +65 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/tsconfig.json +34 -0
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +1 -2
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +1 -1
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +1 -1
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +4 -3
- package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +4 -3
- package/dist/standalone/dist/standalone/package.json +1 -1
- package/dist/standalone/package.json +1 -1
- package/package.json +1 -1
- /package/dist/standalone/.next/static/{mh3xsqAwvHCM4LDWJwA0X → XXfaoTsbOv2IAuUUXz1R4}/_buildManifest.js +0 -0
- /package/dist/standalone/.next/static/{mh3xsqAwvHCM4LDWJwA0X → XXfaoTsbOv2IAuUUXz1R4}/_clientMiddlewareManifest.json +0 -0
- /package/dist/standalone/.next/static/{mh3xsqAwvHCM4LDWJwA0X → XXfaoTsbOv2IAuUUXz1R4}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { useParams } from 'next/navigation'
|
|
6
|
+
import { useQueryState, parseAsString } from 'nuqs'
|
|
7
|
+
import { ThreadsTable } from '@/components/threads-table'
|
|
8
|
+
import { Button } from '@/components/ui/button'
|
|
9
|
+
import { Card, CardContent } from '@/components/ui/card'
|
|
10
|
+
import { Input } from '@/components/ui/input'
|
|
11
|
+
import { Label } from '@/components/ui/label'
|
|
12
|
+
import { rpcClient } from '@/lib/rpc-client'
|
|
13
|
+
import { useConfig } from '@/providers/config-provider'
|
|
14
|
+
import { HugeiconsIcon } from '@hugeicons/react'
|
|
15
|
+
import { FilterIcon, Cancel01Icon } from '@hugeicons/core-free-icons'
|
|
16
|
+
import type { Thread, ApiKey } from '@/lib/api'
|
|
17
|
+
|
|
18
|
+
export default function ThreadsPage() {
|
|
19
|
+
const params = useParams()
|
|
20
|
+
const appId = params.appId as string
|
|
21
|
+
const { config, isLoading: isLoadingConfig } = useConfig()
|
|
22
|
+
|
|
23
|
+
// URL params managed by nuqs
|
|
24
|
+
const [tenantId, setTenantId] = useQueryState(
|
|
25
|
+
'tenantId',
|
|
26
|
+
parseAsString.withDefault(''),
|
|
27
|
+
)
|
|
28
|
+
const [userId, setUserId] = useQueryState(
|
|
29
|
+
'userId',
|
|
30
|
+
parseAsString.withDefault(''),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const [threads, setThreads] = useState<Thread[]>([])
|
|
34
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
35
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
|
36
|
+
const [showFilters, setShowFilters] = useState(false)
|
|
37
|
+
|
|
38
|
+
// Cursor pagination state
|
|
39
|
+
const [nextCursor, setNextCursor] = useState<string | null>(null)
|
|
40
|
+
const [hasMore, setHasMore] = useState(false)
|
|
41
|
+
|
|
42
|
+
// API Keys state
|
|
43
|
+
const [apiKeys, setApiKeys] = useState<ApiKey[]>([])
|
|
44
|
+
const [isLoadingKeys, setIsLoadingKeys] = useState(true)
|
|
45
|
+
|
|
46
|
+
// Pending filter state (for input fields before applying)
|
|
47
|
+
const [pendingTenantId, setPendingTenantId] = useState(tenantId)
|
|
48
|
+
const [pendingUserId, setPendingUserId] = useState(userId)
|
|
49
|
+
|
|
50
|
+
// Sync pending state when URL params change
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
setPendingTenantId(tenantId)
|
|
53
|
+
setPendingUserId(userId)
|
|
54
|
+
}, [tenantId, userId])
|
|
55
|
+
|
|
56
|
+
const fetchThreads = useCallback(
|
|
57
|
+
async (cursor?: string) => {
|
|
58
|
+
if (cursor) {
|
|
59
|
+
setIsLoadingMore(true)
|
|
60
|
+
} else {
|
|
61
|
+
setIsLoading(true)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const query: Record<string, string> = { limit: '50' }
|
|
66
|
+
if (tenantId) query.tenantId = tenantId
|
|
67
|
+
if (userId) query.userId = userId
|
|
68
|
+
if (cursor) query.cursor = cursor
|
|
69
|
+
|
|
70
|
+
const res = await rpcClient.api.admin.apps[':appId'].threads.$get({
|
|
71
|
+
param: { appId },
|
|
72
|
+
query,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if (res.ok) {
|
|
76
|
+
const data = await res.json()
|
|
77
|
+
if (cursor) {
|
|
78
|
+
setThreads((prev) => [...prev, ...data.threads])
|
|
79
|
+
} else {
|
|
80
|
+
setThreads(data.threads)
|
|
81
|
+
}
|
|
82
|
+
setNextCursor(data.nextCursor)
|
|
83
|
+
setHasMore(data.hasMore)
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to fetch threads:', error)
|
|
87
|
+
} finally {
|
|
88
|
+
setIsLoading(false)
|
|
89
|
+
setIsLoadingMore(false)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
[appId, tenantId, userId],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const fetchApiKeys = useCallback(async () => {
|
|
96
|
+
try {
|
|
97
|
+
const res = await rpcClient.api.admin.apps[':appId']['api-keys'].$get({
|
|
98
|
+
param: { appId },
|
|
99
|
+
})
|
|
100
|
+
if (res.ok) {
|
|
101
|
+
const data = await res.json()
|
|
102
|
+
setApiKeys(data.keys)
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('Failed to fetch API keys:', error)
|
|
106
|
+
} finally {
|
|
107
|
+
setIsLoadingKeys(false)
|
|
108
|
+
}
|
|
109
|
+
}, [appId])
|
|
110
|
+
|
|
111
|
+
// Fetch threads when URL params change
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
setNextCursor(null)
|
|
114
|
+
setHasMore(false)
|
|
115
|
+
fetchThreads()
|
|
116
|
+
}, [fetchThreads])
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
fetchApiKeys()
|
|
120
|
+
}, [fetchApiKeys])
|
|
121
|
+
|
|
122
|
+
const applyFilters = async () => {
|
|
123
|
+
await Promise.all([
|
|
124
|
+
setTenantId(pendingTenantId || null),
|
|
125
|
+
setUserId(pendingUserId || null),
|
|
126
|
+
])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const clearFilters = async () => {
|
|
130
|
+
setPendingTenantId('')
|
|
131
|
+
setPendingUserId('')
|
|
132
|
+
await Promise.all([setTenantId(null), setUserId(null)])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const loadMore = () => {
|
|
136
|
+
if (nextCursor && !isLoadingMore) {
|
|
137
|
+
fetchThreads(nextCursor)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const hasFilters = tenantId || userId
|
|
142
|
+
const standaloneMode = config?.standaloneMode ?? true
|
|
143
|
+
const hasNoApiKeys =
|
|
144
|
+
apiKeys.length === 0 &&
|
|
145
|
+
!isLoadingKeys &&
|
|
146
|
+
!isLoadingConfig &&
|
|
147
|
+
!standaloneMode
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className="flex h-full flex-col">
|
|
151
|
+
{/* Fixed Filter Section */}
|
|
152
|
+
<div className="bg-background shrink-0 space-y-4 pb-4">
|
|
153
|
+
{hasNoApiKeys && (
|
|
154
|
+
<Card>
|
|
155
|
+
<CardContent className="py-6 text-center">
|
|
156
|
+
<p className="text-muted-foreground mb-2 text-sm">
|
|
157
|
+
No API keys yet. Create one to start logging.
|
|
158
|
+
</p>
|
|
159
|
+
<Link
|
|
160
|
+
href={`/apps/${appId}/settings`}
|
|
161
|
+
className="text-primary text-sm font-medium hover:underline"
|
|
162
|
+
>
|
|
163
|
+
Go to Settings
|
|
164
|
+
</Link>
|
|
165
|
+
</CardContent>
|
|
166
|
+
</Card>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
<div className="flex items-center justify-between">
|
|
170
|
+
<p className="text-muted-foreground text-sm">
|
|
171
|
+
{threads.length} thread{threads.length !== 1 ? 's' : ''} found
|
|
172
|
+
</p>
|
|
173
|
+
<Button
|
|
174
|
+
variant="outline"
|
|
175
|
+
size="sm"
|
|
176
|
+
onClick={() => setShowFilters(!showFilters)}
|
|
177
|
+
>
|
|
178
|
+
<HugeiconsIcon icon={FilterIcon} className="mr-2 h-4 w-4" />
|
|
179
|
+
Filters
|
|
180
|
+
{hasFilters && (
|
|
181
|
+
<span className="bg-primary text-primary-foreground ml-2 rounded-full px-2 py-0.5 text-xs">
|
|
182
|
+
Active
|
|
183
|
+
</span>
|
|
184
|
+
)}
|
|
185
|
+
</Button>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{showFilters && (
|
|
189
|
+
<div className="space-y-4 rounded-lg border p-4">
|
|
190
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
191
|
+
<div className="space-y-2">
|
|
192
|
+
<Label htmlFor="tenantId">Tenant ID</Label>
|
|
193
|
+
<Input
|
|
194
|
+
id="tenantId"
|
|
195
|
+
placeholder="Filter by tenant..."
|
|
196
|
+
value={pendingTenantId}
|
|
197
|
+
onChange={(e) => setPendingTenantId(e.target.value)}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<div className="space-y-2">
|
|
201
|
+
<Label htmlFor="userId">User ID</Label>
|
|
202
|
+
<Input
|
|
203
|
+
id="userId"
|
|
204
|
+
placeholder="Filter by user..."
|
|
205
|
+
value={pendingUserId}
|
|
206
|
+
onChange={(e) => setPendingUserId(e.target.value)}
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
<div className="flex gap-2">
|
|
211
|
+
<Button size="sm" onClick={applyFilters}>
|
|
212
|
+
Apply Filters
|
|
213
|
+
</Button>
|
|
214
|
+
{hasFilters && (
|
|
215
|
+
<Button size="sm" variant="outline" onClick={clearFilters}>
|
|
216
|
+
<HugeiconsIcon icon={Cancel01Icon} className="mr-2 h-4 w-4" />
|
|
217
|
+
Clear
|
|
218
|
+
</Button>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* Table with sticky header - scrollable area */}
|
|
226
|
+
<div className="min-h-0 flex-1 overflow-auto rounded-md border">
|
|
227
|
+
<ThreadsTable
|
|
228
|
+
threads={threads}
|
|
229
|
+
isLoading={isLoading}
|
|
230
|
+
appId={appId}
|
|
231
|
+
hasMore={hasMore}
|
|
232
|
+
isLoadingMore={isLoadingMore}
|
|
233
|
+
onLoadMore={loadMore}
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
4
|
+
import { useParams } from 'next/navigation'
|
|
5
|
+
import {
|
|
6
|
+
Card,
|
|
7
|
+
CardContent,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from '@/components/ui/card'
|
|
12
|
+
import { Skeleton } from '@/components/ui/skeleton'
|
|
13
|
+
import { Button } from '@/components/ui/button'
|
|
14
|
+
import { Input } from '@/components/ui/input'
|
|
15
|
+
import { Label } from '@/components/ui/label'
|
|
16
|
+
import { ApiKeysTable } from '@/components/api-keys-table'
|
|
17
|
+
import { CreateApiKeyDialog } from '@/components/create-api-key-dialog'
|
|
18
|
+
import { rpcClient } from '@/lib/rpc-client'
|
|
19
|
+
import { toast } from 'sonner'
|
|
20
|
+
import type { App, ApiKey } from '@/lib/api'
|
|
21
|
+
|
|
22
|
+
function formatDate(dateString: string) {
|
|
23
|
+
return new Date(dateString).toLocaleDateString('en-US', {
|
|
24
|
+
year: 'numeric',
|
|
25
|
+
month: 'long',
|
|
26
|
+
day: 'numeric',
|
|
27
|
+
hour: '2-digit',
|
|
28
|
+
minute: '2-digit',
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function SettingsPage() {
|
|
33
|
+
const params = useParams()
|
|
34
|
+
const appId = params.appId as string
|
|
35
|
+
|
|
36
|
+
const [app, setApp] = useState<App | null>(null)
|
|
37
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
38
|
+
const [isSaving, setIsSaving] = useState(false)
|
|
39
|
+
const [name, setName] = useState('')
|
|
40
|
+
|
|
41
|
+
// API Keys state
|
|
42
|
+
const [keys, setKeys] = useState<ApiKey[]>([])
|
|
43
|
+
const [isLoadingKeys, setIsLoadingKeys] = useState(true)
|
|
44
|
+
|
|
45
|
+
const fetchKeys = useCallback(async () => {
|
|
46
|
+
try {
|
|
47
|
+
const res = await rpcClient.api.admin.apps[':appId']['api-keys'].$get({
|
|
48
|
+
param: { appId },
|
|
49
|
+
})
|
|
50
|
+
if (res.ok) {
|
|
51
|
+
const data = await res.json()
|
|
52
|
+
setKeys(data.keys)
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Failed to fetch API keys:', error)
|
|
56
|
+
} finally {
|
|
57
|
+
setIsLoadingKeys(false)
|
|
58
|
+
}
|
|
59
|
+
}, [appId])
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
async function fetchData() {
|
|
63
|
+
try {
|
|
64
|
+
const appRes = await rpcClient.api.admin.apps[':appId'].$get({
|
|
65
|
+
param: { appId },
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (appRes.ok) {
|
|
69
|
+
const appData = await appRes.json()
|
|
70
|
+
setApp(appData as App)
|
|
71
|
+
setName(appData.name)
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Failed to fetch data:', error)
|
|
75
|
+
} finally {
|
|
76
|
+
setIsLoading(false)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
fetchData()
|
|
80
|
+
fetchKeys()
|
|
81
|
+
}, [appId, fetchKeys])
|
|
82
|
+
|
|
83
|
+
const handleSave = async () => {
|
|
84
|
+
if (!name.trim()) {
|
|
85
|
+
toast.error('Name is required')
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setIsSaving(true)
|
|
90
|
+
try {
|
|
91
|
+
const res = await rpcClient.api.admin.apps[':appId'].$put({
|
|
92
|
+
param: { appId },
|
|
93
|
+
json: { name: name.trim() },
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
if (res.ok) {
|
|
97
|
+
const updatedApp = await res.json()
|
|
98
|
+
setApp(updatedApp as App)
|
|
99
|
+
toast.success('App name updated successfully')
|
|
100
|
+
} else {
|
|
101
|
+
toast.error('Failed to update app name')
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('Failed to update app:', error)
|
|
105
|
+
toast.error('Failed to update app name')
|
|
106
|
+
} finally {
|
|
107
|
+
setIsSaving(false)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const hasChanges = app && name !== app.name
|
|
112
|
+
|
|
113
|
+
if (isLoading) {
|
|
114
|
+
return (
|
|
115
|
+
<div className="space-y-6">
|
|
116
|
+
<Card>
|
|
117
|
+
<CardHeader>
|
|
118
|
+
<Skeleton className="h-5 w-24" />
|
|
119
|
+
</CardHeader>
|
|
120
|
+
<CardContent className="space-y-4">
|
|
121
|
+
<Skeleton className="h-4 w-full" />
|
|
122
|
+
<Skeleton className="h-4 w-3/4" />
|
|
123
|
+
</CardContent>
|
|
124
|
+
</Card>
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div className="space-y-6">
|
|
131
|
+
<Card>
|
|
132
|
+
<CardHeader>
|
|
133
|
+
<CardTitle>General</CardTitle>
|
|
134
|
+
<CardDescription>
|
|
135
|
+
Application settings and information
|
|
136
|
+
</CardDescription>
|
|
137
|
+
</CardHeader>
|
|
138
|
+
<CardContent className="space-y-4">
|
|
139
|
+
<div className="space-y-2">
|
|
140
|
+
<Label htmlFor="name">App Name</Label>
|
|
141
|
+
<div className="flex gap-2">
|
|
142
|
+
<Input
|
|
143
|
+
id="name"
|
|
144
|
+
value={name}
|
|
145
|
+
onChange={(e) => setName(e.target.value)}
|
|
146
|
+
placeholder="Enter app name"
|
|
147
|
+
className="max-w-md"
|
|
148
|
+
/>
|
|
149
|
+
<Button onClick={handleSave} disabled={!hasChanges || isSaving}>
|
|
150
|
+
{isSaving ? 'Saving...' : 'Save'}
|
|
151
|
+
</Button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
<div>
|
|
155
|
+
<p className="text-muted-foreground text-sm font-medium">App ID</p>
|
|
156
|
+
<p className="font-mono text-sm">{app?.appId}</p>
|
|
157
|
+
</div>
|
|
158
|
+
<div className="grid grid-cols-2 gap-4">
|
|
159
|
+
<div>
|
|
160
|
+
<p className="text-muted-foreground text-sm font-medium">
|
|
161
|
+
Created
|
|
162
|
+
</p>
|
|
163
|
+
<p className="text-sm">
|
|
164
|
+
{app?.createdAt ? formatDate(app.createdAt) : '-'}
|
|
165
|
+
</p>
|
|
166
|
+
</div>
|
|
167
|
+
<div>
|
|
168
|
+
<p className="text-muted-foreground text-sm font-medium">
|
|
169
|
+
Last Updated
|
|
170
|
+
</p>
|
|
171
|
+
<p className="text-sm">
|
|
172
|
+
{app?.updatedAt ? formatDate(app.updatedAt) : '-'}
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
{app?.metadata && Object.keys(app.metadata).length > 0 && (
|
|
177
|
+
<div>
|
|
178
|
+
<p className="text-muted-foreground mb-2 text-sm font-medium">
|
|
179
|
+
Metadata
|
|
180
|
+
</p>
|
|
181
|
+
<pre className="bg-muted overflow-auto rounded-md p-2 text-xs">
|
|
182
|
+
{JSON.stringify(app.metadata, null, 2)}
|
|
183
|
+
</pre>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</CardContent>
|
|
187
|
+
</Card>
|
|
188
|
+
|
|
189
|
+
<Card>
|
|
190
|
+
<CardHeader>
|
|
191
|
+
<div className="flex items-center justify-between">
|
|
192
|
+
<div>
|
|
193
|
+
<CardTitle>API Keys</CardTitle>
|
|
194
|
+
<CardDescription>
|
|
195
|
+
Manage API keys for this application
|
|
196
|
+
</CardDescription>
|
|
197
|
+
</div>
|
|
198
|
+
<CreateApiKeyDialog
|
|
199
|
+
appId={appId}
|
|
200
|
+
onKeyCreated={fetchKeys}
|
|
201
|
+
isFirstKey={keys.length === 0 && !isLoadingKeys}
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
</CardHeader>
|
|
205
|
+
<CardContent>
|
|
206
|
+
{keys.length === 0 && !isLoadingKeys ? (
|
|
207
|
+
<div className="py-6 text-center">
|
|
208
|
+
<p className="text-muted-foreground text-sm">
|
|
209
|
+
No API keys yet. Create one to start using the API.
|
|
210
|
+
</p>
|
|
211
|
+
</div>
|
|
212
|
+
) : (
|
|
213
|
+
<ApiKeysTable
|
|
214
|
+
keys={keys}
|
|
215
|
+
isLoading={isLoadingKeys}
|
|
216
|
+
appId={appId}
|
|
217
|
+
onRevoke={fetchKeys}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
220
|
+
</CardContent>
|
|
221
|
+
</Card>
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|