diff --git a/apps/admin/app/dashboard/servers/page.tsx b/apps/admin/app/dashboard/servers/page.tsx index 18e2d5c..bfd0b63 100644 --- a/apps/admin/app/dashboard/servers/page.tsx +++ b/apps/admin/app/dashboard/servers/page.tsx @@ -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(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 (
@@ -105,11 +75,6 @@ export default function ServersPage() { title: t('pageTitle'), toolbar: (
- {hasMigrate && ( - - )} ( - - - ({ - ...item, - rules: Array.isArray(item.rules) ? item.rules.join('\n') : '', - }))} - onChange={(values) => { - const converted = values.map((item: any) => ({ - name: item.name, - protocol: item.protocol, - address: item.address, - port: item.port, - password: item.password, - rules: - typeof item.rules === 'string' - ? item.rules.split('\n').map((r: string) => r.trim()) - : item.rules || [], - })); - field.onChange(converted); - }} - /> - - - - )} + render={({ field }) => { + return ( + + + ({ + label: cipher, + value: cipher, + })), + visible: (item: Record) => + item.protocol === 'shadowsocks', + }, + { + name: 'address', + type: 'text', + placeholder: t( + 'server_config.fields.outbound_address_placeholder', + ), + }, + { + name: 'port', + type: 'number', + placeholder: t('server_config.fields.outbound_port_placeholder'), + }, + { + name: 'password', + type: 'text', + placeholder: t( + 'server_config.fields.outbound_password_placeholder', + ), + }, + { + name: 'rules', + type: 'textarea', + className: 'col-span-2', + placeholder: t('server_config.fields.outbound_rules_placeholder'), + }, + ]} + value={(field.value || []).map((item) => ({ + ...item, + rules: Array.isArray(item.rules) ? item.rules.join('\n') : '', + }))} + onChange={(values) => { + const converted = values.map((item: any) => ({ + name: item.name, + protocol: item.protocol, + address: item.address, + port: item.port, + cipher: item.cipher, + password: item.password, + rules: + typeof item.rules === 'string' + ? item.rules.split('\n').map((r: string) => r.trim()) + : item.rules || [], + })); + field.onChange(converted); + }} + /> + + + + ); + }} /> diff --git a/apps/admin/config/use-global.tsx b/apps/admin/config/use-global.tsx index 1b8cca5..24d6fd6 100644 --- a/apps/admin/config/use-global.tsx +++ b/apps/admin/config/use-global.tsx @@ -45,6 +45,12 @@ export const useGlobalStore = create((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, diff --git a/apps/user/config/use-global.tsx b/apps/user/config/use-global.tsx index 1e17321..444baf2 100644 --- a/apps/user/config/use-global.tsx +++ b/apps/user/config/use-global.tsx @@ -48,6 +48,12 @@ export const useGlobalStore = create((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, diff --git a/packages/ui/src/custom-components/dynamic-Inputs.tsx b/packages/ui/src/custom-components/dynamic-Inputs.tsx index b3a5a60..0fb5b02 100644 --- a/packages/ui/src/custom-components/dynamic-Inputs.tsx +++ b/packages/ui/src/custom-components/dynamic-Inputs.tsx @@ -12,6 +12,8 @@ interface FieldConfig extends Omit { 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) => boolean; } interface ObjectInputProps { @@ -40,6 +42,8 @@ export function ObjectInput>({ 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>({ }; return (
- {fields.map((field) => ( -
- {renderField(field)} -
- ))} + {fields.map((field) => { + const node = renderField(field); + if (node === null) return null; // don't render wrapper if field hidden + return ( +
+ {node} +
+ ); + })}
); }