feat: Add log cleanup settings and update localization files

- Introduced log cleanup settings in the admin panel, allowing configuration of automatic log clearing and retention periods.
- Updated English, Spanish, French, German, and other localization files to include new log cleanup settings.
- Added new fields for referral percentage and first purchase only in user settings.
- Implemented API endpoints for getting and updating log settings.
- Enhanced the admin dashboard with a new log cleanup form component.
This commit is contained in:
web 2025-09-01 10:25:04 -07:00
parent d4b37e4997
commit 6ccf9b8bdc
88 changed files with 827 additions and 125 deletions

View File

@ -6,6 +6,7 @@ import {
deleteNode,
filterNodeList,
filterServerList,
resetSortWithNode,
toggleNodeStatus,
updateNode,
} from '@/services/admin/server';
@ -235,6 +236,35 @@ export default function NodesPage() {
];
},
}}
onSort={async (source, target, items) => {
const sourceIndex = items.findIndex((item) => String(item.id) === source);
const targetIndex = items.findIndex((item) => String(item.id) === target);
const originalSorts = items.map((item) => item.sort);
const [movedItem] = items.splice(sourceIndex, 1);
items.splice(targetIndex, 0, movedItem!);
const updatedItems = items.map((item, index) => {
const originalSort = originalSorts[index];
const newSort = originalSort !== undefined ? originalSort : item.sort;
return { ...item, sort: newSort };
});
const changedItems = updatedItems.filter((item, index) => {
return item.sort !== items[index]?.sort;
});
if (changedItems.length > 0) {
resetSortWithNode({
sort: changedItems.map((item) => ({
id: item.id,
sort: item.sort,
})) as API.SortItem[],
});
}
return updatedItems;
}}
/>
);
}

View File

@ -3,7 +3,7 @@
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { IpLink } from '@/components/ip-link';
import { ProTable } from '@/components/pro-table';
import { filterServerList } from '@/services/admin/server';
import { getUserSubscribeById } from '@/services/admin/user';
import { useQuery } from '@tanstack/react-query';
import { Badge } from '@workspace/ui/components/badge';
import {
@ -13,55 +13,85 @@ import {
SheetTitle,
SheetTrigger,
} from '@workspace/ui/components/sheet';
import type { useTranslations } from 'next-intl';
import { formatBytes, formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
function mapOnlineUsers(online: API.ServerStatus['online'] = []): {
uid: string;
ips: string[];
subscribe?: string;
subscribe_id?: number;
traffic?: number;
expired_at?: number;
}[] {
return (online || []).map((u) => ({
uid: String(u.user_id || ''),
ips: Array.isArray(u.ip) ? u.ip.map(String) : [],
subscribe: (u as any).subscribe,
subscribe_id: (u as any).subscribe_id,
traffic: (u as any).traffic,
expired_at: (u as any).expired_at,
}));
}
export default function OnlineUsersCell({
serverId,
status,
t,
function UserSubscribeInfo({
subscribeId,
open,
type,
}: {
serverId?: number;
status?: API.ServerStatus;
t: ReturnType<typeof useTranslations>;
subscribeId: number;
open: boolean;
type: 'account' | 'subscribeName' | 'subscribeId' | 'trafficUsage' | 'expireTime';
}) {
const [open, setOpen] = useState(false);
const { data: latest } = useQuery({
queryKey: ['serverStatusById', serverId, open],
enabled: !!serverId && open,
const t = useTranslations('servers');
const { data } = useQuery({
enabled: subscribeId !== 0 && open,
queryKey: ['getUserSubscribeById', subscribeId],
queryFn: async () => {
const { data } = await filterServerList({ page: 1, size: 1, search: String(serverId) });
const list = (data?.data?.list || []) as API.Server[];
return list[0]?.status as API.ServerStatus | undefined;
const { data } = await getUserSubscribeById({ id: subscribeId });
return data.data;
},
});
const rows = mapOnlineUsers((latest || status)?.online);
const count = rows.length;
if (!data) return <span className='text-muted-foreground'>--</span>;
switch (type) {
case 'account':
if (!data.user_id) return <span className='text-muted-foreground'>--</span>;
return <UserDetail id={data.user_id} />;
case 'subscribeName':
if (!data.subscribe?.name) return <span className='text-muted-foreground'>--</span>;
return <span className='text-sm'>{data.subscribe.name}</span>;
case 'subscribeId':
if (!data.id) return <span className='text-muted-foreground'>--</span>;
return <span className='font-mono text-sm'>{data.id}</span>;
case 'trafficUsage': {
const usedTraffic = data.upload + data.download;
const totalTraffic = data.traffic || 0;
return (
<div className='min-w-0 text-sm'>
<div className='break-words'>
{formatBytes(usedTraffic)} / {totalTraffic > 0 ? formatBytes(totalTraffic) : '无限制'}
</div>
</div>
);
}
case 'expireTime': {
if (!data.expire_time) return <span className='text-muted-foreground'>--</span>;
const isExpired = data.expire_time < Date.now() / 1000;
return (
<div className='flex flex-col gap-1 sm:flex-row sm:items-center sm:gap-2'>
<span className='text-sm'>{formatDate(data.expire_time)}</span>
{isExpired && (
<Badge variant='destructive' className='w-fit px-1 py-0 text-xs'>
{t('expired')}
</Badge>
)}
</div>
);
}
default:
return <span className='text-muted-foreground'>--</span>;
}
}
export default function OnlineUsersCell({ status }: { status?: API.ServerStatus }) {
const t = useTranslations('servers');
const [open, setOpen] = useState(false);
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<button className='hover:text-foreground text-muted-foreground flex items-center gap-2 bg-transparent p-0 text-sm'>
<Badge variant='secondary'>{count}</Badge>
<Badge variant='secondary'>{status?.online.length}</Badge>
<span>{t('onlineUsers')}</span>
</button>
</SheetTrigger>
@ -70,36 +100,20 @@ export default function OnlineUsersCell({
<SheetTitle>{t('onlineUsers')}</SheetTitle>
</SheetHeader>
<div className='-mx-6 h-[calc(100vh-48px-16px)] overflow-y-auto px-6 py-4 sm:h-[calc(100dvh-48px-16px-env(safe-area-inset-top))]'>
<ProTable<
{
uid: string;
ips: string[];
subscribe?: string;
subscribe_id?: number;
traffic?: number;
expired_at?: number;
},
Record<string, unknown>
>
<ProTable<API.ServerOnlineUser, Record<string, unknown>>
header={{ hidden: true }}
columns={[
{
accessorKey: 'ips',
accessorKey: 'ip',
header: t('ipAddresses'),
cell: ({ row }) => {
const ips = row.original.ips;
const ips = row.original.ip;
return (
<div className='flex min-w-0 flex-col gap-1'>
{ips.map((ip, i) => (
<div
key={`${row.original.uid}-${ip}`}
className='whitespace-nowrap text-sm'
>
{i === 0 ? (
<IpLink ip={ip} className='font-medium' />
) : (
<IpLink ip={ip} className='text-muted-foreground' />
)}
{ips.map((item, i) => (
<div className='whitespace-nowrap text-sm' key={i}>
<Badge>{item.protocol}</Badge>
<IpLink ip={item.ip} className='font-medium' />
</div>
))}
</div>
@ -109,51 +123,63 @@ export default function OnlineUsersCell({
{
accessorKey: 'user',
header: t('user'),
cell: ({ row }) => <UserDetail id={Number(row.original.uid)} />,
cell: ({ row }) => (
<UserSubscribeInfo
subscribeId={Number(row.original.subscribe_id)}
open={open}
type='account'
/>
),
},
{
accessorKey: 'subscription',
header: t('subscription'),
cell: ({ row }) => (
<span className='text-sm'>{row.original.subscribe || '--'}</span>
<UserSubscribeInfo
subscribeId={Number(row.original.subscribe_id)}
open={open}
type='subscribeName'
/>
),
},
{
accessorKey: 'subscribeId',
header: t('subscribeId'),
cell: ({ row }) => (
<span className='font-mono text-sm'>{row.original.subscribe_id || '--'}</span>
<UserSubscribeInfo
subscribeId={Number(row.original.subscribe_id)}
open={open}
type='subscribeId'
/>
),
},
{
accessorKey: 'traffic',
header: t('traffic'),
cell: ({ row }) => {
const v = Number(row.original.traffic || 0);
return <span className='text-sm'>{(v / 1024 ** 3).toFixed(2)} GB</span>;
},
cell: ({ row }) => (
<UserSubscribeInfo
subscribeId={Number(row.original.subscribe_id)}
open={open}
type='trafficUsage'
/>
),
},
{
accessorKey: 'expireTime',
header: t('expireTime'),
cell: ({ row }) => {
const ts = Number(row.original.expired_at || 0);
if (!ts) return <span className='text-muted-foreground'>--</span>;
const expired = ts < Date.now() / 1000;
return (
<div className='flex items-center gap-2'>
<span className='text-sm'>{new Date(ts * 1000).toLocaleString()}</span>
{expired && (
<Badge variant='destructive' className='w-fit px-1 py-0 text-xs'>
{t('expired')}
</Badge>
)}
</div>
);
},
cell: ({ row }) => (
<UserSubscribeInfo
subscribeId={Number(row.original.subscribe_id)}
open={open}
type='expireTime'
/>
),
},
]}
request={async () => ({ list: rows, total: rows.length })}
request={async () => ({
list: status?.online || [],
total: status?.online?.length || 0,
})}
/>
</div>
</SheetContent>

View File

@ -5,6 +5,7 @@ import {
createServer,
deleteServer,
filterServerList,
resetSortWithServer,
updateServer,
} from '@/services/admin/server';
import { Badge } from '@workspace/ui/components/badge';
@ -195,13 +196,7 @@ export default function ServersPage() {
{
id: 'online_users',
header: t('onlineUsers'),
cell: ({ row }) => (
<OnlineUsersCell
serverId={row.original.id}
status={row.original.status as API.ServerStatus}
t={t}
/>
),
cell: ({ row }) => <OnlineUsersCell status={row.original.status as API.ServerStatus} />,
},
{
id: 'traffic_ratio',
@ -288,6 +283,35 @@ export default function ServersPage() {
</Button>,
],
}}
onSort={async (source, target, items) => {
const sourceIndex = items.findIndex((item) => String(item.id) === source);
const targetIndex = items.findIndex((item) => String(item.id) === target);
const originalSorts = items.map((item) => item.sort);
const [movedItem] = items.splice(sourceIndex, 1);
items.splice(targetIndex, 0, movedItem!);
const updatedItems = items.map((item, index) => {
const originalSort = originalSorts[index];
const newSort = originalSort !== undefined ? originalSort : item.sort;
return { ...item, sort: newSort };
});
const changedItems = updatedItems.filter((item, index) => {
return item.sort !== items[index]?.sort;
});
if (changedItems.length > 0) {
resetSortWithServer({
sort: changedItems.map((item) => ({
id: item.id,
sort: item.sort,
})) as API.SortItem[],
});
}
return updatedItems;
}}
/>
</div>
);

View File

@ -0,0 +1,164 @@
'use client';
import { getLogSetting, updateLogSetting } from '@/services/admin/log';
import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@workspace/ui/components/form';
import { ScrollArea } from '@workspace/ui/components/scroll-area';
import {
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@workspace/ui/components/sheet';
import { Switch } from '@workspace/ui/components/switch';
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
const logCleanupSchema = z.object({
auto_clear: z.boolean(),
clear_days: z.number().min(1),
});
type LogCleanupFormData = z.infer<typeof logCleanupSchema>;
export default function LogCleanupForm() {
const t = useTranslations('system');
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const { data, refetch } = useQuery({
queryKey: ['getLogSetting'],
queryFn: async () => {
const { data } = await getLogSetting();
return data.data;
},
enabled: open,
});
const form = useForm<LogCleanupFormData>({
resolver: zodResolver(logCleanupSchema),
defaultValues: {
auto_clear: false,
clear_days: 30,
},
});
useEffect(() => {
if (data) {
form.reset(data);
}
}, [data, form]);
async function onSubmit(values: LogCleanupFormData) {
setLoading(true);
try {
await updateLogSetting(values as API.LogSetting);
toast.success(t('common.saveSuccess'));
refetch();
setOpen(false);
} catch (error) {
toast.error(t('common.saveFailed'));
} finally {
setLoading(false);
}
}
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<div className='flex cursor-pointer items-center justify-between transition-colors'>
<div className='flex items-center gap-3'>
<div className='bg-primary/10 flex h-10 w-10 items-center justify-center rounded-lg'>
<Icon icon='mdi:delete-sweep' className='text-primary h-5 w-5' />
</div>
<div className='flex-1'>
<p className='font-medium'>{t('logCleanup.title')}</p>
<p className='text-muted-foreground text-sm'>{t('logCleanup.description')}</p>
</div>
</div>
<Icon icon='mdi:chevron-right' className='size-6' />
</div>
</SheetTrigger>
<SheetContent className='w-[600px] max-w-full md:max-w-screen-md'>
<SheetHeader>
<SheetTitle>{t('logCleanup.title')}</SheetTitle>
</SheetHeader>
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))] px-6'>
<Form {...form}>
<form
id='log-cleanup-form'
onSubmit={form.handleSubmit(onSubmit)}
className='space-y-2 pt-4'
>
<FormField
control={form.control}
name='auto_clear'
render={({ field }) => (
<FormItem>
<FormLabel>{t('logCleanup.autoClear')}</FormLabel>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
className='float-end !mt-0'
/>
</FormControl>
<FormDescription>{t('logCleanup.autoClearDescription')}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='clear_days'
render={({ field }) => (
<FormItem>
<FormLabel>{t('logCleanup.clearDays')}</FormLabel>
<FormControl>
<EnhancedInput
type='number'
placeholder={t('logCleanup.clearDaysPlaceholder')}
value={field.value?.toString()}
onValueChange={(value) => field.onChange(Number(value))}
disabled={!form.watch('auto_clear')}
/>
</FormControl>
<FormDescription>{t('logCleanup.clearDaysDescription')}</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</ScrollArea>
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
<Button variant='outline' disabled={loading} onClick={() => setOpen(false)}>
{t('common.cancel')}
</Button>
<Button disabled={loading} type='submit' form='log-cleanup-form'>
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
{t('common.save')}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}

View File

@ -6,6 +6,7 @@ import CurrencyForm from './basic-settings/currency-form';
import PrivacyPolicyForm from './basic-settings/privacy-policy-form';
import SiteForm from './basic-settings/site-form';
import TosForm from './basic-settings/tos-form';
import LogCleanupForm from './log-cleanup/log-cleanup-form';
import InviteForm from './user-security/invite-form';
import RegisterForm from './user-security/register-form';
import VerifyCodeForm from './user-security/verify-code-form';
@ -14,7 +15,6 @@ import VerifyForm from './user-security/verify-form';
export default function Page() {
const t = useTranslations('system');
// 定义表单配置
const formSections = [
{
title: t('basicSettings'),
@ -34,6 +34,10 @@ export default function Page() {
{ component: VerifyCodeForm },
],
},
{
title: t('logSettings'),
forms: [{ component: LogCleanupForm }],
},
];
return (

View File

@ -58,6 +58,8 @@ export default function UserForm<T extends Record<string, any>>({
password: z.string().optional(),
referer_id: z.number().optional(),
refer_code: z.string().optional(),
referral_percentage: z.number().optional(),
only_first_purchase: z.boolean().optional(),
is_admin: z.boolean().optional(),
balance: z.number().optional(),
gift_amount: z.number().optional(),
@ -218,6 +220,41 @@ export default function UserForm<T extends Record<string, any>>({
</FormItem>
)}
/>
<FormField
control={form.control}
name='referral_percentage'
render={({ field }) => (
<FormItem>
<FormLabel>{t('referralPercentage')}</FormLabel>
<FormControl>
<EnhancedInput
type='number'
min={0}
max={100}
placeholder={t('referralPercentagePlaceholder')}
{...field}
suffix='%'
onValueChange={(value) => {
form.setValue(field.name, Number(value));
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='only_first_purchase'
render={({ field }) => (
<FormItem className='flex items-center justify-between space-x-2'>
<FormLabel>{t('onlyFirstPurchase')}</FormLabel>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name='balance'

View File

@ -30,6 +30,8 @@ const basicInfoSchema = z.object({
gift_amount: z.number().optional(),
refer_code: z.string().optional(),
referer_id: z.number().optional(),
referral_percentage: z.number().optional(),
only_first_purchase: z.boolean().optional(),
is_admin: z.boolean().optional(),
password: z.string().optional(),
enable: z.boolean(),
@ -52,6 +54,8 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
gift_amount: user.gift_amount,
refer_code: user.refer_code,
referer_id: user.referer_id,
referral_percentage: user.referral_percentage,
only_first_purchase: user.only_first_purchase,
is_admin: user.is_admin,
enable: user.enable,
},
@ -216,6 +220,41 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
)}
/>
</div>
<FormField
control={form.control}
name='referral_percentage'
render={({ field }) => (
<FormItem>
<FormLabel>{t('referralPercentage')}</FormLabel>
<FormControl>
<EnhancedInput
type='number'
min={0}
max={100}
value={field.value}
suffix='%'
onValueChange={(value) => {
form.setValue(field.name, value as number);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='only_first_purchase'
render={({ field }) => (
<FormItem className='flex items-center justify-between space-x-2'>
<FormLabel>{t('onlyFirstPurchase')}</FormLabel>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name='avatar'

View File

@ -79,8 +79,8 @@ export default function Statistics() {
<div className='grid grid-cols-2 gap-2 md:grid-cols-4'>
{[
{
title: t('onlineIPCount'),
value: ServerTotal?.online_user_ips || 0,
title: t('onlineUsersCount'),
value: ServerTotal?.online_users || 0,
icon: 'uil:users-alt',
href: '/dashboard/servers',
},

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Provoz uzlu",
"nodes": "uzly",
"offlineNodeCount": "Počet offline uzlů",
"onlineIPCount": "Počet online IP",
"onlineNodeCount": "Počet online uzlů",
"onlineUsersCount": "Uživatelé online",
"pendingTickets": "Čekající lístky",
"register": "Registrovat se",
"repurchase": "opětovný nákup",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Uložení úspěšné",
"title": "Nastavení pozvánek"
},
"logCleanup": {
"autoClear": "Povolit automatické čištění",
"autoClearDescription": "Pokud je povoleno, systém automaticky vymaže vypršené záznamy protokolů",
"clearDays": "Dny uchovávání",
"clearDaysDescription": "Počet dní pro uchovávání protokolů; protokoly starší než toto budou vyčištěny",
"clearDaysPlaceholder": "Zadejte dny uchovávání",
"description": "Nastavte pravidla automatického čištění protokolů a dobu uchovávání",
"title": "Nastavení čištění protokolů"
},
"logSettings": "Nastavení protokolů",
"privacyPolicy": {
"description": "Upravte a spravujte obsah zásad ochrany osobních údajů",
"title": "Zásady ochrany osobních údajů"

View File

@ -50,6 +50,7 @@
"more": "Více",
"notifySettingsTitle": "Nastavení oznámení",
"onlineDevices": "Online zařízení",
"onlyFirstPurchase": "Pouze první nákup",
"orderList": "Seznam objednávek",
"password": "Heslo",
"passwordPlaceholder": "Zadejte nové heslo (volitelné)",
@ -59,6 +60,8 @@
"refererId": "ID doporučitele",
"refererIdPlaceholder": "Zadejte ID doporučitele",
"referralCode": "Doporučovací kód",
"referralPercentage": "Procento doporučení",
"referralPercentagePlaceholder": "Zadejte procento doporučení (0-100)",
"referrerUserId": "Odesílatel (uživatelské ID)",
"remove": "Odstranit",
"resetLogs": "Protokoly resetování",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Knotenverkehr",
"nodes": "Knoten",
"offlineNodeCount": "Anzahl der Offline-Knoten",
"onlineIPCount": "Online-IP-Anzahl",
"onlineNodeCount": "Anzahl der Online-Knoten",
"onlineUsersCount": "Online-Benutzer",
"pendingTickets": "Ausstehende Tickets",
"register": "Registrieren",
"repurchase": "Wiederkauf",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Speichern erfolgreich",
"title": "Einladungseinstellungen"
},
"logCleanup": {
"autoClear": "Automatische Bereinigung aktivieren",
"autoClearDescription": "Wenn aktiviert, wird das System abgelaufene Protokolle automatisch löschen",
"clearDays": "Aufbewahrungstage",
"clearDaysDescription": "Anzahl der Tage, an denen Protokolle aufbewahrt werden; Protokolle, die älter sind als dies, werden gelöscht",
"clearDaysPlaceholder": "Geben Sie die Aufbewahrungstage ein",
"description": "Konfigurieren Sie automatische Protokollbereinigungsregeln und Aufbewahrungsfristen",
"title": "Protokollbereinigungseinstellungen"
},
"logSettings": "Protokolleinstellungen",
"privacyPolicy": {
"description": "Bearbeiten und verwalten Sie den Inhalt der Datenschutzrichtlinie",
"title": "Datenschutzrichtlinie"

View File

@ -50,6 +50,7 @@
"more": "Mehr",
"notifySettingsTitle": "Benachrichtigungseinstellungen",
"onlineDevices": "Online-Geräte",
"onlyFirstPurchase": "Nur Erstkäufe",
"orderList": "Bestellliste",
"password": "Passwort",
"passwordPlaceholder": "Neues Passwort eingeben (optional)",
@ -59,6 +60,8 @@
"refererId": "Referenz-ID",
"refererIdPlaceholder": "Geben Sie die Referenz-ID ein",
"referralCode": "Empfehlungscode",
"referralPercentage": "Empfehlungsprozent",
"referralPercentagePlaceholder": "Geben Sie den Empfehlungsprozentsatz ein (0-100)",
"referrerUserId": "Empfehler (Benutzer-ID)",
"remove": "Entfernen",
"resetLogs": "Zurücksetzprotokolle",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Node Traffic",
"nodes": "Nodes",
"offlineNodeCount": "Offline Nodes",
"onlineIPCount": "Online IPs",
"onlineNodeCount": "Online Nodes",
"onlineUsersCount": "Online Users",
"pendingTickets": "Pending Tickets",
"register": "Register",
"repurchase": "Repurchase",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Save Successful",
"saveFailed": "Save Failed"
},
"logCleanup": {
"title": "Log Cleanup Settings",
"description": "Configure automatic log cleanup rules and retention period",
"autoClear": "Enable Auto Cleanup",
"autoClearDescription": "When enabled, the system will automatically clear expired log records",
"clearDays": "Retention Days",
"clearDaysDescription": "Number of days to retain logs; logs older than this will be cleaned up",
"clearDaysPlaceholder": "Enter retention days"
},
"logSettings": "Log Settings",
"privacyPolicy": {
"title": "Privacy Policy",
"description": "Edit and manage privacy policy content"

View File

@ -50,6 +50,7 @@
"more": "More",
"notifySettingsTitle": "Notification Settings",
"onlineDevices": "Online Devices",
"onlyFirstPurchase": "First Purchase Only",
"orderList": "Order List",
"password": "Password",
"passwordPlaceholder": "Enter new password (optional)",
@ -59,6 +60,8 @@
"refererId": "Referer ID",
"refererIdPlaceholder": "Enter referer ID",
"referralCode": "Referral Code",
"referralPercentage": "Referral Percentage",
"referralPercentagePlaceholder": "Enter referral percentage (0-100)",
"referrerUserId": "Referrer (User ID)",
"remove": "Remove",
"resetLogs": "Reset Logs",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Tráfico de nodo",
"nodes": "nodos",
"offlineNodeCount": "Número de nodos fuera de línea",
"onlineIPCount": "Número de IPs en línea",
"onlineNodeCount": "Número de nodos en línea",
"onlineUsersCount": "Usuarios en línea",
"pendingTickets": "Tickets pendientes",
"register": "registrar",
"repurchase": "recompra",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Guardado Exitoso",
"title": "Configuración de Invitaciones"
},
"logCleanup": {
"autoClear": "Habilitar limpieza automática",
"autoClearDescription": "Cuando está habilitado, el sistema eliminará automáticamente los registros de log expirados",
"clearDays": "Días de retención",
"clearDaysDescription": "Número de días para retener los registros; los registros más antiguos que esto serán eliminados",
"clearDaysPlaceholder": "Introduce los días de retención",
"description": "Configura las reglas de limpieza automática de registros y el período de retención",
"title": "Configuración de limpieza de registros"
},
"logSettings": "Configuración de registros",
"privacyPolicy": {
"description": "Editar y gestionar el contenido de la política de privacidad",
"title": "Política de Privacidad"

View File

@ -50,6 +50,7 @@
"more": "Más",
"notifySettingsTitle": "Configuración de Notificaciones",
"onlineDevices": "Dispositivos en línea",
"onlyFirstPurchase": "Solo Primera Compra",
"orderList": "Lista de Pedidos",
"password": "Contraseña",
"passwordPlaceholder": "Ingrese nueva contraseña (opcional)",
@ -59,6 +60,8 @@
"refererId": "ID del Referente",
"refererIdPlaceholder": "Ingrese el ID del referente",
"referralCode": "Código de Referencia",
"referralPercentage": "Porcentaje de Referencia",
"referralPercentagePlaceholder": "Introduce el porcentaje de referencia (0-100)",
"referrerUserId": "Referente (ID de usuario)",
"remove": "Eliminar",
"resetLogs": "Registros de Reinicio",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Tráfico de nodo",
"nodes": "nodos",
"offlineNodeCount": "Número de nodos fuera de línea",
"onlineIPCount": "Número de IPs en línea",
"onlineNodeCount": "Número de nodos en línea",
"onlineUsersCount": "Usuarios en línea",
"pendingTickets": "Tickets pendientes",
"register": "Registrarse",
"repurchase": "recompra",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Guardado Exitoso",
"title": "Configuración de Invitaciones"
},
"logCleanup": {
"autoClear": "Habilitar Limpieza Automática",
"autoClearDescription": "Cuando está habilitado, el sistema eliminará automáticamente los registros de log expirados",
"clearDays": "Días de Retención",
"clearDaysDescription": "Número de días para retener los registros; los registros más antiguos que esto serán eliminados",
"clearDaysPlaceholder": "Ingresa los días de retención",
"description": "Configura las reglas de limpieza automática de registros y el período de retención",
"title": "Configuración de Limpieza de Registros"
},
"logSettings": "Configuración de Registros",
"privacyPolicy": {
"description": "Editar y gestionar el contenido de la política de privacidad",
"title": "Política de Privacidad"

View File

@ -50,6 +50,7 @@
"more": "Más",
"notifySettingsTitle": "Configuración de Notificaciones",
"onlineDevices": "Dispositivos en línea",
"onlyFirstPurchase": "Solo Primera Compra",
"orderList": "Lista de Pedidos",
"password": "Contraseña",
"passwordPlaceholder": "Ingresa nueva contraseña (opcional)",
@ -59,6 +60,8 @@
"refererId": "ID del Referente",
"refererIdPlaceholder": "Ingrese ID del referente",
"referralCode": "Código de Referencia",
"referralPercentage": "Porcentaje de Referencia",
"referralPercentagePlaceholder": "Ingresa el porcentaje de referencia (0-100)",
"referrerUserId": "Referente (ID de Usuario)",
"remove": "Eliminar",
"resetLogs": "Registros de Reinicio",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "ترافیک نود",
"nodes": "گره‌ها",
"offlineNodeCount": "گره‌های آفلاین",
"onlineIPCount": "آی‌پی‌های آنلاین",
"onlineNodeCount": "گره‌های آنلاین",
"onlineUsersCount": "کاربران آنلاین",
"pendingTickets": "بلیط‌های در انتظار",
"register": "ثبت نام",
"repurchase": "بازخرید",

View File

@ -33,6 +33,16 @@
"saveSuccess": "ذخیره با موفقیت انجام شد",
"title": "تنظیمات دعوتنامه"
},
"logCleanup": {
"autoClear": "فعال‌سازی پاک‌سازی خودکار",
"autoClearDescription": "با فعال‌سازی این گزینه، سیستم به‌طور خودکار رکوردهای لاگ منقضی شده را پاک می‌کند",
"clearDays": "روزهای نگهداری",
"clearDaysDescription": "تعداد روزهایی که لاگ‌ها نگهداری می‌شوند؛ لاگ‌های قدیمی‌تر از این مدت پاک خواهند شد",
"clearDaysPlaceholder": "تعداد روزهای نگهداری را وارد کنید",
"description": "پیکربندی قوانین پاک‌سازی خودکار لاگ و دوره نگهداری",
"title": "تنظیمات پاک‌سازی لاگ"
},
"logSettings": "تنظیمات لاگ",
"privacyPolicy": {
"description": "محتوای سیاست حفظ حریم خصوصی را ویرایش و مدیریت کنید",
"title": "سیاست حفظ حریم خصوصی"

View File

@ -50,6 +50,7 @@
"more": "بیشتر",
"notifySettingsTitle": "تنظیمات اعلان‌ها",
"onlineDevices": "دستگاه‌های آنلاین",
"onlyFirstPurchase": "فقط خرید اول",
"orderList": "لیست سفارشات",
"password": "رمز عبور",
"passwordPlaceholder": "رمز عبور جدید را وارد کنید (اختیاری)",
@ -59,6 +60,8 @@
"refererId": "شناسه معرف",
"refererIdPlaceholder": "شناسه معرف را وارد کنید",
"referralCode": "کد ارجاع",
"referralPercentage": "درصد ارجاع",
"referralPercentagePlaceholder": "درصد ارجاع را وارد کنید (۰-۱۰۰)",
"referrerUserId": "ارجاع‌دهنده (شناسه کاربر)",
"remove": "حذف",
"resetLogs": "گزارشات بازنشانی",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Solmun liikenne",
"nodes": "solmut",
"offlineNodeCount": "Offline-solmujen määrä",
"onlineIPCount": "Verkossa olevien IP-osoitteiden määrä",
"onlineNodeCount": "Verkossa olevien solmujen määrä",
"onlineUsersCount": "Verkkokäyttäjät",
"pendingTickets": "Odottavat liput",
"register": "Rekisteröidy",
"repurchase": "uudelleenosto",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Tallennus onnistui",
"title": "Kutsuasetukset"
},
"logCleanup": {
"autoClear": "Ota automaattinen puhdistus käyttöön",
"autoClearDescription": "Kun tämä on käytössä, järjestelmä puhdistaa automaattisesti vanhentuneet lokitiedot",
"clearDays": "Säilytyspäivät",
"clearDaysDescription": "Päivien määrä, jolloin lokit säilytetään; tätä vanhemmat lokit poistetaan",
"clearDaysPlaceholder": "Syötä säilytyspäivät",
"description": "Määritä automaattiset lokin puhdistus säännöt ja säilytysaika",
"title": "Lokin puhdistuksen asetukset"
},
"logSettings": "Lokiasetukset",
"privacyPolicy": {
"description": "Muokkaa ja hallinnoi tietosuojakäytännön sisältöä",
"title": "Tietosuojakäytäntö"

View File

@ -50,6 +50,7 @@
"more": "Lisää",
"notifySettingsTitle": "Ilmoitusasetukset",
"onlineDevices": "Verkossa olevat laitteet",
"onlyFirstPurchase": "Vain ensimmäinen ostos",
"orderList": "Tilaukset",
"password": "Salasana",
"passwordPlaceholder": "Syötä uusi salasana (valinnainen)",
@ -59,6 +60,8 @@
"refererId": "Viittaajan ID",
"refererIdPlaceholder": "Syötä suosittelijan ID",
"referralCode": "Suosituskoodi",
"referralPercentage": "Suositusprosentti",
"referralPercentagePlaceholder": "Syötä suositusprosentti (0-100)",
"referrerUserId": "Viittaaja (Käyttäjän ID)",
"remove": "Poista",
"resetLogs": "Nollauslokit",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Trafic du nœud",
"nodes": "nœuds",
"offlineNodeCount": "Nombre de nœuds hors ligne",
"onlineIPCount": "Nombre d'IP en ligne",
"onlineNodeCount": "Nombre de nœuds en ligne",
"onlineUsersCount": "Utilisateurs en ligne",
"pendingTickets": "Tickets en attente",
"register": "S'inscrire",
"repurchase": "Rachat",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Enregistrement réussi",
"title": "Paramètres d'invitation"
},
"logCleanup": {
"autoClear": "Activer le nettoyage automatique",
"autoClearDescription": "Lorsque cette option est activée, le système supprimera automatiquement les enregistrements de journaux expirés",
"clearDays": "Jours de conservation",
"clearDaysDescription": "Nombre de jours pour conserver les journaux ; les journaux plus anciens seront supprimés",
"clearDaysPlaceholder": "Entrez le nombre de jours de conservation",
"description": "Configurer les règles de nettoyage automatique des journaux et la période de conservation",
"title": "Paramètres de nettoyage des journaux"
},
"logSettings": "Paramètres des journaux",
"privacyPolicy": {
"description": "Modifier et gérer le contenu de la politique de confidentialité",
"title": "Politique de confidentialité"

View File

@ -50,6 +50,7 @@
"more": "Plus",
"notifySettingsTitle": "Paramètres de notification",
"onlineDevices": "Appareils en ligne",
"onlyFirstPurchase": "Premier achat uniquement",
"orderList": "Liste des commandes",
"password": "Mot de passe",
"passwordPlaceholder": "Entrez un nouveau mot de passe (facultatif)",
@ -59,6 +60,8 @@
"refererId": "ID du référent",
"refererIdPlaceholder": "Entrez l'ID du référent",
"referralCode": "Code de parrainage",
"referralPercentage": "Pourcentage de parrainage",
"referralPercentagePlaceholder": "Entrez le pourcentage de parrainage (0-100)",
"referrerUserId": "Référent (ID utilisateur)",
"remove": "Supprimer",
"resetLogs": "Journaux de réinitialisation",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "नोड ट्रैफिक",
"nodes": "नोड्स",
"offlineNodeCount": "ऑफ़लाइन नोड की संख्या",
"onlineIPCount": "ऑनलाइन IP संख्या",
"onlineNodeCount": "ऑनलाइन नोड की संख्या",
"onlineUsersCount": "ऑनलाइन उपयोगकर्ता",
"pendingTickets": "लंबित टिकट",
"register": "पंजीकरण",
"repurchase": "पुनः खरीद",

View File

@ -33,6 +33,16 @@
"saveSuccess": "सहेजना सफल",
"title": "आमंत्रण सेटिंग्स"
},
"logCleanup": {
"autoClear": "स्वचालित क्लीनअप सक्षम करें",
"autoClearDescription": "जब सक्षम किया जाता है, तो सिस्टम स्वचालित रूप से समाप्त लॉग रिकॉर्ड को साफ करेगा",
"clearDays": "संरक्षण दिन",
"clearDaysDescription": "लॉग को बनाए रखने के लिए दिनों की संख्या; इससे पुराने लॉग को साफ किया जाएगा",
"clearDaysPlaceholder": "संरक्षण दिन दर्ज करें",
"description": "स्वचालित लॉग क्लीनअप नियम और संरक्षण अवधि कॉन्फ़िगर करें",
"title": "लॉग क्लीनअप सेटिंग्स"
},
"logSettings": "लॉग सेटिंग्स",
"privacyPolicy": {
"description": "गोपनीयता नीति सामग्री को संपादित और प्रबंधित करें",
"title": "गोपनीयता नीति"

View File

@ -50,6 +50,7 @@
"more": "और",
"notifySettingsTitle": "सूचना सेटिंग्स",
"onlineDevices": "ऑनलाइन डिवाइस",
"onlyFirstPurchase": "पहली खरीदारी केवल",
"orderList": "ऑर्डर सूची",
"password": "पासवर्ड",
"passwordPlaceholder": "नया पासवर्ड दर्ज करें (वैकल्पिक)",
@ -59,6 +60,8 @@
"refererId": "रेफरर आईडी",
"refererIdPlaceholder": "रेफरर आईडी दर्ज करें",
"referralCode": "रेफरल कोड",
"referralPercentage": "रेफरल प्रतिशत",
"referralPercentagePlaceholder": "रेफरल प्रतिशत दर्ज करें (0-100)",
"referrerUserId": "रेफरर (उपयोगकर्ता आईडी)",
"remove": "हटाएं",
"resetLogs": "रीसेट लॉग",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Csomópont forgalom",
"nodes": "csomópontok",
"offlineNodeCount": "Offline csomópontok száma",
"onlineIPCount": "Online IP-szám",
"onlineNodeCount": "Online csomópontok száma",
"onlineUsersCount": "Online Felhasználók",
"pendingTickets": "Függőben lévő jegyek",
"register": "Regisztráció",
"repurchase": "újravásárlás",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Mentés sikeres",
"title": "Meghívási beállítások"
},
"logCleanup": {
"autoClear": "Automatikus Tisztítás Engedélyezése",
"autoClearDescription": "Ha engedélyezve van, a rendszer automatikusan törli a lejárt naplóbejegyzéseket",
"clearDays": "Megőrzési Napok",
"clearDaysDescription": "A naplók megőrzésének napjai; a régebbi naplók törlésre kerülnek",
"clearDaysPlaceholder": "Adja meg a megőrzési napokat",
"description": "Automatikus napló tisztítási szabályok és megőrzési időszak konfigurálása",
"title": "Napló Tisztítási Beállítások"
},
"logSettings": "Napló Beállítások",
"privacyPolicy": {
"description": "Adatvédelmi irányelvek tartalmának szerkesztése és kezelése",
"title": "Adatvédelmi irányelvek"

View File

@ -50,6 +50,7 @@
"more": "Több",
"notifySettingsTitle": "Értesítési beállítások",
"onlineDevices": "Online eszközök",
"onlyFirstPurchase": "Csak első vásárlás",
"orderList": "Rendelések listája",
"password": "Jelszó",
"passwordPlaceholder": "Adjon meg új jelszót (opcionális)",
@ -59,6 +60,8 @@
"refererId": "Hivatkozó azonosító",
"refererIdPlaceholder": "Adja meg az ajánló azonosítóját",
"referralCode": "Ajánlókód",
"referralPercentage": "Ajánlási százalék",
"referralPercentagePlaceholder": "Írd be az ajánlási százalékot (0-100)",
"referrerUserId": "Ajánló (Felhasználói azonosító)",
"remove": "Eltávolítás",
"resetLogs": "Visszaállítási naplók",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "ノードトラフィック",
"nodes": "ノード",
"offlineNodeCount": "オフラインノード数",
"onlineIPCount": "オンラインIP数",
"onlineNodeCount": "オンラインノード数",
"onlineUsersCount": "オンラインユーザー",
"pendingTickets": "保留中のチケット",
"register": "登録",
"repurchase": "再購入",

View File

@ -33,6 +33,16 @@
"saveSuccess": "保存成功",
"title": "招待設定"
},
"logCleanup": {
"autoClear": "自動クリーンアップを有効にする",
"autoClearDescription": "有効にすると、システムは期限切れのログレコードを自動的にクリアします",
"clearDays": "保持日数",
"clearDaysDescription": "ログを保持する日数; これを超える古いログはクリーンアップされます",
"clearDaysPlaceholder": "保持日数を入力してください",
"description": "自動ログクリーンアップルールと保持期間を設定します",
"title": "ログクリーンアップ設定"
},
"logSettings": "ログ設定",
"privacyPolicy": {
"description": "プライバシーポリシーの内容を編集・管理します",
"title": "プライバシーポリシー"

View File

@ -50,6 +50,7 @@
"more": "もっと見る",
"notifySettingsTitle": "通知設定",
"onlineDevices": "オンラインデバイス",
"onlyFirstPurchase": "初回購入のみ",
"orderList": "注文リスト",
"password": "パスワード",
"passwordPlaceholder": "新しいパスワードを入力してください(任意)",
@ -59,6 +60,8 @@
"refererId": "リファラーID",
"refererIdPlaceholder": "紹介者IDを入力してください",
"referralCode": "紹介コード",
"referralPercentage": "紹介割合",
"referralPercentagePlaceholder": "紹介割合を入力してください0-100",
"referrerUserId": "紹介者ユーザーID",
"remove": "削除",
"resetLogs": "リセット履歴",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "노드 트래픽",
"nodes": "노드",
"offlineNodeCount": "오프라인 노드 수",
"onlineIPCount": "온라인 IP 수",
"onlineNodeCount": "온라인 노드 수",
"onlineUsersCount": "온라인 사용자",
"pendingTickets": "처리 대기 중인 티켓",
"register": "등록",
"repurchase": "재구매",

View File

@ -33,6 +33,16 @@
"saveSuccess": "저장 성공",
"title": "초대 설정"
},
"logCleanup": {
"autoClear": "자동 정리 활성화",
"autoClearDescription": "활성화되면 시스템이 만료된 로그 기록을 자동으로 삭제합니다",
"clearDays": "보존 일수",
"clearDaysDescription": "로그를 보존할 일수; 이보다 오래된 로그는 삭제됩니다",
"clearDaysPlaceholder": "보존 일수를 입력하세요",
"description": "자동 로그 정리 규칙 및 보존 기간 구성",
"title": "로그 정리 설정"
},
"logSettings": "로그 설정",
"privacyPolicy": {
"description": "개인정보 처리방침 내용 편집 및 관리",
"title": "개인정보 처리방침"

View File

@ -50,6 +50,7 @@
"more": "더보기",
"notifySettingsTitle": "알림 설정",
"onlineDevices": "온라인 기기",
"onlyFirstPurchase": "첫 구매 전용",
"orderList": "주문 목록",
"password": "비밀번호",
"passwordPlaceholder": "새 비밀번호 입력 (선택 사항)",
@ -59,6 +60,8 @@
"refererId": "추천인 ID",
"refererIdPlaceholder": "추천인 ID를 입력하세요",
"referralCode": "추천 코드",
"referralPercentage": "추천 비율",
"referralPercentagePlaceholder": "추천 비율 입력 (0-100)",
"referrerUserId": "추천인 (사용자 ID)",
"remove": "제거",
"resetLogs": "초기화 기록",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Nodetrafikk",
"nodes": "noder",
"offlineNodeCount": "Antall frakoblede noder",
"onlineIPCount": "Antall IP-er på nett",
"onlineNodeCount": "Antall noder på nett",
"onlineUsersCount": "Nettbrukere",
"pendingTickets": "Ventende billetter",
"register": "Registrer",
"repurchase": "gjenkjøp",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Lagring vellykket",
"title": "Invitasjonsinnstillinger"
},
"logCleanup": {
"autoClear": "Aktiver automatisk rensing",
"autoClearDescription": "Når aktivert, vil systemet automatisk fjerne utløpte loggposter",
"clearDays": "Oppbevaringsdager",
"clearDaysDescription": "Antall dager for å oppbevare logger; logger eldre enn dette vil bli renset",
"clearDaysPlaceholder": "Skriv inn oppbevaringsdager",
"description": "Konfigurer automatiske regler for loggrensing og oppbevaringsperiode",
"title": "Innstillinger for loggrensing"
},
"logSettings": "Logginnstillinger",
"privacyPolicy": {
"description": "Rediger og administrer innholdet i personvernerklæringen",
"title": "Personvernerklæring"

View File

@ -50,6 +50,7 @@
"more": "Mer",
"notifySettingsTitle": "Varslingsinnstillinger",
"onlineDevices": "Tilkoblede enheter",
"onlyFirstPurchase": "Første kjøp bare",
"orderList": "Ordreliste",
"password": "Passord",
"passwordPlaceholder": "Skriv inn nytt passord (valgfritt)",
@ -59,6 +60,8 @@
"refererId": "Henvisnings-ID",
"refererIdPlaceholder": "Skriv inn referanse-ID",
"referralCode": "Henvisningskode",
"referralPercentage": "Henvisningsprosent",
"referralPercentagePlaceholder": "Skriv inn henvisningsprosent (0-100)",
"referrerUserId": "Henviser (Bruker-ID)",
"remove": "Fjern",
"resetLogs": "Tilbakestill Logg",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Ruch węzła",
"nodes": "węzły",
"offlineNodeCount": "Liczba węzłów offline",
"onlineIPCount": "Liczba IP online",
"onlineNodeCount": "Liczba węzłów online",
"onlineUsersCount": "Użytkownicy online",
"pendingTickets": "Oczekujące zgłoszenia",
"register": "Rejestracja",
"repurchase": "ponowny zakup",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Zapisano pomyślnie",
"title": "Ustawienia zaproszeń"
},
"logCleanup": {
"autoClear": "Włącz automatyczne czyszczenie",
"autoClearDescription": "Po włączeniu system automatycznie usunie przestarzałe rekordy logów",
"clearDays": "Dni przechowywania",
"clearDaysDescription": "Liczba dni, przez które logi będą przechowywane; logi starsze niż ten okres zostaną usunięte",
"clearDaysPlaceholder": "Wprowadź dni przechowywania",
"description": "Skonfiguruj zasady automatycznego czyszczenia logów i okres przechowywania",
"title": "Ustawienia czyszczenia logów"
},
"logSettings": "Ustawienia logów",
"privacyPolicy": {
"description": "Edytuj i zarządzaj treścią polityki prywatności",
"title": "Polityka prywatności"

View File

@ -50,6 +50,7 @@
"more": "Więcej",
"notifySettingsTitle": "Ustawienia powiadomień",
"onlineDevices": "Urządzenia online",
"onlyFirstPurchase": "Tylko pierwsze zakupy",
"orderList": "Lista Zamówień",
"password": "Hasło",
"passwordPlaceholder": "Wprowadź nowe hasło (opcjonalnie)",
@ -59,6 +60,8 @@
"refererId": "Identyfikator polecającego",
"refererIdPlaceholder": "Wprowadź identyfikator polecającego",
"referralCode": "Kod polecający",
"referralPercentage": "Procent polecenia",
"referralPercentagePlaceholder": "Wprowadź procent polecenia (0-100)",
"referrerUserId": "Polecający (ID użytkownika)",
"remove": "Usuń",
"resetLogs": "Logi Resetowania",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Tráfego do Nó",
"nodes": "nós",
"offlineNodeCount": "Contagem de nós offline",
"onlineIPCount": "Contagem de IPs Online",
"onlineNodeCount": "Contagem de nós online",
"onlineUsersCount": "Usuários Online",
"pendingTickets": "Tickets pendentes",
"register": "Registrar",
"repurchase": "recompra",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Salvo com Sucesso",
"title": "Configurações de Convite"
},
"logCleanup": {
"autoClear": "Ativar Limpeza Automática",
"autoClearDescription": "Quando ativado, o sistema limpará automaticamente os registros de log expirados",
"clearDays": "Dias de Retenção",
"clearDaysDescription": "Número de dias para reter logs; logs mais antigos que isso serão limpos",
"clearDaysPlaceholder": "Insira os dias de retenção",
"description": "Configure regras de limpeza automática de logs e período de retenção",
"title": "Configurações de Limpeza de Logs"
},
"logSettings": "Configurações de Logs",
"privacyPolicy": {
"description": "Edite e gerencie o conteúdo da política de privacidade",
"title": "Política de Privacidade"

View File

@ -50,6 +50,7 @@
"more": "Mais",
"notifySettingsTitle": "Configurações de Notificação",
"onlineDevices": "Dispositivos Online",
"onlyFirstPurchase": "Apenas a Primeira Compra",
"orderList": "Lista de Pedidos",
"password": "Senha",
"passwordPlaceholder": "Digite a nova senha (opcional)",
@ -59,6 +60,8 @@
"refererId": "ID do Referente",
"refererIdPlaceholder": "Digite o ID do referenciador",
"referralCode": "Código de Indicação",
"referralPercentage": "Porcentagem de Referência",
"referralPercentagePlaceholder": "Insira a porcentagem de referência (0-100)",
"referrerUserId": "Referente (ID do Usuário)",
"remove": "Remover",
"resetLogs": "Registros de Redefinição",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Traficul nodului",
"nodes": "noduri",
"offlineNodeCount": "Număr de noduri offline",
"onlineIPCount": "Număr de IP-uri online",
"onlineNodeCount": "Număr de noduri online",
"onlineUsersCount": "Utilizatori online",
"pendingTickets": "Tichete în așteptare",
"register": "Înregistrare",
"repurchase": "recomandare",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Salvare cu Succes",
"title": "Setări de Invitație"
},
"logCleanup": {
"autoClear": "Activează curățarea automată",
"autoClearDescription": "Când este activat, sistemul va curăța automat înregistrările de jurnal expirate",
"clearDays": "Zile de păstrare",
"clearDaysDescription": "Numărul de zile pentru care se păstrează jurnalele; jurnalele mai vechi de atât vor fi curățate",
"clearDaysPlaceholder": "Introdu zilele de păstrare",
"description": "Configurează regulile de curățare automată a jurnalelor și perioada de păstrare",
"title": "Setări de curățare a jurnalelor"
},
"logSettings": "Setări jurnal",
"privacyPolicy": {
"description": "Editează și gestionează conținutul politicii de confidențialitate",
"title": "Politica de Confidențialitate"

View File

@ -50,6 +50,7 @@
"more": "Mai mult",
"notifySettingsTitle": "Setări notificări",
"onlineDevices": "Dispozitive Online",
"onlyFirstPurchase": "Doar prima achiziție",
"orderList": "Lista de comenzi",
"password": "Parolă",
"passwordPlaceholder": "Introduceți o parolă nouă (opțional)",
@ -59,6 +60,8 @@
"refererId": "ID Referent",
"refererIdPlaceholder": "Introduceți ID-ul referentului",
"referralCode": "Cod de recomandare",
"referralPercentage": "Procentaj de recomandare",
"referralPercentagePlaceholder": "Introduceți procentajul de recomandare (0-100)",
"referrerUserId": "Referent (ID Utilizator)",
"remove": "Elimină",
"resetLogs": "Jurnale de resetare",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Трафик узла",
"nodes": "узлы",
"offlineNodeCount": "Количество офлайн-узлов",
"onlineIPCount": "Количество онлайн IP",
"onlineNodeCount": "Количество онлайн-узлов",
"onlineUsersCount": "Пользователи онлайн",
"pendingTickets": "Ожидающие заявки",
"register": "Регистрация",
"repurchase": "повторная покупка",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Сохранение успешно",
"title": "Настройки приглашений"
},
"logCleanup": {
"autoClear": "Включить автоматическую очистку",
"autoClearDescription": "При включении система будет автоматически очищать устаревшие записи журналов",
"clearDays": "Дни хранения",
"clearDaysDescription": "Количество дней для хранения журналов; журналы старше этого будут очищены",
"clearDaysPlaceholder": "Введите количество дней хранения",
"description": "Настройте правила автоматической очистки журналов и период хранения",
"title": "Настройки очистки журналов"
},
"logSettings": "Настройки журналов",
"privacyPolicy": {
"description": "Редактирование и управление содержимым политики конфиденциальности",
"title": "Политика конфиденциальности"

View File

@ -50,6 +50,7 @@
"more": "Больше",
"notifySettingsTitle": "Настройки уведомлений",
"onlineDevices": "Подключенные устройства",
"onlyFirstPurchase": "Только первая покупка",
"orderList": "Список заказов",
"password": "Пароль",
"passwordPlaceholder": "Введите новый пароль (необязательно)",
@ -59,6 +60,8 @@
"refererId": "ID реферера",
"refererIdPlaceholder": "Введите ID реферера",
"referralCode": "Реферальный код",
"referralPercentage": "Процент вознаграждения за рекомендацию",
"referralPercentagePlaceholder": "Введите процент вознаграждения за рекомендацию (0-100)",
"referrerUserId": "Реферер (ID пользователя)",
"remove": "Удалить",
"resetLogs": "Журналы сброса",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "ปริมาณการใช้งานของโหนด",
"nodes": "โหนด",
"offlineNodeCount": "จำนวนโหนดออฟไลน์",
"onlineIPCount": "จำนวน IP ออนไลน์",
"onlineNodeCount": "จำนวนโหนดออนไลน์",
"onlineUsersCount": "ผู้ใช้งานออนไลน์",
"pendingTickets": "บัตรงานที่รอดำเนินการ",
"register": "ลงทะเบียน",
"repurchase": "ซื้อซ้ำ",

View File

@ -33,6 +33,16 @@
"saveSuccess": "บันทึกสำเร็จ",
"title": "การตั้งค่าการเชิญ"
},
"logCleanup": {
"autoClear": "เปิดใช้งานการล้างอัตโนมัติ",
"autoClearDescription": "เมื่อเปิดใช้งาน ระบบจะล้างบันทึกที่หมดอายุโดยอัตโนมัติ",
"clearDays": "จำนวนวันในการเก็บรักษา",
"clearDaysDescription": "จำนวนวันที่จะเก็บบันทึก; บันทึกที่เก่ากว่านี้จะถูกลบออก",
"clearDaysPlaceholder": "กรอกจำนวนวันในการเก็บรักษา",
"description": "กำหนดกฎการล้างบันทึกอัตโนมัติและระยะเวลาการเก็บรักษา",
"title": "การตั้งค่าการล้างบันทึก"
},
"logSettings": "การตั้งค่าบันทึก",
"privacyPolicy": {
"description": "แก้ไขและจัดการเนื้อหานโยบายความเป็นส่วนตัว",
"title": "นโยบายความเป็นส่วนตัว"

View File

@ -50,6 +50,7 @@
"more": "เพิ่มเติม",
"notifySettingsTitle": "การตั้งค่าการแจ้งเตือน",
"onlineDevices": "อุปกรณ์ที่ออนไลน์",
"onlyFirstPurchase": "การซื้อครั้งแรกเท่านั้น",
"orderList": "รายการสั่งซื้อ",
"password": "รหัสผ่าน",
"passwordPlaceholder": "กรุณาใส่รหัสผ่านใหม่ (ไม่บังคับ)",
@ -59,6 +60,8 @@
"refererId": "รหัสผู้แนะนำ",
"refererIdPlaceholder": "กรอก ID ผู้แนะนำ",
"referralCode": "รหัสแนะนำ",
"referralPercentage": "เปอร์เซ็นต์การแนะนำ",
"referralPercentagePlaceholder": "กรอกเปอร์เซ็นต์การแนะนำ (0-100)",
"referrerUserId": "ผู้อ้างอิง (รหัสผู้ใช้)",
"remove": "ลบ",
"resetLogs": "บันทึกการรีเซ็ต",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Düğüm Trafiği",
"nodes": "düğümler",
"offlineNodeCount": "Çevrimdışı Düğüm Sayısı",
"onlineIPCount": "Çevrimiçi IP Sayısı",
"onlineNodeCount": "Çevrimiçi Düğüm Sayısı",
"onlineUsersCount": "Çevrimiçi Kullanıcılar",
"pendingTickets": "Bekleyen Biletler",
"register": "Kayıt Ol",
"repurchase": "yeniden satın alma",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Başarıyla Kaydedildi",
"title": "Davet Ayarları"
},
"logCleanup": {
"autoClear": "Otomatik Temizlemeyi Etkinleştir",
"autoClearDescription": "Etkinleştirildiğinde, sistem süresi dolmuş log kayıtlarını otomatik olarak temizleyecektir",
"clearDays": "Saklama Günleri",
"clearDaysDescription": "Logların saklanacağı gün sayısı; bu süreden daha eski loglar temizlenecektir",
"clearDaysPlaceholder": "Saklama günlerini girin",
"description": "Otomatik log temizleme kurallarını ve saklama süresini yapılandırın",
"title": "Log Temizleme Ayarları"
},
"logSettings": "Log Ayarları",
"privacyPolicy": {
"description": "Gizlilik politikası içeriğini düzenleyin ve yönetin",
"title": "Gizlilik Politikası"

View File

@ -50,6 +50,7 @@
"more": "Daha Fazla",
"notifySettingsTitle": "Bildirim Ayarları",
"onlineDevices": "Çevrimiçi Cihazlar",
"onlyFirstPurchase": "Sadece İlk Alım",
"orderList": "Sipariş Listesi",
"password": "Şifre",
"passwordPlaceholder": "Yeni şifreyi girin (isteğe bağlı)",
@ -59,6 +60,8 @@
"refererId": "Referans Kimliği",
"refererIdPlaceholder": "Referans kimliğini girin",
"referralCode": "Referans Kodu",
"referralPercentage": "Referans Yüzdesi",
"referralPercentagePlaceholder": "Referans yüzdesini girin (0-100)",
"referrerUserId": "Yönlendiren (Kullanıcı Kimliği)",
"remove": "Kaldır",
"resetLogs": "Sıfırlama Kayıtları",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Трафік вузла",
"nodes": "вузли",
"offlineNodeCount": "Кількість офлайн-вузлів",
"onlineIPCount": "Кількість онлайн IP",
"onlineNodeCount": "Кількість онлайн-вузлів",
"onlineUsersCount": "Користувачі онлайн",
"pendingTickets": "Невирішені заявки",
"register": "Реєстрація",
"repurchase": "повторна покупка",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Успішно збережено",
"title": "Налаштування запрошень"
},
"logCleanup": {
"autoClear": "Увімкнути автоматичне очищення",
"autoClearDescription": "При увімкненні система автоматично очищатиме застарілі записи журналів",
"clearDays": "Дні зберігання",
"clearDaysDescription": "Кількість днів для зберігання журналів; журнали старші за цей термін будуть очищені",
"clearDaysPlaceholder": "Введіть дні зберігання",
"description": "Налаштуйте правила автоматичного очищення журналів та період зберігання",
"title": "Налаштування очищення журналів"
},
"logSettings": "Налаштування журналів",
"privacyPolicy": {
"description": "Редагуйте та керуйте змістом політики конфіденційності",
"title": "Політика конфіденційності"

View File

@ -50,6 +50,7 @@
"more": "Більше",
"notifySettingsTitle": "Налаштування сповіщень",
"onlineDevices": "Пристрої в мережі",
"onlyFirstPurchase": "Тільки перша покупка",
"orderList": "Список замовлень",
"password": "Пароль",
"passwordPlaceholder": "Введіть новий пароль (необов'язково)",
@ -59,6 +60,8 @@
"refererId": "Ідентифікатор реферера",
"refererIdPlaceholder": "Введіть ID реферера",
"referralCode": "Реферальний код",
"referralPercentage": "Відсоток рефералів",
"referralPercentagePlaceholder": "Введіть відсоток рефералів (0-100)",
"referrerUserId": "Реферер (ID користувача)",
"remove": "Видалити",
"resetLogs": "Журнали скидання",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "Lưu lượng nút",
"nodes": "nút",
"offlineNodeCount": "Số lượng nút ngoại tuyến",
"onlineIPCount": "Số lượng IP trực tuyến",
"onlineNodeCount": "Số lượng nút trực tuyến",
"onlineUsersCount": "Người dùng trực tuyến",
"pendingTickets": "Phiếu hỗ trợ đang chờ xử lý",
"register": "Đăng ký",
"repurchase": "mua lại",

View File

@ -33,6 +33,16 @@
"saveSuccess": "Lưu thành công",
"title": "Cài đặt mời"
},
"logCleanup": {
"autoClear": "Bật Dọn dẹp Tự động",
"autoClearDescription": "Khi được bật, hệ thống sẽ tự động xóa các bản ghi nhật ký đã hết hạn",
"clearDays": "Số Ngày Lưu giữ",
"clearDaysDescription": "Số ngày để lưu giữ nhật ký; nhật ký cũ hơn sẽ bị xóa",
"clearDaysPlaceholder": "Nhập số ngày lưu giữ",
"description": "Cấu hình quy tắc dọn dẹp nhật ký tự động và thời gian lưu giữ",
"title": "Cài đặt Dọn dẹp Nhật ký"
},
"logSettings": "Cài đặt Nhật ký",
"privacyPolicy": {
"description": "Chỉnh sửa và quản lý nội dung chính sách bảo mật",
"title": "Chính sách bảo mật"

View File

@ -50,6 +50,7 @@
"more": "Thêm",
"notifySettingsTitle": "Cài Đặt Thông Báo",
"onlineDevices": "Thiết bị trực tuyến",
"onlyFirstPurchase": "Chỉ Mua Hàng Đầu Tiên",
"orderList": "Danh sách đơn hàng",
"password": "Mật khẩu",
"passwordPlaceholder": "Nhập mật khẩu mới (không bắt buộc)",
@ -59,6 +60,8 @@
"refererId": "ID người giới thiệu",
"refererIdPlaceholder": "Nhập mã người giới thiệu",
"referralCode": "Mã Giới Thiệu",
"referralPercentage": "Tỷ Lệ Giới Thiệu",
"referralPercentagePlaceholder": "Nhập tỷ lệ giới thiệu (0-100)",
"referrerUserId": "Người giới thiệu (ID người dùng)",
"remove": "Xóa",
"resetLogs": "Nhật ký đặt lại",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "节点流量",
"nodes": "节点",
"offlineNodeCount": "离线节点数",
"onlineIPCount": "在线IP数",
"onlineNodeCount": "在线节点数",
"onlineUsersCount": "在线用户数",
"pendingTickets": "待处理工单",
"register": "注册",
"repurchase": "复购",

View File

@ -33,6 +33,16 @@
"saveSuccess": "保存成功",
"saveFailed": "保存失败"
},
"logCleanup": {
"title": "日志清理设置",
"description": "配置系统日志自动清理规则和保留时间",
"autoClear": "启用自动清理",
"autoClearDescription": "启用后系统将自动清理过期的日志记录",
"clearDays": "保留天数",
"clearDaysDescription": "日志保留的天数,超过此时间的日志将被清理",
"clearDaysPlaceholder": "请输入保留天数"
},
"logSettings": "日志设置",
"privacyPolicy": {
"title": "隐私政策",
"description": "编辑和管理隐私政策内容"

View File

@ -50,6 +50,7 @@
"more": "更多",
"notifySettingsTitle": "通知设置",
"onlineDevices": "在线设备",
"onlyFirstPurchase": "仅首次购买奖励",
"orderList": "订单列表",
"password": "密码",
"passwordPlaceholder": "输入新密码(选填)",
@ -59,6 +60,8 @@
"refererId": "推荐人ID",
"refererIdPlaceholder": "输入推荐人ID",
"referralCode": "推荐码",
"referralPercentage": "推荐奖励比例",
"referralPercentagePlaceholder": "输入推荐奖励比例0-100",
"referrerUserId": "推荐人用户ID",
"remove": "移除",
"resetLogs": "重置日志",

View File

@ -7,8 +7,8 @@
"nodeTraffic": "節點流量",
"nodes": "節點",
"offlineNodeCount": "離線節點數",
"onlineIPCount": "線上IP數",
"onlineNodeCount": "線上節點數",
"onlineUsersCount": "線上用戶數",
"pendingTickets": "待處理工單",
"register": "註冊",
"repurchase": "回購",

View File

@ -33,6 +33,16 @@
"saveSuccess": "保存成功",
"title": "邀請設置"
},
"logCleanup": {
"autoClear": "啟用自動清理",
"autoClearDescription": "啟用後,系統將自動清除過期的日誌記錄",
"clearDays": "保留天數",
"clearDaysDescription": "保留日誌的天數;超過此天數的日誌將被清除",
"clearDaysPlaceholder": "輸入保留天數",
"description": "配置自動日誌清理規則和保留期限",
"title": "日誌清理設置"
},
"logSettings": "日誌設置",
"privacyPolicy": {
"description": "編輯和管理隱私政策內容",
"title": "隱私政策"

View File

@ -50,6 +50,7 @@
"more": "更多",
"notifySettingsTitle": "通知設定",
"onlineDevices": "在線裝置",
"onlyFirstPurchase": "首次購買專享",
"orderList": "訂單列表",
"password": "密碼",
"passwordPlaceholder": "輸入新密碼(可選)",
@ -59,6 +60,8 @@
"refererId": "推薦人 ID",
"refererIdPlaceholder": "輸入推薦人 ID",
"referralCode": "推薦碼",
"referralPercentage": "推薦百分比",
"referralPercentagePlaceholder": "輸入推薦百分比0-100",
"referrerUserId": "推薦人(用戶 ID",
"remove": "移除",
"resetLogs": "重置日誌",

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as ads from './ads';

View File

@ -155,6 +155,26 @@ export async function filterServerTrafficLog(
);
}
/** Get log setting GET /v1/admin/log/setting */
export async function getLogSetting(options?: { [key: string]: any }) {
return request<API.Response & { data?: API.LogSetting }>('/v1/admin/log/setting', {
method: 'GET',
...(options || {}),
});
}
/** Update log setting POST /v1/admin/log/setting */
export async function updateLogSetting(body: API.LogSetting, options?: { [key: string]: any }) {
return request<API.Response & { data?: any }>('/v1/admin/log/setting', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** Filter subscribe log GET /v1/admin/log/subscribe/list */
export async function filterSubscribeLog(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)

View File

@ -111,6 +111,21 @@ export async function filterNodeList(
);
}
/** Reset node sort POST /v1/admin/server/node/sort */
export async function resetSortWithNode(
body: API.ResetSortRequest,
options?: { [key: string]: any },
) {
return request<API.Response & { data?: any }>('/v1/admin/server/node/sort', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** Toggle Node Status POST /v1/admin/server/node/status/toggle */
export async function toggleNodeStatus(
body: API.ToggleNodeStatusRequest,
@ -156,6 +171,21 @@ export async function getServerProtocols(
);
}
/** Reset server sort POST /v1/admin/server/server/sort */
export async function resetSortWithServer(
body: API.ResetSortRequest,
options?: { [key: string]: any },
) {
return request<API.Response & { data?: any }>('/v1/admin/server/server/sort', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** Update Server POST /v1/admin/server/update */
export async function updateServer(
body: API.UpdateServerRequest,

View File

@ -366,6 +366,8 @@ declare namespace API {
password: string;
product_id: number;
duration: number;
referral_percentage: number;
only_first_purchase: boolean;
referer_user: string;
refer_code: string;
balance: number;
@ -1289,6 +1291,11 @@ declare namespace API {
list: Record<string, any>;
};
type LogSetting = {
auto_clear: boolean;
clear_days: number;
};
type MessageLog = {
id: number;
type: number;
@ -1321,6 +1328,7 @@ declare namespace API {
server_id: number;
protocol: string;
enabled: boolean;
sort?: number;
created_at: number;
updated_at: number;
};
@ -1622,6 +1630,10 @@ declare namespace API {
order_no: string;
};
type ResetSortRequest = {
sort: SortItem[];
};
type ResetSubscribeLog = {
type: number;
user_id: number;
@ -1696,8 +1708,13 @@ declare namespace API {
updated_at: number;
};
type ServerOnlineIP = {
ip: string;
protocol: string;
};
type ServerOnlineUser = {
ip: string[];
ip: ServerOnlineIP[];
user_id: number;
subscribe: string;
subscribe_id: number;
@ -1719,14 +1736,15 @@ declare namespace API {
};
type ServerStatus = {
online: ServerOnlineUser[];
cpu: number;
mem: number;
disk: number;
protocol: string;
online: ServerOnlineUser[];
};
type ServerTotalDataResponse = {
online_user_ips: number;
online_users: number;
online_servers: number;
offline_servers: number;
today_upload: number;
@ -2161,6 +2179,8 @@ declare namespace API {
avatar: string;
balance: number;
commission: number;
referral_percentage: number;
only_first_purchase: boolean;
gift_amount: number;
telegram: number;
refer_code: string;
@ -2191,6 +2211,8 @@ declare namespace API {
avatar: string;
balance: number;
commission: number;
referral_percentage: number;
only_first_purchase: boolean;
gift_amount: number;
telegram: number;
refer_code: string;

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as auth from './auth';

View File

@ -890,6 +890,8 @@ declare namespace API {
avatar: string;
balance: number;
commission: number;
referral_percentage: number;
only_first_purchase: boolean;
gift_amount: number;
telegram: number;
refer_code: string;

View File

@ -46,7 +46,8 @@ export default function Affiliate() {
<Display type='currency' value={data?.total_commission} />
</span>
<span className='text-muted-foreground text-sm'>
({t('commissionRate')}: {common?.invite?.referral_percentage}%)
({t('commissionRate')}:{' '}
{user?.referral_percentage || common?.invite?.referral_percentage}%)
</span>
</div>
</CardContent>

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as auth from './auth';

View File

@ -890,6 +890,8 @@ declare namespace API {
avatar: string;
balance: number;
commission: number;
referral_percentage: number;
only_first_purchase: boolean;
gift_amount: number;
telegram: number;
refer_code: string;

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as announcement from './announcement';

View File

@ -971,6 +971,8 @@ declare namespace API {
avatar: string;
balance: number;
commission: number;
referral_percentage: number;
only_first_purchase: boolean;
gift_amount: number;
telegram: number;
refer_code: string;