mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat: show new updates in dashboard (#690)
* feat: show new update banner * feat(http): add request logger * refactor: updates checker * feat: make update check optional * fix: empty releases * add toggle switch for update checks * feat: toggle updates check from settings * feat: toggle updates check from settings * feat: check on toggle enabled --------- Co-authored-by: soup <soup@r4tio.dev>
This commit is contained in:
parent
3fdd7cf5e4
commit
2917a7d42d
24 changed files with 687 additions and 121 deletions
|
@ -1,5 +1,6 @@
|
|||
import { baseUrl, sseBaseUrl } from "../utils";
|
||||
import { AuthContext } from "../utils/Context";
|
||||
import { GithubRelease } from "../types/Update";
|
||||
|
||||
interface ConfigType {
|
||||
body?: BodyInit | Record<string, unknown> | unknown;
|
||||
|
@ -80,7 +81,8 @@ export const APIClient = {
|
|||
delete: (key: string) => appClient.Delete(`api/keys/${key}`)
|
||||
},
|
||||
config: {
|
||||
get: () => appClient.Get<Config>("api/config")
|
||||
get: () => appClient.Get<Config>("api/config"),
|
||||
update: (config: ConfigUpdate) => appClient.Patch("api/config", config)
|
||||
},
|
||||
download_clients: {
|
||||
getAll: () => appClient.Get<DownloadClient[]>("api/download_clients"),
|
||||
|
@ -180,5 +182,9 @@ export const APIClient = {
|
|||
indexerOptions: () => appClient.Get<string[]>("api/release/indexers"),
|
||||
stats: () => appClient.Get<ReleaseStats>("api/release/stats"),
|
||||
delete: () => appClient.Delete("api/release/all")
|
||||
},
|
||||
updates: {
|
||||
check: () => appClient.Get("api/updates/check"),
|
||||
getLatestRelease: () => appClient.Get<GithubRelease|undefined>("api/updates/latest")
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,11 +2,13 @@ import { Fragment } from "react";
|
|||
import { Link, NavLink, Outlet } from "react-router-dom";
|
||||
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
||||
import { BookOpenIcon, UserIcon } from "@heroicons/react/24/solid";
|
||||
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { Bars3Icon, XMarkIcon, MegaphoneIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
import { AuthContext } from "../utils/Context";
|
||||
|
||||
import logo from "../logo.png";
|
||||
import { useQuery } from "react-query";
|
||||
import { APIClient } from "../api/APIClient";
|
||||
|
||||
interface NavItem {
|
||||
name: string;
|
||||
|
@ -27,6 +29,17 @@ export default function Base() {
|
|||
{ name: "Logs", path: "/logs" }
|
||||
];
|
||||
|
||||
|
||||
const { data } = useQuery(
|
||||
["updates"],
|
||||
() => APIClient.updates.getLatestRelease(),
|
||||
{
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
onError: err => console.log(err)
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Disclosure
|
||||
|
@ -185,6 +198,16 @@ export default function Base() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data && data.html_url && (
|
||||
<a href={data.html_url} target="_blank">
|
||||
<div className="flex mt-4 py-2 bg-blue-500 rounded justify-center">
|
||||
<MegaphoneIcon className="h-6 w-6 text-blue-100"/>
|
||||
<span className="text-blue-100 font-medium mx-3">New update available!</span>
|
||||
<span className="inline-flex items-center rounded-md bg-blue-100 px-2.5 py-0.5 text-sm font-medium text-blue-800">{data?.name}</span>
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Disclosure.Panel className="border-b border-gray-300 dark:border-gray-700 md:hidden">
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { useQuery } from "react-query";
|
||||
import { useMutation, useQuery } from "react-query";
|
||||
import { APIClient } from "../../api/APIClient";
|
||||
import { Checkbox } from "../../components/Checkbox";
|
||||
import { SettingsContext } from "../../utils/Context";
|
||||
import { GithubRelease } from "../../types/Update";
|
||||
import { toast } from "react-hot-toast";
|
||||
import Toast from "../../components/notifications/Toast";
|
||||
import { queryClient } from "../../App";
|
||||
|
||||
interface RowItemProps {
|
||||
label: string;
|
||||
value?: string;
|
||||
title?: string;
|
||||
newUpdate?: GithubRelease;
|
||||
}
|
||||
|
||||
const RowItem = ({ label, value, title }: RowItemProps) => {
|
||||
|
@ -23,6 +28,25 @@ const RowItem = ({ label, value, title }: RowItemProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const RowItemVersion = ({ label, value, title, newUpdate }: RowItemProps) => {
|
||||
if (!value)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
|
||||
<dt className="font-medium text-gray-500 dark:text-white" title={title}>{label}:</dt>
|
||||
<dd className="mt-1 text-gray-900 dark:text-white sm:mt-0 sm:col-span-2 break-all">
|
||||
{value}
|
||||
{newUpdate && newUpdate.html_url && (
|
||||
<span>
|
||||
<a href={newUpdate.html_url} target="_blank"><span className="ml-2 inline-flex items-center rounded-md bg-green-100 px-2.5 py-0.5 text-sm font-medium text-green-800">{newUpdate.name} available!</span></a>
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function ApplicationSettings() {
|
||||
const [settings, setSettings] = SettingsContext.use();
|
||||
|
||||
|
@ -36,6 +60,38 @@ function ApplicationSettings() {
|
|||
}
|
||||
);
|
||||
|
||||
const { data: updateData } = useQuery(
|
||||
["updates"],
|
||||
() => APIClient.updates.getLatestRelease(),
|
||||
{
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
onError: err => console.log(err)
|
||||
}
|
||||
);
|
||||
|
||||
const checkUpdateMutation = useMutation(
|
||||
() => APIClient.updates.check(),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(["updates"]);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const toggleCheckUpdateMutation = useMutation(
|
||||
(value: boolean) => APIClient.config.update({ check_for_updates: value }),
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.custom((t) => <Toast type="success" body={"Config successfully updated!"} t={t}/>);
|
||||
|
||||
queryClient.invalidateQueries(["config"]);
|
||||
|
||||
checkUpdateMutation.mutate();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="divide-y divide-gray-200 dark:divide-gray-700 lg:col-span-9">
|
||||
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
||||
|
@ -98,7 +154,7 @@ function ApplicationSettings() {
|
|||
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div className="px-4 py-5 sm:p-0">
|
||||
<dl className="sm:divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<RowItem label="Version" value={data?.version} />
|
||||
<RowItemVersion label="Version" value={data?.version} newUpdate={updateData ?? undefined} />
|
||||
<RowItem label="Commit" value={data?.commit} />
|
||||
<RowItem label="Build date" value={data?.date} />
|
||||
<RowItem label="Log path" value={data?.log_path} title="Set in config.toml" />
|
||||
|
@ -117,6 +173,16 @@ function ApplicationSettings() {
|
|||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-4 sm:px-6 py-1">
|
||||
<Checkbox
|
||||
label="Check for updates"
|
||||
description="Get notified of new updates."
|
||||
value={data?.check_for_updates ?? true}
|
||||
setValue={(newValue: boolean) => {
|
||||
toggleCheckUpdateMutation.mutate(newValue);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-4 sm:px-6 py-1">
|
||||
<Checkbox
|
||||
label="Dark theme"
|
||||
|
|
20
web/src/types/Config.d.ts
vendored
Normal file
20
web/src/types/Config.d.ts
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
interface Config {
|
||||
host: string;
|
||||
port: number;
|
||||
log_level: string;
|
||||
log_path: string;
|
||||
base_url: string;
|
||||
check_for_updates: boolean;
|
||||
version: string;
|
||||
commit: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface ConfigUpdate {
|
||||
host?: string;
|
||||
port?: number;
|
||||
log_level?: string;
|
||||
log_path?: string;
|
||||
base_url?: string;
|
||||
check_for_updates: boolean;
|
||||
}
|
11
web/src/types/Irc.d.ts
vendored
11
web/src/types/Irc.d.ts
vendored
|
@ -67,14 +67,3 @@ interface IrcAuth {
|
|||
account?: string; // optional
|
||||
password?: string; // optional
|
||||
}
|
||||
|
||||
interface Config {
|
||||
host: string;
|
||||
port: number;
|
||||
log_level: string;
|
||||
log_path: string;
|
||||
base_url: string;
|
||||
version: string;
|
||||
commit: string;
|
||||
date: string;
|
||||
}
|
46
web/src/types/Update.d.ts
vendored
Normal file
46
web/src/types/Update.d.ts
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
interface UpdateAvailableResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface GithubRelease {
|
||||
id: number;
|
||||
node_id: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
tag_name: string;
|
||||
target_commitish: string;
|
||||
name: string;
|
||||
body: string;
|
||||
created_at: Date;
|
||||
published_at: Date;
|
||||
author: GithubAuthor;
|
||||
assets: GitHubReleaseAsset[];
|
||||
}
|
||||
|
||||
export interface GitHubReleaseAsset {
|
||||
url: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
name: string;
|
||||
label: string;
|
||||
uploader: GithubAuthor;
|
||||
content_type: string;
|
||||
state: string;
|
||||
size: number;
|
||||
download_count: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
browser_download_url: string;
|
||||
}
|
||||
|
||||
export interface GithubAuthor {
|
||||
login: string;
|
||||
id: number;
|
||||
node_id: string;
|
||||
avatar_url: string;
|
||||
gravatar_id: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
type: string;
|
||||
}
|
|
@ -45,6 +45,7 @@ export const AuthContext = newRidgeState<AuthInfo>(
|
|||
|
||||
interface SettingsType {
|
||||
debug: boolean;
|
||||
checkForUpdates: boolean;
|
||||
darkTheme: boolean;
|
||||
scrollOnNewLog: boolean;
|
||||
indentLogLines: boolean;
|
||||
|
@ -54,6 +55,7 @@ interface SettingsType {
|
|||
export const SettingsContext = newRidgeState<SettingsType>(
|
||||
{
|
||||
debug: false,
|
||||
checkForUpdates: true,
|
||||
darkTheme: true,
|
||||
scrollOnNewLog: false,
|
||||
indentLogLines: false,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue