metacubexd/src/pages/Config.tsx

547 lines
14 KiB
TypeScript
Raw Normal View History

import { createForm } from '@felte/solid'
import { validator } from '@felte/validator-zod'
import { useNavigate } from '@solidjs/router'
import {
Accessor,
Component,
For,
Show,
createEffect,
createResource,
createSignal,
onMount,
} from 'solid-js'
import { toast } from 'solid-toast'
2023-08-28 22:13:48 +08:00
import { z } from 'zod'
2023-09-12 21:33:16 +08:00
import {
fetchBackendConfigAPI,
fetchBackendVersionAPI,
flushFakeIPDataAPI,
flushingFakeIPData,
isUpdateAvailableAPI,
reloadConfigFileAPI,
reloadingConfigFile,
2023-09-12 21:33:16 +08:00
restartBackendAPI,
restartingBackend,
updateBackendConfigAPI,
updateGEODatabasesAPI,
updatingGEODatabases,
upgradeBackendAPI,
upgradingBackend,
} from '~/apis'
2023-09-15 23:43:55 +08:00
import { Button, ConfigTitle } from '~/components'
import { LANG, MODE_OPTIONS, ROUTES, themes } from '~/constants'
import { isSingBox } from '~/helpers'
import { locale, setLocale, useI18n } from '~/i18n'
import {
2023-09-02 19:06:02 +08:00
autoSwitchTheme,
favDayTheme,
favNightTheme,
setAutoSwitchTheme,
setFavDayTheme,
setFavNightTheme,
setSelectedEndpoint,
2023-09-16 23:55:42 +08:00
setUseTwemoji,
useRequest,
useTwemoji,
} from '~/signals'
2023-09-12 21:33:16 +08:00
import type { DNSQuery } from '~/types'
2023-08-28 22:13:48 +08:00
const dnsQueryFormSchema = z.object({
name: z.string(),
type: z.string(),
})
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: (values) =>
2023-09-03 02:22:38 +08:00
request
.get('dns/query', {
searchParams: { name: values.name, type: values.type },
})
.json<DNSQuery>()
.then(({ Answer }) =>
setDNSQueryResult(Answer?.map(({ data }) => data) || []),
)
.catch((err) => toast.error(err.message)),
},
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 gap-2 sm:flex-row">
2023-09-10 16:42:00 +08:00
<input
type="search"
name="name"
class="input input-bordered min-w-0 flex-1"
placeholder="google.com"
2023-09-10 16:42:00 +08:00
/>
<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: Component<{ backendVersion: Accessor<string> }> = ({
backendVersion,
}) => {
const [t] = useI18n()
const navigate = useNavigate()
2023-09-01 14:06:21 +08:00
const portList = [
2023-08-28 22:13:48 +08:00
{
label: () => t('port', { name: 'HTTP' }),
2023-08-28 22:13:48 +08:00
key: 'port',
onChange: (e: Event & { target: HTMLInputElement }) =>
void updateBackendConfigAPI('port', Number(e.target.value), refetch),
2023-08-28 22:13:48 +08:00
},
{
label: () => t('port', { name: 'Socks' }),
2023-08-28 22:13:48 +08:00
key: 'socks-port',
onChange: (e: Event & { target: HTMLInputElement }) =>
void updateBackendConfigAPI(
'socks-port',
Number(e.target.value),
refetch,
),
2023-08-28 22:13:48 +08:00
},
{
label: () => t('port', { name: 'Redir' }),
2023-08-28 22:13:48 +08:00
key: 'redir-port',
onChange: (e: Event & { target: HTMLInputElement }) =>
void updateBackendConfigAPI(
'redir-port',
Number(e.target.value),
refetch,
),
2023-08-28 22:13:48 +08:00
},
{
label: () => t('port', { name: 'TProxy' }),
2023-08-28 22:13:48 +08:00
key: 'tproxy-port',
onChange: (e: Event & { target: HTMLInputElement }) =>
void updateBackendConfigAPI(
'tproxy-port',
Number(e.target.value),
refetch,
),
2023-08-28 22:13:48 +08:00
},
{
label: () => t('port', { name: 'Mixed' }),
2023-08-28 22:13:48 +08:00
key: 'mixed-port',
onChange: (e: Event & { target: HTMLInputElement }) =>
void updateBackendConfigAPI(
'mixed-port',
Number(e.target.value),
refetch,
),
2023-08-28 22:13:48 +08:00
},
]
const { form, setInitialValues, reset } = createForm<
z.infer<typeof configFormSchema>
>({ extend: validator({ schema: configFormSchema }) })
2023-09-01 14:06:21 +08:00
const [configsData, { refetch }] = createResource(fetchBackendConfigAPI)
createEffect(() => {
const configs = configsData()
if (configs) {
setInitialValues(configs)
reset()
}
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={configsData()?.mode}
onChange={(e) =>
void updateBackendConfigAPI('mode', e.target.value, refetch)
}
2023-09-12 20:50:52 +08:00
>
<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>
<Show when={!isSingBox(backendVersion())}>
<form class="grid grid-cols-3 gap-2 sm:grid-cols-5" use:form={form}>
<For each={portList}>
{(item) => (
<div class="form-control">
<label for={item.key} class="label">
<span class="label-text">{item.label()}</span>
</label>
<input
id={item.key}
name={item.key}
type="number"
class="input input-bordered"
placeholder={item.label()}
onChange={item.onChange}
/>
</div>
)}
</For>
</form>
<div class="grid grid-cols-2 gap-2 sm:grid-cols-4">
2023-12-03 22:09:09 +08:00
<div class="form-control">
<label for="enable-allow-lan" class="label gap-2">
<span class="label-text">{t('allowLan')}</span>
</label>
<input
id="enable-allow-lan"
type="checkbox"
class="toggle"
checked={configsData()?.['allow-lan']}
onChange={(e) =>
void updateBackendConfigAPI(
'allow-lan',
e.target.checked,
refetch,
)
}
/>
</div>
<div class="form-control">
<label for="enable-tun-device" class="label gap-2">
<span class="label-text">{t('enableTunDevice')}</span>
</label>
<input
id="enable-tun-device"
type="checkbox"
class="toggle"
checked={configsData()?.tun.enable}
onChange={(e) =>
void updateBackendConfigAPI(
'tun',
{ enable: e.target.checked },
refetch,
)
}
/>
</div>
<div class="form-control">
<label for="tun-ip-stack" class="label gap-2">
<span class="label-text">{t('tunModeStack')}</span>
</label>
<select
id="tun-ip-stack"
class="select select-bordered flex-1"
value={configsData()?.tun.stack}
onChange={(e) =>
void updateBackendConfigAPI(
'tun',
{ stack: e.target.value },
refetch,
)
}
>
<option>Mixed</option>
<option>gVisor</option>
<option>System</option>
<option>LWIP</option>
</select>
</div>
<div class="form-control">
<label for="device-name" class="label gap-2">
<span class="label-text">{t('tunDeviceName')}</span>
</label>
<input
id="device-name"
class="input input-bordered min-w-0"
value={configsData()?.tun.device}
onChange={(e) =>
void updateBackendConfigAPI(
'tun',
{ device: e.target.value },
refetch,
)
}
/>
</div>
<div class="form-control">
<label for="interface-name" class="label gap-2">
<span class="label-text">{t('interfaceName')}</span>
</label>
<input
id="interface-name"
class="input input-bordered min-w-0"
value={configsData()?.['interface-name']}
onChange={(e) =>
void updateBackendConfigAPI(
'interface-name',
e.target.value,
refetch,
)
}
/>
</div>
</div>
</Show>
2023-09-22 16:05:36 +08:00
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
<Button
class="btn-primary"
loading={reloadingConfigFile()}
onClick={reloadConfigFileAPI}
>
{t('reloadConfig')}
</Button>
2023-09-12 21:33:16 +08:00
<Button
class="btn-secondary"
2023-09-12 21:33:16 +08:00
loading={updatingGEODatabases()}
onClick={updateGEODatabasesAPI}
>
{t('updateGEODatabases')}
</Button>
<Button
class="btn-accent"
loading={flushingFakeIPData()}
onClick={flushFakeIPDataAPI}
>
{t('flushFakeIP')}
</Button>
<Show when={!isSingBox(backendVersion())}>
<Button
class="btn-error"
loading={upgradingBackend()}
onClick={upgradeBackendAPI}
>
{t('upgradeCore')}
</Button>
</Show>
<Button
class="btn-warning"
loading={restartingBackend()}
onClick={restartBackendAPI}
>
{t('restartCore')}
</Button>
<Button
class="btn-info"
onClick={() => {
setSelectedEndpoint('')
navigate(ROUTES.Setup)
}}
>
{t('switchEndpoint')}
</Button>
</div>
</div>
)
}
2023-09-01 14:06:21 +08:00
2023-09-02 15:08:35 +08:00
const ConfigForXd = () => {
const [t] = useI18n()
2023-09-02 14:25:22 +08:00
const languages = [
{
label: () => t('en'),
value: LANG.EN,
},
{
label: () => t('zh'),
value: LANG.ZH,
},
]
2023-09-16 23:55:42 +08:00
return (
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-2">
<div class="flex flex-col items-center">
<ConfigTitle>{t('useTwemoji')}</ConfigTitle>
<input
type="checkbox"
class="toggle"
checked={useTwemoji()}
onChange={(e) => setUseTwemoji(e.target.checked)}
/>
</div>
<div class="flex flex-col">
<ConfigTitle>{t('switchLanguage')}</ConfigTitle>
<select
class="select select-bordered"
onChange={(e) => setLocale(e.target.value as LANG)}
>
<For each={languages}>
{(lang) => (
<option selected={locale() === lang.value} value={lang.value}>
{lang.label()}
</option>
)}
</For>
</select>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex flex-col items-center">
2023-09-16 23:55:42 +08:00
<ConfigTitle>{t('autoSwitchTheme')}</ConfigTitle>
<input
type="checkbox"
class="toggle"
checked={autoSwitchTheme()}
onChange={(e) => setAutoSwitchTheme(e.target.checked)}
/>
</div>
2023-09-16 23:55:42 +08:00
<Show when={autoSwitchTheme()}>
<div class="flex flex-col gap-2">
2023-09-16 23:55:42 +08:00
<div class="flex flex-col">
<ConfigTitle>{t('favDayTheme')}</ConfigTitle>
<select
class="select select-bordered"
value={favDayTheme()}
onChange={(e) =>
setFavDayTheme(e.target.value as (typeof themes)[number])
}
2023-09-16 23:55:42 +08:00
>
<For each={themes}>
{(theme) => <option value={theme}>{theme}</option>}
2023-09-16 23:55:42 +08:00
</For>
</select>
</div>
2023-09-16 23:55:42 +08:00
<div class="flex flex-col">
<ConfigTitle>{t('favNightTheme')}</ConfigTitle>
<select
class="select select-bordered"
value={favNightTheme()}
onChange={(e) =>
setFavNightTheme(e.target.value as (typeof themes)[number])
}
2023-09-16 23:55:42 +08:00
>
<For each={themes}>
{(theme) => <option value={theme}>{theme}</option>}
2023-09-16 23:55:42 +08:00
</For>
</select>
</div>
</div>
</Show>
2023-09-02 19:06:02 +08:00
</div>
</div>
2023-09-02 15:08:35 +08:00
)
}
const Versions: Component<{ backendVersion: Accessor<string> }> = ({
backendVersion,
}) => {
const [isUpdateAvailable, setIsUpdateAvailable] = createSignal(false)
2023-09-10 16:42:00 +08:00
createEffect(async () => {
const version = backendVersion()
if (!version) return
setIsUpdateAvailable(await isUpdateAvailableAPI(version))
2023-09-10 16:42:00 +08:00
})
return (
<div class="grid grid-cols-2 gap-4">
<kbd class="kbd">{import.meta.env.VITE_APP_VERSION}</kbd>
<div class="relative">
<Show when={isUpdateAvailable()}>
<span class="absolute -right-1 -top-1 flex h-3 w-3">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-info opacity-75" />
<span class="inline-flex h-3 w-3 rounded-full bg-info" />
</span>
</Show>
<kbd class="kbd w-full">{backendVersion()}</kbd>
</div>
2023-09-10 16:42:00 +08:00
</div>
)
}
2023-09-02 15:08:35 +08:00
export default () => {
const [t] = useI18n()
const [backendVersion, setBackendVersion] = createSignal('')
onMount(async () => {
setBackendVersion(await fetchBackendVersionAPI())
})
return (
2023-09-16 23:55:42 +08:00
<div class="mx-auto flex max-w-screen-md flex-col gap-4">
<Show when={!isSingBox(backendVersion())}>
<ConfigTitle withDivider>{t('dnsQuery')}</ConfigTitle>
2023-09-17 00:05:15 +08:00
<DNSQueryForm />
</Show>
2023-09-17 00:05:15 +08:00
<ConfigTitle withDivider>{t('coreConfig')}</ConfigTitle>
<ConfigForm backendVersion={backendVersion} />
2023-09-17 00:05:15 +08:00
<ConfigTitle withDivider>{t('xdConfig')}</ConfigTitle>
2023-09-02 15:08:35 +08:00
<ConfigForXd />
2023-09-03 18:16:09 +08:00
2023-09-17 00:05:15 +08:00
<ConfigTitle withDivider>{t('version')}</ConfigTitle>
<Versions backendVersion={backendVersion} />
2023-08-28 22:13:48 +08:00
</div>
)
2023-08-24 04:20:53 +08:00
}