🐛 fix: Remove unnecessary migration function code and add device configuration options

This commit is contained in:
web 2025-10-21 04:33:29 -07:00
parent a46657d5ef
commit 521a7a97fb
5 changed files with 128 additions and 125 deletions

View File

@ -5,14 +5,11 @@ import {
createServer,
deleteServer,
filterServerList,
hasMigrateSeverNode,
migrateServerNode,
resetSortWithServer,
updateServer,
} from '@/services/admin/server';
import { useNode } from '@/store/node';
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 { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
@ -66,33 +63,6 @@ export default function ServersPage() {
const [migrating, setMigrating] = useState(false);
const ref = useRef<ProTableActions>(null);
const { data: hasMigrate, refetch: refetchHasMigrate } = useQuery({
queryKey: ['hasMigrateSeverNode'],
queryFn: async () => {
const { data } = await hasMigrateSeverNode();
return data.data?.has_migrate;
},
});
const handleMigrate = async () => {
setMigrating(true);
try {
const { data } = await migrateServerNode();
const fail = data.data?.fail || 0;
if (fail > 0) {
toast.error(data.data?.message);
} else {
toast.success(t('migrated'));
}
refetchHasMigrate();
ref.current?.refresh();
} catch (error) {
toast.error(t('migrateFailed'));
} finally {
setMigrating(false);
}
};
return (
<div className='space-y-4'>
<div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
@ -105,11 +75,6 @@ export default function ServersPage() {
title: t('pageTitle'),
toolbar: (
<div className='flex gap-2'>
{hasMigrate && (
<Button variant='outline' onClick={handleMigrate} disabled={migrating}>
{migrating ? t('migrating') : t('migrate')}
</Button>
)}
<ServerForm
trigger={t('create')}
title={t('drawerCreateTitle')}

View File

@ -43,6 +43,7 @@ import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
import { SS_CIPHERS } from './form-schema';
const dnsConfigSchema = z.object({
proto: z.string(), // z.enum(['tcp', 'udp', 'tls', 'https', 'quic']),
@ -55,6 +56,7 @@ const outboundConfigSchema = z.object({
protocol: z.string(),
address: z.string(),
port: z.number(),
cipher: z.string().optional(),
password: z.string().optional(),
rules: z.array(z.string()).optional(),
});
@ -92,7 +94,7 @@ export default function ServerConfig() {
node_pull_interval: undefined,
node_push_interval: undefined,
traffic_report_threshold: undefined,
ip_strategy: undefined,
ip_strategy: 'prefer_ipv4',
dns: [],
block: [],
outbound: [],
@ -106,7 +108,8 @@ export default function ServerConfig() {
node_pull_interval: cfgResp.node_pull_interval as number | undefined,
node_push_interval: cfgResp.node_push_interval as number | undefined,
traffic_report_threshold: cfgResp.traffic_report_threshold as number | undefined,
ip_strategy: cfgResp.ip_strategy as 'prefer_ipv4' | 'prefer_ipv6' | undefined,
ip_strategy:
(cfgResp.ip_strategy as 'prefer_ipv4' | 'prefer_ipv6' | undefined) || 'prefer_ipv4',
dns: cfgResp.dns || [],
block: cfgResp.block || [],
outbound: cfgResp.outbound || [],
@ -364,7 +367,8 @@ export default function ServerConfig() {
<FormField
control={form.control}
name='outbound'
render={({ field }) => (
render={({ field }) => {
return (
<FormItem>
<FormControl>
<ArrayInput
@ -400,10 +404,22 @@ export default function ServerConfig() {
{ label: 'Reject', value: 'reject' },
],
},
{
name: 'cipher',
type: 'select',
options: SS_CIPHERS.map((cipher) => ({
label: cipher,
value: cipher,
})),
visible: (item: Record<string, any>) =>
item.protocol === 'shadowsocks',
},
{
name: 'address',
type: 'text',
placeholder: t('server_config.fields.outbound_address_placeholder'),
placeholder: t(
'server_config.fields.outbound_address_placeholder',
),
},
{
name: 'port',
@ -434,6 +450,7 @@ export default function ServerConfig() {
protocol: item.protocol,
address: item.address,
port: item.port,
cipher: item.cipher,
password: item.password,
rules:
typeof item.rules === 'string'
@ -446,7 +463,8 @@ export default function ServerConfig() {
</FormControl>
<FormMessage />
</FormItem>
)}
);
}}
/>
</TabsContent>

View File

@ -45,6 +45,12 @@ export const useGlobalStore = create<GlobalStore>((set, get) => ({
ip_register_limit: 0,
ip_register_limit_duration: 0,
},
device: {
enable: false,
show_ads: false,
enable_security: false,
only_real_device: false,
},
},
invite: {
forced_invite: false,

View File

@ -48,6 +48,12 @@ export const useGlobalStore = create<GlobalStore>((set, get) => ({
ip_register_limit: 0,
ip_register_limit_duration: 0,
},
device: {
enable: false,
show_ads: false,
enable_security: false,
only_real_device: false,
},
},
invite: {
forced_invite: false,

View File

@ -12,6 +12,8 @@ interface FieldConfig extends Omit<EnhancedInputProps, 'type'> {
name: string;
type: 'text' | 'number' | 'select' | 'time' | 'boolean' | 'textarea';
options?: { label: string; value: string }[];
// optional per-item visibility function: returns true to show the field for the given item
visible?: (item: Record<string, any>) => boolean;
}
interface ObjectInputProps<T> {
@ -40,6 +42,8 @@ export function ObjectInput<T extends Record<string, any>>({
onChange(updatedInternalState);
};
const renderField = (field: FieldConfig) => {
// if visible callback exists and returns false for current item, don't render
if (field.visible && !field.visible(internalState)) return null;
switch (field.type) {
case 'select':
return (
@ -86,11 +90,15 @@ export function ObjectInput<T extends Record<string, any>>({
};
return (
<div className={cn('flex flex-1 flex-wrap gap-4', className)}>
{fields.map((field) => (
{fields.map((field) => {
const node = renderField(field);
if (node === null) return null; // don't render wrapper if field hidden
return (
<div key={field.name} className={cn('flex-1', field.className)}>
{renderField(field)}
{node}
</div>
))}
);
})}
</div>
);
}