@allbluecn/admin-app 0.1.0 → 0.2.1
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/package.json +9 -9
- package/src/components/admin/admin-user-menu.tsx +12 -6
- package/src/components/admin/developer-log.tsx +42 -44
- package/src/components/ai-provider/model-list-editor.tsx +5 -4
- package/src/components/ai-provider/provider-form.tsx +63 -64
- package/src/components/ai-provider/provider-table.tsx +35 -36
- package/src/components/auth/admin-login-form.tsx +23 -24
- package/src/components/auth/oauth-section.tsx +3 -5
- package/src/components/auth/password-input.tsx +2 -3
- package/src/components/common/placeholder-page.tsx +2 -4
- package/src/components/component/component-form-dialog.tsx +48 -49
- package/src/components/component/component-import-dialog.tsx +5 -5
- package/src/components/component/component-table.tsx +13 -14
- package/src/components/data-table/data-table-pagination.tsx +4 -6
- package/src/components/data-table/table-detail-layout.tsx +2 -3
- package/src/components/dependency/changelog-dialog.tsx +3 -4
- package/src/components/dependency/columns.tsx +32 -24
- package/src/components/dependency/dependency-table.tsx +37 -29
- package/src/components/dependency/edit-dialog.tsx +24 -25
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
- package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
- package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
- package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
- package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
- package/src/components/market/columns.tsx +20 -21
- package/src/components/market/market-app-form.tsx +45 -38
- package/src/components/market/market-app-list.tsx +16 -17
- package/src/components/monitoring/error-list.tsx +18 -19
- package/src/components/monitoring/health-cards.tsx +11 -13
- package/src/components/monitoring/metrics-stats.tsx +11 -12
- package/src/components/monitoring/request-log-table.tsx +8 -9
- package/src/components/ui/pagination.tsx +0 -2
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/user/columns.tsx +9 -9
- package/src/components/user/settings-activity-log.tsx +39 -29
- package/src/components/user/settings-profile.tsx +36 -36
- package/src/components/user/user-create-dialog.tsx +4 -4
- package/src/components/user/user-filters.tsx +16 -18
- package/src/lib/i18n/config.ts +0 -32
- package/src/providers/index.tsx +6 -16
- package/src/routes/__root.tsx +9 -23
- package/src/routes/_nav/apps/route.lazy.tsx +2 -3
- package/src/routes/_nav/audit/route.lazy.tsx +10 -11
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
- package/src/routes/_nav/billing/route.lazy.tsx +3 -4
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
- package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
- package/src/routes/_nav/components/route.lazy.tsx +6 -6
- package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
- package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
- package/src/routes/_nav/docs/route.lazy.tsx +2 -3
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
- package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
- package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
- package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
- package/src/routes/_nav/settings/route.lazy.tsx +2 -3
- package/src/routes/_nav/sso/route.lazy.tsx +20 -18
- package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
- package/src/routes/_nav/users/route.lazy.tsx +4 -5
- package/src/routes/setup/route.lazy.tsx +15 -16
- package/src/start.ts +2 -2
- package/src/lib/i18n/client.ts +0 -32
- package/src/lib/i18n/locales/en/admin-ai.json +0 -128
- package/src/lib/i18n/locales/en/admin-billing.json +0 -56
- package/src/lib/i18n/locales/en/admin-users.json +0 -88
- package/src/lib/i18n/locales/en/common.json +0 -877
- package/src/lib/i18n/locales/en/enterprise.json +0 -208
- package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
- package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
- package/src/lib/i18n/locales/zh/admin-users.json +0 -88
- package/src/lib/i18n/locales/zh/common.json +0 -877
- package/src/lib/i18n/locales/zh/enterprise.json +0 -208
- package/src/lib/i18n/server.ts +0 -18
- package/src/lib/i18n/types.ts +0 -5
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { createLazyFileRoute, useNavigate, useRouter } from '@tanstack/react-router'
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
19
20
|
import { useState, useTransition } from 'react'
|
|
20
|
-
import { useTranslation } from 'react-i18next'
|
|
21
21
|
import { PlusIcon, PencilIcon, Trash2Icon, SearchIcon } from 'lucide-react'
|
|
22
22
|
import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
|
|
23
23
|
import { Button } from '@/components/ui/button'
|
|
@@ -47,7 +47,6 @@ export const Route = createLazyFileRoute('/_nav/knowledge-public/legal')({
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
function LegalPage() {
|
|
50
|
-
const { t } = useTranslation('common')
|
|
51
50
|
const { items: rawItems, total } = Route.useLoaderData()
|
|
52
51
|
const items = rawItems as unknown as LegalRow[]
|
|
53
52
|
const { search } = Route.useSearch()
|
|
@@ -84,11 +83,11 @@ function LegalPage() {
|
|
|
84
83
|
startDelete(async () => {
|
|
85
84
|
try {
|
|
86
85
|
await adminDeleteLegalFn({ data: { id: deleting.id } })
|
|
87
|
-
toast.success(
|
|
86
|
+
toast.success(m.common_common_deleted({ name: m.common_knowledgePublic_jurisdictionLabel() }))
|
|
88
87
|
setDeleting(null)
|
|
89
88
|
refresh()
|
|
90
89
|
} catch (err) {
|
|
91
|
-
toast.error(err instanceof Error ? err.message :
|
|
90
|
+
toast.error(err instanceof Error ? err.message : m.common_common_deleteFailed())
|
|
92
91
|
}
|
|
93
92
|
})
|
|
94
93
|
}
|
|
@@ -96,13 +95,13 @@ function LegalPage() {
|
|
|
96
95
|
const columns: AdminColumnDef<LegalRow>[] = [
|
|
97
96
|
{
|
|
98
97
|
accessorKey: 'title',
|
|
99
|
-
header:
|
|
100
|
-
cell: ({ row }) => <span className="font-medium">{row.original.title}</span>,
|
|
98
|
+
header: m.common_knowledgePublic_legal_columns_title(),
|
|
99
|
+
cell: ({ row }: any) => <span className="font-medium">{row.original.title}</span>,
|
|
101
100
|
},
|
|
102
101
|
{
|
|
103
102
|
accessorKey: 'jurisdiction',
|
|
104
|
-
header:
|
|
105
|
-
cell: ({ row }) => (
|
|
103
|
+
header: m.common_knowledgePublic_legal_columns_jurisdiction(),
|
|
104
|
+
cell: ({ row }: any) => (
|
|
106
105
|
<Badge variant="outline">
|
|
107
106
|
{row.original.jurisdiction}
|
|
108
107
|
</Badge>
|
|
@@ -110,20 +109,20 @@ function LegalPage() {
|
|
|
110
109
|
},
|
|
111
110
|
{
|
|
112
111
|
accessorKey: 'category',
|
|
113
|
-
header:
|
|
114
|
-
cell: ({ row }) => (
|
|
112
|
+
header: m.common_knowledgePublic_legal_columns_category(),
|
|
113
|
+
cell: ({ row }: any) => (
|
|
115
114
|
<span className="text-muted-foreground">{row.original.category}</span>
|
|
116
115
|
),
|
|
117
116
|
},
|
|
118
117
|
{
|
|
119
118
|
accessorKey: 'tags',
|
|
120
|
-
header:
|
|
121
|
-
cell: ({ row }) => (
|
|
119
|
+
header: m.common_knowledgePublic_legal_columns_tags(),
|
|
120
|
+
cell: ({ row }: any) => (
|
|
122
121
|
<div className="flex max-w-xs flex-wrap gap-1">
|
|
123
122
|
{row.original.tags.length === 0 ? (
|
|
124
123
|
<span className="text-muted-foreground">-</span>
|
|
125
124
|
) : (
|
|
126
|
-
row.original.tags.map((tag) => (
|
|
125
|
+
row.original.tags.map((tag: string) => (
|
|
127
126
|
<Badge key={tag} variant="secondary" className="font-normal">
|
|
128
127
|
{tag}
|
|
129
128
|
</Badge>
|
|
@@ -134,8 +133,8 @@ function LegalPage() {
|
|
|
134
133
|
},
|
|
135
134
|
{
|
|
136
135
|
accessorKey: 'createdAt',
|
|
137
|
-
header:
|
|
138
|
-
cell: ({ row }) => (
|
|
136
|
+
header: m.common_knowledgePublic_legal_columns_createdAt(),
|
|
137
|
+
cell: ({ row }: any) => (
|
|
139
138
|
<span className="text-muted-foreground">
|
|
140
139
|
{new Date(row.original.createdAt).toLocaleDateString('zh-CN')}
|
|
141
140
|
</span>
|
|
@@ -144,30 +143,30 @@ function LegalPage() {
|
|
|
144
143
|
{
|
|
145
144
|
id: 'actions',
|
|
146
145
|
header: '',
|
|
147
|
-
cell: ({ row }) => (
|
|
146
|
+
cell: ({ row }: any) => (
|
|
148
147
|
<div className="flex items-center gap-1">
|
|
149
148
|
<Button
|
|
150
149
|
variant="ghost"
|
|
151
150
|
size="sm"
|
|
152
|
-
onClick={(e) => {
|
|
151
|
+
onClick={(e: React.MouseEvent) => {
|
|
153
152
|
e.stopPropagation()
|
|
154
153
|
openEdit(row.original)
|
|
155
154
|
}}
|
|
156
155
|
>
|
|
157
156
|
<PencilIcon className="mr-1 size-3.5" />
|
|
158
|
-
{
|
|
157
|
+
{m.common_common_edit()}
|
|
159
158
|
</Button>
|
|
160
159
|
<Button
|
|
161
160
|
variant="ghost"
|
|
162
161
|
size="sm"
|
|
163
162
|
className="text-destructive hover:text-destructive"
|
|
164
|
-
onClick={(e) => {
|
|
163
|
+
onClick={(e: React.MouseEvent) => {
|
|
165
164
|
e.stopPropagation()
|
|
166
165
|
setDeleting(row.original)
|
|
167
166
|
}}
|
|
168
167
|
>
|
|
169
168
|
<Trash2Icon className="mr-1 size-3.5" />
|
|
170
|
-
{
|
|
169
|
+
{m.common_common_delete()}
|
|
171
170
|
</Button>
|
|
172
171
|
</div>
|
|
173
172
|
),
|
|
@@ -178,12 +177,12 @@ function LegalPage() {
|
|
|
178
177
|
<div className="space-y-4 p-6">
|
|
179
178
|
<div className="flex items-center justify-between">
|
|
180
179
|
<div>
|
|
181
|
-
<h2 className="text-xl font-bold">{
|
|
182
|
-
<p className="text-sm text-muted-foreground">{
|
|
180
|
+
<h2 className="text-xl font-bold">{m.common_knowledgePublic_legal_title()}</h2>
|
|
181
|
+
<p className="text-sm text-muted-foreground">{m.common_knowledgePublic_legal_subtitle({ total })}</p>
|
|
183
182
|
</div>
|
|
184
183
|
<Button onClick={openCreate}>
|
|
185
184
|
<PlusIcon className="mr-1 size-4" />
|
|
186
|
-
{
|
|
185
|
+
{m.common_common_create()}
|
|
187
186
|
</Button>
|
|
188
187
|
</div>
|
|
189
188
|
|
|
@@ -192,16 +191,16 @@ function LegalPage() {
|
|
|
192
191
|
<SearchIcon className="absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
193
192
|
<Input
|
|
194
193
|
value={searchInput}
|
|
195
|
-
onChange={(e) => setSearchInput(e.target.value)}
|
|
196
|
-
onKeyDown={(e) => {
|
|
194
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchInput(e.target.value)}
|
|
195
|
+
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
197
196
|
if (e.key === 'Enter') applySearch()
|
|
198
197
|
}}
|
|
199
|
-
placeholder={
|
|
198
|
+
placeholder={m.common_knowledgePublic_legal_placeholder()}
|
|
200
199
|
className="pl-8"
|
|
201
200
|
/>
|
|
202
201
|
</div>
|
|
203
202
|
<Button variant="outline" onClick={applySearch}>
|
|
204
|
-
{
|
|
203
|
+
{m.common_common_search()}
|
|
205
204
|
</Button>
|
|
206
205
|
</div>
|
|
207
206
|
|
|
@@ -214,25 +213,25 @@ function LegalPage() {
|
|
|
214
213
|
onSuccess={refresh}
|
|
215
214
|
/>
|
|
216
215
|
|
|
217
|
-
<AlertDialog open={!!deleting} onOpenChange={(o) => !o && setDeleting(null)}>
|
|
216
|
+
<AlertDialog open={!!deleting} onOpenChange={(o: boolean) => !o && setDeleting(null)}>
|
|
218
217
|
<AlertDialogContent>
|
|
219
218
|
<AlertDialogHeader>
|
|
220
|
-
<AlertDialogTitle>{
|
|
219
|
+
<AlertDialogTitle>{m.common_knowledgePublic_legal_deleteConfirmTitle()}</AlertDialogTitle>
|
|
221
220
|
<AlertDialogDescription>
|
|
222
|
-
{
|
|
221
|
+
{m.common_knowledgePublic_legal_deleteConfirmDesc({ title: deleting?.title ?? '' })}
|
|
223
222
|
</AlertDialogDescription>
|
|
224
223
|
</AlertDialogHeader>
|
|
225
224
|
<AlertDialogFooter>
|
|
226
|
-
<AlertDialogCancel disabled={deletePending}>{
|
|
225
|
+
<AlertDialogCancel disabled={deletePending}>{m.common_common_cancel()}</AlertDialogCancel>
|
|
227
226
|
<AlertDialogAction
|
|
228
|
-
onClick={(e) => {
|
|
227
|
+
onClick={(e: React.MouseEvent) => {
|
|
229
228
|
e.preventDefault()
|
|
230
229
|
confirmDelete()
|
|
231
230
|
}}
|
|
232
231
|
disabled={deletePending}
|
|
233
232
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
234
233
|
>
|
|
235
|
-
{deletePending ?
|
|
234
|
+
{deletePending ? m.common_common_deleting() : m.common_common_delete()}
|
|
236
235
|
</AlertDialogAction>
|
|
237
236
|
</AlertDialogFooter>
|
|
238
237
|
</AlertDialogContent>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { createLazyFileRoute, Link, useNavigate } from '@tanstack/react-router'
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { useServerFn } from '@tanstack/react-start'
|
|
21
21
|
import { toast } from 'sonner'
|
|
22
22
|
import type React from 'react'
|
|
@@ -34,7 +34,6 @@ export const Route = createLazyFileRoute('/_nav/licenses/$licenseId')({
|
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
function LicenseDetail(): React.ReactElement {
|
|
37
|
-
const { t } = useTranslation('enterprise')
|
|
38
37
|
const navigate = useNavigate()
|
|
39
38
|
const license = Route.useLoaderData() as {
|
|
40
39
|
id: string
|
|
@@ -66,11 +65,11 @@ function LicenseDetail(): React.ReactElement {
|
|
|
66
65
|
|
|
67
66
|
const handleRevoke = async () => {
|
|
68
67
|
if (license.revokedAt) {
|
|
69
|
-
toast.info(
|
|
68
|
+
toast.info(m.enterprise_license_revokedToast())
|
|
70
69
|
return
|
|
71
70
|
}
|
|
72
71
|
await revokeFn({ data: { licenseId: license.id } })
|
|
73
|
-
toast.success(
|
|
72
|
+
toast.success(m.enterprise_license_revokedSuccess())
|
|
74
73
|
await navigate({ to: '/licenses' })
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -85,7 +84,7 @@ function LicenseDetail(): React.ReactElement {
|
|
|
85
84
|
<div className="flex items-center justify-between">
|
|
86
85
|
<div className="flex items-center gap-3">
|
|
87
86
|
<Link to="/licenses">
|
|
88
|
-
<Button variant="ghost" size="sm">{
|
|
87
|
+
<Button variant="ghost" size="sm">{m.common_button_back()}</Button>
|
|
89
88
|
</Link>
|
|
90
89
|
<h1 className="text-2xl font-bold">{license.customerName}</h1>
|
|
91
90
|
</div>
|
|
@@ -94,46 +93,46 @@ function LicenseDetail(): React.ReactElement {
|
|
|
94
93
|
onClick={handleRevoke}
|
|
95
94
|
disabled={!!license.revokedAt}
|
|
96
95
|
>
|
|
97
|
-
{license.revokedAt ?
|
|
96
|
+
{license.revokedAt ? m.enterprise_license_revoked() : m.enterprise_license_revoke()}
|
|
98
97
|
</Button>
|
|
99
98
|
</div>
|
|
100
99
|
|
|
101
100
|
<div className="grid grid-cols-2 gap-4">
|
|
102
101
|
<Card>
|
|
103
102
|
<CardHeader>
|
|
104
|
-
<CardTitle className="text-base">{
|
|
103
|
+
<CardTitle className="text-base">{m.enterprise_license_basicInfo()}</CardTitle>
|
|
105
104
|
</CardHeader>
|
|
106
105
|
<CardContent className="space-y-2 text-sm">
|
|
107
106
|
<div className="flex justify-between">
|
|
108
|
-
<span className="text-muted-foreground">{
|
|
107
|
+
<span className="text-muted-foreground">{m.enterprise_license_email()}</span>
|
|
109
108
|
<span>{license.customerEmail}</span>
|
|
110
109
|
</div>
|
|
111
110
|
<div className="flex justify-between">
|
|
112
|
-
<span className="text-muted-foreground">{
|
|
111
|
+
<span className="text-muted-foreground">{m.enterprise_license_customerId()}</span>
|
|
113
112
|
<span>{license.customerId}</span>
|
|
114
113
|
</div>
|
|
115
114
|
<div className="flex justify-between">
|
|
116
|
-
<span className="text-muted-foreground">{
|
|
115
|
+
<span className="text-muted-foreground">{m.enterprise_license_plan()}</span>
|
|
117
116
|
<span>{license.plan}</span>
|
|
118
117
|
</div>
|
|
119
118
|
<div className="flex justify-between">
|
|
120
|
-
<span className="text-muted-foreground">{
|
|
119
|
+
<span className="text-muted-foreground">{m.enterprise_license_seats()}</span>
|
|
121
120
|
<span>{license.seats}</span>
|
|
122
121
|
</div>
|
|
123
122
|
<div className="flex justify-between">
|
|
124
|
-
<span className="text-muted-foreground">{
|
|
123
|
+
<span className="text-muted-foreground">{m.enterprise_license_issuedAt()}</span>
|
|
125
124
|
<span>{new Date(license.issuedAt).toLocaleDateString()}</span>
|
|
126
125
|
</div>
|
|
127
126
|
<div className="flex justify-between">
|
|
128
|
-
<span className="text-muted-foreground">{
|
|
127
|
+
<span className="text-muted-foreground">{m.enterprise_license_expiresAt()}</span>
|
|
129
128
|
<span>
|
|
130
129
|
{license.expiresAt
|
|
131
130
|
? new Date(license.expiresAt).toLocaleDateString()
|
|
132
|
-
:
|
|
131
|
+
: m.enterprise_license_permanent()}
|
|
133
132
|
</span>
|
|
134
133
|
</div>
|
|
135
134
|
<div className="flex justify-between">
|
|
136
|
-
<span className="text-muted-foreground">{
|
|
135
|
+
<span className="text-muted-foreground">{m.enterprise_license_issuer()}</span>
|
|
137
136
|
<span>{license.issuedBy?.username || '—'}</span>
|
|
138
137
|
</div>
|
|
139
138
|
</CardContent>
|
|
@@ -142,7 +141,7 @@ function LicenseDetail(): React.ReactElement {
|
|
|
142
141
|
<Card>
|
|
143
142
|
<CardHeader>
|
|
144
143
|
<CardTitle className="text-base">
|
|
145
|
-
{
|
|
144
|
+
{m.enterprise_license_featureAuthorization()}
|
|
146
145
|
</CardTitle>
|
|
147
146
|
</CardHeader>
|
|
148
147
|
<CardContent>
|
|
@@ -159,7 +158,7 @@ function LicenseDetail(): React.ReactElement {
|
|
|
159
158
|
</div>
|
|
160
159
|
) : (
|
|
161
160
|
<p className="text-sm text-muted-foreground">
|
|
162
|
-
{
|
|
161
|
+
{m.enterprise_license_noFeatureAuthorization()}
|
|
163
162
|
</p>
|
|
164
163
|
)}
|
|
165
164
|
</CardContent>
|
|
@@ -168,7 +167,7 @@ function LicenseDetail(): React.ReactElement {
|
|
|
168
167
|
|
|
169
168
|
<Card>
|
|
170
169
|
<CardHeader>
|
|
171
|
-
<CardTitle className="text-base">{
|
|
170
|
+
<CardTitle className="text-base">{m.enterprise_license_keyTitle()}</CardTitle>
|
|
172
171
|
</CardHeader>
|
|
173
172
|
<CardContent>
|
|
174
173
|
<code className="block rounded-md bg-muted p-2 text-xs break-all">
|
|
@@ -181,7 +180,7 @@ function LicenseDetail(): React.ReactElement {
|
|
|
181
180
|
<Card>
|
|
182
181
|
<CardHeader>
|
|
183
182
|
<CardTitle className="text-base">
|
|
184
|
-
{
|
|
183
|
+
{m.enterprise_license_activationsTitle({ count: license.activations.length })}
|
|
185
184
|
</CardTitle>
|
|
186
185
|
</CardHeader>
|
|
187
186
|
<CardContent>
|
|
@@ -192,35 +191,29 @@ function LicenseDetail(): React.ReactElement {
|
|
|
192
191
|
className="flex items-center justify-between rounded-md border border-border p-2 text-sm"
|
|
193
192
|
>
|
|
194
193
|
<div>
|
|
195
|
-
<span className="font-medium">{
|
|
194
|
+
<span className="font-medium">{m.enterprise_license_instance()}</span>
|
|
196
195
|
<code className="ml-2 text-muted-foreground">
|
|
197
196
|
{a.instanceId}
|
|
198
197
|
</code>
|
|
199
198
|
</div>
|
|
200
199
|
<div className="flex items-center gap-3">
|
|
201
200
|
<span className="text-muted-foreground">
|
|
202
|
-
{
|
|
203
|
-
date: new Date(a.activatedAt).toLocaleDateString(),
|
|
204
|
-
})}
|
|
201
|
+
{m.enterprise_license_activatedAt({ date: new Date(a.activatedAt).toLocaleDateString(), })}
|
|
205
202
|
</span>
|
|
206
203
|
<span className="text-muted-foreground">
|
|
207
|
-
{
|
|
208
|
-
fingerprint: a.fingerprint.slice(0, 8),
|
|
209
|
-
})}
|
|
204
|
+
{m.enterprise_license_fingerprint({ fingerprint: a.fingerprint.slice(0, 8), })}
|
|
210
205
|
</span>
|
|
211
206
|
{a.revokedAt ? (
|
|
212
207
|
<span className="text-destructive">
|
|
213
|
-
{
|
|
208
|
+
{m.enterprise_license_revoked()}
|
|
214
209
|
</span>
|
|
215
210
|
) : a.lastSeenAt ? (
|
|
216
211
|
<span className="text-muted-foreground">
|
|
217
|
-
{
|
|
218
|
-
date: new Date(a.lastSeenAt).toLocaleDateString(),
|
|
219
|
-
})}
|
|
212
|
+
{m.enterprise_license_lastActive({ date: new Date(a.lastSeenAt).toLocaleDateString(), })}
|
|
220
213
|
</span>
|
|
221
214
|
) : (
|
|
222
215
|
<span className="text-muted-foreground">
|
|
223
|
-
{
|
|
216
|
+
{m.enterprise_license_neverActive()}
|
|
224
217
|
</span>
|
|
225
218
|
)}
|
|
226
219
|
</div>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { createLazyFileRoute, Link, useNavigate } from '@tanstack/react-router'
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import type React from 'react'
|
|
21
21
|
import { useState } from 'react'
|
|
22
22
|
import { useForm } from '@tanstack/react-form'
|
|
@@ -35,7 +35,6 @@ export const Route = createLazyFileRoute('/_nav/licenses/new')({
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
function IssueLicensePage(): React.ReactElement {
|
|
38
|
-
const { t } = useTranslation('enterprise')
|
|
39
38
|
const navigate = useNavigate()
|
|
40
39
|
const issue = useServerFn(issueLicenseFn)
|
|
41
40
|
const [issuedKey, setIssuedKey] = useState<string | null>(null)
|
|
@@ -70,10 +69,10 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
70
69
|
if (issuedKey) {
|
|
71
70
|
return (
|
|
72
71
|
<div className="space-y-4">
|
|
73
|
-
<h1 className="text-2xl font-bold">{
|
|
72
|
+
<h1 className="text-2xl font-bold">{m.enterprise_issue_successTitle()}</h1>
|
|
74
73
|
<Alert>
|
|
75
|
-
<AlertTitle>{
|
|
76
|
-
<AlertDescription>{
|
|
74
|
+
<AlertTitle>{m.enterprise_issue_keyOnceTitle()}</AlertTitle>
|
|
75
|
+
<AlertDescription>{m.enterprise_issue_keyOnceDescription()}</AlertDescription>
|
|
77
76
|
</Alert>
|
|
78
77
|
<div className="flex items-center gap-2">
|
|
79
78
|
<code className="max-w-full flex-1 break-all rounded-lg border border-border bg-muted px-3 py-2 text-xs">
|
|
@@ -92,7 +91,7 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
92
91
|
</Button>
|
|
93
92
|
</div>
|
|
94
93
|
<Button onClick={() => void navigate({ to: '/licenses' })}>
|
|
95
|
-
{
|
|
94
|
+
{m.common_button_back()}
|
|
96
95
|
</Button>
|
|
97
96
|
</div>
|
|
98
97
|
)
|
|
@@ -102,15 +101,15 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
102
101
|
<div className="space-y-4">
|
|
103
102
|
<div className="flex items-center gap-3">
|
|
104
103
|
<Link to="/licenses">
|
|
105
|
-
<Button variant="ghost" size="sm">{
|
|
104
|
+
<Button variant="ghost" size="sm">{m.common_button_back()}</Button>
|
|
106
105
|
</Link>
|
|
107
|
-
<h1 className="text-2xl font-bold">{
|
|
106
|
+
<h1 className="text-2xl font-bold">{m.enterprise_issue_title()}</h1>
|
|
108
107
|
</div>
|
|
109
108
|
|
|
110
109
|
<Card>
|
|
111
110
|
<CardHeader>
|
|
112
|
-
<CardTitle>{
|
|
113
|
-
<CardDescription>{
|
|
111
|
+
<CardTitle>{m.enterprise_issue_enterpriseTitle()}</CardTitle>
|
|
112
|
+
<CardDescription>{m.enterprise_issue_description()}</CardDescription>
|
|
114
113
|
</CardHeader>
|
|
115
114
|
<CardContent>
|
|
116
115
|
<form
|
|
@@ -123,11 +122,11 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
123
122
|
<form.Field name="customerName">
|
|
124
123
|
{(field) => (
|
|
125
124
|
<div className="space-y-2">
|
|
126
|
-
<Label htmlFor={field.name}>{
|
|
125
|
+
<Label htmlFor={field.name}>{m.enterprise_issue_customerName()}</Label>
|
|
127
126
|
<Input
|
|
128
127
|
id={field.name}
|
|
129
128
|
value={field.value}
|
|
130
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
129
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
|
|
131
130
|
onBlur={field.handleBlur}
|
|
132
131
|
/>
|
|
133
132
|
</div>
|
|
@@ -136,12 +135,12 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
136
135
|
<form.Field name="customerEmail">
|
|
137
136
|
{(field) => (
|
|
138
137
|
<div className="space-y-2">
|
|
139
|
-
<Label htmlFor={field.name}>{
|
|
138
|
+
<Label htmlFor={field.name}>{m.enterprise_issue_customerEmail()}</Label>
|
|
140
139
|
<Input
|
|
141
140
|
id={field.name}
|
|
142
141
|
type="email"
|
|
143
142
|
value={field.value}
|
|
144
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
143
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
|
|
145
144
|
onBlur={field.handleBlur}
|
|
146
145
|
/>
|
|
147
146
|
</div>
|
|
@@ -150,11 +149,11 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
150
149
|
<form.Field name="customerId">
|
|
151
150
|
{(field) => (
|
|
152
151
|
<div className="space-y-2">
|
|
153
|
-
<Label htmlFor={field.name}>{
|
|
152
|
+
<Label htmlFor={field.name}>{m.enterprise_issue_customerId()}</Label>
|
|
154
153
|
<Input
|
|
155
154
|
id={field.name}
|
|
156
155
|
value={field.value}
|
|
157
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
156
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
|
|
158
157
|
onBlur={field.handleBlur}
|
|
159
158
|
/>
|
|
160
159
|
</div>
|
|
@@ -163,28 +162,28 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
163
162
|
<form.Field name="plan">
|
|
164
163
|
{(field) => (
|
|
165
164
|
<div className="space-y-2">
|
|
166
|
-
<Label htmlFor={field.name}>{
|
|
165
|
+
<Label htmlFor={field.name}>{m.enterprise_issue_plan()}</Label>
|
|
167
166
|
<Input
|
|
168
167
|
id={field.name}
|
|
169
168
|
value={field.value}
|
|
170
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
169
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
|
|
171
170
|
onBlur={field.handleBlur}
|
|
172
171
|
placeholder="enterprise"
|
|
173
172
|
/>
|
|
174
|
-
<p className="text-xs text-muted-foreground">{
|
|
173
|
+
<p className="text-xs text-muted-foreground">{m.enterprise_issue_planHint()}</p>
|
|
175
174
|
</div>
|
|
176
175
|
)}
|
|
177
176
|
</form.Field>
|
|
178
177
|
<form.Field name="seats">
|
|
179
178
|
{(field) => (
|
|
180
179
|
<div className="space-y-2">
|
|
181
|
-
<Label htmlFor={field.name}>{
|
|
180
|
+
<Label htmlFor={field.name}>{m.enterprise_issue_seats()}</Label>
|
|
182
181
|
<Input
|
|
183
182
|
id={field.name}
|
|
184
183
|
type="number"
|
|
185
184
|
min={1}
|
|
186
185
|
value={field.value}
|
|
187
|
-
onChange={(e) => field.handleChange(Number(e.target.value))}
|
|
186
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(Number(e.target.value))}
|
|
188
187
|
onBlur={field.handleBlur}
|
|
189
188
|
/>
|
|
190
189
|
</div>
|
|
@@ -193,22 +192,22 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
193
192
|
<form.Field name="expiresAt">
|
|
194
193
|
{(field) => (
|
|
195
194
|
<div className="space-y-2">
|
|
196
|
-
<Label htmlFor={field.name}>{
|
|
195
|
+
<Label htmlFor={field.name}>{m.enterprise_issue_expiresAt()}</Label>
|
|
197
196
|
<Input
|
|
198
197
|
id={field.name}
|
|
199
198
|
type="date"
|
|
200
199
|
value={field.value}
|
|
201
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
200
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
|
|
202
201
|
onBlur={field.handleBlur}
|
|
203
202
|
/>
|
|
204
|
-
<p className="text-xs text-muted-foreground">{
|
|
203
|
+
<p className="text-xs text-muted-foreground">{m.enterprise_issue_expiresAtHint()}</p>
|
|
205
204
|
</div>
|
|
206
205
|
)}
|
|
207
206
|
</form.Field>
|
|
208
207
|
<form.Field name="features">
|
|
209
208
|
{(field) => (
|
|
210
209
|
<div className="space-y-2">
|
|
211
|
-
<Label>{
|
|
210
|
+
<Label>{m.enterprise_issue_features()}</Label>
|
|
212
211
|
<div className="space-y-2">
|
|
213
212
|
{Object.keys(FeatureRegistry).map((key) => (
|
|
214
213
|
<label key={key} className="flex items-center gap-2 text-sm">
|
|
@@ -227,7 +226,7 @@ function IssueLicensePage(): React.ReactElement {
|
|
|
227
226
|
)}
|
|
228
227
|
</form.Field>
|
|
229
228
|
<Button type="submit" disabled={form.state.isSubmitting}>
|
|
230
|
-
{
|
|
229
|
+
{m.enterprise_issue_submit()}
|
|
231
230
|
</Button>
|
|
232
231
|
</form>
|
|
233
232
|
</CardContent>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { createLazyFileRoute, Link } from '@tanstack/react-router'
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import type React from 'react'
|
|
21
21
|
import { Button } from '@/components/ui/button'
|
|
22
22
|
|
|
@@ -30,7 +30,6 @@ export const Route = createLazyFileRoute('/_nav/licenses')({
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
function LicensesList(): React.ReactElement {
|
|
33
|
-
const { t } = useTranslation('enterprise')
|
|
34
33
|
const { items, total } = Route.useLoaderData() as {
|
|
35
34
|
items: Array<{
|
|
36
35
|
id: string
|
|
@@ -50,19 +49,19 @@ function LicensesList(): React.ReactElement {
|
|
|
50
49
|
<div className="space-y-4">
|
|
51
50
|
<div className="flex items-center justify-between">
|
|
52
51
|
<div>
|
|
53
|
-
<h1 className="text-2xl font-bold">{
|
|
52
|
+
<h1 className="text-2xl font-bold">{m.enterprise_license_managementTitle()}</h1>
|
|
54
53
|
<p className="text-sm text-muted-foreground mt-1">
|
|
55
|
-
{
|
|
54
|
+
{m.enterprise_license_count({ count: total })}
|
|
56
55
|
</p>
|
|
57
56
|
</div>
|
|
58
57
|
<Link to="/licenses/new">
|
|
59
|
-
<Button>{
|
|
58
|
+
<Button>{m.enterprise_license_issue()}</Button>
|
|
60
59
|
</Link>
|
|
61
60
|
</div>
|
|
62
61
|
|
|
63
62
|
{items.length === 0 ? (
|
|
64
63
|
<div className="rounded-lg border border-border p-8 text-center text-muted-foreground">
|
|
65
|
-
{
|
|
64
|
+
{m.enterprise_license_empty()}
|
|
66
65
|
</div>
|
|
67
66
|
) : (
|
|
68
67
|
<div className="rounded-lg border border-border overflow-hidden">
|
|
@@ -70,28 +69,28 @@ function LicensesList(): React.ReactElement {
|
|
|
70
69
|
<thead>
|
|
71
70
|
<tr className="border-b border-border bg-muted/50">
|
|
72
71
|
<th className="p-3 text-left font-medium">
|
|
73
|
-
{
|
|
72
|
+
{m.enterprise_license_customer()}
|
|
74
73
|
</th>
|
|
75
74
|
<th className="p-3 text-left font-medium">
|
|
76
|
-
{
|
|
75
|
+
{m.enterprise_license_email()}
|
|
77
76
|
</th>
|
|
78
77
|
<th className="p-3 text-left font-medium">
|
|
79
|
-
{
|
|
78
|
+
{m.enterprise_license_plan()}
|
|
80
79
|
</th>
|
|
81
80
|
<th className="p-3 text-left font-medium">
|
|
82
|
-
{
|
|
81
|
+
{m.enterprise_license_seats()}
|
|
83
82
|
</th>
|
|
84
83
|
<th className="p-3 text-left font-medium">
|
|
85
|
-
{
|
|
84
|
+
{m.enterprise_license_activationCount()}
|
|
86
85
|
</th>
|
|
87
86
|
<th className="p-3 text-left font-medium">
|
|
88
|
-
{
|
|
87
|
+
{m.enterprise_license_expiresAtLabel()}
|
|
89
88
|
</th>
|
|
90
89
|
<th className="p-3 text-left font-medium">
|
|
91
|
-
{
|
|
90
|
+
{m.enterprise_license_status()}
|
|
92
91
|
</th>
|
|
93
92
|
<th className="p-3 text-left font-medium">
|
|
94
|
-
{
|
|
93
|
+
{m.enterprise_license_action()}
|
|
95
94
|
</th>
|
|
96
95
|
</tr>
|
|
97
96
|
</thead>
|
|
@@ -110,16 +109,16 @@ function LicensesList(): React.ReactElement {
|
|
|
110
109
|
<td className="p-3">
|
|
111
110
|
{license.expiresAt
|
|
112
111
|
? new Date(license.expiresAt).toLocaleDateString()
|
|
113
|
-
:
|
|
112
|
+
: m.enterprise_license_permanent()}
|
|
114
113
|
</td>
|
|
115
114
|
<td className="p-3">
|
|
116
115
|
{license.revokedAt ? (
|
|
117
116
|
<span className="inline-flex rounded-md bg-destructive/10 px-2 py-0.5 text-xs text-destructive">
|
|
118
|
-
{
|
|
117
|
+
{m.enterprise_license_revoked()}
|
|
119
118
|
</span>
|
|
120
119
|
) : (
|
|
121
120
|
<span className="inline-flex rounded-md bg-green-500/10 px-2 py-0.5 text-xs text-green-600 dark:text-green-400">
|
|
122
|
-
{
|
|
121
|
+
{m.enterprise_license_active()}
|
|
123
122
|
</span>
|
|
124
123
|
)}
|
|
125
124
|
</td>
|
|
@@ -129,7 +128,7 @@ function LicensesList(): React.ReactElement {
|
|
|
129
128
|
params={{ licenseId: license.id }}
|
|
130
129
|
className="text-primary hover:underline"
|
|
131
130
|
>
|
|
132
|
-
{
|
|
131
|
+
{m.enterprise_license_detail()}
|
|
133
132
|
</Link>
|
|
134
133
|
</td>
|
|
135
134
|
</tr>
|