feat(func): support virtual host

This commit is contained in:
pikachuim
2026-03-09 14:49:58 +08:00
parent 91004bd1fe
commit 3caab77bfd
9 changed files with 277 additions and 0 deletions

View File

@@ -13,6 +13,7 @@ import shares from "./shares.json"
import storages from "./storages.json"
import tasks from "./tasks.json"
import users from "./users.json"
import virtual_hosts from "./virtual_hosts.json"
export const dict = {
br,
@@ -30,4 +31,5 @@ export const dict = {
storages,
tasks,
users,
virtual_hosts,
}

View File

@@ -11,6 +11,7 @@
"storages": "Storages",
"shares": "Shares",
"metas": "Metas",
"virtual_hosts": "Virtual Hosts",
"profile": "Profile",
"about": "About",
"tasks": "Tasks",

View File

@@ -0,0 +1,9 @@
{
"domain": "Domain",
"domain_help": "The domain name to bind (e.g. blog.example.com)",
"path": "Path",
"path_help": "The OpenList path to map this domain to",
"enabled": "Enabled",
"web_hosting": "Web Hosting",
"web_hosting_help": "If enabled, HTML files will be served directly instead of the file browser"
}

View File

@@ -39,6 +39,14 @@ const hide_routes: Route[] = [
to: "/metas/edit/:id",
component: lazy(() => import("./metas/AddOrEdit")),
},
{
to: "/virtual_hosts/add",
component: lazy(() => import("./virtual_hosts/AddOrEdit")),
},
{
to: "/virtual_hosts/edit/:id",
component: lazy(() => import("./virtual_hosts/AddOrEdit")),
},
{
to: "/2fa",
component: lazy(() => import("./users/2fa")),

View File

@@ -14,6 +14,7 @@ import {
BsBucket,
BsHddNetwork,
BsArrowLeftRight,
BsGlobe,
} from "solid-icons/bs"
import { FiLogIn } from "solid-icons/fi"
import { SiMetabase } from "solid-icons/si"
@@ -187,6 +188,12 @@ export const side_menu_items: SideMenuItem[] = [
to: "/@manage/metas",
component: lazy(() => import("./metas/Metas")),
},
{
title: "manage.sidemenu.virtual_hosts",
icon: BsGlobe,
to: "/@manage/virtual_hosts",
component: lazy(() => import("./virtual_hosts/VirtualHosts")),
},
{
title: "manage.sidemenu.indexes",
icon: BsSearch,

View File

@@ -0,0 +1,127 @@
import {
Button,
Switch as HopeSwitch,
FormControl,
FormHelperText,
FormLabel,
Heading,
Input,
VStack,
} from "@hope-ui/solid"
import { MaybeLoading, FolderChooseInput } from "~/components"
import { useFetch, useRouter, useT } from "~/hooks"
import { handleResp, notify, r } from "~/utils"
import { VirtualHost, PEmptyResp, PResp } from "~/types"
import { createStore } from "solid-js/store"
const AddOrEdit = () => {
const t = useT()
const { params, back } = useRouter()
const { id } = params
const [vhost, setVhost] = createStore<VirtualHost>({
id: 0,
enabled: true,
domain: "",
path: "",
web_hosting: false,
})
const [vhostLoading, loadVhost] = useFetch(
(): PResp<VirtualHost> => r.get(`/admin/vhost/get?id=${id}`),
)
const initEdit = async () => {
const resp = await loadVhost()
handleResp<VirtualHost>(resp, setVhost)
}
if (id) {
initEdit()
}
const [okLoading, ok] = useFetch((): PEmptyResp => {
return r.post(`/admin/vhost/${id ? "update" : "create"}`, vhost)
})
return (
<MaybeLoading loading={vhostLoading()}>
<VStack w="$full" alignItems="start" spacing="$4">
<Heading>{t(`global.${id ? "edit" : "add"}`)}</Heading>
{/* 启用开关 */}
<FormControl
w="$full"
display="flex"
flexDirection="row"
alignItems="center"
gap="$3"
>
<FormLabel for="enabled" mb="0">
{t("virtual_hosts.enabled")}
</FormLabel>
<HopeSwitch
id="enabled"
checked={vhost.enabled}
onChange={(e: any) => setVhost("enabled", e.currentTarget.checked)}
/>
</FormControl>
{/* 域名 */}
<FormControl w="$full" display="flex" flexDirection="column" required>
<FormLabel for="domain">{t("virtual_hosts.domain")}</FormLabel>
<Input
id="domain"
placeholder="example.com"
value={vhost.domain}
onInput={(e) => setVhost("domain", e.currentTarget.value)}
/>
<FormHelperText>{t("virtual_hosts.domain_help")}</FormHelperText>
</FormControl>
{/* 路径 */}
<FormControl w="$full" display="flex" flexDirection="column" required>
<FormLabel for="path">{t("virtual_hosts.path")}</FormLabel>
<FolderChooseInput
id="path"
value={vhost.path}
onChange={(path) => setVhost("path", path)}
/>
<FormHelperText>{t("virtual_hosts.path_help")}</FormHelperText>
</FormControl>
{/* Web 托管开关 */}
<FormControl w="$full" display="flex" flexDirection="column">
<FormControl
display="flex"
flexDirection="row"
alignItems="center"
gap="$3"
>
<FormLabel for="web_hosting" mb="0">
{t("virtual_hosts.web_hosting")}
</FormLabel>
<HopeSwitch
id="web_hosting"
checked={vhost.web_hosting}
onChange={(e: any) =>
setVhost("web_hosting", e.currentTarget.checked)
}
/>
</FormControl>
<FormHelperText>{t("virtual_hosts.web_hosting_help")}</FormHelperText>
</FormControl>
<Button
loading={okLoading()}
onClick={async () => {
const resp = await ok()
handleResp(resp, () => {
notify.success(t("global.save_success"))
back()
})
}}
>
{t(`global.${id ? "save" : "add"}`)}
</Button>
</VStack>
</MaybeLoading>
)
}
export default AddOrEdit

View File

@@ -0,0 +1,115 @@
import {
Box,
Button,
HStack,
Table,
Tbody,
Td,
Th,
Thead,
Tr,
VStack,
} from "@hope-ui/solid"
import { createSignal, For } from "solid-js"
import {
useFetch,
useListFetch,
useManageTitle,
useRouter,
useT,
} from "~/hooks"
import { handleResp, notify, r } from "~/utils"
import { VirtualHost, PEmptyResp, PPageResp } from "~/types"
import { DeletePopover } from "../common/DeletePopover"
import { Wether } from "~/components"
const VirtualHosts = () => {
const t = useT()
useManageTitle("manage.sidemenu.virtual_hosts")
const { to } = useRouter()
const [getVhostsLoading, getVhosts] = useFetch(
(): PPageResp<VirtualHost> => r.get("/admin/vhost/list"),
)
const [vhosts, setVhosts] = createSignal<VirtualHost[]>([])
const refresh = async () => {
const resp = await getVhosts()
handleResp(resp, (data) => setVhosts(data.content))
}
refresh()
const [deleting, deleteVhost] = useListFetch(
(id: number): PEmptyResp => r.post(`/admin/vhost/delete?id=${id}`),
)
return (
<VStack spacing="$2" alignItems="start" w="$full">
<HStack spacing="$2">
<Button
colorScheme="accent"
loading={getVhostsLoading()}
onClick={refresh}
>
{t("global.refresh")}
</Button>
<Button
onClick={() => {
to("/@manage/virtual_hosts/add")
}}
>
{t("global.add")}
</Button>
</HStack>
<Box w="$full" overflowX="auto">
<Table highlightOnHover dense>
<Thead>
<Tr>
<For each={["domain", "path", "enabled", "web_hosting"]}>
{(title) => <Th>{t(`virtual_hosts.${title}`)}</Th>}
</For>
<Th>{t("global.operations")}</Th>
</Tr>
</Thead>
<Tbody>
<For each={vhosts()}>
{(vhost) => (
<Tr>
<Td>{vhost.domain}</Td>
<Td>{vhost.path}</Td>
<Td>
<Wether yes={vhost.enabled} />
</Td>
<Td>
<Wether yes={vhost.web_hosting} />
</Td>
<Td>
<HStack spacing="$2">
<Button
onClick={() => {
to(`/@manage/virtual_hosts/edit/${vhost.id}`)
}}
>
{t("global.edit")}
</Button>
<DeletePopover
name={vhost.domain}
loading={deleting() === vhost.id}
onClick={async () => {
const resp = await deleteVhost(vhost.id)
handleResp(resp, () => {
notify.success(t("global.delete_success"))
refresh()
})
}}
/>
</HStack>
</Td>
</Tr>
)}
</For>
</Tbody>
</Table>
</Box>
</VStack>
)
}
export default VirtualHosts

View File

@@ -8,3 +8,4 @@ export * from "./item_type"
export * from "./meta"
export * from "./task"
export * from "./share"
export * from "./virtual_host"

View File

@@ -0,0 +1,7 @@
export interface VirtualHost {
id: number
enabled: boolean
domain: string
path: string
web_hosting: boolean
}