feat(subscription): add admin quota reset actions (#5952)
* feat(subscription): add admin quota reset actions * fix(subscription): keep quota reset in plan row actions * refactor(subscription): move user subscription actions into menu
This commit is contained in:
+25
@@ -24,6 +24,9 @@ import type {
|
||||
PlanPayload,
|
||||
UserSubscriptionRecord,
|
||||
CreateUserSubscriptionRequest,
|
||||
ResetUserSubscriptionsRequest,
|
||||
ResetPlanSubscriptionsRequest,
|
||||
SubscriptionResetResult,
|
||||
SubscriptionPayResponse,
|
||||
SubscriptionPayRequest,
|
||||
SelfSubscriptionData,
|
||||
@@ -105,6 +108,28 @@ export async function deleteUserSubscription(
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function resetUserSubscriptionsByPlan(
|
||||
userId: number,
|
||||
data: ResetUserSubscriptionsRequest
|
||||
): Promise<ApiResponse<SubscriptionResetResult>> {
|
||||
const res = await api.post(
|
||||
`/api/subscription/admin/users/${userId}/subscriptions/reset`,
|
||||
data
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function resetPlanSubscriptions(
|
||||
planId: number,
|
||||
data: ResetPlanSubscriptionsRequest
|
||||
): Promise<ApiResponse<SubscriptionResetResult>> {
|
||||
const res = await api.post(
|
||||
`/api/subscription/admin/plans/${planId}/subscriptions/reset`,
|
||||
data
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// User-facing Subscription Payment
|
||||
// ============================================================================
|
||||
|
||||
+23
-1
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import type { Row } from '@tanstack/react-table'
|
||||
import { Pencil, Power, PowerOff } from 'lucide-react'
|
||||
import { Pencil, Power, PowerOff, RotateCcw } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -50,6 +50,11 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
setOpen('toggle-status')
|
||||
}
|
||||
|
||||
const handleResetSubscriptions = () => {
|
||||
setCurrentRow(row.original)
|
||||
setOpen('reset-subscriptions')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='-ml-1.5 flex items-center gap-1'>
|
||||
<Tooltip>
|
||||
@@ -69,6 +74,23 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
<TooltipContent>{t('Edit')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='icon-sm'
|
||||
disabled={!complianceConfirmed}
|
||||
onClick={handleResetSubscriptions}
|
||||
aria-label={t('Reset subscription quota')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RotateCcw />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('Reset subscription quota')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
|
||||
import { resetPlanSubscriptions } from '../../api'
|
||||
import { useSubscriptions } from '../subscriptions-provider'
|
||||
|
||||
export function ResetSubscriptionsDialog() {
|
||||
const { t } = useTranslation()
|
||||
const { open, setOpen, currentRow, triggerRefresh } = useSubscriptions()
|
||||
const [advanceResetTime, setAdvanceResetTime] = useState(true)
|
||||
const [resetting, setResetting] = useState(false)
|
||||
const isOpen = open === 'reset-subscriptions'
|
||||
const plan = currentRow?.plan
|
||||
const planLabel = plan?.title || (plan?.id ? `#${plan.id}` : '-')
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) setAdvanceResetTime(true)
|
||||
}, [isOpen])
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!plan?.id) return
|
||||
setResetting(true)
|
||||
try {
|
||||
const res = await resetPlanSubscriptions(plan.id, {
|
||||
advance_reset_time: advanceResetTime,
|
||||
})
|
||||
if (res.success) {
|
||||
toast.success(
|
||||
t('Reset {{count}} active subscriptions', {
|
||||
count: res.data?.reset_count || 0,
|
||||
})
|
||||
)
|
||||
triggerRefresh()
|
||||
setOpen(null)
|
||||
}
|
||||
} catch {
|
||||
toast.error(t('Operation failed'))
|
||||
} finally {
|
||||
setResetting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
open={isOpen}
|
||||
onOpenChange={(nextOpen) => !nextOpen && setOpen(null)}
|
||||
title={t('Reset subscription quota')}
|
||||
desc={t('Reset all active subscriptions under {{plan}}?', {
|
||||
plan: planLabel,
|
||||
})}
|
||||
confirmText={t('Reset quota')}
|
||||
handleConfirm={handleConfirm}
|
||||
disabled={!plan?.id}
|
||||
isLoading={resetting}
|
||||
>
|
||||
<label className='flex items-center justify-between gap-3 rounded-md border px-3 py-2 text-sm'>
|
||||
<span>{t('Advance next reset time')}</span>
|
||||
<Switch
|
||||
checked={advanceResetTime}
|
||||
onCheckedChange={(checked) => setAdvanceResetTime(!!checked)}
|
||||
aria-label={t('Advance next reset time')}
|
||||
/>
|
||||
</label>
|
||||
</ConfirmDialog>
|
||||
)
|
||||
}
|
||||
+106
-24
@@ -16,13 +16,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { Plus } from 'lucide-react'
|
||||
import { Ban, Plus, RotateCcw, Trash2 } from 'lucide-react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { StaticDataTable } from '@/components/data-table'
|
||||
import { DataTableRowActionMenu, StaticDataTable } from '@/components/data-table'
|
||||
import {
|
||||
sideDrawerContentClassName,
|
||||
sideDrawerFormClassName,
|
||||
@@ -31,6 +31,11 @@ import {
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { TableId } from '@/components/table-id'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -46,6 +51,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
} from '@/components/ui/sheet'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { formatQuota } from '@/lib/format'
|
||||
|
||||
import {
|
||||
@@ -54,6 +60,7 @@ import {
|
||||
createUserSubscription,
|
||||
invalidateUserSubscription,
|
||||
deleteUserSubscription,
|
||||
resetUserSubscriptionsByPlan,
|
||||
} from '../../api'
|
||||
import { formatTimestamp } from '../../lib'
|
||||
import type { PlanRecord, UserSubscriptionRecord } from '../../types'
|
||||
@@ -73,7 +80,7 @@ function SubscriptionStatusBadge(props: {
|
||||
const now = Date.now() / 1000
|
||||
const isExpired = (props.sub.end_time || 0) > 0 && props.sub.end_time < now
|
||||
const isActive = props.sub.status === 'active' && !isExpired
|
||||
if (isActive)
|
||||
if (isActive) {
|
||||
return (
|
||||
<StatusBadge
|
||||
label={props.t('Active')}
|
||||
@@ -81,7 +88,8 @@ function SubscriptionStatusBadge(props: {
|
||||
copyable={false}
|
||||
/>
|
||||
)
|
||||
if (props.sub.status === 'cancelled')
|
||||
}
|
||||
if (props.sub.status === 'cancelled') {
|
||||
return (
|
||||
<StatusBadge
|
||||
label={props.t('Invalidated')}
|
||||
@@ -89,6 +97,7 @@ function SubscriptionStatusBadge(props: {
|
||||
copyable={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<StatusBadge
|
||||
label={props.t('Expired')}
|
||||
@@ -105,6 +114,12 @@ export function UserSubscriptionsDialog(props: Props) {
|
||||
const [plans, setPlans] = useState<PlanRecord[]>([])
|
||||
const [subs, setSubs] = useState<UserSubscriptionRecord[]>([])
|
||||
const [selectedPlanId, setSelectedPlanId] = useState<string>('')
|
||||
const [resetting, setResetting] = useState(false)
|
||||
const [advanceResetTime, setAdvanceResetTime] = useState(true)
|
||||
const [resetAction, setResetAction] = useState<{
|
||||
planId: number
|
||||
planTitle: string
|
||||
} | null>(null)
|
||||
const [confirmAction, setConfirmAction] = useState<{
|
||||
type: 'invalidate' | 'delete'
|
||||
subId: number
|
||||
@@ -190,6 +205,31 @@ export function UserSubscriptionsDialog(props: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetConfirm = async () => {
|
||||
if (!props.user?.id || !resetAction) return
|
||||
setResetting(true)
|
||||
try {
|
||||
const res = await resetUserSubscriptionsByPlan(props.user.id, {
|
||||
plan_id: resetAction.planId,
|
||||
advance_reset_time: advanceResetTime,
|
||||
})
|
||||
if (res.success) {
|
||||
toast.success(
|
||||
t('Reset {{count}} active subscriptions', {
|
||||
count: res.data?.reset_count || 0,
|
||||
})
|
||||
)
|
||||
await loadData()
|
||||
props.onSuccess?.()
|
||||
}
|
||||
} catch {
|
||||
toast.error(t('Operation failed'))
|
||||
} finally {
|
||||
setResetting(false)
|
||||
setResetAction(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sheet open={props.open} onOpenChange={props.onOpenChange}>
|
||||
@@ -204,17 +244,15 @@ export function UserSubscriptionsDialog(props: Props) {
|
||||
<div className={sideDrawerFormClassName()}>
|
||||
<div className='flex gap-2'>
|
||||
<Select
|
||||
items={[
|
||||
...plans.map((p) => ({
|
||||
value: String(p.plan.id),
|
||||
label: (
|
||||
<>
|
||||
{p.plan.title}($
|
||||
{Number(p.plan.price_amount || 0).toFixed(2)})
|
||||
</>
|
||||
),
|
||||
})),
|
||||
]}
|
||||
items={plans.map((p) => ({
|
||||
value: String(p.plan.id),
|
||||
label: (
|
||||
<>
|
||||
{p.plan.title}($
|
||||
{Number(p.plan.price_amount || 0).toFixed(2)})
|
||||
</>
|
||||
),
|
||||
}))}
|
||||
value={selectedPlanId}
|
||||
onValueChange={(v) => v !== null && setSelectedPlanId(v)}
|
||||
>
|
||||
@@ -322,10 +360,25 @@ export function UserSubscriptionsDialog(props: Props) {
|
||||
const isActive = sub.status === 'active' && !isExpired
|
||||
|
||||
return (
|
||||
<div className='flex justify-end gap-1'>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='outline'
|
||||
<DataTableRowActionMenu ariaLabel={t('Actions')}>
|
||||
<DropdownMenuItem
|
||||
disabled={!isActive}
|
||||
onClick={() => {
|
||||
setAdvanceResetTime(true)
|
||||
setResetAction({
|
||||
planId: sub.plan_id,
|
||||
planTitle:
|
||||
planTitleMap.get(sub.plan_id) ||
|
||||
`#${sub.plan_id}`,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{t('Reset quota')}
|
||||
<DropdownMenuShortcut>
|
||||
<RotateCcw size={16} />
|
||||
</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={!isActive}
|
||||
onClick={() =>
|
||||
setConfirmAction({
|
||||
@@ -335,9 +388,12 @@ export function UserSubscriptionsDialog(props: Props) {
|
||||
}
|
||||
>
|
||||
{t('Invalidate')}
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
<DropdownMenuShortcut>
|
||||
<Ban size={16} />
|
||||
</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant='destructive'
|
||||
onClick={() =>
|
||||
setConfirmAction({
|
||||
@@ -347,8 +403,11 @@ export function UserSubscriptionsDialog(props: Props) {
|
||||
}
|
||||
>
|
||||
{t('Delete')}
|
||||
</Button>
|
||||
</div>
|
||||
<DropdownMenuShortcut>
|
||||
<Trash2 size={16} />
|
||||
</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DataTableRowActionMenu>
|
||||
)
|
||||
},
|
||||
},
|
||||
@@ -380,6 +439,29 @@ export function UserSubscriptionsDialog(props: Props) {
|
||||
destructive={confirmAction.type === 'delete'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{resetAction && (
|
||||
<ConfirmDialog
|
||||
open
|
||||
onOpenChange={(v) => !v && setResetAction(null)}
|
||||
title={t('Reset subscription quota')}
|
||||
desc={t('Reset active {{plan}} subscriptions for this user?', {
|
||||
plan: resetAction.planTitle,
|
||||
})}
|
||||
confirmText={t('Reset quota')}
|
||||
handleConfirm={handleResetConfirm}
|
||||
isLoading={resetting}
|
||||
>
|
||||
<label className='flex items-center justify-between gap-3 rounded-md border px-3 py-2 text-sm'>
|
||||
<span>{t('Advance next reset time')}</span>
|
||||
<Switch
|
||||
checked={advanceResetTime}
|
||||
onCheckedChange={(checked) => setAdvanceResetTime(!!checked)}
|
||||
aria-label={t('Advance next reset time')}
|
||||
/>
|
||||
</label>
|
||||
</ConfirmDialog>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { ResetSubscriptionsDialog } from './dialogs/reset-subscriptions-dialog'
|
||||
import { ToggleStatusDialog } from './dialogs/toggle-status-dialog'
|
||||
import { SubscriptionsMutateDrawer } from './subscriptions-mutate-drawer'
|
||||
import { useSubscriptions } from './subscriptions-provider'
|
||||
@@ -32,6 +33,7 @@ export function SubscriptionsDialogs() {
|
||||
currentRow={isUpdate ? currentRow || undefined : undefined}
|
||||
/>
|
||||
<ToggleStatusDialog />
|
||||
<ResetSubscriptionsDialog />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+22
-1
@@ -117,6 +117,23 @@ export interface CreateUserSubscriptionRequest {
|
||||
plan_id: number
|
||||
}
|
||||
|
||||
export interface ResetUserSubscriptionsRequest {
|
||||
plan_id: number
|
||||
advance_reset_time: boolean
|
||||
}
|
||||
|
||||
export interface ResetPlanSubscriptionsRequest {
|
||||
advance_reset_time: boolean
|
||||
}
|
||||
|
||||
export interface SubscriptionResetResult {
|
||||
plan_id: number
|
||||
matched_count: number
|
||||
reset_count: number
|
||||
user_count: number
|
||||
advance_reset_time: boolean
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Self Subscription Data (user-facing)
|
||||
// ============================================================================
|
||||
@@ -131,4 +148,8 @@ export interface SelfSubscriptionData {
|
||||
// Dialog Types
|
||||
// ============================================================================
|
||||
|
||||
export type SubscriptionsDialogType = 'create' | 'update' | 'toggle-status'
|
||||
export type SubscriptionsDialogType =
|
||||
| 'create'
|
||||
| 'update'
|
||||
| 'toggle-status'
|
||||
| 'reset-subscriptions'
|
||||
|
||||
Vendored
+7
@@ -238,6 +238,7 @@
|
||||
"Administer user accounts and roles.": "Administer user accounts and roles.",
|
||||
"Administrator account": "Administrator account",
|
||||
"Administrator username": "Administrator username",
|
||||
"Advance next reset time": "Advance next reset time",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"Advanced Custom": "Advanced Custom",
|
||||
@@ -3715,8 +3716,11 @@
|
||||
"Resend ({{seconds}}s)": "Resend ({{seconds}}s)",
|
||||
"Reserved for viewing complete channel keys after secure verification.": "Reserved for viewing complete channel keys after secure verification.",
|
||||
"Reset": "Reset",
|
||||
"Reset {{count}} active subscriptions": "Reset {{count}} active subscriptions",
|
||||
"Reset 2FA": "Reset 2FA",
|
||||
"Reset 2FA for {{username}}? The user must set up 2FA again to continue using it.": "Reset 2FA for {{username}}? The user must set up 2FA again to continue using it.",
|
||||
"Reset active {{plan}} subscriptions for this user?": "Reset active {{plan}} subscriptions for this user?",
|
||||
"Reset all active subscriptions under {{plan}}?": "Reset all active subscriptions under {{plan}}?",
|
||||
"Reset all model prices?": "Reset all model prices?",
|
||||
"Reset all model ratios?": "Reset all model ratios?",
|
||||
"Reset all settings to default values": "Reset all settings to default values",
|
||||
@@ -3736,8 +3740,10 @@
|
||||
"Reset password": "Reset password",
|
||||
"Reset Period": "Reset Period",
|
||||
"Reset prices": "Reset prices",
|
||||
"Reset quota": "Reset quota",
|
||||
"Reset ratios": "Reset ratios",
|
||||
"Reset Stats": "Reset Stats",
|
||||
"Reset subscription quota": "Reset subscription quota",
|
||||
"Reset the user passkey": "Reset the user passkey",
|
||||
"Reset to default": "Reset to default",
|
||||
"Reset to Default": "Reset to Default",
|
||||
@@ -3929,6 +3935,7 @@
|
||||
"Select a timestamp before clearing logs.": "Select a timestamp before clearing logs.",
|
||||
"Select a usage mode to continue": "Select a usage mode to continue",
|
||||
"Select a verification method first": "Select a verification method first",
|
||||
"Select active subscription plan": "Select active subscription plan",
|
||||
"Select all": "Select all",
|
||||
"Select all (filtered)": "Select all (filtered)",
|
||||
"Select all models": "Select all models",
|
||||
|
||||
Vendored
+7
@@ -238,6 +238,7 @@
|
||||
"Administer user accounts and roles.": "Gérer les comptes d'utilisateurs et les rôles.",
|
||||
"Administrator account": "Compte administrateur",
|
||||
"Administrator username": "Nom d'utilisateur administrateur",
|
||||
"Advance next reset time": "Avancer la prochaine réinitialisation",
|
||||
"Advanced": "Avancé",
|
||||
"Advanced Configuration": "Configuration avancée",
|
||||
"Advanced Custom": "Personnalisé avancé",
|
||||
@@ -3715,8 +3716,11 @@
|
||||
"Resend ({{seconds}}s)": "Renvoyer ({{seconds}}s)",
|
||||
"Reserved for viewing complete channel keys after secure verification.": "Réservé à l'affichage des clés complètes des canaux après une vérification sécurisée.",
|
||||
"Reset": "Réinitialiser",
|
||||
"Reset {{count}} active subscriptions": "{{count}} abonnements actifs réinitialisés",
|
||||
"Reset 2FA": "Réinitialiser la 2FA",
|
||||
"Reset 2FA for {{username}}? The user must set up 2FA again to continue using it.": "Réinitialiser la 2FA de {{username}} ? L’utilisateur devra configurer à nouveau la 2FA pour continuer à l’utiliser.",
|
||||
"Reset active {{plan}} subscriptions for this user?": "Réinitialiser les abonnements {{plan}} actifs de cet utilisateur ?",
|
||||
"Reset all active subscriptions under {{plan}}?": "Réinitialiser tous les abonnements actifs de {{plan}} ?",
|
||||
"Reset all model prices?": "Réinitialiser tous les prix des modèles ?",
|
||||
"Reset all model ratios?": "Réinitialiser tous les ratios de modèle ?",
|
||||
"Reset all settings to default values": "Réinitialiser tous les paramètres aux valeurs par défaut",
|
||||
@@ -3736,8 +3740,10 @@
|
||||
"Reset password": "Réinitialiser le mot de passe",
|
||||
"Reset Period": "Période de réinitialisation",
|
||||
"Reset prices": "Réinitialiser les prix",
|
||||
"Reset quota": "Réinitialiser le quota",
|
||||
"Reset ratios": "Réinitialiser les ratios",
|
||||
"Reset Stats": "Réinitialiser les statistiques",
|
||||
"Reset subscription quota": "Réinitialiser le quota d'abonnement",
|
||||
"Reset the user passkey": "Clé d'accès de l'utilisateur réinitialisée",
|
||||
"Reset to default": "Réinitialiser par défaut",
|
||||
"Reset to Default": "Réinitialiser par défaut",
|
||||
@@ -3929,6 +3935,7 @@
|
||||
"Select a timestamp before clearing logs.": "Sélectionnez un horodatage avant de vider les journaux.",
|
||||
"Select a usage mode to continue": "Sélectionnez un mode d'utilisation pour continuer",
|
||||
"Select a verification method first": "Sélectionnez d'abord une méthode de vérification",
|
||||
"Select active subscription plan": "Sélectionner un forfait actif",
|
||||
"Select all": "Tout sélectionner",
|
||||
"Select all (filtered)": "Tout sélectionner (filtré)",
|
||||
"Select all models": "Sélectionner tous les modèles",
|
||||
|
||||
Vendored
+7
@@ -238,6 +238,7 @@
|
||||
"Administer user accounts and roles.": "ユーザーアカウントとロールを管理します。",
|
||||
"Administrator account": "管理者アカウント",
|
||||
"Administrator username": "管理者ユーザー名",
|
||||
"Advance next reset time": "次回リセット時刻を進める",
|
||||
"Advanced": "高度な設定",
|
||||
"Advanced Configuration": "詳細設定",
|
||||
"Advanced Custom": "高度なカスタム",
|
||||
@@ -3715,8 +3716,11 @@
|
||||
"Resend ({{seconds}}s)": "再送信 ({{seconds}}秒)",
|
||||
"Reserved for viewing complete channel keys after secure verification.": "安全な検証後に完全なチャンネルキーを表示するために予約されています。",
|
||||
"Reset": "リセット",
|
||||
"Reset {{count}} active subscriptions": "{{count}} 件の有効なサブスクリプションをリセットしました",
|
||||
"Reset 2FA": "2FAをリセット",
|
||||
"Reset 2FA for {{username}}? The user must set up 2FA again to continue using it.": "{{username}} の 2FA をリセットしますか?引き続き使用するには、2FA を再設定する必要があります。",
|
||||
"Reset active {{plan}} subscriptions for this user?": "このユーザーの有効な {{plan}} サブスクリプションをリセットしますか?",
|
||||
"Reset all active subscriptions under {{plan}}?": "{{plan}} のすべての有効なサブスクリプションをリセットしますか?",
|
||||
"Reset all model prices?": "すべてのモデル価格をリセットしますか?",
|
||||
"Reset all model ratios?": "すべてのモデル比率をリセットしますか?",
|
||||
"Reset all settings to default values": "すべての設定をデフォルト値にリセット",
|
||||
@@ -3736,8 +3740,10 @@
|
||||
"Reset password": "パスワードをリセット",
|
||||
"Reset Period": "リセット期間",
|
||||
"Reset prices": "価格をリセット",
|
||||
"Reset quota": "クォータをリセット",
|
||||
"Reset ratios": "比率をリセット",
|
||||
"Reset Stats": "統計をリセット",
|
||||
"Reset subscription quota": "サブスクリプションのクォータをリセット",
|
||||
"Reset the user passkey": "ユーザーのパスキーをリセットしました",
|
||||
"Reset to default": "デフォルトにリセット",
|
||||
"Reset to Default": "デフォルトにリセット",
|
||||
@@ -3929,6 +3935,7 @@
|
||||
"Select a timestamp before clearing logs.": "ログをクリアする前にタイムスタンプを選択してください。",
|
||||
"Select a usage mode to continue": "続行するには使用モードを選択してください",
|
||||
"Select a verification method first": "まず検証方法を選択してください",
|
||||
"Select active subscription plan": "有効なサブスクリプションプランを選択",
|
||||
"Select all": "すべて選択",
|
||||
"Select all (filtered)": "フィルタ結果をすべて選択(S)",
|
||||
"Select all models": "すべてのモデルを選択",
|
||||
|
||||
Vendored
+7
@@ -238,6 +238,7 @@
|
||||
"Administer user accounts and roles.": "Управление учетными записями пользователей и ролями.",
|
||||
"Administrator account": "Учетная запись администратора",
|
||||
"Administrator username": "Имя пользователя администратора",
|
||||
"Advance next reset time": "Перенести следующее время сброса",
|
||||
"Advanced": "Расширенные",
|
||||
"Advanced Configuration": "Расширенная конфигурация",
|
||||
"Advanced Custom": "Расширенный пользовательский",
|
||||
@@ -3715,8 +3716,11 @@
|
||||
"Resend ({{seconds}}s)": "Отправить повторно ({{seconds}}с)",
|
||||
"Reserved for viewing complete channel keys after secure verification.": "Зарезервировано для просмотра полных ключей каналов после безопасной проверки.",
|
||||
"Reset": "Сброс",
|
||||
"Reset {{count}} active subscriptions": "Сброшено активных подписок: {{count}}",
|
||||
"Reset 2FA": "Сбросить 2FA",
|
||||
"Reset 2FA for {{username}}? The user must set up 2FA again to continue using it.": "Сбросить 2FA для {{username}}? Пользователь должен будет настроить 2FA заново, чтобы продолжить ее использовать.",
|
||||
"Reset active {{plan}} subscriptions for this user?": "Сбросить активные подписки {{plan}} для этого пользователя?",
|
||||
"Reset all active subscriptions under {{plan}}?": "Сбросить все активные подписки по {{plan}}?",
|
||||
"Reset all model prices?": "Сбросить все цены моделей?",
|
||||
"Reset all model ratios?": "Сбросить все соотношения моделей?",
|
||||
"Reset all settings to default values": "Сбросить все настройки до значений по умолчанию",
|
||||
@@ -3736,8 +3740,10 @@
|
||||
"Reset password": "Сбросить пароль",
|
||||
"Reset Period": "Период сброса",
|
||||
"Reset prices": "Сбросить цены",
|
||||
"Reset quota": "Сбросить квоту",
|
||||
"Reset ratios": "Сбросить соотношения",
|
||||
"Reset Stats": "Сбросить статистику",
|
||||
"Reset subscription quota": "Сбросить квоту подписки",
|
||||
"Reset the user passkey": "Ключ доступа пользователя сброшен",
|
||||
"Reset to default": "Сбросить до значений по умолчанию",
|
||||
"Reset to Default": "Сбросить по умолчанию",
|
||||
@@ -3929,6 +3935,7 @@
|
||||
"Select a timestamp before clearing logs.": "Выберите временную метку перед очисткой журналов.",
|
||||
"Select a usage mode to continue": "Выберите режим использования для продолжения",
|
||||
"Select a verification method first": "Сначала выберите метод верификации",
|
||||
"Select active subscription plan": "Выберите активный тариф подписки",
|
||||
"Select all": "Выбрать все",
|
||||
"Select all (filtered)": "& Выбрать все отфильтрованные",
|
||||
"Select all models": "Выбрать все модели",
|
||||
|
||||
Vendored
+7
@@ -238,6 +238,7 @@
|
||||
"Administer user accounts and roles.": "Quản lý tài khoản người dùng và vai trò.",
|
||||
"Administrator account": "Tài khoản quản trị viên",
|
||||
"Administrator username": "Tên người dùng quản trị viên",
|
||||
"Advance next reset time": "Dời thời gian đặt lại tiếp theo",
|
||||
"Advanced": "Nâng cao",
|
||||
"Advanced Configuration": "Cấu hình nâng cao",
|
||||
"Advanced Custom": "Tùy chỉnh nâng cao",
|
||||
@@ -3715,8 +3716,11 @@
|
||||
"Resend ({{seconds}}s)": "Gửi lại ({{seconds}}s)",
|
||||
"Reserved for viewing complete channel keys after secure verification.": "Dành riêng để xem khóa kênh đầy đủ sau khi xác minh bảo mật.",
|
||||
"Reset": "Đặt lại",
|
||||
"Reset {{count}} active subscriptions": "Đã đặt lại {{count}} gói đăng ký đang hoạt động",
|
||||
"Reset 2FA": "Đặt lại 2FA",
|
||||
"Reset 2FA for {{username}}? The user must set up 2FA again to continue using it.": "Đặt lại 2FA cho {{username}}? Người dùng phải thiết lập lại 2FA để tiếp tục sử dụng.",
|
||||
"Reset active {{plan}} subscriptions for this user?": "Đặt lại các gói đăng ký {{plan}} đang hoạt động của người dùng này?",
|
||||
"Reset all active subscriptions under {{plan}}?": "Đặt lại tất cả gói đăng ký đang hoạt động trong {{plan}}?",
|
||||
"Reset all model prices?": "Đặt lại tất cả giá mô hình?",
|
||||
"Reset all model ratios?": "Đặt lại tất cả tỷ lệ mô hình?",
|
||||
"Reset all settings to default values": "Đặt lại tất cả cài đặt về giá trị mặc định",
|
||||
@@ -3736,8 +3740,10 @@
|
||||
"Reset password": "Đặt lại mật khẩu",
|
||||
"Reset Period": "Chu kỳ đặt lại",
|
||||
"Reset prices": "Đặt lại giá",
|
||||
"Reset quota": "Đặt lại hạn mức",
|
||||
"Reset ratios": "Đặt lại tỷ lệ",
|
||||
"Reset Stats": "Đặt lại thống kê",
|
||||
"Reset subscription quota": "Đặt lại hạn mức gói đăng ký",
|
||||
"Reset the user passkey": "Đã đặt lại passkey của người dùng",
|
||||
"Reset to default": "Đặt lại mặc định",
|
||||
"Reset to Default": "Đặt lại mặc định",
|
||||
@@ -3929,6 +3935,7 @@
|
||||
"Select a timestamp before clearing logs.": "Chọn một dấu thời gian trước khi xóa nhật ký.",
|
||||
"Select a usage mode to continue": "Chọn chế độ sử dụng để tiếp tục",
|
||||
"Select a verification method first": "Vui lòng chọn phương thức xác thực trước",
|
||||
"Select active subscription plan": "Chọn gói đăng ký đang hoạt động",
|
||||
"Select all": "Chọn tất cả",
|
||||
"Select all (filtered)": "Chọn tất cả (đã lọc)",
|
||||
"Select all models": "Chọn tất cả mô hình",
|
||||
|
||||
Vendored
+7
@@ -238,6 +238,7 @@
|
||||
"Administer user accounts and roles.": "管理用户账户和角色。",
|
||||
"Administrator account": "管理员账户",
|
||||
"Administrator username": "管理员用户名",
|
||||
"Advance next reset time": "推进下次重置时间",
|
||||
"Advanced": "高级",
|
||||
"Advanced Configuration": "高级配置",
|
||||
"Advanced Custom": "高级自定义",
|
||||
@@ -3715,8 +3716,11 @@
|
||||
"Resend ({{seconds}}s)": "重新发送 ({{seconds}}s)",
|
||||
"Reserved for viewing complete channel keys after secure verification.": "预留用于在安全验证后查看完整渠道密钥。",
|
||||
"Reset": "重置",
|
||||
"Reset {{count}} active subscriptions": "已重置 {{count}} 个有效订阅",
|
||||
"Reset 2FA": "重置 2FA",
|
||||
"Reset 2FA for {{username}}? The user must set up 2FA again to continue using it.": "要重置 {{username}} 的 2FA 吗?该用户必须重新设置 2FA 后才能继续使用。",
|
||||
"Reset active {{plan}} subscriptions for this user?": "要重置该用户的有效 {{plan}} 订阅吗?",
|
||||
"Reset all active subscriptions under {{plan}}?": "要重置 {{plan}} 下的所有有效订阅吗?",
|
||||
"Reset all model prices?": "重置所有模型价格吗?",
|
||||
"Reset all model ratios?": "重置所有模型比例吗?",
|
||||
"Reset all settings to default values": "将所有设置重置为默认值",
|
||||
@@ -3736,8 +3740,10 @@
|
||||
"Reset password": "重置密码",
|
||||
"Reset Period": "重置周期",
|
||||
"Reset prices": "重置价格",
|
||||
"Reset quota": "重置额度",
|
||||
"Reset ratios": "重置比例",
|
||||
"Reset Stats": "重置统计",
|
||||
"Reset subscription quota": "重置订阅额度",
|
||||
"Reset the user passkey": "重置了用户的通行密钥",
|
||||
"Reset to default": "重置为默认",
|
||||
"Reset to Default": "重置为默认",
|
||||
@@ -3929,6 +3935,7 @@
|
||||
"Select a timestamp before clearing logs.": "清除日志前请选择一个时间戳。",
|
||||
"Select a usage mode to continue": "选择使用模式以继续",
|
||||
"Select a verification method first": "请先选择验证方式",
|
||||
"Select active subscription plan": "选择有效订阅套餐",
|
||||
"Select all": "全选",
|
||||
"Select all (filtered)": "全选(筛选结果)",
|
||||
"Select all models": "选择所有模型",
|
||||
|
||||
Reference in New Issue
Block a user