mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(releases): replay actions (#932)
* feat(releases): replay actions * feat(releases): replay actions component * fix: update filter actions * fix: select filter_id from ras
This commit is contained in:
parent
97333d334f
commit
6898ad8315
16 changed files with 752 additions and 189 deletions
|
@ -193,7 +193,8 @@ export const APIClient = {
|
|||
},
|
||||
indexerOptions: () => appClient.Get<string[]>("api/release/indexers"),
|
||||
stats: () => appClient.Get<ReleaseStats>("api/release/stats"),
|
||||
delete: () => appClient.Delete("api/release/all")
|
||||
delete: () => appClient.Delete("api/release/all"),
|
||||
replayAction: (releaseId: number, actionId: number) => appClient.Post(`api/release/${releaseId}/actions/${actionId}/retry`)
|
||||
},
|
||||
updates: {
|
||||
check: () => appClient.Get("api/updates/check"),
|
||||
|
|
|
@ -5,11 +5,17 @@
|
|||
|
||||
import * as React from "react";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
import { CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { ArrowPathIcon, CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
import { classNames, simplifyDate } from "@utils";
|
||||
import { Tooltip } from "../tooltips/Tooltip";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { APIClient } from "@api/APIClient";
|
||||
import { filterKeys } from "@screens/filters/list";
|
||||
import { toast } from "react-hot-toast";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
import { RingResizeSpinner } from "@components/Icons";
|
||||
|
||||
interface CellProps {
|
||||
value: string;
|
||||
|
@ -57,6 +63,46 @@ export const TitleCell = ({ value }: CellProps) => (
|
|||
</div>
|
||||
);
|
||||
|
||||
interface RetryActionButtonProps {
|
||||
status: ReleaseActionStatus;
|
||||
}
|
||||
|
||||
interface RetryAction {
|
||||
releaseId: number;
|
||||
actionId: number;
|
||||
}
|
||||
|
||||
const RetryActionButton = ({ status }: RetryActionButtonProps) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: (vars: RetryAction) => APIClient.release.replayAction(vars.releaseId, vars.actionId),
|
||||
onSuccess: () => {
|
||||
// Invalidate filters just in case, most likely not necessary but can't hurt.
|
||||
queryClient.invalidateQueries({ queryKey: filterKeys.lists() });
|
||||
|
||||
toast.custom((t) => (
|
||||
<Toast type="success" body={`${status?.action} replayed`} t={t} />
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
const replayAction = () => {
|
||||
console.log("replay action");
|
||||
mutation.mutate({ releaseId: status.release_id,actionId: status.id });
|
||||
};
|
||||
|
||||
return (
|
||||
<button className="flex items-center px-1.5 py-1 ml-2 border-gray-500 bg-gray-700 rounded hover:bg-gray-600" onClick={replayAction}>
|
||||
<span className="mr-1.5">Retry</span>
|
||||
{mutation.isLoading
|
||||
? <RingResizeSpinner className="text-blue-500 w-4 h-4 iconHeight" aria-hidden="true" />
|
||||
: <ArrowPathIcon className="h-4 w-4" />
|
||||
}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
interface ReleaseStatusCellProps {
|
||||
value: ReleaseActionStatus[];
|
||||
}
|
||||
|
@ -64,69 +110,89 @@ interface ReleaseStatusCellProps {
|
|||
interface StatusCellMapEntry {
|
||||
colors: string;
|
||||
icon: React.ReactElement;
|
||||
textFormatter: (text: string) => React.ReactElement;
|
||||
textFormatter: (status: ReleaseActionStatus) => React.ReactElement;
|
||||
}
|
||||
|
||||
const StatusCellMap: Record<string, StatusCellMapEntry> = {
|
||||
"PUSH_ERROR": {
|
||||
colors: "bg-pink-100 text-pink-800 hover:bg-pink-300",
|
||||
icon: <ExclamationCircleIcon className="h-5 w-5" aria-hidden="true" />,
|
||||
textFormatter: (text: string) => (
|
||||
textFormatter: (status: ReleaseActionStatus) => (
|
||||
<>
|
||||
<span>
|
||||
Action
|
||||
{" "}
|
||||
<span className="font-bold underline underline-offset-2 decoration-2 decoration-red-500">
|
||||
{" "}
|
||||
<span className="font-bold underline underline-offset-2 decoration-2 decoration-red-500">
|
||||
error
|
||||
</span>
|
||||
{": "}
|
||||
{status.action}
|
||||
</span>
|
||||
{": "}
|
||||
{text}
|
||||
<div>
|
||||
{status.action_id > 0 && <RetryActionButton status={status} />}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
},
|
||||
"PUSH_REJECTED": {
|
||||
colors: "bg-blue-100 dark:bg-blue-100 text-blue-400 dark:text-blue-800 hover:bg-blue-300 dark:hover:bg-blue-400",
|
||||
icon: <NoSymbolIcon className="h-5 w-5" aria-hidden="true" />,
|
||||
textFormatter: (text: string) => (
|
||||
textFormatter: (status: ReleaseActionStatus) => (
|
||||
<>
|
||||
<span>
|
||||
Action
|
||||
{" "}
|
||||
<span
|
||||
className="font-bold underline underline-offset-2 decoration-2 decoration-sky-500"
|
||||
>
|
||||
{" "}
|
||||
<span
|
||||
className="font-bold underline underline-offset-2 decoration-2 decoration-sky-500"
|
||||
>
|
||||
rejected
|
||||
</span>
|
||||
{": "}
|
||||
{status.action}
|
||||
</span>
|
||||
{": "}
|
||||
{text}
|
||||
<div>
|
||||
{status.action_id > 0 && <RetryActionButton status={status} />}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
},
|
||||
"PUSH_APPROVED": {
|
||||
colors: "bg-green-100 text-green-800 hover:bg-green-300",
|
||||
icon: <CheckIcon className="h-5 w-5" aria-hidden="true" />,
|
||||
textFormatter: (text: string) => (
|
||||
textFormatter: (status: ReleaseActionStatus) => (
|
||||
<>
|
||||
Action
|
||||
{" "}
|
||||
<span className="font-bold underline underline-offset-2 decoration-2 decoration-green-500">
|
||||
<span>
|
||||
Action
|
||||
{" "}
|
||||
<span className="font-bold underline underline-offset-2 decoration-2 decoration-green-500">
|
||||
approved
|
||||
</span>
|
||||
{": "}
|
||||
{status.action}
|
||||
</span>
|
||||
{": "}
|
||||
{text}
|
||||
{/*<div>*/}
|
||||
{/* {status.action_id > 0 && <RetryActionButton status={status} />}*/}
|
||||
{/*</div>*/}
|
||||
</>
|
||||
)
|
||||
},
|
||||
"PENDING": {
|
||||
colors: "bg-yellow-100 text-yellow-800 hover:bg-yellow-200",
|
||||
icon: <ClockIcon className="h-5 w-5" aria-hidden="true" />,
|
||||
textFormatter: (text: string) => (
|
||||
textFormatter: (status: ReleaseActionStatus) => (
|
||||
<>
|
||||
Action
|
||||
{" "}
|
||||
<span className="font-bold underline underline-offset-2 decoration-2 decoration-yellow-500">
|
||||
<span>
|
||||
Action
|
||||
{" "}
|
||||
<span className="font-bold underline underline-offset-2 decoration-2 decoration-yellow-500">
|
||||
pending
|
||||
</span>
|
||||
{": "}
|
||||
{status.action}
|
||||
</span>
|
||||
{": "}
|
||||
{text}
|
||||
<div>
|
||||
{status.action_id > 0 && <RetryActionButton status={status} />}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -156,7 +222,7 @@ export const ReleaseStatusCell = ({ value }: ReleaseStatusCellProps) => (
|
|||
>
|
||||
<Tooltip
|
||||
label={StatusCellMap[v.status].icon}
|
||||
title={StatusCellMap[v.status].textFormatter(v.action)}
|
||||
title={StatusCellMap[v.status].textFormatter(v)}
|
||||
>
|
||||
<div className="mb-1">
|
||||
<CellLine title="Type">{v.type}</CellLine>
|
||||
|
|
|
@ -48,7 +48,7 @@ export const Tooltip = ({
|
|||
})}
|
||||
>
|
||||
{title ? (
|
||||
<div className="p-2 border-b border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-800 rounded-t-md">
|
||||
<div className="flex justify-between items-center p-2 border-b border-gray-300 bg-gray-100 dark:border-gray-700 dark:bg-gray-800 rounded-t-md">
|
||||
{title}
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
*/
|
||||
|
||||
import React, { Fragment, useEffect, useRef, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { Field, FieldArray, FieldProps, FormikValues, useFormikContext } from "formik";
|
||||
import { Dialog, Switch as SwitchBasic, Transition } from "@headlessui/react";
|
||||
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
import {
|
||||
ActionContentLayoutOptions,
|
||||
|
@ -25,6 +26,7 @@ import { classNames } from "@utils";
|
|||
import { DeleteModal } from "@components/modals";
|
||||
import { CollapsableSection } from "./details";
|
||||
import { TextArea } from "@components/inputs/input";
|
||||
import Toast from "@components/notifications/Toast";
|
||||
|
||||
interface FilterActionsProps {
|
||||
filter: Filter;
|
||||
|
@ -543,6 +545,23 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
|
|||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
|
||||
const [edit, toggleEdit] = useToggle(initialEdit);
|
||||
|
||||
const removeMutation = useMutation({
|
||||
mutationFn: (id: number) => APIClient.actions.delete(id),
|
||||
onSuccess: () => {
|
||||
remove(idx);
|
||||
// Invalidate filters just in case, most likely not necessary but can't hurt.
|
||||
// queryClient.invalidateQueries({ queryKey: filterKeys.detail(id) });
|
||||
|
||||
toast.custom((t) => (
|
||||
<Toast type="success" body={`Action ${action?.name} was deleted`} t={t} />
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
const removeAction = (id: number) => {
|
||||
removeMutation.mutate(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<li>
|
||||
<div
|
||||
|
@ -622,7 +641,7 @@ function FilterActionsItem({ action, clients, idx, initialEdit, remove }: Filter
|
|||
isOpen={deleteModalIsOpen}
|
||||
buttonRef={cancelButtonRef}
|
||||
toggle={toggleDeleteModal}
|
||||
deleteAction={() => remove(idx)}
|
||||
deleteAction={() => removeAction(action.id)}
|
||||
title="Remove filter action"
|
||||
text="Are you sure you want to remove this action? This action cannot be undone."
|
||||
/>
|
||||
|
|
3
web/src/types/Release.d.ts
vendored
3
web/src/types/Release.d.ts
vendored
|
@ -23,9 +23,12 @@ interface ReleaseActionStatus {
|
|||
id: number;
|
||||
status: string;
|
||||
action: string;
|
||||
action_id: number;
|
||||
type: string;
|
||||
client: string;
|
||||
filter: string;
|
||||
filter_id: number;
|
||||
release_id: number;
|
||||
rejections: string[];
|
||||
timestamp: string
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue