mirror of
https://github.com/perfect-panel/ppanel-web.git
synced 2026-02-06 11:40:28 -05:00
🐛 fix: Add DynamicMultiplier component for managing node multipliers and update ServersPage layout
This commit is contained in:
parent
47f66030db
commit
bb6671c14f
47
CHANGELOG.md
47
CHANGELOG.md
@ -1,57 +1,52 @@
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
## [1.4.8](https://github.com/perfect-panel/ppanel-web/compare/v1.4.7...v1.4.8) (2025-09-23)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* Rename 'server_id' to 'protocol' in NodesPage and clean up unused imports and code in ServerConfig ([70b3484](https://github.com/perfect-panel/ppanel-web/commit/70b3484))
|
||||
* Update announcement page to display timeline of announcements with Markdown content ([3c036eb](https://github.com/perfect-panel/ppanel-web/commit/3c036eb))
|
||||
* Update Empty component to support border prop and adjust usage in various pages ([ce9ab89](https://github.com/perfect-panel/ppanel-web/commit/ce9ab89))
|
||||
- Rename 'server_id' to 'protocol' in NodesPage and clean up unused imports and code in ServerConfig ([70b3484](https://github.com/perfect-panel/ppanel-web/commit/70b3484))
|
||||
- Update announcement page to display timeline of announcements with Markdown content ([3c036eb](https://github.com/perfect-panel/ppanel-web/commit/3c036eb))
|
||||
- Update Empty component to support border prop and adjust usage in various pages ([ce9ab89](https://github.com/perfect-panel/ppanel-web/commit/ce9ab89))
|
||||
|
||||
## [1.4.7](https://github.com/perfect-panel/ppanel-web/compare/v1.4.6...v1.4.7) (2025-09-23)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* Add unique key to ProTable for improved rendering with user ID filters ([2bff15f](https://github.com/perfect-panel/ppanel-web/commit/2bff15f))
|
||||
* Adjust layout spacing and chart aspect ratio in ServerConfig component ([05a61d8](https://github.com/perfect-panel/ppanel-web/commit/05a61d8))
|
||||
* Refactor server ID cell rendering for improved readability and consistency ([0345b7c](https://github.com/perfect-panel/ppanel-web/commit/0345b7c))
|
||||
* Update announcement page to format creation date and enhance content display ([8445e30](https://github.com/perfect-panel/ppanel-web/commit/8445e30))
|
||||
* Update OnlineUsersCell to display user count with icon instead of badge ([7a4ebdf](https://github.com/perfect-panel/ppanel-web/commit/7a4ebdf))
|
||||
* Update subscribe name fallback to return '--' instead of 'Unknown' ([0a07d25](https://github.com/perfect-panel/ppanel-web/commit/0a07d25))
|
||||
- Add unique key to ProTable for improved rendering with user ID filters ([2bff15f](https://github.com/perfect-panel/ppanel-web/commit/2bff15f))
|
||||
- Adjust layout spacing and chart aspect ratio in ServerConfig component ([05a61d8](https://github.com/perfect-panel/ppanel-web/commit/05a61d8))
|
||||
- Refactor server ID cell rendering for improved readability and consistency ([0345b7c](https://github.com/perfect-panel/ppanel-web/commit/0345b7c))
|
||||
- Update announcement page to format creation date and enhance content display ([8445e30](https://github.com/perfect-panel/ppanel-web/commit/8445e30))
|
||||
- Update OnlineUsersCell to display user count with icon instead of badge ([7a4ebdf](https://github.com/perfect-panel/ppanel-web/commit/7a4ebdf))
|
||||
- Update subscribe name fallback to return '--' instead of 'Unknown' ([0a07d25](https://github.com/perfect-panel/ppanel-web/commit/0a07d25))
|
||||
|
||||
## [1.4.6](https://github.com/perfect-panel/ppanel-web/compare/v1.4.5...v1.4.6) (2025-09-17)
|
||||
|
||||
|
||||
### 🎫 Chores
|
||||
|
||||
* Merge branch 'main' into develop ([41f06bf](https://github.com/perfect-panel/ppanel-web/commit/41f06bf))
|
||||
|
||||
- Merge branch 'main' into develop ([41f06bf](https://github.com/perfect-panel/ppanel-web/commit/41f06bf))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* Add loaded state to node, server, and subscribe stores for better loading management ([13dce0c](https://github.com/perfect-panel/ppanel-web/commit/13dce0c))
|
||||
* Removed node metadata fields in subscription schema to simplify structure ([0cadd83](https://github.com/perfect-panel/ppanel-web/commit/0cadd83))
|
||||
- Add loaded state to node, server, and subscribe stores for better loading management ([13dce0c](https://github.com/perfect-panel/ppanel-web/commit/13dce0c))
|
||||
- Removed node metadata fields in subscription schema to simplify structure ([0cadd83](https://github.com/perfect-panel/ppanel-web/commit/0cadd83))
|
||||
|
||||
## [1.4.5](https://github.com/perfect-panel/ppanel-web/compare/v1.4.4...v1.4.5) (2025-09-17)
|
||||
|
||||
|
||||
### ♻ Code Refactoring
|
||||
|
||||
* Replace useQuery with Zustand store for subscription and node data management ([c6dd0b6](https://github.com/perfect-panel/ppanel-web/commit/c6dd0b6))
|
||||
* Simplify TemplatePreview component structure by consolidating Sheet and Button elements ([1b715c5](https://github.com/perfect-panel/ppanel-web/commit/1b715c5))
|
||||
|
||||
- Replace useQuery with Zustand store for subscription and node data management ([c6dd0b6](https://github.com/perfect-panel/ppanel-web/commit/c6dd0b6))
|
||||
- Simplify TemplatePreview component structure by consolidating Sheet and Button elements ([1b715c5](https://github.com/perfect-panel/ppanel-web/commit/1b715c5))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* Add showLineNumbers prop handling in MonacoEditor for improved placeholder positioning ([bd67ece](https://github.com/perfect-panel/ppanel-web/commit/bd67ece))
|
||||
* Add fetchTags method to NodesPage and ensure tags are fetched alongside nodes ([a3c5e31](https://github.com/perfect-panel/ppanel-web/commit/a3c5e31))
|
||||
* Add NEXT_PUBLIC_HIDDEN_TUTORIAL_DOCUMENT to control tutorial visibility and update page query accordingly ([e94405d](https://github.com/perfect-panel/ppanel-web/commit/e94405d))
|
||||
* Add subscribeSchema for subscription management with detailed proxy and user information ([49b3dcc](https://github.com/perfect-panel/ppanel-web/commit/49b3dcc))
|
||||
* Enhance server ID display in ServerTrafficLogPage with badges and server ratio ([6dfac27](https://github.com/perfect-panel/ppanel-web/commit/6dfac27))
|
||||
* Update platform handling in Content component to ensure available platforms are correctly filtered and displayed ([1dde708](https://github.com/perfect-panel/ppanel-web/commit/1dde708))
|
||||
- Add showLineNumbers prop handling in MonacoEditor for improved placeholder positioning ([bd67ece](https://github.com/perfect-panel/ppanel-web/commit/bd67ece))
|
||||
- Add fetchTags method to NodesPage and ensure tags are fetched alongside nodes ([a3c5e31](https://github.com/perfect-panel/ppanel-web/commit/a3c5e31))
|
||||
- Add NEXT_PUBLIC_HIDDEN_TUTORIAL_DOCUMENT to control tutorial visibility and update page query accordingly ([e94405d](https://github.com/perfect-panel/ppanel-web/commit/e94405d))
|
||||
- Add subscribeSchema for subscription management with detailed proxy and user information ([49b3dcc](https://github.com/perfect-panel/ppanel-web/commit/49b3dcc))
|
||||
- Enhance server ID display in ServerTrafficLogPage with badges and server ratio ([6dfac27](https://github.com/perfect-panel/ppanel-web/commit/6dfac27))
|
||||
- Update platform handling in Content component to ensure available platforms are correctly filtered and displayed ([1dde708](https://github.com/perfect-panel/ppanel-web/commit/1dde708))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
|
||||
115
apps/admin/app/dashboard/servers/dynamic-multiplier.tsx
Normal file
115
apps/admin/app/dashboard/servers/dynamic-multiplier.tsx
Normal file
@ -0,0 +1,115 @@
|
||||
'use client';
|
||||
|
||||
import { getNodeMultiplier, setNodeMultiplier } from '@/services/admin/system';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Card, CardContent } from '@workspace/ui/components/card';
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@workspace/ui/components/sheet';
|
||||
import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function DynamicMultiplier() {
|
||||
const t = useTranslations('servers');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [timeSlots, setTimeSlots] = useState<API.TimePeriod[]>([]);
|
||||
|
||||
const { data: periodsResp, refetch: refetchPeriods } = useQuery({
|
||||
queryKey: ['getNodeMultiplier'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getNodeMultiplier();
|
||||
return (data.data?.periods || []) as API.TimePeriod[];
|
||||
},
|
||||
enabled: open,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (periodsResp) {
|
||||
setTimeSlots(periodsResp);
|
||||
}
|
||||
}, [periodsResp]);
|
||||
|
||||
async function savePeriods() {
|
||||
try {
|
||||
await setNodeMultiplier({ periods: timeSlots });
|
||||
await refetchPeriods();
|
||||
toast.success(t('config.saveSuccess'));
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
toast.error(t('config.saveError'));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Card>
|
||||
<CardContent className='p-4'>
|
||||
<div className='flex cursor-pointer items-center justify-between'>
|
||||
<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:clock-time-eight' className='text-primary h-5 w-5' />
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<p className='font-medium'>{t('config.dynamicMultiplier')}</p>
|
||||
<p className='text-muted-foreground truncate text-sm'>
|
||||
{t('config.dynamicMultiplierDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Icon icon='mdi:chevron-right' className='size-6' />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</SheetTrigger>
|
||||
|
||||
<SheetContent className='w-[600px] max-w-full md:max-w-screen-md'>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{t('config.dynamicMultiplier')}</SheetTitle>
|
||||
<SheetDescription>{t('config.dynamicMultiplierDescription')}</SheetDescription>
|
||||
</SheetHeader>
|
||||
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-60px-env(safe-area-inset-top))] px-6'>
|
||||
<div className='space-y-4 pt-4'>
|
||||
<ArrayInput<API.TimePeriod>
|
||||
fields={[
|
||||
{ name: 'start_time', prefix: t('config.startTime'), type: 'time' },
|
||||
{ name: 'end_time', prefix: t('config.endTime'), type: 'time' },
|
||||
{
|
||||
name: 'multiplier',
|
||||
prefix: t('config.multiplier'),
|
||||
type: 'number',
|
||||
placeholder: '0',
|
||||
},
|
||||
]}
|
||||
value={timeSlots}
|
||||
onChange={setTimeSlots}
|
||||
/>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<SheetFooter className='flex-row justify-between pt-3'>
|
||||
<Button variant='outline' onClick={() => setTimeSlots(periodsResp || [])}>
|
||||
{t('config.reset')}
|
||||
</Button>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='outline' onClick={() => setOpen(false)}>
|
||||
{t('config.actions.cancel')}
|
||||
</Button>
|
||||
<Button onClick={savePeriods}>{t('config.actions.save')}</Button>
|
||||
</div>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@ -15,12 +15,12 @@ import { useServer } from '@/store/server';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Card, CardContent } from '@workspace/ui/components/card';
|
||||
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import DynamicMultiplier from './dynamic-multiplier';
|
||||
import OnlineUsersCell from './online-users-cell';
|
||||
import ServerConfig from './server-config';
|
||||
import ServerForm from './server-form';
|
||||
@ -95,11 +95,10 @@ export default function ServersPage() {
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<Card>
|
||||
<CardContent className='p-4'>
|
||||
<ServerConfig />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
|
||||
<DynamicMultiplier />
|
||||
<ServerConfig />
|
||||
</div>
|
||||
<ProTable<API.Server, { search: string }>
|
||||
action={ref}
|
||||
header={{
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
getNodeConfig,
|
||||
getNodeMultiplier,
|
||||
setNodeMultiplier,
|
||||
updateNodeConfig,
|
||||
} from '@/services/admin/system';
|
||||
import { getNodeConfig, updateNodeConfig } from '@/services/admin/system';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
|
||||
import { Card, CardContent } from '@workspace/ui/components/card';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -19,7 +14,6 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@workspace/ui/components/form';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||
import {
|
||||
Sheet,
|
||||
@ -29,7 +23,6 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@workspace/ui/components/sheet';
|
||||
import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { DicesIcon } from 'lucide-react';
|
||||
@ -51,7 +44,6 @@ export default function ServerConfig() {
|
||||
const t = useTranslations('servers');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [timeSlots, setTimeSlots] = useState<API.TimePeriod[]>([]);
|
||||
|
||||
const { data: cfgResp, refetch: refetchCfg } = useQuery({
|
||||
queryKey: ['getNodeConfig'],
|
||||
@ -62,15 +54,6 @@ export default function ServerConfig() {
|
||||
enabled: open,
|
||||
});
|
||||
|
||||
const { data: periodsResp, refetch: refetchPeriods } = useQuery({
|
||||
queryKey: ['getNodeMultiplier'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getNodeMultiplier();
|
||||
return (data.data?.periods || []) as API.TimePeriod[];
|
||||
},
|
||||
enabled: open,
|
||||
});
|
||||
|
||||
const form = useForm<NodeConfigFormData>({
|
||||
resolver: zodResolver(nodeConfigSchema),
|
||||
defaultValues: {
|
||||
@ -90,12 +73,6 @@ export default function ServerConfig() {
|
||||
}
|
||||
}, [cfgResp, form]);
|
||||
|
||||
useEffect(() => {
|
||||
if (periodsResp) {
|
||||
setTimeSlots(periodsResp);
|
||||
}
|
||||
}, [periodsResp]);
|
||||
|
||||
async function onSubmit(values: NodeConfigFormData) {
|
||||
setSaving(true);
|
||||
try {
|
||||
@ -108,32 +85,32 @@ export default function ServerConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
async function savePeriods() {
|
||||
await setNodeMultiplier({ periods: timeSlots });
|
||||
await refetchPeriods();
|
||||
toast.success(t('config.saveSuccess'));
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<div className='flex cursor-pointer items-center justify-between'>
|
||||
<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:resistor-nodes' className='text-primary h-5 w-5' />
|
||||
<Card>
|
||||
<CardContent className='p-4'>
|
||||
<div className='flex cursor-pointer items-center justify-between'>
|
||||
<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:resistor-nodes' className='text-primary h-5 w-5' />
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<p className='font-medium'>{t('config.title')}</p>
|
||||
<p className='text-muted-foreground truncate text-sm'>
|
||||
{t('config.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Icon icon='mdi:chevron-right' className='size-6' />
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<p className='font-medium'>{t('config.title')}</p>
|
||||
<p className='text-muted-foreground text-sm'>{t('config.description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Icon icon='mdi:chevron-right' className='size-6' />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</SheetTrigger>
|
||||
|
||||
<SheetContent className='w-[720px] max-w-full md:max-w-screen-md'>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{t('config.title')}</SheetTitle>
|
||||
<SheetTitle>节点配置</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))] px-6'>
|
||||
@ -218,45 +195,6 @@ export default function ServerConfig() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='mt-6 space-y-3'>
|
||||
<Label className='text-base'>{t('config.dynamicMultiplier')}</Label>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
{t('config.dynamicMultiplierDescription')}
|
||||
</p>
|
||||
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='w-full'>
|
||||
<ArrayInput<API.TimePeriod>
|
||||
fields={[
|
||||
{ name: 'start_time', prefix: t('config.startTime'), type: 'time' },
|
||||
{ name: 'end_time', prefix: t('config.endTime'), type: 'time' },
|
||||
{
|
||||
name: 'multiplier',
|
||||
prefix: t('config.multiplier'),
|
||||
type: 'number',
|
||||
placeholder: '0',
|
||||
},
|
||||
]}
|
||||
value={timeSlots}
|
||||
onChange={setTimeSlots}
|
||||
/>
|
||||
<div className='mt-3 flex gap-2'>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
variant='outline'
|
||||
onClick={() => setTimeSlots(periodsResp || [])}
|
||||
>
|
||||
{t('config.reset')}
|
||||
</Button>
|
||||
<Button size='sm' type='button' onClick={savePeriods}>
|
||||
{t('config.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</ScrollArea>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"city": "City",
|
||||
"config": {
|
||||
"title": "Node configuration",
|
||||
"description": "Manage node communication keys, pull/push intervals, and dynamic multipliers.",
|
||||
"description": "Manage node communication keys, pull/push intervals.",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"communicationKey": "Communication key",
|
||||
"inputPlaceholder": "Please enter",
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
},
|
||||
"communicationKey": "通信密钥",
|
||||
"communicationKeyDescription": "用于节点鉴权。",
|
||||
"description": "管理节点通信密钥、拉取/推送间隔与动态倍率。",
|
||||
"description": "管理节点通信密钥、拉取/推送间隔。",
|
||||
"dynamicMultiplier": "动态倍率",
|
||||
"dynamicMultiplierDescription": "按时间段设置倍率,用于调节流量或计费。",
|
||||
"endTime": "结束时间",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user