Add global stats to UI (not desktop yet)
This commit is contained in:
parent
ae606fac4a
commit
61b7a643aa
6 changed files with 89 additions and 11 deletions
|
|
@ -34,6 +34,21 @@ export interface Speed {
|
|||
human_readable: string;
|
||||
}
|
||||
|
||||
export interface AggregatePeerStats {
|
||||
queued: number;
|
||||
connecting: number;
|
||||
live: number;
|
||||
seen: number;
|
||||
dead: number;
|
||||
not_needed: number;
|
||||
}
|
||||
|
||||
export interface SessionStats {
|
||||
download_speed: Speed;
|
||||
upload_speed: Speed;
|
||||
peers: AggregatePeerStats;
|
||||
}
|
||||
|
||||
// Interface for the Torrent Stats API response
|
||||
export interface LiveTorrentStats {
|
||||
snapshot: {
|
||||
|
|
@ -46,14 +61,7 @@ export interface LiveTorrentStats {
|
|||
remaining_bytes: number;
|
||||
total_bytes: number;
|
||||
total_piece_download_ms: number;
|
||||
peer_stats: {
|
||||
queued: number;
|
||||
connecting: number;
|
||||
live: number;
|
||||
seen: number;
|
||||
dead: number;
|
||||
not_needed: number;
|
||||
};
|
||||
peer_stats: AggregatePeerStats;
|
||||
};
|
||||
average_piece_download_time: {
|
||||
secs: number;
|
||||
|
|
@ -182,4 +190,5 @@ export interface RqbitAPI {
|
|||
start: (index: number) => Promise<void>;
|
||||
forget: (index: number) => Promise<void>;
|
||||
delete: (index: number) => Promise<void>;
|
||||
stats: () => Promise<SessionStats>;
|
||||
}
|
||||
|
|
|
|||
12
crates/librqbit/webui/src/components/Footer.tsx
Normal file
12
crates/librqbit/webui/src/components/Footer.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useStatsStore } from "../stores/statsStore";
|
||||
import { Speed } from "./Speed";
|
||||
|
||||
export const Footer: React.FC<{}> = () => {
|
||||
let stats = useStatsStore((stats) => stats.stats);
|
||||
return (
|
||||
<div className="sticky bottom-0 bg-white/10 dark:text-gray-200 backdrop-blur text-nowrap text-xs font-medium text-gray-500 flex p-1 gap-x-3 justify-center">
|
||||
<div>↓ {stats.download_speed.human_readable}</div>
|
||||
<div>↑ {stats.upload_speed.human_readable}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -19,7 +19,9 @@ export const TorrentsList = (props: {
|
|||
<p className="text-center">No existing torrents found.</p>
|
||||
) : (
|
||||
props.torrents.map((t: TorrentId) => (
|
||||
<Torrent id={t.id} key={t.id} torrent={t} />
|
||||
<>
|
||||
<Torrent id={t.id} key={t.id} torrent={t} />
|
||||
</>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
ErrorDetails,
|
||||
ListTorrentsResponse,
|
||||
RqbitAPI,
|
||||
SessionStats,
|
||||
TorrentDetails,
|
||||
TorrentStats,
|
||||
} from "./api-types";
|
||||
|
|
@ -82,6 +83,9 @@ export const API: RqbitAPI & { getVersion: () => Promise<string> } = {
|
|||
getTorrentStats: (index: number): Promise<TorrentStats> => {
|
||||
return makeRequest("GET", `/torrents/${index}/stats/v1`);
|
||||
},
|
||||
stats: (): Promise<SessionStats> => {
|
||||
return makeRequest("GET", "/stats");
|
||||
},
|
||||
|
||||
uploadTorrent: (data, opts): Promise<AddTorrentResponse> => {
|
||||
let url = "/torrents?&overwrite=true";
|
||||
|
|
@ -152,6 +156,6 @@ export const API: RqbitAPI & { getVersion: () => Promise<string> } = {
|
|||
return url;
|
||||
},
|
||||
getPlaylistUrl: (index: number) => {
|
||||
return (apiUrl || window.origin) + `/torrents/${index}/playlist`;
|
||||
return (apiUrl || window.origin) + `/torrents/${index}/playlist`;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import { DarkMode } from "./helper/darkMode";
|
|||
import { useTorrentStore } from "./stores/torrentStore";
|
||||
import { useErrorStore } from "./stores/errorStore";
|
||||
import { AlertModal } from "./components/modal/AlertModal";
|
||||
import { useStatsStore } from "./stores/statsStore";
|
||||
import { Footer } from "./components/Footer";
|
||||
|
||||
export interface ErrorWithLabel {
|
||||
text: string;
|
||||
|
|
@ -49,6 +51,8 @@ export const RqbitWebUI = (props: {
|
|||
};
|
||||
setRefreshTorrents(refreshTorrents);
|
||||
|
||||
const setStats = useStatsStore((state) => state.setStats);
|
||||
|
||||
useEffect(() => {
|
||||
return customSetInterval(
|
||||
async () =>
|
||||
|
|
@ -67,8 +71,25 @@ export const RqbitWebUI = (props: {
|
|||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return customSetInterval(
|
||||
async () =>
|
||||
API.stats().then(
|
||||
(stats) => {
|
||||
setStats(stats);
|
||||
return 1000;
|
||||
},
|
||||
(e) => {
|
||||
console.error(e);
|
||||
return 5000;
|
||||
}
|
||||
),
|
||||
0
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="dark:bg-gray-900 dark:text-gray-200 min-h-screen">
|
||||
<div className="dark:bg-gray-900 dark:text-gray-200 min-h-screen flex flex-col">
|
||||
<Header title={props.title} version={props.version} />
|
||||
<div className="relative">
|
||||
{/* Menu buttons */}
|
||||
|
|
@ -82,10 +103,14 @@ export const RqbitWebUI = (props: {
|
|||
<BsMoon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grow">
|
||||
<RootContent />
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
<LogStreamModal show={logsOpened} onClose={() => setLogsOpened(false)} />
|
||||
<AlertModal />
|
||||
</div>
|
||||
|
|
|
|||
26
crates/librqbit/webui/src/stores/statsStore.ts
Normal file
26
crates/librqbit/webui/src/stores/statsStore.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { create } from "zustand";
|
||||
|
||||
import { SessionStats } from "../api-types";
|
||||
|
||||
export interface StatsStore {
|
||||
stats: SessionStats;
|
||||
setStats: (stats: SessionStats) => void;
|
||||
}
|
||||
|
||||
export const useStatsStore = create<StatsStore>((set) => ({
|
||||
stats: {
|
||||
download_speed: { human_readable: "N/A", mbps: 0 },
|
||||
upload_speed: { human_readable: "N/A", mbps: 0 },
|
||||
peers: {
|
||||
connecting: 0,
|
||||
dead: 0,
|
||||
live: 0,
|
||||
not_needed: 0,
|
||||
queued: 0,
|
||||
seen: 0,
|
||||
},
|
||||
},
|
||||
setStats: (stats) => {
|
||||
set({ stats });
|
||||
},
|
||||
}));
|
||||
Loading…
Add table
Add a link
Reference in a new issue