mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-25 19:52:48 +00:00
feat(dashboard): admin can now view task detailed steps
This commit is contained in:
parent
756c4518c7
commit
ec341164d5
|
|
@ -510,6 +510,7 @@ export interface Task extends CommonMixin {
|
|||
};
|
||||
|
||||
user_hash_id?: string;
|
||||
task_hash_id?: string;
|
||||
summary?: TaskSummary;
|
||||
node?: Node;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { TaskSummary, TaskType } from "../../../api/workflow";
|
|||
import CrUri, { Filesystem } from "../../../util/uri";
|
||||
import TaskSummaryTitle from "../../Pages/Tasks/TaskSummaryTitle";
|
||||
|
||||
const userTaskTypes: string[] = [
|
||||
export const userTaskTypes: string[] = [
|
||||
TaskType.relocate,
|
||||
TaskType.create_archive,
|
||||
TaskType.extract_archive,
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ import UserAvatar from "../../../Common/User/UserAvatar";
|
|||
import FileBadge from "../../../FileManager/FileBadge";
|
||||
import SettingForm from "../../../Pages/Setting/SettingForm";
|
||||
import DownloadFileList from "../../../Pages/Tasks/DownloadFileList";
|
||||
import TaskProgress from "../../../Pages/Tasks/TaskProgress";
|
||||
import { getTaskStatusText } from "../../../Pages/Tasks/TaskProps";
|
||||
import UserDialog from "../../User/UserDialog/UserDialog";
|
||||
import { processTaskContent } from "../TaskContent";
|
||||
import { processTaskContent, userTaskTypes } from "../TaskContent";
|
||||
import BlobErrors from "./BlobErrors";
|
||||
dayjs.extend(duration);
|
||||
|
||||
|
|
@ -66,6 +67,10 @@ const TaskForm = ({ values }: { values: Task }) => {
|
|||
return res;
|
||||
}, [values]);
|
||||
|
||||
const isUserTask = useMemo(() => {
|
||||
return userTaskTypes.includes(values.type ?? "");
|
||||
}, [values]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserDialog open={userDialogOpen} onClose={() => setUserDialogOpen(false)} userID={userDialogID} />
|
||||
|
|
@ -225,6 +230,18 @@ const TaskForm = ({ values }: { values: Task }) => {
|
|||
</SettingForm>
|
||||
)}
|
||||
|
||||
{isUserTask && (
|
||||
<SettingForm title={t("application:setting.taskProgress")} noContainer lgWidth={12}>
|
||||
<TaskProgress
|
||||
taskId={values.task_hash_id ?? ""}
|
||||
taskStatus={values.status?.toString() ?? ""}
|
||||
taskType={values.type ?? ""}
|
||||
summary={values.summary}
|
||||
node={values.node ? { type: values.node.type?.toString() ?? "" } : undefined}
|
||||
/>
|
||||
</SettingForm>
|
||||
)}
|
||||
|
||||
{values?.public_state?.error_history && (
|
||||
<SettingForm title={t("application:setting.retryErrorHistory")} noContainer lgWidth={12}>
|
||||
<TableContainer component={StyledTableContainerPaper}>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import HoverPopover from "material-ui-popup-state/HoverPopover";
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTasksPhaseProgress } from "../../../api/api.ts";
|
||||
import { TaskProgress, TaskProgresses, TaskResponse } from "../../../api/workflow.ts";
|
||||
import { TaskProgress, TaskProgresses } from "../../../api/workflow.ts";
|
||||
import { useAppDispatch } from "../../../redux/hooks.ts";
|
||||
import { sizeToString } from "../../../util";
|
||||
import StepProgressBar from "./StepProgressBar.tsx";
|
||||
|
||||
export interface StepProgressPopoverProps extends PopoverProps {
|
||||
task: TaskResponse;
|
||||
taskId: string;
|
||||
}
|
||||
|
||||
export const ProgressKeys = {
|
||||
|
|
@ -167,7 +167,7 @@ const ProgressBar = ({ pkey, p }: { pkey: string; p: TaskProgress }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const StepProgressPopover = ({ task, open, ...rest }: StepProgressPopoverProps) => {
|
||||
const StepProgressPopover = ({ taskId, open, ...rest }: StepProgressPopoverProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const stopPropagation = useCallback((e: any) => e.stopPropagation(), []);
|
||||
|
|
@ -175,9 +175,9 @@ const StepProgressPopover = ({ task, open, ...rest }: StepProgressPopoverProps)
|
|||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
dispatch(getTasksPhaseProgress(task.id)).then((res) => setProgress(res));
|
||||
dispatch(getTasksPhaseProgress(taskId)).then((res) => setProgress(res));
|
||||
}
|
||||
}, [open, task]);
|
||||
}, [open, taskId]);
|
||||
|
||||
return (
|
||||
<HoverPopover
|
||||
|
|
|
|||
|
|
@ -47,7 +47,13 @@ const TaskDetail = ({ task, downloading }: TaskDetailProps) => {
|
|||
</Alert>
|
||||
)}
|
||||
{task.status == TaskStatus.error && <Alert severity={"error"}>{task.error}</Alert>}
|
||||
<TaskProgress task={task} />
|
||||
<TaskProgress
|
||||
taskId={task.id}
|
||||
taskStatus={task.status}
|
||||
taskType={task.type}
|
||||
summary={task.summary}
|
||||
node={task.node}
|
||||
/>
|
||||
<Divider />
|
||||
</Stack>
|
||||
<Stack spacing={1}>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import { Box, Stepper, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { NodeTypes, TaskResponse, TaskStatus, TaskType } from "../../../api/workflow.ts";
|
||||
import { NodeTypes, TaskStatus, TaskSummary, TaskType } from "../../../api/workflow.ts";
|
||||
import PieceProgress from "./PieceProgress.tsx";
|
||||
import TaskProgressStep from "./TaskProgressStep.tsx";
|
||||
|
||||
export interface TaskProgressProps {
|
||||
task: TaskResponse;
|
||||
taskId: string;
|
||||
taskStatus: string;
|
||||
taskType: string;
|
||||
summary?: TaskSummary;
|
||||
node?: {
|
||||
type: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface StepModel {
|
||||
|
|
@ -191,56 +197,52 @@ const stepOptions: {
|
|||
],
|
||||
};
|
||||
|
||||
const TaskProgress = ({ task }: TaskProgressProps) => {
|
||||
const TaskProgress = ({ taskId, taskStatus, taskType, summary, node }: TaskProgressProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const steps = useMemo((): StepModel[] => {
|
||||
return stepOptions[task.type]?.[task.node?.type == NodeTypes.slave ? 1 : 0] ?? [];
|
||||
}, [task.id, task.node?.type]);
|
||||
return stepOptions[taskType]?.[node?.type == NodeTypes.slave ? 1 : 0] ?? [];
|
||||
}, [taskId, node?.type]);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||
|
||||
useEffect(() => {
|
||||
if (task.status == TaskStatus.queued) {
|
||||
if (taskStatus == TaskStatus.queued) {
|
||||
setActiveStep(0);
|
||||
return;
|
||||
}
|
||||
if (task.status == TaskStatus.completed) {
|
||||
if (taskStatus == TaskStatus.completed) {
|
||||
setActiveStep(steps.length);
|
||||
return;
|
||||
}
|
||||
let active = 1;
|
||||
for (let i = 1; i < steps.length; i++) {
|
||||
if (steps[i].state == task.summary?.phase) {
|
||||
if (steps[i].state == summary?.phase) {
|
||||
active = i;
|
||||
}
|
||||
}
|
||||
|
||||
setActiveStep(active);
|
||||
}, [steps, task]);
|
||||
}, [steps, taskStatus, summary?.phase]);
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stepper activeStep={activeStep} orientation={isMobile ? "vertical" : "horizontal"}>
|
||||
{steps.map((step, index) => (
|
||||
<TaskProgressStep
|
||||
progressing={activeStep == index && task.status != TaskStatus.error}
|
||||
task={task}
|
||||
progressing={activeStep == index && taskStatus != TaskStatus.error}
|
||||
taskId={taskId}
|
||||
taskStatus={taskStatus}
|
||||
description={step.description}
|
||||
title={step.title}
|
||||
showProgress={step.supportProgress}
|
||||
key={task.id + "_" + step.title}
|
||||
key={taskId + "_" + step.title}
|
||||
/>
|
||||
))}
|
||||
</Stepper>
|
||||
{task.type == TaskType.remote_download &&
|
||||
task.summary?.props.download?.pieces &&
|
||||
task.summary?.phase == "monitor" && (
|
||||
<PieceProgress
|
||||
total={task.summary?.props.download.num_pieces ?? 1}
|
||||
pieces={task.summary.props.download?.pieces}
|
||||
/>
|
||||
)}
|
||||
{taskType == TaskType.remote_download && summary?.props.download?.pieces && summary?.phase == "monitor" && (
|
||||
<PieceProgress total={summary?.props.download.num_pieces ?? 1} pieces={summary?.props.download?.pieces} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,76 +1,51 @@
|
|||
import { TaskResponse, TaskStatus } from "../../../api/workflow.ts";
|
||||
import {
|
||||
Step,
|
||||
StepIcon,
|
||||
StepIconProps,
|
||||
StepLabel,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Step, StepIcon, StepIconProps, StepLabel, Typography } from "@mui/material";
|
||||
import { StepProps } from "@mui/material/Step/Step";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
|
||||
import DismissCircleFilled from "../../Icons/DismissCircleFilled.tsx";
|
||||
import { useMemo } from "react";
|
||||
import CheckCircleFilled from "../../Icons/CheckCircleFilled.tsx";
|
||||
import { usePopupState } from "material-ui-popup-state/hooks";
|
||||
import { bindHover, bindPopover } from "material-ui-popup-state";
|
||||
import { usePopupState } from "material-ui-popup-state/hooks";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TaskStatus } from "../../../api/workflow.ts";
|
||||
import FacebookCircularProgress from "../../Common/CircularProgress.tsx";
|
||||
import CheckCircleFilled from "../../Icons/CheckCircleFilled.tsx";
|
||||
import DismissCircleFilled from "../../Icons/DismissCircleFilled.tsx";
|
||||
import StepProgressPopover from "./StepProgressPopover.tsx";
|
||||
|
||||
interface ProgressStepIconProps extends StepIconProps {}
|
||||
|
||||
const ProgressStepIcon =
|
||||
(task: TaskResponse) => (props: ProgressStepIconProps) => {
|
||||
const { active, completed, icon, ...rest } = props;
|
||||
const ProgressStepIcon = (status: string) => (props: ProgressStepIconProps) => {
|
||||
const { active, completed, icon, ...rest } = props;
|
||||
|
||||
let newIcon = icon;
|
||||
if (active) {
|
||||
newIcon = <FacebookCircularProgress sx={{ pt: "5px" }} size={24} />;
|
||||
if (task.status == TaskStatus.error) {
|
||||
newIcon = (
|
||||
<DismissCircleFilled
|
||||
sx={{ fontSize: 28.5, color: (theme) => theme.palette.error.main }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (completed) {
|
||||
newIcon = (
|
||||
<CheckCircleFilled
|
||||
sx={{ fontSize: 28.5, color: (theme) => theme.palette.primary.main }}
|
||||
/>
|
||||
);
|
||||
let newIcon = icon;
|
||||
if (active) {
|
||||
newIcon = <FacebookCircularProgress sx={{ pt: "5px" }} size={24} />;
|
||||
if (status == TaskStatus.error) {
|
||||
newIcon = <DismissCircleFilled sx={{ fontSize: 28.5, color: (theme) => theme.palette.error.main }} />;
|
||||
}
|
||||
} else if (completed) {
|
||||
newIcon = <CheckCircleFilled sx={{ fontSize: 28.5, color: (theme) => theme.palette.primary.main }} />;
|
||||
}
|
||||
|
||||
if (active && task.status == TaskStatus.error) {
|
||||
newIcon = (
|
||||
<DismissCircleFilled
|
||||
sx={{ fontSize: 28.5, color: (theme) => theme.palette.error.main }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (active && status == TaskStatus.error) {
|
||||
newIcon = <DismissCircleFilled sx={{ fontSize: 28.5, color: (theme) => theme.palette.error.main }} />;
|
||||
}
|
||||
|
||||
if (active && task.status == TaskStatus.canceled) {
|
||||
newIcon = (
|
||||
<DismissCircleFilled
|
||||
sx={{
|
||||
fontSize: 28.5,
|
||||
color: (theme) => theme.palette.action.active,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StepIcon
|
||||
icon={newIcon}
|
||||
active={active}
|
||||
completed={completed}
|
||||
{...rest}
|
||||
if (active && status == TaskStatus.canceled) {
|
||||
newIcon = (
|
||||
<DismissCircleFilled
|
||||
sx={{
|
||||
fontSize: 28.5,
|
||||
color: (theme) => theme.palette.action.active,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return <StepIcon icon={newIcon} active={active} completed={completed} {...rest} />;
|
||||
};
|
||||
|
||||
export interface TaskProgressStepProps extends StepProps {
|
||||
task: TaskResponse;
|
||||
taskId: string;
|
||||
taskStatus: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
showProgress?: boolean;
|
||||
|
|
@ -78,7 +53,8 @@ export interface TaskProgressStepProps extends StepProps {
|
|||
}
|
||||
|
||||
const TaskProgressStep = ({
|
||||
task,
|
||||
taskId,
|
||||
taskStatus,
|
||||
title,
|
||||
description,
|
||||
showProgress,
|
||||
|
|
@ -87,36 +63,27 @@ const TaskProgressStep = ({
|
|||
}: TaskProgressStepProps) => {
|
||||
const popupState = usePopupState({
|
||||
variant: "popover",
|
||||
popupId: `progress_${task.id}_${title}`,
|
||||
popupId: `progress_${taskId}_${title}`,
|
||||
});
|
||||
const { open, ...restPopup } = bindPopover(popupState);
|
||||
|
||||
const StepIconComponent = useMemo(() => {
|
||||
return ProgressStepIcon(task);
|
||||
}, [task.status]);
|
||||
return ProgressStepIcon(taskStatus);
|
||||
}, [taskStatus]);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Step
|
||||
{...rest}
|
||||
{...(showProgress && progressing ? bindHover(popupState) : [])}
|
||||
>
|
||||
<Step {...rest} {...(showProgress && progressing ? bindHover(popupState) : [])}>
|
||||
<StepLabel
|
||||
optional={
|
||||
description ? (
|
||||
<Typography variant={"caption"}>{t(description)}</Typography>
|
||||
) : undefined
|
||||
}
|
||||
optional={description ? <Typography variant={"caption"}>{t(description)}</Typography> : undefined}
|
||||
slots={{
|
||||
stepIcon: StepIconComponent
|
||||
stepIcon: StepIconComponent,
|
||||
}}
|
||||
>
|
||||
{t(title)}
|
||||
</StepLabel>
|
||||
</Step>
|
||||
{showProgress && progressing && (
|
||||
<StepProgressPopover task={task} open={open} {...restPopup} />
|
||||
)}
|
||||
{showProgress && progressing && <StepProgressPopover taskId={taskId} open={open} {...restPopup} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue