Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/lang/en/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,12 @@
"name": "Name",
"refresh_success": "Refresh successfully",
"refresh_failed": "Refresh failed",
"required": "required"
"required": "required",
"generate_strm": "Generate Strm",
"generate_strm_start": "Generation started",
"generate_strm_progress": "Generating Strm...",
"generate_strm_done": "Strm generated",
"generate_strm_failed": "Generation failed",
"generate_strm_hide": "Hide",
"generate_strm_background": "The task keeps running in the background. Track it in the task center."
}
1 change: 1 addition & 0 deletions src/lang/en/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"new_file": "New File",
"input_filename": "Input filename",
"cancel_select": "Cancel Select",
"generate_strm": "Generate Strm",
"offline_download": "Offline download",
"offline_download-tips": "One URL per line",
"delete_policy": {
Expand Down
1 change: 1 addition & 0 deletions src/lang/en/manage.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"permissions_config": "Config",
"upload": "Upload",
"copy": "Copy",
"strm_generate": "Strm Generate",
"decompress": "Decompress",
"s3_transition": "S3 Transition",
"backup-restore": "Backup & Restore",
Expand Down
4 changes: 4 additions & 0 deletions src/lang/en/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"offline_download_transfer": "Transfer downloaded file to corresponding storage",
"upload": "Upload file to corresponding storage",
"copy": "Copy file from a storage to another storage",
"strm_generate": "Generate local .strm files for a Strm storage",
"decompress": "Download and decompress an archive file",
"decompress_upload": "Upload extracted file into target storage",
"s3_transition": "Archive and restore S3 objects",
Expand Down Expand Up @@ -45,6 +46,9 @@
"upload": {
"path": "Path"
},
"strm_generate": {
"path": "Path"
},
"offline_download": {
"url": "URL",
"path": "Destination Path",
Expand Down
2 changes: 2 additions & 0 deletions src/pages/home/toolbar/Right.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { usePath } from "~/hooks"
import { Motion } from "@motionone/solid"
import { isTocVisible, setTocDisabled } from "~/components"
import { BiSolidBookContent } from "solid-icons/bi"
import { ToolbarStrmGenerate } from "./StrmGenerate"

export const Right = () => {
const { isOpen, onToggle } = createDisclosure({
Expand Down Expand Up @@ -122,6 +123,7 @@ export const Right = () => {
}}
/>
</Show>
<ToolbarStrmGenerate />
<Show when={isTocVisible()}>
<RightIcon
as={BiSolidBookContent}
Expand Down
21 changes: 21 additions & 0 deletions src/pages/home/toolbar/StrmGenerate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Show } from "solid-js"
import { TbFileExport } from "solid-icons/tb"
import { useRouter } from "~/hooks"
import { me, objStore } from "~/store"
import { StrmGenerate } from "~/pages/manage/storages/StrmGenerate"
import { RightIcon } from "./Icon"

export const ToolbarStrmGenerate = () => {
const { pathname } = useRouter()
const isAdmin = () => (me().role || []).includes(2)
const isStrm = () => objStore.provider === "Strm"
return (
<Show when={isAdmin() && isStrm()}>
<StrmGenerate path={pathname()}>
{({ start }) => (
<RightIcon as={TbFileExport} tips="generate_strm" onClick={start} />
)}
</StrmGenerate>
</Show>
)
}
9 changes: 8 additions & 1 deletion src/pages/manage/sidemenu_items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Component, lazy } from "solid-js"
import { joinBase } from "~/utils"
import { Group, UserRole } from "~/types"
import { FaSolidBook, FaSolidDatabase } from "solid-icons/fa"
import { TbArchive, TbDevices2 } from "solid-icons/tb"
import { TbArchive, TbDevices2, TbFileExport } from "solid-icons/tb"
import { TbLink } from "solid-icons/tb"
import { FaSolidUserGear } from "solid-icons/fa"
import { BiRegularMessageAltDetail } from "solid-icons/bi"
Expand Down Expand Up @@ -180,6 +180,13 @@ export const side_menu_items: SideMenuItem[] = [
role: UserRole.GENERAL,
component: lazy(() => import("./tasks/Copy")),
},
{
title: "manage.sidemenu.strm_generate",
icon: TbFileExport,
to: "/@manage/tasks/strm_generate",
role: UserRole.ADMIN,
component: lazy(() => import("./tasks/StrmGenerate")),
},
{
title: "manage.sidemenu.decompress",
icon: TbArchive,
Expand Down
5 changes: 5 additions & 0 deletions src/pages/manage/storages/Storage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
useColorModeValue,
VStack,
} from "@hope-ui/solid"
import { Show } from "solid-js"
import { useFetch, useRouter, useT } from "~/hooks"
import { getMainColor } from "~/store"
import { PEmptyResp, Storage } from "~/types"
import { handleResp, handleRespWithNotifySuccess, notify, r } from "~/utils"
import { DeletePopover } from "../common/DeletePopover"
import { StrmGenerateButton } from "./StrmGenerate"

interface StorageProps {
storage: Storage
Expand Down Expand Up @@ -66,6 +68,9 @@ function StorageOp(props: StorageProps) {
})
}}
/>
<Show when={props.storage.driver === "Strm"}>
<StrmGenerateButton path={props.storage.mount_path} />
</Show>
</>
)
}
Expand Down
186 changes: 186 additions & 0 deletions src/pages/manage/storages/StrmGenerate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Progress,
ProgressIndicator,
Text,
VStack,
} from "@hope-ui/solid"
import { createSignal, JSX, onCleanup, Show } from "solid-js"
import { useT } from "~/hooks"
import { r, notify, handleResp } from "~/utils"

