metacubexd/src/pages/Config.tsx

476 lines
11 KiB
TypeScript
Raw Normal View History

import { createForm } from '@felte/solid'
import { validator } from '@felte/validator-zod'
import { useI18n } from '@solid-primitives/i18n'
import { useNavigate } from '@solidjs/router'
import {
For,
ParentComponent,
Show,
children,
createSignal,
onMount,
} from 'solid-js'
2023-08-28 22:13:48 +08:00
import { z } from 'zod'
import { Button } from '~/components'
import {
LANG,
2023-09-12 20:50:52 +08:00
MODE_OPTIONS,
PROXIES_ORDERING_TYPE,
PROXIES_PREVIEW_TYPE,
ROUTES,
TAILWINDCSS_SIZE,
themes,
} from '~/constants'
import {
2023-09-02 19:06:02 +08:00
applyThemeByMode,
autoCloseConns,
2023-09-02 19:06:02 +08:00
autoSwitchTheme,
2023-09-12 20:50:52 +08:00
backendConfig,
2023-09-02 19:06:02 +08:00
favDayTheme,
favNightTheme,
2023-09-12 20:50:52 +08:00
fetchBackendConfig,
latencyTestTimeoutDuration,
proxiesOrderingType,
proxiesPreviewType,
renderInTwoColumns,
setAutoCloseConns,
2023-09-02 19:06:02 +08:00
setAutoSwitchTheme,
2023-09-12 20:50:52 +08:00
setBackendConfig,
2023-09-02 19:06:02 +08:00
setFavDayTheme,
setFavNightTheme,
setLatencyTestTimeoutDuration,
setProxiesOrderingType,
setProxiesPreviewType,
setRenderInTwoColumns,
setSelectedEndpoint,
setTableSize,
setTwemoji,
2023-09-03 05:40:39 +08:00
setUrlForLatencyTest,
tableSize,
2023-09-12 20:50:52 +08:00
updateBackendConfig,
2023-09-03 05:40:39 +08:00
urlForLatencyTest,
useRequest,
useTwemoji,
} from '~/signals'
2023-09-12 20:50:52 +08:00
import type { BackendVersion, DNSQuery } from '~/types'
2023-08-28 22:13:48 +08:00
const dnsQueryFormSchema = z.object({
name: z.string(),
type: z.string(),
})
const ConfigTitle: ParentComponent = (props) => (
<div class="pb-4 text-lg font-semibold">
{children(() => props.children)()}
</div>
)
const DNSQueryForm = () => {
const [t] = useI18n()
const request = useRequest()
2023-09-03 02:22:38 +08:00
const { form, isSubmitting } = createForm<z.infer<typeof dnsQueryFormSchema>>(
{
extend: validator({ schema: dnsQueryFormSchema }),
onSubmit: async (values) => {
request
.get('dns/query', {
searchParams: { name: values.name, type: values.type },
})
.json<DNSQuery>()
.then(({ Answer }) =>
setDNSQueryResult(Answer?.map(({ data }) => data) || []),
)
},
},
2023-09-03 02:22:38 +08:00
)
const [DNSQueryResult, setDNSQueryResult] = createSignal<string[]>([])
return (
2023-09-02 14:02:25 +08:00
<div class="flex flex-col">
<form use:form={form} class="flex flex-col gap-2 sm:flex-row">
2023-09-10 16:42:00 +08:00
<input
type="search"
name="name"
class="input input-bordered w-full sm:flex-1"
/>
<div class="flex items-center gap-2">
<select name="type" class="select select-bordered">
<option>A</option>
<option>AAAA</option>
<option>MX</option>
</select>
2023-09-03 02:22:38 +08:00
<Button type="submit" class="btn-primary" loading={isSubmitting()}>
{t('dnsQuery')}
2023-09-03 02:22:38 +08:00
</Button>
</div>
</form>
<Show when={DNSQueryResult().length > 0}>
<div class="flex flex-col p-4">
<For each={DNSQueryResult()}>
{(item) => <div class="py-2">{item}</div>}
</For>
</div>
</Show>
</div>
)
}
const configFormSchema = z.object({
2023-08-28 22:13:48 +08:00
port: z.number(),
'socks-port': z.number(),
'redir-port': z.number(),
'tproxy-port': z.number(),
'mixed-port': z.number(),
})
const ConfigForm = () => {
const [t] = useI18n()
2023-08-28 22:13:48 +08:00
const request = useRequest()
2023-09-01 14:06:21 +08:00
const portsList = [
2023-08-28 22:13:48 +08:00
{
label: 'Http Port',
2023-08-28 22:13:48 +08:00
key: 'port',
},
{
label: 'Socks Port',
2023-08-28 22:13:48 +08:00
key: 'socks-port',
},
{
label: 'Redir Port',
2023-08-28 22:13:48 +08:00
key: 'redir-port',
},
{
label: 'Tproxy Port',
2023-08-28 22:13:48 +08:00
key: 'tproxy-port',
},
{
label: 'Mixed Port',
2023-08-28 22:13:48 +08:00
key: 'mixed-port',
},
]
const { form, setInitialValues, reset } = createForm<
z.infer<typeof configFormSchema>
>({ extend: validator({ schema: configFormSchema }) })
2023-09-01 14:06:21 +08:00
2023-08-28 22:13:48 +08:00
onMount(async () => {
2023-09-12 20:50:52 +08:00
const configs = await fetchBackendConfig()
setBackendConfig(configs)
2023-08-28 22:13:48 +08:00
setInitialValues(configs)
reset()
})
const [updatingGEODatabases, setUpdatingGEODatabases] = createSignal(false)
const [upgrading, setUpgrading] = createSignal(false)
const [restarting, setRestarting] = createSignal(false)
const onUpdateGEODatabases = async () => {
setUpdatingGEODatabases(true)
try {
await request.post('configs/geo')
} catch {}
setUpdatingGEODatabases(false)
}
const onUpgrade = async () => {
setUpgrading(true)
try {
await request.post('upgrade')
} catch {}
setUpgrading(false)
}
const onRestart = async () => {
setRestarting(true)
try {
await request.post('restart')
} catch {}
setRestarting(false)
}
2023-08-28 22:13:48 +08:00
return (
<div class="flex flex-col gap-4">
2023-09-12 20:50:52 +08:00
<select
class="select select-bordered"
value={backendConfig()?.mode}
onChange={(e) => updateBackendConfig('mode', e.target.value)}
>
<option value={MODE_OPTIONS.Global}>{t('global')}</option>
<option value={MODE_OPTIONS.Rule}>{t('rule')}</option>
<option value={MODE_OPTIONS.Direct}>{t('direct')}</option>
</select>
<form class="contents" use:form={form}>
<For each={portsList}>
{(item) => (
<div class="form-control w-64 max-w-sm">
<label class="label">
<span class="label-text">{item.label}</span>
</label>
<input
name={item.key}
type="number"
class="input input-bordered"
placeholder={item.label}
/>
</div>
)}
</For>
2023-09-01 14:06:21 +08:00
</form>
<div class="flex flex-wrap items-center gap-2">
<Button loading={updatingGEODatabases()} onClick={onUpdateGEODatabases}>
{t('updateGEODatabases')}
</Button>
<Button loading={upgrading()} onClick={onUpgrade}>
{t('upgradeCore')}
</Button>
<Button loading={restarting()} onClick={onRestart}>
{t('restartCore')}
</Button>
</div>
<div class="flex flex-col">
<ConfigTitle>{t('urlForLatencyTest')}</ConfigTitle>
<input
class="input input-bordered max-w-md"
value={urlForLatencyTest()}
onChange={(e) => setUrlForLatencyTest(e.target.value)}
/>
</div>
<div>
<ConfigTitle>
{t('latencyTestTimeoutDuration')} ({t('ms')})
</ConfigTitle>
<input
type="number"
class="input input-bordered w-full max-w-md"
value={latencyTestTimeoutDuration()}
onChange={(e) =>
setLatencyTestTimeoutDuration(Number(e.target.value))
}
/>
</div>
<div>
<ConfigTitle>{t('autoCloseConns')}</ConfigTitle>
<input
class="toggle"
type="checkbox"
checked={autoCloseConns()}
onChange={(e) => setAutoCloseConns(e.target.checked)}
/>
</div>
</div>
)
}
2023-09-01 14:06:21 +08:00
2023-09-02 15:08:35 +08:00
const ConfigForXd = () => {
const [t, { locale }] = useI18n()
const navigate = useNavigate()
const onSwitchEndpointClick = () => {
setSelectedEndpoint('')
navigate(ROUTES.Setup)
}
2023-09-02 14:25:22 +08:00
const autoSwitchThemeSubChild = () => (
<Show when={autoSwitchTheme()}>
2023-09-02 21:00:20 +08:00
<div class="flex flex-col">
<ConfigTitle>{t('favDayTheme')}</ConfigTitle>
<select
class="select select-bordered w-full max-w-xs"
onChange={(e) => {
setFavDayTheme(e.target.value)
applyThemeByMode()
}}
>
<For each={themes}>
{(theme) => (
<option selected={favDayTheme() === theme} value={theme}>
{theme}
</option>
)}
</For>
</select>
</div>
2023-09-02 19:06:02 +08:00
<div class="flex flex-col">
<ConfigTitle>{t('favNightTheme')}</ConfigTitle>
<select
class="select select-bordered w-full max-w-xs"
2023-09-02 19:06:02 +08:00
onChange={(e) => {
setFavNightTheme(e.target.value)
2023-09-02 19:06:02 +08:00
applyThemeByMode()
}}
>
<For each={themes}>
{(theme) => (
<option selected={favNightTheme() === theme} value={theme}>
{theme}
</option>
)}
</For>
</select>
2023-09-02 19:06:02 +08:00
</div>
</Show>
)
const checkboxList = [
{
label: 'renderInTwoColumns',
value: renderInTwoColumns,
onChange: setRenderInTwoColumns,
},
{
label: 'autoSwitchTheme',
value: autoSwitchTheme,
onChange: (value: boolean) => {
setAutoSwitchTheme(value)
applyThemeByMode()
},
subChild: autoSwitchThemeSubChild,
},
{
label: 'useTwemoji',
value: useTwemoji,
onChange: setTwemoji,
},
]
return (
<div class="grid gap-4">
<For each={checkboxList}>
{(checkbox) => (
<>
<div class="flex flex-col">
<ConfigTitle>{t(checkbox.label)}</ConfigTitle>
<input
type="checkbox"
class="toggle"
checked={checkbox.value()}
onChange={(e) => {
checkbox.onChange(e.target.checked)
}}
/>
</div>
{checkbox.subChild?.()}
</>
)}
</For>
<div>
<ConfigTitle>{t('proxiesPreviewType')}</ConfigTitle>
<select
class="select select-bordered w-full max-w-xs"
value={proxiesPreviewType()}
onChange={(e) =>
setProxiesPreviewType(e.target.value as PROXIES_PREVIEW_TYPE)
}
>
<For each={Object.values(PROXIES_PREVIEW_TYPE)}>
{(value) => <option value={value}>{t(value)}</option>}
</For>
</select>
2023-09-02 15:08:35 +08:00
</div>
<div>
<ConfigTitle>{t('proxiesSorting')}</ConfigTitle>
<select
class="select select-bordered w-full max-w-xs"
value={proxiesOrderingType()}
onChange={(e) =>
setProxiesOrderingType(e.target.value as PROXIES_ORDERING_TYPE)
}
>
<For each={Object.values(PROXIES_ORDERING_TYPE)}>
{(value) => (
<option class="flex items-center gap-2" value={value}>
{t(value)}
</option>
)}
</For>
</select>
</div>
2023-09-02 15:08:35 +08:00
<div>
<ConfigTitle>{t('tableSize')}</ConfigTitle>
<select
class="select select-bordered w-full max-w-xs"
value={tableSize()}
onChange={(e) => setTableSize(e.target.value as TAILWINDCSS_SIZE)}
>
<For each={Object.values(TAILWINDCSS_SIZE)}>
{(value) => <option value={value}>{t(value)}</option>}
</For>
</select>
</div>
<div>
<Button
onClick={() => {
const curLocale = locale()
locale(curLocale === LANG.EN ? LANG.ZH : LANG.EN)
}}
>
{t('switchLanguage')}
</Button>
</div>
<div>
<Button onClick={onSwitchEndpointClick}>{t('switchEndpoint')}</Button>
2023-09-02 15:08:35 +08:00
</div>
</div>
2023-09-02 15:08:35 +08:00
)
}
2023-09-10 16:42:00 +08:00
const Versions = () => {
const request = useRequest()
const [backendVersion, setBackendVersion] = createSignal('')
onMount(async () => {
const { version } = await request.get('version').json<BackendVersion>()
setBackendVersion(version)
})
return (
<div class="flex gap-4">
<kbd class="kbd">{import.meta.env.version}</kbd>
<kbd class="kbd">{backendVersion()}</kbd>
</div>
)
}
2023-09-02 15:08:35 +08:00
export default () => {
return (
<div class="flex flex-col gap-4">
<DNSQueryForm />
<ConfigForm />
2023-09-02 15:08:35 +08:00
<ConfigForXd />
2023-09-03 18:16:09 +08:00
2023-09-10 16:42:00 +08:00
<Versions />
2023-08-28 22:13:48 +08:00
</div>
)
2023-08-24 04:20:53 +08:00
}