mirror of
https://github.com/OpenListTeam/OpenList-Frontend.git
synced 2026-03-13 11:20:24 +00:00
feat(func): support virtual host
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"storages": "Storages",
|
||||
"shares": "Shares",
|
||||
"metas": "Metas",
|
||||
"virtual_hosts": "Virtual Hosts",
|
||||
"profile": "Profile",
|
||||
"about": "About",
|
||||
"tasks": "Tasks",
|
||||
|
||||
9
src/lang/en/virtual_hosts.json
Normal file
9
src/lang/en/virtual_hosts.json
Normal 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"
|
||||
}
|
||||
@@ -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")),
|
||||
|
||||
@@ -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,
|
||||
|
||||
127
src/pages/manage/virtual_hosts/AddOrEdit.tsx
Normal file
127
src/pages/manage/virtual_hosts/AddOrEdit.tsx
Normal 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
|
||||
115
src/pages/manage/virtual_hosts/VirtualHosts.tsx
Normal file
115
src/pages/manage/virtual_hosts/VirtualHosts.tsx
Normal 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
|
||||
@@ -8,3 +8,4 @@ export * from "./item_type"
|
||||
export * from "./meta"
|
||||
export * from "./task"
|
||||
export * from "./share"
|
||||
export * from "./virtual_host"
|
||||
|
||||
7
src/types/virtual_host.ts
Normal file
7
src/types/virtual_host.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface VirtualHost {
|
||||
id: number
|
||||
enabled: boolean
|
||||
domain: string
|
||||
path: string
|
||||
web_hosting: boolean
|
||||
}
|
||||
Reference in New Issue
Block a user