diff options
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/src/Router/index.tsx | 8 | ||||
-rw-r--r-- | frontend/src/apis/hooks/system.ts | 41 | ||||
-rw-r--r-- | frontend/src/apis/queries/keys.ts | 1 | ||||
-rw-r--r-- | frontend/src/apis/raw/system.ts | 13 | ||||
-rw-r--r-- | frontend/src/pages/System/Announcements/index.tsx | 24 | ||||
-rw-r--r-- | frontend/src/pages/System/Announcements/table.tsx | 91 | ||||
-rw-r--r-- | frontend/src/pages/System/system.test.tsx | 5 | ||||
-rw-r--r-- | frontend/src/types/api.d.ts | 1 | ||||
-rw-r--r-- | frontend/src/types/form.d.ts | 4 | ||||
-rw-r--r-- | frontend/src/types/system.d.ts | 8 |
10 files changed, 194 insertions, 2 deletions
diff --git a/frontend/src/Router/index.tsx b/frontend/src/Router/index.tsx index 5e90290c8..d13ea1417 100644 --- a/frontend/src/Router/index.tsx +++ b/frontend/src/Router/index.tsx @@ -23,6 +23,7 @@ import SettingsSchedulerView from "@/pages/Settings/Scheduler"; import SettingsSonarrView from "@/pages/Settings/Sonarr"; import SettingsSubtitlesView from "@/pages/Settings/Subtitles"; import SettingsUIView from "@/pages/Settings/UI"; +import SystemAnnouncementsView from "@/pages/System/Announcements"; import SystemBackupsView from "@/pages/System/Backups"; import SystemLogsView from "@/pages/System/Logs"; import SystemProvidersView from "@/pages/System/Providers"; @@ -278,6 +279,12 @@ function useRoutes(): CustomRouteObject[] { name: "Releases", element: <SystemReleasesView></SystemReleasesView>, }, + { + path: "announcements", + name: "Announcements", + badge: data?.announcements, + element: <SystemAnnouncementsView></SystemAnnouncementsView>, + }, ], }, { @@ -299,6 +306,7 @@ function useRoutes(): CustomRouteObject[] { data?.providers, data?.sonarr_signalr, data?.radarr_signalr, + data?.announcements, radarr, sonarr, ] diff --git a/frontend/src/apis/hooks/system.ts b/frontend/src/apis/hooks/system.ts index 9be28c4d2..29e379a20 100644 --- a/frontend/src/apis/hooks/system.ts +++ b/frontend/src/apis/hooks/system.ts @@ -6,7 +6,15 @@ import { QueryKeys } from "../queries/keys"; import api from "../raw"; export function useBadges() { - return useQuery([QueryKeys.System, QueryKeys.Badges], () => api.badges.all()); + return useQuery( + [QueryKeys.System, QueryKeys.Badges], + () => api.badges.all(), + { + refetchOnWindowFocus: "always", + refetchInterval: 1000 * 60, + staleTime: 1000 * 10, + } + ); } export function useFileSystem( @@ -73,7 +81,7 @@ export function useSystemLogs() { return useQuery([QueryKeys.System, QueryKeys.Logs], () => api.system.logs(), { refetchOnWindowFocus: "always", refetchInterval: 1000 * 60, - staleTime: 1000, + staleTime: 1000 * 10, }); } @@ -90,6 +98,35 @@ export function useDeleteLogs() { ); } +export function useSystemAnnouncements() { + return useQuery( + [QueryKeys.System, QueryKeys.Announcements], + () => api.system.announcements(), + { + refetchOnWindowFocus: "always", + refetchInterval: 1000 * 60, + staleTime: 1000 * 10, + } + ); +} + +export function useSystemAnnouncementsAddDismiss() { + const client = useQueryClient(); + return useMutation( + [QueryKeys.System, QueryKeys.Announcements], + (param: { hash: string }) => { + const { hash } = param; + return api.system.addAnnouncementsDismiss(hash); + }, + { + onSuccess: (_, { hash }) => { + client.invalidateQueries([QueryKeys.System, QueryKeys.Announcements]); + client.invalidateQueries([QueryKeys.System, QueryKeys.Badges]); + }, + } + ); +} + export function useSystemTasks() { return useQuery( [QueryKeys.System, QueryKeys.Tasks], diff --git a/frontend/src/apis/queries/keys.ts b/frontend/src/apis/queries/keys.ts index a3b6e94a7..45f30f12e 100644 --- a/frontend/src/apis/queries/keys.ts +++ b/frontend/src/apis/queries/keys.ts @@ -13,6 +13,7 @@ export enum QueryKeys { Blacklist = "blacklist", Search = "search", Actions = "actions", + Announcements = "announcements", Tasks = "tasks", Backups = "backups", Logs = "logs", diff --git a/frontend/src/apis/raw/system.ts b/frontend/src/apis/raw/system.ts index c2f0382ef..1b64d6b24 100644 --- a/frontend/src/apis/raw/system.ts +++ b/frontend/src/apis/raw/system.ts @@ -87,6 +87,19 @@ class SystemApi extends BaseApi { await this.delete("/logs"); } + async announcements() { + const response = await this.get<DataWrapper<System.Announcements[]>>( + "/announcements" + ); + return response.data; + } + + async addAnnouncementsDismiss(hash: string) { + await this.post<DataWrapper<System.Announcements[]>>("/announcements", { + hash, + }); + } + async tasks() { const response = await this.get<DataWrapper<System.Task[]>>("/tasks"); return response.data; diff --git a/frontend/src/pages/System/Announcements/index.tsx b/frontend/src/pages/System/Announcements/index.tsx new file mode 100644 index 000000000..4e204431e --- /dev/null +++ b/frontend/src/pages/System/Announcements/index.tsx @@ -0,0 +1,24 @@ +import { useSystemAnnouncements } from "@/apis/hooks"; +import { QueryOverlay } from "@/components/async"; +import { Container } from "@mantine/core"; +import { useDocumentTitle } from "@mantine/hooks"; +import { FunctionComponent } from "react"; +import Table from "./table"; + +const SystemAnnouncementsView: FunctionComponent = () => { + const announcements = useSystemAnnouncements(); + + const { data } = announcements; + + useDocumentTitle("Announcements - Bazarr (System)"); + + return ( + <QueryOverlay result={announcements}> + <Container fluid px={0}> + <Table announcements={data ?? []}></Table> + </Container> + </QueryOverlay> + ); +}; + +export default SystemAnnouncementsView; diff --git a/frontend/src/pages/System/Announcements/table.tsx b/frontend/src/pages/System/Announcements/table.tsx new file mode 100644 index 000000000..97f8cbe3e --- /dev/null +++ b/frontend/src/pages/System/Announcements/table.tsx @@ -0,0 +1,91 @@ +import { useSystemAnnouncementsAddDismiss } from "@/apis/hooks"; +import { SimpleTable } from "@/components"; +import { MutateAction } from "@/components/async"; +import { useTableStyles } from "@/styles"; +import { faWindowClose } from "@fortawesome/free-solid-svg-icons"; +import { Anchor, Text } from "@mantine/core"; +import { FunctionComponent, useMemo } from "react"; +import { Column } from "react-table"; + +interface Props { + announcements: readonly System.Announcements[]; +} + +const Table: FunctionComponent<Props> = ({ announcements }) => { + const columns: Column<System.Announcements>[] = useMemo< + Column<System.Announcements>[] + >( + () => [ + { + Header: "Since", + accessor: "timestamp", + Cell: ({ value }) => { + const { classes } = useTableStyles(); + return <Text className={classes.primary}>{value}</Text>; + }, + }, + { + Header: "Announcement", + accessor: "text", + Cell: ({ value }) => { + const { classes } = useTableStyles(); + return <Text className={classes.primary}>{value}</Text>; + }, + }, + { + Header: "More info", + accessor: "link", + Cell: ({ value }) => { + if (value) { + return <Label link={value}>Link</Label>; + } else { + return <Text>n/a</Text>; + } + }, + }, + { + Header: "Dismiss", + accessor: "hash", + Cell: ({ row, value }) => { + const add = useSystemAnnouncementsAddDismiss(); + return ( + <MutateAction + label="Dismiss announcement" + disabled={!row.original.dismissible} + icon={faWindowClose} + mutation={add} + args={() => ({ + hash: value, + })} + ></MutateAction> + ); + }, + }, + ], + [] + ); + + return ( + <SimpleTable + columns={columns} + data={announcements} + tableStyles={{ emptyText: "No announcements for now, come back later!" }} + ></SimpleTable> + ); +}; + +export default Table; + +interface LabelProps { + link: string; + children: string; +} + +function Label(props: LabelProps): JSX.Element { + const { link, children } = props; + return ( + <Anchor href={link} target="_blank" rel="noopener noreferrer"> + {children} + </Anchor> + ); +} diff --git a/frontend/src/pages/System/system.test.tsx b/frontend/src/pages/System/system.test.tsx index f9c0b5dad..813654a7b 100644 --- a/frontend/src/pages/System/system.test.tsx +++ b/frontend/src/pages/System/system.test.tsx @@ -1,3 +1,4 @@ +import SystemAnnouncementsView from "@/pages/System/Announcements"; import { renderTest, RenderTestCase } from "@/tests/render"; import SystemBackupsView from "./Backups"; import SystemLogsView from "./Logs"; @@ -31,6 +32,10 @@ const cases: RenderTestCase[] = [ name: "tasks page", ui: SystemTasksView, }, + { + name: "announcements page", + ui: SystemAnnouncementsView, + }, ]; renderTest("System", cases); diff --git a/frontend/src/types/api.d.ts b/frontend/src/types/api.d.ts index ffd931d43..b19c682c0 100644 --- a/frontend/src/types/api.d.ts +++ b/frontend/src/types/api.d.ts @@ -5,6 +5,7 @@ interface Badge { status: number; sonarr_signalr: string; radarr_signalr: string; + announcements: number; } declare namespace Language { diff --git a/frontend/src/types/form.d.ts b/frontend/src/types/form.d.ts index 99b16da88..6019a3fa0 100644 --- a/frontend/src/types/form.d.ts +++ b/frontend/src/types/form.d.ts @@ -74,4 +74,8 @@ declare namespace FormType { subtitle: unknown; original_format: PythonBoolean; } + + interface AddAnnouncementsDismiss { + hash: number; + } } diff --git a/frontend/src/types/system.d.ts b/frontend/src/types/system.d.ts index dc4e33799..544d969ae 100644 --- a/frontend/src/types/system.d.ts +++ b/frontend/src/types/system.d.ts @@ -1,4 +1,12 @@ declare namespace System { + interface Announcements { + text: string; + link: string; + hash: string; + dismissible: boolean; + timestamp: string; + } + interface Task { interval: string; job_id: string; |