Skip to content

Commit

Permalink
feat: s3 server settings page (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
itsHenry35 committed Mar 2, 2024
1 parent 088e7ec commit a2ee5d3
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/lang/en/global.json
Expand Up @@ -27,5 +27,6 @@
"go_login": "Go to login",
"close": "Close",
"no_support_now": "Not currently supported",
"empty_input": "Please enter"
"empty_input": "Please enter",
"name": "Name"
}
3 changes: 2 additions & 1 deletion src/lang/en/manage.json
Expand Up @@ -21,7 +21,8 @@
"sso": "Single Sign-on",
"docs": "Documentation",
"offline_download": "Offline Download",
"ldap": "LDAP"
"ldap": "LDAP",
"s3": "S3"
},
"title": "AList Manage",
"not_admin": "You are not admin user, please login with admin account.",
Expand Down
9 changes: 8 additions & 1 deletion src/lang/en/settings.json
Expand Up @@ -98,5 +98,12 @@
"version": "Version",
"video_autoplay": "Video autoplay",
"video_types": "Video types",
"webauthn_login_enabled": "Webauthn login enabled"
"webauthn_login_enabled": "Webauthn login enabled",
"s3_enabled": "S3 enabled",
"s3_access_key_id": "S3 access key id",
"s3_secret_access_key": "S3 secret access key",
"s3_buckets": "S3 buckets",
"s3_buckets_duplicate_name": "Bucket name is duplicate",
"s3_buckets_empty": "Bucket name or path is empty",
"s3_restart_to_apply": "Above settings need restart to apply"
}
98 changes: 98 additions & 0 deletions src/pages/manage/settings/S3.tsx
@@ -0,0 +1,98 @@
import { useFetch, useT, useManageTitle } from "~/hooks"
import { Group, SettingItem, PResp, PEmptyResp, EmptyResp } from "~/types"
import { r, notify, getTarget, handleResp } from "~/utils"
import { createStore } from "solid-js/store"
import { Button, HStack, Heading, VStack } from "@hope-ui/solid"
import { createSignal, Index, Show } from "solid-js"
import { Item } from "./SettingItem"
import { ResponsiveGrid } from "../common/ResponsiveGrid"
import S3Buckets from "./S3Buckets"

const bucket_parse = (settings: SettingItem[]) => {
const string = { ...settings.find((i) => i.key === "s3_buckets")! }
if (!string.value) return []
return JSON.parse(string.value)
}

const S3Settings = () => {
const t = useT()
useManageTitle(`manage.sidemenu.s3`)
const [settingsLoading, getSettings] = useFetch(
(): PResp<SettingItem[]> => r.get(`/admin/setting/list?group=${Group.S3}`),
)
const [settings, setSettings] = createStore<SettingItem[]>([])
const refresh = async () => {
const resp = await getSettings()
handleResp(resp, setSettings)
}
refresh()
const [saveLoading, saveSettings] = useFetch(
(): PEmptyResp => r.post("/admin/setting/save", getTarget(settings)),
)
const [loading, setLoading] = createSignal(false)
return (
<VStack w="$full" alignItems="start" spacing="$2">
<ResponsiveGrid>
<Index each={settings}>
{(item, _) => (
<Show when={item().key != "s3_buckets"}>
<Item
{...item()}
onChange={(val) => {
setSettings((i) => item().key === i.key, "value", val)
}}
onDelete={async () => {
setLoading(true)
const resp: EmptyResp = await r.post(
`/admin/setting/delete?key=${item().key}`,
)
setLoading(false)
handleResp(resp, () => {
notify.success(t("global.delete_success"))
refresh()
})
}}
/>
</Show>
)}
</Index>
<Heading>{t("settings.s3_restart_to_apply")}</Heading>
<S3Buckets buckets={bucket_parse(settings)} setSettings={setSettings} />
</ResponsiveGrid>
<HStack spacing="$2">
<Button
colorScheme="accent"
onClick={refresh}
loading={settingsLoading() || loading()}
>
{t("global.refresh")}
</Button>
<Button
loading={saveLoading()}
onClick={async () => {
//check that bucket path and name cannot be duplicated or empty
const buckets = bucket_parse(settings)
const names = new Set<string>()
for (const bucket of buckets) {
if (bucket.name === "" || bucket.path === "") {
notify.error(t("settings.s3_buckets_empty"))
return
}
if (names.has(bucket.name)) {
notify.error(t("settings.s3_buckets_duplicate_name"))
return
}
names.add(bucket.name)
}
const resp = await saveSettings()
handleResp(resp, () => notify.success(t("global.save_success")))
}}
>
{t("global.save")}
</Button>
</HStack>
</VStack>
)
}