type TaskInfo = { id: string; progress: number; state: number; error: string }
type Status = "idle" | "running" | "done" | "failed"

export type StrmGenerateProps = {
path: string
// render-prop trigger: receives `start` to kick off generation. Lets callers
// render either a Button (storages page) or a toolbar icon while sharing the
// progress dialog + polling logic below.
children: (api: { start: () => void }) => JSX.Element
}

export type StrmGenerateButtonProps = {
path: string
size?: "xs" | "sm" | "md" | "lg"
colorScheme?: string
}

export const StrmGenerate = (props: StrmGenerateProps) => {
const t = useT()
const [opened, setOpened] = createSignal(false)
const [progress, setProgress] = createSignal(0)
const [status, setStatus] = createSignal<Status>("idle")
const [err, setErr] = createSignal("")
let timer: ReturnType<typeof setInterval> | undefined
// generation increments on every start/close/cleanup so any in-flight request
// or interval tick that resolves late can detect it is stale and bail out.
let generation = 0

const stop = () => {
if (timer) {
clearInterval(timer)
timer = undefined
}
}
onCleanup(() => {
generation++
stop()
})

const fail = (msg: string) => {
stop()
setStatus("failed")
setErr(msg)
}

const poll = (gen: number, tid: string) => {
stop()
timer = setInterval(async () => {
const resp = await r.post(`/admin/task/strm_generate/info?tid=${tid}`)
if (gen !== generation) return
handleResp(
resp,
(info: TaskInfo) => {
if (gen !== generation) return
if (info.error) {
fail(info.error)
return
}
setProgress(Math.floor(info.progress))
if (info.progress >= 100) {
stop()
setStatus("done")
notify.success(t("global.generate_strm_done"))
}
},
(msg: string) => {
if (gen === generation) fail(msg)
},
)
}, 1000)
}

const start = async () => {
stop()
const gen = ++generation
setOpened(true)
setStatus("running")
setProgress(0)
setErr("")
const resp = await r.post("/admin/strm/generate", { path: props.path })
if (gen !== generation) return
handleResp(
resp,
(data: { task: TaskInfo }) => {
if (gen !== generation) return
notify.info(t("global.generate_strm_start"))
poll(gen, data.task.id)
},
(msg: string) => {
if (gen === generation) fail(msg)
},
)
}

// hide just closes the foreground window; the backend task keeps running and
// polling continues, so the success toast still fires and progress stays
// visible in the task center.
const hide = () => setOpened(false)

// close is used once the task reached a terminal state: stop polling and reset.
const close = () => {
generation++
stop()
setOpened(false)
}

const isRunning = () => status() === "running"
const dismiss = () => (isRunning() ? hide() : close())

const statusText = () => {
switch (status()) {
case "failed":
return t("global.generate_strm_failed") + ": " + err()
case "done":
return t("global.generate_strm_done")
case "running":
return t("global.generate_strm_progress") + " " + progress() + "%"
default:
return ""
}
}

return (
<>
{props.children({ start })}
<Modal opened={opened()} onClose={dismiss} blockScrollOnMount={false}>
<ModalOverlay />
<ModalContent>
<ModalHeader>{t("global.generate_strm")}</ModalHeader>
<ModalBody>
<VStack spacing="$3" alignItems="stretch" py="$2">
<Progress value={progress()} trackColor="$neutral4">
<ProgressIndicator color="$success9" />
</Progress>
<Text>{statusText()}</Text>
<Show when={isRunning()}>
<Text size="sm" color="$neutral10">
{t("global.generate_strm_background")}
</Text>
</Show>
</VStack>
</ModalBody>
<ModalFooter>
<Button colorScheme="neutral" onClick={dismiss}>
{isRunning() ? t("global.generate_strm_hide") : t("global.close")}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
)
}

export const StrmGenerateButton = (props: StrmGenerateButtonProps) => {
const t = useT()
return (
<StrmGenerate path={props.path}>
{({ start }) => (
<Button
size={props.size}
colorScheme={(props.colorScheme as any) ?? "accent"}
onClick={start}
>
{t("global.generate_strm")}
</Button>
)}
</StrmGenerate>
)
}
23 changes: 23 additions & 0 deletions src/pages/manage/tasks/StrmGenerate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useManageTitle, useT } from "~/hooks"
import { TypeTasks } from "./Tasks"

const StrmGenerate = () => {
const t = useT()
useManageTitle("manage.sidemenu.strm_generate")
return (
<TypeTasks
type="strm_generate"
canRetry
nameAnalyzer={{
// backend name: `generate strm [<mount>](<path>)`
regex: /^generate strm \[(.+)]\((.*)\)$/,
title: (matches) => matches[1],
attrs: {
[t(`tasks.attr.strm_generate.path`)]: (matches) => matches[2] || "/",
},
}}
/>
)
}

export default StrmGenerate