feat: settings per-page

This commit is contained in:
kunish 2023-09-15 23:43:55 +08:00
parent c607a31070
commit d77626058a
No known key found for this signature in database
GPG Key ID: 647A12B4F782C430
14 changed files with 340 additions and 214 deletions

View File

@ -0,0 +1,14 @@
import { children, ParentComponent } from 'solid-js'
export const ConfigTitle: ParentComponent<{ withDivider?: boolean }> = (
props,
) => (
<div
class="pb-4 text-lg font-semibold"
classList={{
divider: props.withDivider,
}}
>
{children(() => props.children)()}
</div>
)

View File

@ -14,17 +14,20 @@ import {
useDragDropContext,
} from '@thisbeyond/solid-dnd'
import { Component, For, Show, createSignal } from 'solid-js'
import { Button } from '~/components'
import { Button, ConfigTitle } from '~/components'
import {
CONNECTIONS_TABLE_ACCESSOR_KEY,
CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER,
CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY,
MODAL,
TAILWINDCSS_SIZE,
} from '~/constants'
import { connectionsTableSize, setConnectionsTableSize } from '~/signals'
type ColumnVisibility = Partial<Record<CONNECTIONS_TABLE_ACCESSOR_KEY, boolean>>
type ColumnOrder = CONNECTIONS_TABLE_ACCESSOR_KEY[]
export const ConnectionsTableOrderingModal = (props: {
export const ConnectionsSettingsModal = (props: {
order: ColumnOrder
visible: ColumnVisibility
onOrderChange: (value: ColumnOrder) => void
@ -76,8 +79,9 @@ export const ConnectionsTableOrderingModal = (props: {
'transition-transform': !!state.active.draggable,
}}
>
<div class="my-1 flex cursor-grab justify-between p-1">
<div class="flex cursor-grab justify-between py-2">
<span class="select-none">{t(key)}</span>
<input
type="checkbox"
class="toggle"
@ -96,10 +100,32 @@ export const ConnectionsTableOrderingModal = (props: {
return (
<dialog
id="connections-table-ordering-modal"
id={MODAL.CONNECTIONS_SETTINGS}
class="modal modal-bottom sm:modal-middle"
>
<div class="modal-box" onContextMenu={(e) => e.preventDefault()}>
<div
class="modal-box flex flex-col gap-4"
onContextMenu={(e) => e.preventDefault()}
>
<div>
<ConfigTitle withDivider>{t('tableSize')}</ConfigTitle>
<select
class="select select-bordered w-full"
value={connectionsTableSize()}
onChange={(e) =>
setConnectionsTableSize(e.target.value as TAILWINDCSS_SIZE)
}
>
<For each={Object.values(TAILWINDCSS_SIZE)}>
{(value) => <option value={value}>{t(value)}</option>}
</For>
</select>
</div>
<div>
<ConfigTitle withDivider>{t('sort')}</ConfigTitle>
<DragDropProvider
onDragStart={onDragStart}
onDragEnd={onDragEnd as DragEventHandler}
@ -117,6 +143,7 @@ export const ConnectionsTableOrderingModal = (props: {
</Show>
</DragOverlay>
</DragDropProvider>
</div>
<div class="modal-action">
<Button

View File

@ -1,4 +1,5 @@
import { Component, Show } from 'solid-js'
import { MODAL } from '~/constants'
import { allConnections } from '~/signals'
export const ConnectionsTableDetailsModal: Component<{
@ -6,7 +7,7 @@ export const ConnectionsTableDetailsModal: Component<{
}> = (props) => {
return (
<dialog
id="connections-table-details-modal"
id={MODAL.CONNECTIONS_TABLE_DETAILS}
class="modal modal-bottom sm:modal-middle"
>
<div class="modal-box">

View File

@ -0,0 +1,81 @@
import { useI18n } from '@solid-primitives/i18n'
import { For } from 'solid-js'
import { ConfigTitle } from '~/components'
import {
LOG_LEVEL,
LOGS_TABLE_MAX_ROWS_LIST,
MODAL,
TAILWINDCSS_SIZE,
} from '~/constants'
import {
logMaxRows,
logsTableSize,
setLogLevel,
setLogMaxRows,
setLogsTableSize,
} from '~/signals'
export const LogsSettingsModal = () => {
const [t] = useI18n()
return (
<dialog id={MODAL.LOGS_SETTINGS} class="modal modal-bottom sm:modal-middle">
<div class="modal-box flex flex-col gap-4">
<div>
<ConfigTitle withDivider>{t('tableSize')}</ConfigTitle>
<select
class="select select-bordered w-full"
value={logsTableSize()}
onChange={(e) =>
setLogsTableSize(e.target.value as TAILWINDCSS_SIZE)
}
>
<For each={Object.values(TAILWINDCSS_SIZE)}>
{(value) => <option value={value}>{t(value)}</option>}
</For>
</select>
</div>
<div>
<ConfigTitle withDivider>{t('logLevel')}</ConfigTitle>
<select
class="select select-bordered w-full"
onChange={(e) => setLogLevel(e.target.value as LOG_LEVEL)}
>
<For
each={[
LOG_LEVEL.Info,
LOG_LEVEL.Error,
LOG_LEVEL.Warning,
LOG_LEVEL.Debug,
LOG_LEVEL.Silent,
]}
>
{(level) => <option value={level}>{t(level)}</option>}
</For>
</select>
</div>
<div>
<ConfigTitle withDivider>{t('logMaxRows')}</ConfigTitle>
<select
class="select select-bordered w-full"
value={logMaxRows()}
onChange={(e) => setLogMaxRows(parseInt(e.target.value))}
>
<For each={LOGS_TABLE_MAX_ROWS_LIST}>
{(rows) => <option value={rows}>{rows}</option>}
</For>
</select>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button />
</form>
</dialog>
)
}

View File

@ -0,0 +1,107 @@
import { useI18n } from '@solid-primitives/i18n'
import { For } from 'solid-js'
import { ConfigTitle } from '~/components'
import { MODAL, PROXIES_ORDERING_TYPE, PROXIES_PREVIEW_TYPE } from '~/constants'
import {
autoCloseConns,
latencyTestTimeoutDuration,
proxiesOrderingType,
proxiesPreviewType,
setAutoCloseConns,
setLatencyTestTimeoutDuration,
setProxiesOrderingType,
setProxiesPreviewType,
setUrlForLatencyTest,
urlForLatencyTest,
} from '~/signals'
export const ProxiesSettingsModal = () => {
const [t] = useI18n()
return (
<dialog
id={MODAL.PROXIES_SETTINGS}
class="modal modal-bottom sm:modal-middle"
>
<div class="modal-box flex flex-col gap-4">
<div>
<ConfigTitle withDivider>{t('autoCloseConns')}</ConfigTitle>
<div class="flex w-full justify-center">
<input
class="toggle"
type="checkbox"
checked={autoCloseConns()}
onChange={(e) => setAutoCloseConns(e.target.checked)}
/>
</div>
</div>
<div class="flex flex-col">
<ConfigTitle withDivider>{t('urlForLatencyTest')}</ConfigTitle>
<input
class="input input-bordered w-full"
value={urlForLatencyTest()}
onChange={(e) => setUrlForLatencyTest(e.target.value)}
/>
</div>
<div>
<ConfigTitle withDivider>
{t('latencyTestTimeoutDuration')} ({t('ms')})
</ConfigTitle>
<input
type="number"
class="input input-bordered w-full"
value={latencyTestTimeoutDuration()}
onChange={(e) =>
setLatencyTestTimeoutDuration(Number(e.target.value))
}
/>
</div>
<div>
<ConfigTitle withDivider>{t('proxiesSorting')}</ConfigTitle>
<select
class="select select-bordered w-full"
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>
<div>
<ConfigTitle withDivider>{t('proxiesPreviewType')}</ConfigTitle>
<select
class="select select-bordered w-full"
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>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button />
</form>
</dialog>
)
}

View File

@ -1,11 +1,14 @@
export * from './Button'
export * from './Collapse'
export * from './ConfigTitle'
export * from './ConnectionsSettingsModal'
export * from './ConnectionsTableDetailsModal'
export * from './ConnectionsTableOrderingModal'
export * from './ForTwoColumns'
export * from './Header'
export * from './Latency'
export * from './LogoText'
export * from './LogsSettingsModal'
export * from './ProxiesSettingsModal'
export * from './ProxyCardGroups'
export * from './ProxyNodeCard'
export * from './ProxyNodePreview'

View File

@ -163,3 +163,11 @@ export enum LOG_LEVEL {
}
export const LOGS_TABLE_MAX_ROWS_LIST = [200, 300, 500, 800, 1000]
export enum MODAL {
PROXIES_SETTINGS = 'proxies-settings',
RULES_SETTINGS = 'rules-settings',
CONNECTIONS_SETTINGS = 'connections-settings',
CONNECTIONS_TABLE_DETAILS = 'connections-table-details',
LOGS_SETTINGS = 'logs-settings',
}

View File

@ -59,7 +59,6 @@ export default {
orderName_desc: 'By name alphabetically (Z-A)',
ms: 'ms',
updated: 'Updated',
renderProxiesInSamePage: 'Render proxies and proxy provider in same page',
tableSize: 'Table size',
logLevel: 'Log Level',
info: 'info',
@ -67,7 +66,7 @@ export default {
debug: 'debug',
warning: 'warning',
error: 'error',
logMaxRows: 'Log Maxinum Reserved Rows',
logMaxRows: 'Log Maximum Reserved Rows',
xs: 'Extra small size',
sm: 'Small size',
md: 'Normal size',
@ -86,4 +85,5 @@ export default {
direct: 'Direct',
active: 'Active',
closed: 'Closed',
sort: 'Sort',
}

View File

@ -59,7 +59,6 @@ export default {
orderName_desc: '按名称字母排序 (Z-A)',
ms: '毫秒',
updated: '更新于',
renderProxiesInSamePage: '将代理和代理提供者显示在同一页',
tableSize: '表格大小',
logLevel: '日志等级',
info: '信息',
@ -86,4 +85,5 @@ export default {
direct: '直连',
active: '活动',
closed: '已关闭',
sort: '排序',
}

View File

@ -2,14 +2,7 @@ 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'
import { For, Show, createSignal, onMount } from 'solid-js'
import { z } from 'zod'
import {
fetchBackendConfigAPI,
@ -22,47 +15,21 @@ import {
upgradeBackendAPI,
upgradingBackend,
} from '~/apis'
import { Button } from '~/components'
import { Button, ConfigTitle } from '~/components'
import { LANG, MODE_OPTIONS, ROUTES, themes } from '~/constants'
import {
LANG,
LOGS_TABLE_MAX_ROWS_LIST,
LOG_LEVEL,
MODE_OPTIONS,
PROXIES_ORDERING_TYPE,
PROXIES_PREVIEW_TYPE,
ROUTES,
TAILWINDCSS_SIZE,
themes,
} from '~/constants'
import {
autoCloseConns,
autoSwitchTheme,
backendConfig,
favDayTheme,
favNightTheme,
latencyTestTimeoutDuration,
logLevel,
logMaxRows,
proxiesOrderingType,
proxiesPreviewType,
renderInTwoColumns,
setAutoCloseConns,
setAutoSwitchTheme,
setBackendConfig,
setFavDayTheme,
setFavNightTheme,
setLatencyTestTimeoutDuration,
setLogLevel,
setLogMaxRows,
setProxiesOrderingType,
setProxiesPreviewType,
setRenderInTwoColumns,
setSelectedEndpoint,
setTableSize,
setTwemoji,
setUrlForLatencyTest,
tableSize,
urlForLatencyTest,
useRequest,
useTwemoji,
} from '~/signals'
@ -73,12 +40,6 @@ const dnsQueryFormSchema = z.object({
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()
@ -147,7 +108,7 @@ const ConfigForm = () => {
const portsList = [
{
label: 'Http Port',
label: 'HTTP Port',
key: 'port',
},
{
@ -159,7 +120,7 @@ const ConfigForm = () => {
key: 'redir-port',
},
{
label: 'Tproxy Port',
label: 'TProxy Port',
key: 'tproxy-port',
},
{
@ -226,42 +187,6 @@ const ConfigForm = () => {
{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>
)
}
@ -355,97 +280,6 @@ const ConfigForXd = () => {
)}
</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>
</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>
<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>
<ConfigTitle>{t('logLevel')}</ConfigTitle>
<select
class="select select-bordered w-full max-w-xs"
onChange={(e) => {
setLogLevel(e.target.value as LOG_LEVEL)
}}
>
<For
each={[
LOG_LEVEL.Info,
LOG_LEVEL.Error,
LOG_LEVEL.Warning,
LOG_LEVEL.Debug,
LOG_LEVEL.Silent,
]}
>
{(level) => (
<option selected={logLevel() === level} value={level}>
{t(level)}
</option>
)}
</For>
</select>
</div>
<div>
<ConfigTitle>{t('logMaxRows')}</ConfigTitle>
<select
class="select select-bordered w-full max-w-xs"
value={logMaxRows()}
onChange={(e) => setLogMaxRows(parseInt(e.target.value))}
>
<For each={LOGS_TABLE_MAX_ROWS_LIST}>
{(rows) => <option value={rows}>{rows}</option>}
</For>
</select>
</div>
<div>
<Button
onClick={() => {

View File

@ -33,16 +33,21 @@ import { twMerge } from 'tailwind-merge'
import { closeAllConnectionsAPI, closeSingleConnectionAPI } from '~/apis'
import {
Button,
ConnectionsSettingsModal,
ConnectionsTableDetailsModal,
ConnectionsTableOrderingModal,
} from '~/components'
import {
CONNECTIONS_TABLE_ACCESSOR_KEY,
CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER,
CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY,
MODAL,
} from '~/constants'
import { formatTimeFromNow } from '~/helpers'
import { tableSize, tableSizeClassName, useConnections } from '~/signals'
import {
connectionsTableSize,
tableSizeClassName,
useConnections,
} from '~/signals'
import type { Connection } from '~/types'
type ColumnVisibility = Partial<Record<CONNECTIONS_TABLE_ACCESSOR_KEY, boolean>>
@ -107,7 +112,7 @@ export default () => {
setSelectedConnectionID(row.original.id)
const modal = document.querySelector(
'#connections-table-details-modal',
`#${MODAL.CONNECTIONS_TABLE_DETAILS}`,
) as HTMLDialogElement | null
modal?.showModal()
@ -357,7 +362,7 @@ export default () => {
class="btn join-item btn-sm sm:btn-md"
onClick={() => {
const modal = document.querySelector(
'#connections-table-ordering-modal',
`#${MODAL.CONNECTIONS_SETTINGS}`,
) as HTMLDialogElement | null
modal?.showModal()
@ -371,7 +376,7 @@ export default () => {
<div class="overflow-x-auto whitespace-nowrap rounded-md bg-base-300">
<table
class={twMerge(
tableSizeClassName(tableSize()),
tableSizeClassName(connectionsTableSize()),
'table table-zebra relative rounded-none',
)}
>
@ -488,7 +493,7 @@ export default () => {
</table>
</div>
<ConnectionsTableOrderingModal
<ConnectionsSettingsModal
order={columnOrder()}
visible={columnVisibility()}
onOrderChange={(data: ColumnOrder) => setColumnOrder(data)}

View File

@ -1,4 +1,5 @@
import { useI18n } from '@solid-primitives/i18n'
import { IconSettings } from '@tabler/icons-solidjs'
import {
ColumnDef,
createSolidTable,
@ -7,8 +8,9 @@ import {
} from '@tanstack/solid-table'
import { For, Index, createEffect, createSignal } from 'solid-js'
import { twMerge } from 'tailwind-merge'
import { LOG_LEVEL } from '~/constants'
import { tableSize, tableSizeClassName, useWsRequest } from '~/signals'
import { Button, LogsSettingsModal } from '~/components'
import { LOG_LEVEL, MODAL } from '~/constants'
import { logsTableSize, tableSizeClassName, useWsRequest } from '~/signals'
import { logLevel, logMaxRows } from '~/signals/config'
import { Log } from '~/types'
@ -85,17 +87,32 @@ export default () => {
return (
<div class="flex h-full flex-col gap-4 p-1">
<div class="join w-full">
<input
type="search"
class="input input-primary input-sm flex-shrink-0 sm:input-md"
class="input join-item input-primary input-sm flex-1 flex-shrink-0 sm:input-md"
placeholder={t('search')}
onInput={(e) => setSearch(e.target.value)}
/>
<Button
class="join-item btn-sm sm:btn-md"
onClick={() => {
const modal = document.querySelector(
`#${MODAL.LOGS_SETTINGS}`,
) as HTMLDialogElement | null
modal?.showModal()
}}
>
<IconSettings />
</Button>
</div>
<div class="overflow-x-auto whitespace-nowrap rounded-md bg-base-300">
<table
class={twMerge(
tableSizeClassName(tableSize()),
tableSizeClassName(logsTableSize()),
'table relative rounded-none',
)}
>
@ -150,6 +167,8 @@ export default () => {
</tbody>
</table>
</div>
<LogsSettingsModal />
</div>
)
}

View File

@ -1,15 +1,21 @@
import { useI18n } from '@solid-primitives/i18n'
import { IconBrandSpeedtest, IconReload } from '@tabler/icons-solidjs'
import {
IconBrandSpeedtest,
IconReload,
IconSettings,
} from '@tabler/icons-solidjs'
import { For, Show, createSignal } from 'solid-js'
import { twMerge } from 'tailwind-merge'
import {
Button,
Collapse,
ForTwoColumns,
ProxiesSettingsModal,
ProxyCardGroups,
ProxyNodePreview,
SubscriptionInfo,
} from '~/components'
import { MODAL } from '~/constants'
import {
formatTimeFromNow,
sortProxiesByOrderingType,
@ -123,6 +129,21 @@ export default () => {
/>
</Button>
</Show>
<div class="ml-auto">
<Button
class="btn-circle btn-sm sm:btn-md"
onClick={() => {
const modal = document.querySelector(
`#${MODAL.PROXIES_SETTINGS}`,
) as HTMLDialogElement | null
modal?.showModal()
}}
>
<IconSettings />
</Button>
</div>
</div>
</Show>
@ -274,6 +295,8 @@ export default () => {
/>
</Show>
</div>
<ProxiesSettingsModal />
</div>
)
}

View File

@ -48,9 +48,13 @@ export const [renderInTwoColumns, setRenderInTwoColumns] = makePersisted(
createSignal(true),
{ name: 'renderInTwoColumn', storage: localStorage },
)
export const [tableSize, setTableSize] = makePersisted(
export const [connectionsTableSize, setConnectionsTableSize] = makePersisted(
createSignal<TAILWINDCSS_SIZE>(TAILWINDCSS_SIZE.XS),
{ name: 'tableSize', storage: localStorage },
{ name: 'connectionsTableSize', storage: localStorage },
)
export const [logsTableSize, setLogsTableSize] = makePersisted(
createSignal<TAILWINDCSS_SIZE>(TAILWINDCSS_SIZE.XS),
{ name: 'logsTableSize', storage: localStorage },
)
export const [logLevel, setLogLevel] = makePersisted(