export default S3Settings
67 changes: 67 additions & 0 deletions src/pages/manage/settings/S3BucketItem.tsx
@@ -0,0 +1,67 @@
import { Button, FormControl, FormLabel, Input, Stack } from "@hope-ui/solid"
import { FolderChooseInput } from "~/components"
import { useT } from "~/hooks"

export type S3Bucket = {
name: string
path: string
}

type props = S3Bucket & {
onChange: (val: S3Bucket) => void
onDelete: () => void
}

export const S3BucketItem = (props: props) => {
const t = useT()
return (
<Stack
w="$full"
overflowX="auto"
shadow="$md"
rounded="$lg"
p="$2"
direction={{ "@initial": "column", "@xl": "row" }}
spacing="$2"
>
<FormControl w="$full" display="flex" flexDirection="column" required>
<FormLabel for="path" display="flex" alignItems="center">
{t(`global.name`)}
</FormLabel>
<Input
id="name"
value={props.name}
onChange={(e) =>
props.onChange({ ...props, name: e.currentTarget.value })
}
/>
</FormControl>

<FormControl w="$full" display="flex" flexDirection="column" required>
<FormLabel for="path" display="flex" alignItems="center">
{t(`metas.path`)}
</FormLabel>
<FolderChooseInput
id="path"
value={props.path}
onChange={(e) => props.onChange({ ...props, path: e })}
/>
</FormControl>

<Stack
direction={{ "@initial": "row", "@xl": "column" }}
justifyContent={{ "@xl": "center" }}
spacing="$1"
>
<Button
colorScheme="danger"
onClick={async () => {
props.onDelete()
}}
>
{t("global.delete")}
</Button>
</Stack>
</Stack>
)
}
63 changes: 63 additions & 0 deletions src/pages/manage/settings/S3Buckets.tsx
@@ -0,0 +1,63 @@
import { VStack, Button, FormLabel } from "@hope-ui/solid"
import { For } from "solid-js"
import { SetStoreFunction } from "solid-js/store"
import { SettingItem } from "~/types"
import { S3BucketItem, S3Bucket } from "./S3BucketItem"
import { useT } from "~/hooks"

export type S3BucketsProps = {
buckets: S3Bucket[]
setSettings: SetStoreFunction<SettingItem[]>
}

const S3Buckets = (props: S3BucketsProps) => {
const t = useT()
console.log(props.buckets)
return (
<VStack alignItems="start" w="$full">
<FormLabel display="flex" alignItems="center">
{t("settings.s3_buckets")}
</FormLabel>
<Button
onClick={() => {
props.setSettings(
(i) => i.key === "s3_buckets",
"value",
JSON.stringify([...props.buckets, { name: "", path: "" }]),
)
console.log(props.buckets)
}}
>
{t("global.add")}
</Button>
<For each={props.buckets}>
{(item) => (
<S3BucketItem
{...item}
onChange={(val) => {
console.log(val)
props.setSettings(
(i) => i.key === "s3_buckets",
"value",
JSON.stringify(
props.buckets.map((b) => (b.name === item.name ? val : b)),
),
)
}}
onDelete={() => {
props.setSettings(
(i) => i.key === "s3_buckets",
"value",
JSON.stringify(
props.buckets.filter((b) => b.name !== item.name),
),
)
}}
/>
)}
</For>
</VStack>
)
}

export default S3Buckets
7 changes: 7 additions & 0 deletions src/pages/manage/sidemenu_items.tsx
Expand Up @@ -11,6 +11,7 @@ import {
BsFront,
BsCloudUploadFill,
BsSearch,
BsBucket,
} from "solid-icons/bs"
import { FiLogIn } from "solid-icons/fi"
import { SiMetabase } from "solid-icons/si"
Expand Down Expand Up @@ -77,6 +78,12 @@ export const side_menu_items: SideMenuItem[] = [
to: "/@manage/settings/ldap",
component: () => <CommonSettings group={Group.LDAP} />,
},
{
title: "manage.sidemenu.s3",
icon: BsBucket,
to: "/@manage/settings/s3",
component: lazy(() => import("./settings/S3")),
},
{
title: "manage.sidemenu.other",
icon: BsMedium,
Expand Down
1 change: 1 addition & 0 deletions src/types/setting.ts
Expand Up @@ -10,6 +10,7 @@ export enum Group {
INDEX,
SSO,
LDAP,
S3,
}
export enum Flag {
PUBLIC,
Expand Down

0 comments on commit a2ee5d3

Please sign in to comment.