feat: show current page title, closes #474

This commit is contained in:
kunish 2024-10-10 00:49:58 +08:00
parent 443cb251ba
commit 9c76d7ff76
No known key found for this signature in database
GPG Key ID: 647A12B4F782C430
16 changed files with 4729 additions and 2143 deletions

1
auto-imports.d.ts vendored
View File

@ -3,6 +3,7 @@
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
// biome-ignore lint: disable
export { } export { }
declare global { declare global {
const $DEVCOMP: (typeof import('solid-js'))['$DEVCOMP'] const $DEVCOMP: (typeof import('solid-js'))['$DEVCOMP']

View File

@ -9,9 +9,8 @@
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/pwa-192x192.png" /> <link rel="apple-touch-icon" href="/pwa-192x192.png" />
<title>metacubexd</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -28,6 +28,7 @@
"@solid-primitives/storage": "^4.2.1", "@solid-primitives/storage": "^4.2.1",
"@solid-primitives/timer": "^1.3.10", "@solid-primitives/timer": "^1.3.10",
"@solid-primitives/websocket": "^1.2.2", "@solid-primitives/websocket": "^1.2.2",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.14.7", "@solidjs/router": "^0.14.7",
"@tabler/icons-solidjs": "^3.19.0", "@tabler/icons-solidjs": "^3.19.0",
"@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/match-sorter-utils": "^8.19.4",
@ -44,7 +45,7 @@
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"byte-size": "^9.0.0", "byte-size": "^9.0.0",
"commitlint": "^19.5.0", "commitlint": "^19.5.0",
"daisyui": "^4.12.12", "daisyui": "^4.12.13",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"eslint": "^9.12.0", "eslint": "^9.12.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@ -73,5 +74,5 @@
"vite-plugin-solid": "^2.10.2", "vite-plugin-solid": "^2.10.2",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"packageManager": "pnpm@9.10.0" "packageManager": "pnpm@9.12.1"
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
import { Title } from '@solidjs/meta'
import { ParentComponent } from 'solid-js'
export default (({ children }) => {
return <Title>{children} - MetaCubeXD</Title>
}) as ParentComponent

View File

@ -119,7 +119,7 @@ export const Header = () => {
<div class="drawer-side"> <div class="drawer-side">
<label for="navs" class="drawer-overlay" /> <label for="navs" class="drawer-overlay" />
<ul class="menu min-h-full w-2/5 gap-2 rounded-r-box bg-base-300 pt-20"> <ul class="min-w-2/5 menu min-h-full gap-2 rounded-r-box bg-base-300 pt-20">
<For each={navs()}> <For each={navs()}>
{({ href, name, icon }) => ( {({ href, name, icon }) => (
<li onClick={() => setOpenedDrawer(false)}> <li onClick={() => setOpenedDrawer(false)}>

View File

@ -1,5 +1,6 @@
export default { export default {
add: 'Add', add: 'Add',
setup: 'Setup',
overview: 'Overview', overview: 'Overview',
proxies: 'Proxies', proxies: 'Proxies',
proxiesSettings: 'Proxies Settings', proxiesSettings: 'Proxies Settings',

View File

@ -2,6 +2,7 @@ import { Dict } from '~/i18n/dict'
export default { export default {
add: '添加', add: '添加',
setup: '设置',
overview: '概览', overview: '概览',
proxies: '代理', proxies: '代理',
proxiesSettings: '代理设置', proxiesSettings: '代理设置',

View File

@ -1,6 +1,7 @@
/* @refresh reload */ /* @refresh reload */
import '~/index.css' import '~/index.css'
import { MetaProvider } from '@solidjs/meta'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-cn'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
@ -24,6 +25,7 @@ dayjs.extend(relativeTime)
render( render(
() => ( () => (
<I18nProvider locale={locale()}> <I18nProvider locale={locale()}>
<MetaProvider>
<HashRouter root={App}> <HashRouter root={App}>
<Route path={ROUTES.Setup} component={Setup} /> <Route path={ROUTES.Setup} component={Setup} />
<Route path="*" component={Overview} /> <Route path="*" component={Overview} />
@ -34,6 +36,7 @@ render(
<Route path={ROUTES.Log} component={Logs} /> <Route path={ROUTES.Log} component={Logs} />
<Route path={ROUTES.Config} component={Config} /> <Route path={ROUTES.Config} component={Config} />
</HashRouter> </HashRouter>
</MetaProvider>
<Toaster position="bottom-center" /> <Toaster position="bottom-center" />
</I18nProvider> </I18nProvider>

View File

@ -21,6 +21,7 @@ import {
upgradingUI, upgradingUI,
} from '~/apis' } from '~/apis'
import { Button, ConfigTitle } from '~/components' import { Button, ConfigTitle } from '~/components'
import DocumentTitle from '~/components/DocumentTitle'
import { LANG, ROUTES, themes } from '~/constants' import { LANG, ROUTES, themes } from '~/constants'
import { locale, setLocale, useI18n } from '~/i18n' import { locale, setLocale, useI18n } from '~/i18n'
import { import {
@ -540,6 +541,9 @@ export default () => {
updateBackendVersion() updateBackendVersion()
return ( return (
<>
<DocumentTitle>{t('config')}</DocumentTitle>
<div class="mx-auto flex max-w-screen-md flex-col gap-4"> <div class="mx-auto flex max-w-screen-md flex-col gap-4">
<Show when={!isSingBox()}> <Show when={!isSingBox()}>
<ConfigTitle withDivider>{t('dnsQuery')}</ConfigTitle> <ConfigTitle withDivider>{t('dnsQuery')}</ConfigTitle>
@ -559,5 +563,6 @@ export default () => {
<Versions backendVersion={backendVersion} /> <Versions backendVersion={backendVersion} />
</div> </div>
</>
) )
} }

View File

@ -35,6 +35,7 @@ import {
ConnectionsSettingsModal, ConnectionsSettingsModal,
ConnectionsTableDetailsModal, ConnectionsTableDetailsModal,
} from '~/components' } from '~/components'
import DocumentTitle from '~/components/DocumentTitle'
import { CONNECTIONS_TABLE_ACCESSOR_KEY } from '~/constants' import { CONNECTIONS_TABLE_ACCESSOR_KEY } from '~/constants'
import { useI18n } from '~/i18n' import { useI18n } from '~/i18n'
import { import {
@ -355,6 +356,9 @@ export default () => {
]) ])
return ( return (
<>
<DocumentTitle>{t('connections')}</DocumentTitle>
<div class="flex h-full flex-col gap-2"> <div class="flex h-full flex-col gap-2">
<div class="flex w-full flex-wrap items-center gap-2"> <div class="flex w-full flex-wrap items-center gap-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -376,7 +380,9 @@ export default () => {
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<span class="mr-2 hidden lg:inline-block">{t('quickFilter')}:</span> <span class="mr-2 hidden lg:inline-block">
{t('quickFilter')}:
</span>
<input <input
type="checkbox" type="checkbox"
class="toggle" class="toggle"
@ -513,7 +519,8 @@ export default () => {
e.preventDefault() e.preventDefault()
const value = cell.renderValue() as null | string const value = cell.renderValue() as null | string
value && writeClipboard(value).catch(() => {})
if (value) writeClipboard(value).catch(() => {})
}} }}
> >
{cell.getIsGrouped() ? ( {cell.getIsGrouped() ? (
@ -581,5 +588,6 @@ export default () => {
selectedConnectionID={selectedConnectionID()} selectedConnectionID={selectedConnectionID()}
/> />
</div> </div>
</>
) )
} }

View File

@ -19,6 +19,7 @@ import {
} from '@tanstack/solid-table' } from '@tanstack/solid-table'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { Button, LogsSettingsModal } from '~/components' import { Button, LogsSettingsModal } from '~/components'
import DocumentTitle from '~/components/DocumentTitle'
import { LOG_LEVEL } from '~/constants' import { LOG_LEVEL } from '~/constants'
import { useI18n } from '~/i18n' import { useI18n } from '~/i18n'
import { endpoint, logsTableSize, tableSizeClassName } from '~/signals' import { endpoint, logsTableSize, tableSizeClassName } from '~/signals'
@ -120,6 +121,9 @@ export default () => {
}) })
return ( return (
<>
<DocumentTitle>{t('logs')}</DocumentTitle>
<div class="flex h-full flex-col gap-2"> <div class="flex h-full flex-col gap-2">
<div class="join w-full"> <div class="join w-full">
<input <input
@ -179,7 +183,8 @@ export default () => {
{{ {{
asc: <IconSortAscending />, asc: <IconSortAscending />,
desc: <IconSortDescending />, desc: <IconSortDescending />,
}[header.column.getIsSorted() as string] ?? null} }[header.column.getIsSorted() as string] ??
null}
</div> </div>
</th> </th>
) )
@ -214,5 +219,6 @@ export default () => {
<LogsSettingsModal ref={(el) => (logsSettingsModalRef = el)} /> <LogsSettingsModal ref={(el) => (logsSettingsModalRef = el)} />
</div> </div>
</>
) )
} }

View File

@ -4,6 +4,7 @@ import byteSize from 'byte-size'
import { merge } from 'lodash' import { merge } from 'lodash'
import { SolidApexCharts } from 'solid-apexcharts' import { SolidApexCharts } from 'solid-apexcharts'
import type { JSX, ParentComponent } from 'solid-js' import type { JSX, ParentComponent } from 'solid-js'
import DocumentTitle from '~/components/DocumentTitle'
import { CHART_MAX_XAXIS, DEFAULT_CHART_OPTIONS } from '~/constants' import { CHART_MAX_XAXIS, DEFAULT_CHART_OPTIONS } from '~/constants'
import { useI18n } from '~/i18n' import { useI18n } from '~/i18n'
import { endpoint, latestConnectionMsg, useWsRequest } from '~/signals' import { endpoint, latestConnectionMsg, useWsRequest } from '~/signals'
@ -87,6 +88,9 @@ export default () => {
]) ])
return ( return (
<>
<DocumentTitle>{t('overview')}</DocumentTitle>
<div class="flex flex-col gap-2 lg:h-full"> <div class="flex flex-col gap-2 lg:h-full">
<div class="stats stats-vertical w-full flex-shrink-0 grid-cols-2 bg-gradient-to-br from-primary to-secondary shadow lg:stats-horizontal lg:flex"> <div class="stats stats-vertical w-full flex-shrink-0 grid-cols-2 bg-gradient-to-br from-primary to-secondary shadow lg:stats-horizontal lg:flex">
<TrafficWidget label={t('upload')}> <TrafficWidget label={t('upload')}>
@ -135,5 +139,6 @@ export default () => {
{endpoint()?.url} {endpoint()?.url}
</footer> </footer>
</div> </div>
</>
) )
} }

View File

@ -14,6 +14,7 @@ import {
ProxyNodePreview, ProxyNodePreview,
SubscriptionInfo, SubscriptionInfo,
} from '~/components' } from '~/components'
import DocumentTitle from '~/components/DocumentTitle'
import { import {
filterProxiesByAvailability, filterProxiesByAvailability,
sortProxiesByOrderingType, sortProxiesByOrderingType,
@ -125,6 +126,9 @@ export default () => {
] ]
return ( return (
<>
<DocumentTitle>{t('proxies')}</DocumentTitle>
<div class="flex h-full flex-col gap-2"> <div class="flex h-full flex-col gap-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="tabs-boxed tabs gap-2"> <div class="tabs-boxed tabs gap-2">
@ -173,7 +177,9 @@ export default () => {
<div <div
class={twMerge( class={twMerge(
'grid grid-cols-1 place-items-start gap-2', 'grid grid-cols-1 place-items-start gap-2',
renderProxiesInTwoColumns() ? 'sm:grid-cols-2' : 'sm:grid-cols-1', renderProxiesInTwoColumns()
? 'sm:grid-cols-2'
: 'sm:grid-cols-1',
)} )}
> >
<For each={renderProxies()}> <For each={renderProxies()}>
@ -218,8 +224,9 @@ export default () => {
icon={ icon={
<IconBrandSpeedtest <IconBrandSpeedtest
class={twMerge( class={twMerge(
proxyGroupLatencyTestingMap()[proxyGroup.name] && proxyGroupLatencyTestingMap()[
'animate-pulse text-success', proxyGroup.name
] && 'animate-pulse text-success',
)} )}
/> />
} }
@ -229,7 +236,8 @@ export default () => {
<div class="flex items-center justify-between text-sm text-slate-500"> <div class="flex items-center justify-between text-sm text-slate-500">
<span> <span>
{proxyGroup.type}{' '} {proxyGroup.type}{' '}
{proxyGroup.now?.length > 0 && ` :: ${proxyGroup.now}`} {proxyGroup.now?.length > 0 &&
` :: ${proxyGroup.now}`}
</span> </span>
<span> <span>
{byteSize( {byteSize(
@ -278,7 +286,9 @@ export default () => {
<div <div
class={twMerge( class={twMerge(
'grid grid-cols-1 place-items-start gap-2', 'grid grid-cols-1 place-items-start gap-2',
renderProxiesInTwoColumns() ? 'sm:grid-cols-2' : 'sm:grid-cols-1', renderProxiesInTwoColumns()
? 'sm:grid-cols-2'
: 'sm:grid-cols-1',
)} )}
> >
<For each={proxyProviders()}> <For each={proxyProviders()}>
@ -320,7 +330,9 @@ export default () => {
<Button <Button
class="btn btn-circle btn-sm" class="btn btn-circle btn-sm"
disabled={ disabled={
proxyProviderLatencyTestingMap()[proxyProvider.name] proxyProviderLatencyTestingMap()[
proxyProvider.name
]
} }
onClick={(e) => onClick={(e) =>
onProxyProviderLatencyTestClick( onProxyProviderLatencyTestClick(
@ -377,5 +389,6 @@ export default () => {
<ProxiesSettingsModal ref={(el) => (proxiesSettingsModalRef = el)} /> <ProxiesSettingsModal ref={(el) => (proxiesSettingsModalRef = el)} />
</div> </div>
</>
) )
} }

View File

@ -3,6 +3,7 @@ import { createVirtualizer } from '@tanstack/solid-virtual'
import { matchSorter } from 'match-sorter' import { matchSorter } from 'match-sorter'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { Button } from '~/components' import { Button } from '~/components'
import DocumentTitle from '~/components/DocumentTitle'
import { useStringBooleanMap } from '~/helpers' import { useStringBooleanMap } from '~/helpers'
import { useI18n } from '~/i18n' import { useI18n } from '~/i18n'
import { endpoint, formatTimeFromNow, useRules } from '~/signals' import { endpoint, formatTimeFromNow, useRules } from '~/signals'
@ -124,6 +125,9 @@ export default () => {
const ruleProviderVirtualizerItems = ruleProviderVirtualizer.getVirtualItems() const ruleProviderVirtualizerItems = ruleProviderVirtualizer.getVirtualItems()
return ( return (
<>
<DocumentTitle>{t('rules')}</DocumentTitle>
<div class="flex h-full flex-col gap-2"> <div class="flex h-full flex-col gap-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="tabs-boxed tabs gap-2"> <div class="tabs-boxed tabs gap-2">
@ -218,7 +222,8 @@ export default () => {
{ruleProviderVirtualizerItems.map((virtualizerItem) => { {ruleProviderVirtualizerItems.map((virtualizerItem) => {
const ruleProvider = ruleProviders().find( const ruleProvider = ruleProviders().find(
(ruleProvider) => (ruleProvider) =>
getRuleProviderItemKey(ruleProvider) === virtualizerItem.key, getRuleProviderItemKey(ruleProvider) ===
virtualizerItem.key,
)! )!
return ( return (
@ -235,12 +240,15 @@ export default () => {
<div class="card card-bordered card-compact bg-base-200 p-4"> <div class="card card-bordered card-compact bg-base-200 p-4">
<div class="flex items-center gap-2 pr-8"> <div class="flex items-center gap-2 pr-8">
<span class="break-all">{ruleProvider.name}</span> <span class="break-all">{ruleProvider.name}</span>
<div class="badge badge-sm">{ruleProvider.ruleCount}</div> <div class="badge badge-sm">
{ruleProvider.ruleCount}
</div>
</div> </div>
<div class="text-xs text-slate-500"> <div class="text-xs text-slate-500">
{ruleProvider.vehicleType} / {ruleProvider.behavior} / {ruleProvider.vehicleType} / {ruleProvider.behavior} /
{t('updated')} {formatTimeFromNow(ruleProvider.updatedAt)} {t('updated')}{' '}
{formatTimeFromNow(ruleProvider.updatedAt)}
</div> </div>
<Button <Button
@ -266,5 +274,6 @@ export default () => {
</Show> </Show>
</div> </div>
</div> </div>
</>
) )
} }

View File

@ -6,6 +6,7 @@ import { toast } from 'solid-toast'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { z } from 'zod' import { z } from 'zod'
import { Button } from '~/components' import { Button } from '~/components'
import DocumentTitle from '~/components/DocumentTitle'
import { transformEndpointURL } from '~/helpers' import { transformEndpointURL } from '~/helpers'
import { useI18n } from '~/i18n' import { useI18n } from '~/i18n'
import { import {
@ -140,6 +141,9 @@ export default () => {
}) })
return ( return (
<>
<DocumentTitle>{t('setup')}</DocumentTitle>
<div class="mx-auto flex max-w-screen-sm flex-col items-center gap-4 py-10"> <div class="mx-auto flex max-w-screen-sm flex-col items-center gap-4 py-10">
<form class="contents" use:form={form}> <form class="contents" use:form={form}>
<div class="flex w-full flex-col gap-4"> <div class="flex w-full flex-col gap-4">
@ -206,5 +210,6 @@ export default () => {
</For> </For>
</div> </div>
</div> </div>
</>
) )
} }