{
- triblerService.getTorrentHealth(torrent.infohash)
setChecking(true);
+ triblerService.getTorrentHealth(torrent.infohash).then((response) => {
+ if (response === undefined){
+ setChecking(false);
+ toast.error(`${t("ToastErrorDownloadCheck")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)) {
+ setChecking(false);
+ toast.error(`${t("ToastErrorDownloadCheck")} ${response.error}`);
+ }
+ });
}}
>
{checking ?
diff --git a/src/tribler/ui/src/dialogs/CreateTorrent.tsx b/src/tribler/ui/src/dialogs/CreateTorrent.tsx
index 9d38ba94b1..32a0429035 100644
--- a/src/tribler/ui/src/dialogs/CreateTorrent.tsx
+++ b/src/tribler/ui/src/dialogs/CreateTorrent.tsx
@@ -1,5 +1,6 @@
import SimpleTable from "@/components/ui/simple-table";
import { useEffect, useState } from "react";
+import toast from 'react-hot-toast';
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { DialogProps } from "@radix-ui/react-dialog";
@@ -11,6 +12,7 @@ import { ColumnDef } from "@tanstack/react-table";
import { Textarea } from "@/components/ui/textarea";
import { useTranslation } from "react-i18next";
import { triblerService } from "@/services/tribler.service";
+import { ErrorDict, isErrorDict } from "@/services/reporting";
import SelectRemotePath from "./SelectRemotePath";
import { PathInput } from "@/components/path-input";
import { Settings } from "@/models/settings.model";
@@ -52,15 +54,27 @@ export default function CreateTorrent(props: JSX.IntrinsicAttributes & DialogPro
async function resetPath() {
const settings = await triblerService.getSettings();
- setDestination(settings.libtorrent.download_defaults.saveas);
+ if (settings === undefined){
+ toast.error(`${t("ToastErrorDefaultDLDir")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(settings)) {
+ toast.error(`${t("ToastErrorDefaultDLDir")} ${settings.error}`);
+ } else {
+ setDestination(settings.libtorrent.download_defaults.saveas);
+ }
}
async function addDir(dirname: string) {
const response = await triblerService.listFiles(dirname, true);
- setFiles([
- ...files,
- ...response.paths.filter((item) => !item.dir)
- ]);
+ if (response === undefined){
+ toast.error(`${t("ToastErrorDirectoryAdd")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)) {
+ toast.error(`${t("ToastErrorDirectoryAdd")} ${response.error}`);
+ } else {
+ setFiles([
+ ...files,
+ ...response.paths.filter((item) => !item.dir)
+ ]);
+ }
}
async function addFile(filename: string) {
@@ -160,7 +174,16 @@ export default function CreateTorrent(props: JSX.IntrinsicAttributes & DialogPro
variant="outline"
type="submit"
onClick={() => {
- triblerService.createTorrent(name, description, files.map((f) => f.path), destination, seed);
+ triblerService.createTorrent(name, description, files.map((f) => f.path), destination, seed).then(
+ (response) => {
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorCreateTorrent", {name: name})} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)) {
+ // Quinten: according to the typing, response could not be a ErrorDict here?!
+ toast.error(`${t("ToastErrorCreateTorrent", {name: name})} ${(response as ErrorDict).error}`);
+ }
+ }
+ );
if (props.onOpenChange)
props.onOpenChange(false);
}}
diff --git a/src/tribler/ui/src/dialogs/SaveAs.tsx b/src/tribler/ui/src/dialogs/SaveAs.tsx
index ad7bba55d2..31aa460b16 100644
--- a/src/tribler/ui/src/dialogs/SaveAs.tsx
+++ b/src/tribler/ui/src/dialogs/SaveAs.tsx
@@ -1,6 +1,8 @@
import SimpleTable from "@/components/ui/simple-table";
import { useEffect, useState } from "react";
+import toast from 'react-hot-toast';
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { formatBytes, getFilesFromMetainfo, getRowSelection, translateHeader } from "@/lib/utils";
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
@@ -12,9 +14,19 @@ import { ColumnDef } from "@tanstack/react-table";
import { useNavigate } from "react-router-dom";
import { Settings } from "@/models/settings.model";
import { useTranslation } from "react-i18next";
+import { TFunction } from 'i18next';
import { PathInput } from "@/components/path-input";
+function startDownloadCallback(response: any, t: TFunction) {
+ // We have to receive a translation function. Otherwise, we violate React's hook scoping.
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadStart")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadStart")} ${response.error}`);
+ }
+}
+
const fileColumns: ColumnDef
[] = [
{
id: "select",
@@ -95,6 +107,13 @@ export default function SaveAs(props: SaveAsProps & JSX.IntrinsicAttributes & Di
setExists(false);
setFiles([])
const newSettings = await triblerService.getSettings();
+ if (newSettings === undefined) {
+ setError(`${t("ToastErrorGetSettings")} ${t("ToastErrorGenNetworkErr")}`);
+ return;
+ } else if (isErrorDict(newSettings)){
+ setError(`${t("ToastErrorGetSettings")} ${newSettings.error}`);
+ return;
+ }
const safeSeeding = !!newSettings?.libtorrent?.download_defaults?.safeseeding_enabled;
const safeDownloading = !!newSettings?.libtorrent?.download_defaults?.anonymity_enabled;
setSettings(newSettings);
@@ -114,8 +133,10 @@ export default function SaveAs(props: SaveAsProps & JSX.IntrinsicAttributes & Di
response = await triblerService.getMetainfo(uri);
}
- if (response?.error) {
- setError(response.error);
+ if (response === undefined) {
+ setError(`${t("ToastErrorGetMetainfo")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)) {
+ setError(`t("ToastErrorGetMetainfo")} ${response.error}`);
} else if (response) {
setFiles(getFilesFromMetainfo(response.metainfo));
setExists(!!response.download_exists);
@@ -145,10 +166,10 @@ export default function SaveAs(props: SaveAsProps & JSX.IntrinsicAttributes & Di
if (!settings) return;
if (torrent) {
- triblerService.startDownloadFromFile(torrent, params);
+ triblerService.startDownloadFromFile(torrent, params).then((response) => {startDownloadCallback(response, t)});
}
else if (uri) {
- triblerService.startDownload(uri, params);
+ triblerService.startDownload(uri, params).then((response) => {startDownloadCallback(response, t)});
}
if (props.onOpenChange) {
diff --git a/src/tribler/ui/src/dialogs/SelectRemotePath.tsx b/src/tribler/ui/src/dialogs/SelectRemotePath.tsx
index f760cf002d..3fb39aca85 100644
--- a/src/tribler/ui/src/dialogs/SelectRemotePath.tsx
+++ b/src/tribler/ui/src/dialogs/SelectRemotePath.tsx
@@ -1,5 +1,7 @@
import { useEffect, useState } from "react";
+import toast from 'react-hot-toast';
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { DialogProps } from "@radix-ui/react-dialog";
@@ -37,9 +39,15 @@ export default function SelectRemotePath(props: SelectRemotePathProps & JSX.Intr
async function reloadPaths(dir: string) {
const response = await triblerService.browseFiles(dir, showFiles || false);
- setPaths(response.paths);
- setCurrentPath(response.current);
- setLastClicked((selectDir) ? { name: '', path: response.current, dir: true } : undefined);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorBrowseFiles")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorBrowseFiles")} ${response.error}`);
+ } else {
+ setPaths(response.paths);
+ setCurrentPath(response.current);
+ setLastClicked((selectDir) ? { name: '', path: response.current, dir: true } : undefined);
+ }
}
return (
diff --git a/src/tribler/ui/src/models/bucket.model.tsx b/src/tribler/ui/src/models/bucket.model.tsx
index 274d97fefd..184a38cbea 100644
--- a/src/tribler/ui/src/models/bucket.model.tsx
+++ b/src/tribler/ui/src/models/bucket.model.tsx
@@ -18,3 +18,34 @@ export interface Bucket {
endpoint: endpoint;
peers: DHTPeer[];
}
+
+export interface Endpoint {
+ endpoint: string;
+ node_id: string;
+ routing_table_size: number;
+ routing_table_buckets: number;
+ num_keys_in_store: number;
+}
+
+export interface DHTStats {
+ peer_id: string;
+ num_tokens: number;
+ endpoints: Endpoint[];
+ num_peers_in_store?: Map;
+ num_store_for_me?: Map;
+}
+
+export interface Values {
+ values: {
+ public_key: string | null;
+ key: string;
+ value: string;
+ }[];
+ debug: {
+ requests: number;
+ responses: number;
+ responses_with_nodes: number;
+ responses_with_values: number;
+ time: number;
+ };
+}
\ No newline at end of file
diff --git a/src/tribler/ui/src/models/drift.model.tsx b/src/tribler/ui/src/models/drift.model.tsx
new file mode 100644
index 0000000000..3c0c0e07c2
--- /dev/null
+++ b/src/tribler/ui/src/models/drift.model.tsx
@@ -0,0 +1,4 @@
+export interface Drift {
+ timestamp: number;
+ drift: number;
+}
diff --git a/src/tribler/ui/src/models/metainfo.tsx b/src/tribler/ui/src/models/metainfo.tsx
index b5103052da..3f757cfd47 100644
--- a/src/tribler/ui/src/models/metainfo.tsx
+++ b/src/tribler/ui/src/models/metainfo.tsx
@@ -7,7 +7,7 @@ export interface Metainfo {
}
}
-interface MetainfoFile {
+export interface MetainfoFile {
length: number;
path: string[];
diff --git a/src/tribler/ui/src/models/task.model.tsx b/src/tribler/ui/src/models/task.model.tsx
new file mode 100644
index 0000000000..e4ba2777f8
--- /dev/null
+++ b/src/tribler/ui/src/models/task.model.tsx
@@ -0,0 +1,8 @@
+export interface Task {
+ name: string;
+ running: boolean;
+ stack: string[];
+ taskmanager?: string;
+ start_time?: number;
+ interval?: number;
+}
diff --git a/src/tribler/ui/src/pages/Debug/Asyncio/Health.tsx b/src/tribler/ui/src/pages/Debug/Asyncio/Health.tsx
index 8297ef3ba9..7ec2056c3d 100644
--- a/src/tribler/ui/src/pages/Debug/Asyncio/Health.tsx
+++ b/src/tribler/ui/src/pages/Debug/Asyncio/Health.tsx
@@ -1,31 +1,48 @@
import { useEffect, useRef, useState } from 'react';
+import toast from 'react-hot-toast';
import { ipv8Service } from '@/services/ipv8.service';
+import { isErrorDict } from "@/services/reporting";
+import { Drift } from "@/models/drift.model";
import { average, median } from '@/lib/utils';
import { useInterval } from '@/hooks/useInterval';
+import { useTranslation } from "react-i18next";
-interface Drift {
- timestamp: number;
- drift: number;
-}
-
export default function Health() {
const [drifts, setDrifts] = useState([]);
- const ref = useRef(null)
+ const ref = useRef(null);
+ const { t } = useTranslation();
useInterval(async () => {
let measurements = await ipv8Service.getDrift();
- const now = new Date().getTime() / 1000;
- setDrifts(measurements.filter((drift: Drift) => drift.timestamp > now - 11.0));
+ if (!(measurements === undefined) && !isErrorDict(measurements)) {
+ // We ignore errors and correct with the missing information on the next call
+ const now = new Date().getTime() / 1000;
+ setDrifts(measurements.filter((drift: Drift) => drift.timestamp > now - 11.0));
- updateCanvas();
+ updateCanvas();
+ }
}, 250, true);
useEffect(() => {
- (async () => { await ipv8Service.enableDrift(true) })();
+ (async () => {
+ const response = await ipv8Service.enableDrift(true);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorEnableHealth")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorEnableHealth")} ${response.error}`);
+ }
+ })();
return () => {
- (async () => { await ipv8Service.enableDrift(false) })();
+ (async () => {
+ const response = await ipv8Service.enableDrift(false);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDisableHealth")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDisableHealth")} ${response.error}`);
+ }
+ })();
}
}, []);
diff --git a/src/tribler/ui/src/pages/Debug/Asyncio/SlowTasks.tsx b/src/tribler/ui/src/pages/Debug/Asyncio/SlowTasks.tsx
index def8b57d0b..64276d8acc 100644
--- a/src/tribler/ui/src/pages/Debug/Asyncio/SlowTasks.tsx
+++ b/src/tribler/ui/src/pages/Debug/Asyncio/SlowTasks.tsx
@@ -1,5 +1,8 @@
import { useEffect, useState } from "react";
+import toast from 'react-hot-toast';
+import { useTranslation } from "react-i18next";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -9,17 +12,36 @@ import { ScrollArea } from "@/components/ui/scroll-area";
export default function SlowTasks() {
+ const { t } = useTranslation();
const [debug, setDebug] = useState<{ messages?: { message: string }[] }>({})
const [slownessThreshold, setSlownessThreshold] = useState(0.1)
useInterval(async () => {
- setDebug(await ipv8Service.getAsyncioDebug());
+ const response = await ipv8Service.getAsyncioDebug();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setDebug(response);
+ }
}, 5000, true);
useEffect(() => {
- (async () => { await ipv8Service.setAsyncioDebug(true, slownessThreshold) })();
+ (async () => {
+ const response = await ipv8Service.setAsyncioDebug(true, slownessThreshold);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorEnableAsyncio")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorEnableAsyncio")} ${response.error}`);
+ }
+ })();
return () => {
- (async () => { await ipv8Service.setAsyncioDebug(false, slownessThreshold) })();
+ (async () => {
+ const response = await ipv8Service.setAsyncioDebug(false, slownessThreshold);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDisableAsyncio")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDisableAsyncio")} ${response.error}`);
+ }
+ })();
}
}, []);
@@ -40,7 +62,15 @@ export default function SlowTasks() {
diff --git a/src/tribler/ui/src/pages/Debug/Asyncio/Tasks.tsx b/src/tribler/ui/src/pages/Debug/Asyncio/Tasks.tsx
index b16763e4eb..06aa34d93e 100644
--- a/src/tribler/ui/src/pages/Debug/Asyncio/Tasks.tsx
+++ b/src/tribler/ui/src/pages/Debug/Asyncio/Tasks.tsx
@@ -2,18 +2,12 @@ import SimpleTable from "@/components/ui/simple-table";
import { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
+import { Task } from "@/models/task.model";
import { useInterval } from '@/hooks/useInterval';
import { formatTimeDiff } from "@/lib/utils";
-interface Task {
- taskmanager: string;
- name: string;
- running: boolean;
- interval: number;
- start_time: number;
-}
-
const taskColumns: ColumnDef[] = [
{
accessorKey: "taskmanager",
@@ -47,7 +41,11 @@ export default function Tasks() {
const [tasks, setTasks] = useState([])
useInterval(async () => {
- setTasks((await ipv8Service.getTasks()));
+ const response = await ipv8Service.getTasks();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setTasks(response);
+ }
}, 5000, true);
return (
diff --git a/src/tribler/ui/src/pages/Debug/DHT/Buckets.tsx b/src/tribler/ui/src/pages/Debug/DHT/Buckets.tsx
index 974d5cf2b5..099f828428 100644
--- a/src/tribler/ui/src/pages/Debug/DHT/Buckets.tsx
+++ b/src/tribler/ui/src/pages/Debug/DHT/Buckets.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Bucket } from "@/models/bucket.model";
import { ColumnDef } from "@tanstack/react-table";
import { formatTimeDiff } from "@/lib/utils";
@@ -32,7 +33,11 @@ export default function Buckets() {
const [buckets, setBuckets] = useState([])
useInterval(async () => {
- setBuckets((await ipv8Service.getBuckets()));
+ const response = await ipv8Service.getBuckets();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setBuckets(response);
+ }
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Debug/DHT/Statistics.tsx b/src/tribler/ui/src/pages/Debug/DHT/Statistics.tsx
index d6ed5c2713..2ca4928472 100644
--- a/src/tribler/ui/src/pages/Debug/DHT/Statistics.tsx
+++ b/src/tribler/ui/src/pages/Debug/DHT/Statistics.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { KeyValue } from "@/models/keyvalue.model";
import { ColumnDef } from "@tanstack/react-table";
import { useInterval } from '@/hooks/useInterval';
@@ -25,10 +26,14 @@ export default function Statistics() {
useInterval(async () => {
let stats: KeyValue[] = [];
- for (const [key, value] of Object.entries(await ipv8Service.getDHTStatistics())) {
- stats.push({ key: key, value: (typeof value !== 'string') ? JSON.stringify(value, null, 4) : value });
+ const response = await ipv8Service.getDHTStatistics();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ for (const [key, value] of Object.entries(response)) {
+ stats.push({ key: key, value: (typeof value !== 'string') ? JSON.stringify(value, null, 4) : value });
+ }
+ setStatistics(stats);
}
- setStatistics(stats);
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Debug/General/index.tsx b/src/tribler/ui/src/pages/Debug/General/index.tsx
index b535d12dbe..277f5329b0 100644
--- a/src/tribler/ui/src/pages/Debug/General/index.tsx
+++ b/src/tribler/ui/src/pages/Debug/General/index.tsx
@@ -2,6 +2,7 @@ import SimpleTable from "@/components/ui/simple-table";
import { formatBytes } from "@/lib/utils";
import { KeyValue } from "@/models/keyvalue.model";
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
import { useInterval } from '@/hooks/useInterval';
@@ -22,14 +23,37 @@ export default function General() {
const [stats, setStats] = useState([])
useInterval(async () => {
- const ipv8Stats = await triblerService.getIPv8Statistics();
+ const newStats = new Array();
+
const triblerStats = await triblerService.getTriblerStatistics();
- setStats([
- { key: 'Database size', value: formatBytes(triblerStats.db_size) },
- { key: 'Number of torrents collected', value: triblerStats.num_torrents },
- { key: 'Total IPv8 bytes up', value: formatBytes(ipv8Stats.total_up) },
- { key: 'Total IPv8 bytes down', value: formatBytes(ipv8Stats.total_down) }
- ]);
+ if (triblerStats === undefined || isErrorDict(triblerStats)){
+ if (stats) {
+ newStats.push({ key: 'Database size', value: stats.filter((entry) => entry.key == 'Database size')[0].value });
+ newStats.push({ key: 'Number of torrents collected', value: stats.filter((entry) => entry.key == 'Number of torrents collected')[0].value });
+ } else {
+ newStats.push({ key: 'Database size', value: '?' });
+ newStats.push({ key: 'Number of torrents collected', value: '?' });
+ }
+ } else {
+ newStats.push({ key: 'Database size', value: formatBytes(triblerStats.db_size) });
+ newStats.push({ key: 'Number of torrents collected', value: "" + triblerStats.num_torrents });
+ }
+
+ const ipv8Stats = await triblerService.getIPv8Statistics();
+ if (ipv8Stats === undefined || isErrorDict(ipv8Stats)){
+ if (stats) {
+ newStats.push({ key: 'Total IPv8 bytes up', value: stats.filter((entry) => entry.key == 'Total IPv8 bytes up')[0].value });
+ newStats.push({ key: 'Total IPv8 bytes down', value: stats.filter((entry) => entry.key == 'Total IPv8 bytes down')[0].value });
+ } else {
+ newStats.push({ key: 'Total IPv8 bytes up', value: '?' });
+ newStats.push({ key: 'Total IPv8 bytes down', value: '?' });
+ }
+ } else {
+ newStats.push({ key: 'Total IPv8 bytes up', value: formatBytes(ipv8Stats.total_up) });
+ newStats.push({ key: 'Total IPv8 bytes down', value: formatBytes(ipv8Stats.total_down) });
+ }
+
+ setStats(newStats);
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Debug/IPv8/Details.tsx b/src/tribler/ui/src/pages/Debug/IPv8/Details.tsx
index 32ae94423f..7c7aa500c8 100644
--- a/src/tribler/ui/src/pages/Debug/IPv8/Details.tsx
+++ b/src/tribler/ui/src/pages/Debug/IPv8/Details.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { OverlayMsgStats } from "@/models/overlay.model";
import { ColumnDef } from "@tanstack/react-table";
import { useInterval } from '@/hooks/useInterval';
@@ -50,28 +51,32 @@ export default function Details() {
useInterval(async () => {
let stats: OverlayMsgStats[] = [];
- for (var overlayStats of (await ipv8Service.getOverlayStatistics())) {
- for (const [communityName, communityStats] of Object.entries(overlayStats)) {
- if (Object.entries(communityStats).length === 0) { break }
- stats.push({
- name: communityName,
- identifier: -1,
- num_up: 0,
- num_down: 0,
- bytes_up: 0,
- bytes_down: 0,
- first_measured_up: 0,
- first_measured_down: 0,
- last_measured_up: 0,
- last_measured_down: 0,
- });
- for (const [msgName, msgStats] of Object.entries(communityStats)) {
- msgStats.name = msgName;
- stats.push(msgStats);
+ const response = await ipv8Service.getOverlayStatistics();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ for (var overlayStats of response) {
+ for (const [communityName, communityStats] of Object.entries(overlayStats)) {
+ if (Object.entries(communityStats).length === 0) { break }
+ stats.push({
+ name: communityName,
+ identifier: -1,
+ num_up: 0,
+ num_down: 0,
+ bytes_up: 0,
+ bytes_down: 0,
+ first_measured_up: 0,
+ first_measured_down: 0,
+ last_measured_up: 0,
+ last_measured_down: 0,
+ });
+ for (const [msgName, msgStats] of Object.entries(communityStats)) {
+ msgStats.name = msgName;
+ stats.push(msgStats);
+ }
}
}
+ setStatistics(stats);
}
- setStatistics(stats);
}, 5000, true);
if (statistics.length === 0) {
diff --git a/src/tribler/ui/src/pages/Debug/IPv8/Overlays.tsx b/src/tribler/ui/src/pages/Debug/IPv8/Overlays.tsx
index 3d72fc525b..1b85992cec 100644
--- a/src/tribler/ui/src/pages/Debug/IPv8/Overlays.tsx
+++ b/src/tribler/ui/src/pages/Debug/IPv8/Overlays.tsx
@@ -2,6 +2,7 @@ import SimpleTable from "@/components/ui/simple-table";
import { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Overlay, Peer } from "@/models/overlay.model";
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import { useInterval } from '@/hooks/useInterval';
@@ -104,7 +105,11 @@ export default function Overlays() {
const [selectedOverlay, setSelectedOverlay] = useState()
useInterval(async () => {
- setOverlays((await ipv8Service.getOverlays()));
+ const response = await ipv8Service.getOverlays();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setOverlays(response);
+ }
}, 5000, true);
// We're not getting resize event for elements within ResizeablePanel, so we track the ResizablePanel itself.
diff --git a/src/tribler/ui/src/pages/Debug/Libtorrent/index.tsx b/src/tribler/ui/src/pages/Debug/Libtorrent/index.tsx
index 06b014bc85..4fedf847e7 100644
--- a/src/tribler/ui/src/pages/Debug/Libtorrent/index.tsx
+++ b/src/tribler/ui/src/pages/Debug/Libtorrent/index.tsx
@@ -3,6 +3,7 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrig
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { KeyValue } from "@/models/keyvalue.model";
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
import { useInterval } from '@/hooks/useInterval';
@@ -25,17 +26,25 @@ export default function Libtorrent() {
const [session, setSession] = useState([])
useInterval(async () => {
- let settings = [];
- for (const [key, value] of Object.entries(await triblerService.getLibtorrentSettings(hops))) {
- settings.push({ key: key, value: (typeof value !== 'string') ? JSON.stringify(value) : value });
+ const libtorrentSettings = await triblerService.getLibtorrentSettings(hops);
+ if (!(libtorrentSettings === undefined) && !isErrorDict(libtorrentSettings)) {
+ // Don't bother the user on error, just try again later.
+ let settings = [];
+ for (const [key, value] of Object.entries(libtorrentSettings)) {
+ settings.push({ key: key, value: (typeof value !== 'string') ? JSON.stringify(value) : value });
+ }
+ setSettings(settings)
}
- setSettings(settings)
- let session = [];
- for (const [key, value] of Object.entries(await triblerService.getLibtorrentSession(hops))) {
- session.push({ key: key, value: (typeof value !== 'string') ? JSON.stringify(value) : value });
+ const libtorrentSession = await triblerService.getLibtorrentSession(hops);
+ if (!(libtorrentSession === undefined) && !isErrorDict(libtorrentSession)) {
+ // Don't bother the user on error, just try again later.
+ let session = [];
+ for (const [key, value] of Object.entries(libtorrentSession)) {
+ session.push({ key: key, value: (typeof value !== 'string') ? JSON.stringify(value) : value });
+ }
+ setSession(session)
}
- setSession(session)
}, 5000, true);
diff --git a/src/tribler/ui/src/pages/Debug/Tunnels/Circuits.tsx b/src/tribler/ui/src/pages/Debug/Tunnels/Circuits.tsx
index baf8b6d2b5..0e136f51c4 100644
--- a/src/tribler/ui/src/pages/Debug/Tunnels/Circuits.tsx
+++ b/src/tribler/ui/src/pages/Debug/Tunnels/Circuits.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Circuit } from "@/models/circuit.model";
import { ColumnDef } from "@tanstack/react-table";
import { formatBytes, formatFlags, formatTimeDiff } from "@/lib/utils";
@@ -61,7 +62,11 @@ export default function Circuits() {
const [circuits, setCircuits] = useState([])
useInterval(async () => {
- setCircuits((await ipv8Service.getCircuits()));
+ const response = await ipv8Service.getCircuits();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setCircuits(response);
+ }
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Debug/Tunnels/Exits.tsx b/src/tribler/ui/src/pages/Debug/Tunnels/Exits.tsx
index f610678ab7..e67d0d0f4e 100644
--- a/src/tribler/ui/src/pages/Debug/Tunnels/Exits.tsx
+++ b/src/tribler/ui/src/pages/Debug/Tunnels/Exits.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Exit } from "@/models/exit.model";
import { ColumnDef } from "@tanstack/react-table";
import { formatBytes, formatTimeDiff } from "@/lib/utils";
@@ -43,7 +44,11 @@ export default function Exits() {
const [exits, setExits] = useState([])
useInterval(async () => {
- setExits((await ipv8Service.getExits()));
+ const response = await ipv8Service.getExits();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setExits(response);
+ }
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Debug/Tunnels/Peers.tsx b/src/tribler/ui/src/pages/Debug/Tunnels/Peers.tsx
index 159654501e..643876a916 100644
--- a/src/tribler/ui/src/pages/Debug/Tunnels/Peers.tsx
+++ b/src/tribler/ui/src/pages/Debug/Tunnels/Peers.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Peer } from "@/models/tunnelpeer.model";
import { ColumnDef } from "@tanstack/react-table";
import { formatFlags } from "@/lib/utils";
@@ -37,7 +38,11 @@ export default function Peers() {
const [peers, setPeers] = useState([])
useInterval(async () => {
- setPeers((await ipv8Service.getTunnelPeers()));
+ const response = await await ipv8Service.getTunnelPeers();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setPeers(response);
+ }
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Debug/Tunnels/Relays.tsx b/src/tribler/ui/src/pages/Debug/Tunnels/Relays.tsx
index 2f13305f91..1c23af349e 100644
--- a/src/tribler/ui/src/pages/Debug/Tunnels/Relays.tsx
+++ b/src/tribler/ui/src/pages/Debug/Tunnels/Relays.tsx
@@ -2,6 +2,7 @@ import SimpleTable from "@/components/ui/simple-table";
import { ColumnDef } from "@tanstack/react-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Relay } from "@/models/relay.model";
import { formatBytes, formatTimeDiff } from "@/lib/utils";
import { useInterval } from '@/hooks/useInterval';
@@ -47,7 +48,11 @@ export default function Relays() {
const [relays, setRelays] = useState([])
useInterval(async () => {
- setRelays((await ipv8Service.getRelays()));
+ const response = await ipv8Service.getRelays();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setRelays(response);
+ }
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Debug/Tunnels/Swarms.tsx b/src/tribler/ui/src/pages/Debug/Tunnels/Swarms.tsx
index 0da6192866..c276e50928 100644
--- a/src/tribler/ui/src/pages/Debug/Tunnels/Swarms.tsx
+++ b/src/tribler/ui/src/pages/Debug/Tunnels/Swarms.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useState } from "react";
import { ipv8Service } from "@/services/ipv8.service";
+import { isErrorDict } from "@/services/reporting";
import { Swarm } from "@/models/swarm.model";
import { ColumnDef } from "@tanstack/react-table";
import { formatBytes, formatTimeDiff } from "@/lib/utils";
@@ -55,7 +56,11 @@ export default function Swarms() {
const [swarms, setSwarms] = useState([])
useInterval(async () => {
- setSwarms((await ipv8Service.getSwarms()));
+ const response = await ipv8Service.getSwarms();
+ if (!(response === undefined) && !isErrorDict(response)) {
+ // We ignore errors and correct with the missing information on the next call
+ setSwarms(response);
+ }
}, 5000, true);
return
diff --git a/src/tribler/ui/src/pages/Downloads/Actions.tsx b/src/tribler/ui/src/pages/Downloads/Actions.tsx
index c2be556233..e9c8c1861b 100644
--- a/src/tribler/ui/src/pages/Downloads/Actions.tsx
+++ b/src/tribler/ui/src/pages/Downloads/Actions.tsx
@@ -1,6 +1,7 @@
import { Download } from "@/models/download.model";
import { triblerService } from "@/services/tribler.service";
-
+import { isErrorDict } from "@/services/reporting";
+import toast from 'react-hot-toast';
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@@ -31,23 +32,51 @@ export default function Actions({ selectedDownloads }: { selectedDownloads: Down
const onPlay = () => {
selectedDownloads.forEach((download) => {
- (async () => { await triblerService.resumeDownload(download.infohash) })();
+ (async () => {
+ const response = await triblerService.resumeDownload(download.infohash);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadPlay")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadPlay")} ${response.error}`);
+ }
+ })();
});
}
const onPause = () => {
selectedDownloads.forEach((download) => {
- (async () => { await triblerService.stopDownload(download.infohash) })();
+ (async () => {
+ const response = await triblerService.stopDownload(download.infohash);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadStop")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadStop")} ${response.error}`);
+ }
+ })();
});
}
const onRemove = (removeData: boolean) => {
selectedDownloads.forEach((download) => {
- (async () => { await triblerService.removeDownload(download.infohash, removeData) })();
+ (async () => {
+ const response = await triblerService.removeDownload(download.infohash, removeData);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadRemove")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadRemove")} ${response.error}`);
+ }
+ })();
});
setRemoveDialogOpen(false);
}
const onRecheck = () => {
selectedDownloads.forEach((download) => {
- (async () => { await triblerService.recheckDownload(download.infohash) })();
+ (async () => {
+ const response = await triblerService.recheckDownload(download.infohash);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadCheck")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadCheck")} ${response.error}`);
+ }
+ })();
});
}
const onExportTorrent = () => {
@@ -64,12 +93,25 @@ export default function Actions({ selectedDownloads }: { selectedDownloads: Down
}
}
const onMoveDownloadConfirmed = () => {
- triblerService.moveDownload(selectedDownloads[0].infohash, storageLocation);
+ triblerService.moveDownload(selectedDownloads[0].infohash, storageLocation).then(async (response) => {
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadMove")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadMove")} ${response.error}`);
+ }
+ });
setStorageDialogOpen(false);
}
const onSetHops = (hops: number) => {
selectedDownloads.forEach((download) => {
- (async () => { await triblerService.setDownloadHops(download.infohash, hops) })();
+ (async () => {
+ const response = await triblerService.setDownloadHops(download.infohash, hops);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadSetHops")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadSetHops")} ${response.error}`);
+ }
+ })();
});
}
diff --git a/src/tribler/ui/src/pages/Downloads/Files.tsx b/src/tribler/ui/src/pages/Downloads/Files.tsx
index b041c44bd0..5033f93886 100644
--- a/src/tribler/ui/src/pages/Downloads/Files.tsx
+++ b/src/tribler/ui/src/pages/Downloads/Files.tsx
@@ -1,11 +1,14 @@
+import toast from 'react-hot-toast';
import { ColumnDef } from "@tanstack/react-table";
import { File } from "@/models/file.model";
import { Download } from "@/models/download.model";
-import { useEffect, useRef, useState } from "react";
+import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react";
+import { isErrorDict } from "@/services/reporting";
import { triblerService } from "@/services/tribler.service";
import SimpleTable from "@/components/ui/simple-table";
import { Checkbox } from "@/components/ui/checkbox";
import { formatBytes, getRowSelection, translateHeader } from "@/lib/utils";
+import { useTranslation } from "react-i18next";
const fileColumns: ColumnDef[] = [
@@ -54,7 +57,18 @@ const fileColumns: ColumnDef[] = [
},
]
+async function updateFiles(setFiles: Dispatch>, infohash: string, initialized: MutableRefObject) {
+ const response = await triblerService.getDownloadFiles(infohash);
+ if (response !== undefined && !isErrorDict(response)) {
+ setFiles(response);
+ } else {
+ // Don't bother the user on error, just try again later.
+ initialized.current = false;
+ }
+}
+
export default function Files({ download }: { download: Download }) {
+ const { t } = useTranslation();
const [files, setFiles] = useState([]);
const initialized = useRef(false)
@@ -78,7 +92,13 @@ export default function Files({ download }: { download: Download }) {
}
if (shouldUpdate)
- triblerService.setDownloadFiles(download.infohash, selectIndices);
+ triblerService.setDownloadFiles(download.infohash, selectIndices).then((response) => {
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorDownloadSetFiles")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorDownloadSetFiles")} ${response.error}`);
+ }
+ });
}
useEffect(() => {
@@ -87,7 +107,7 @@ export default function Files({ download }: { download: Download }) {
return;
}
initialized.current = true;
- (async () => setFiles(await triblerService.getDownloadFiles(download.infohash)))();
+ updateFiles(setFiles, download.infohash, initialized);
}, []);
// We'll wait until the API call returns so the selection gets set by initialRowSelection
diff --git a/src/tribler/ui/src/pages/Downloads/index.tsx b/src/tribler/ui/src/pages/Downloads/index.tsx
index 87f9f37133..f24f000e83 100644
--- a/src/tribler/ui/src/pages/Downloads/index.tsx
+++ b/src/tribler/ui/src/pages/Downloads/index.tsx
@@ -5,6 +5,7 @@ import { Download } from "@/models/download.model";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress"
import { capitalize, formatBytes, translateHeader } from "@/lib/utils";
+import { isErrorDict } from "@/services/reporting";
import { triblerService } from "@/services/tribler.service";
import { CaretSortIcon } from "@radix-ui/react-icons";
import { ColumnDef } from "@tanstack/react-table"
@@ -155,9 +156,14 @@ export default function Downloads({ statusFilter }: { statusFilter: number[] })
let infohash = (selectedDownloads.length === 1) ? selectedDownloads[0].infohash : '';
if (infohashes)
infohash = infohashes[0] ?? "";
- setDownloads((await triblerService.getDownloads(infohash, !!infohash, !!infohash)).filter((download) => {
- return statusFilter.includes(download.status_code);
- }))
+
+ // Don't bother the user on error, just try again later.
+ const response = await triblerService.getDownloads(infohash, !!infohash, !!infohash);
+ if (response !== undefined && !isErrorDict(response)) {
+ setDownloads(response.filter((download: Download) => {
+ return statusFilter.includes(download.status_code);
+ }));
+ }
}
useEffect(() => {
diff --git a/src/tribler/ui/src/pages/Popular/index.tsx b/src/tribler/ui/src/pages/Popular/index.tsx
index 350286eccb..216a9c60ac 100644
--- a/src/tribler/ui/src/pages/Popular/index.tsx
+++ b/src/tribler/ui/src/pages/Popular/index.tsx
@@ -2,6 +2,7 @@ import SimpleTable from "@/components/ui/simple-table";
import SaveAs from "@/dialogs/SaveAs";
import { useCallback, useEffect, useMemo, useState } from "react";
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { Torrent } from "@/models/torrent.model";
import { ColumnDef } from "@tanstack/react-table";
import { categoryIcon, filterDuplicates, formatBytes, formatTimeAgo, getMagnetLink, translateHeader } from "@/lib/utils";
@@ -60,10 +61,15 @@ export default function Popular() {
const [request, setRequest] = useState("");
useInterval(async () => {
- const popular = await triblerService.getPopularTorrents(true);
- setTorrents(filterDuplicates(popular, 'infohash'));
+ const popular = await triblerService.getPopularTorrents();
+ if (!(popular === undefined) && !isErrorDict(popular)) {
+ // Don't bother the user on error, just try again later.
+ setTorrents(filterDuplicates(popular, 'infohash'));
+ }
const remoteQuery = await triblerService.searchTorrentsRemote('', true);
- setRequest(remoteQuery.request_uuid);
+ if (!(remoteQuery === undefined) && !isErrorDict(remoteQuery)) {
+ setRequest(remoteQuery.request_uuid);
+ }
}, 5000, true);
useEffect(() => {
diff --git a/src/tribler/ui/src/pages/Search/index.tsx b/src/tribler/ui/src/pages/Search/index.tsx
index 7e70c19f78..381c87ca3a 100644
--- a/src/tribler/ui/src/pages/Search/index.tsx
+++ b/src/tribler/ui/src/pages/Search/index.tsx
@@ -1,6 +1,7 @@
import SimpleTable from "@/components/ui/simple-table";
import { useCallback, useEffect, useMemo, useState } from "react";
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { Torrent } from "@/models/torrent.model";
import { ColumnDef } from "@tanstack/react-table";
import { categoryIcon, filterDuplicates, formatBytes, formatTimeAgo, getMagnetLink } from "@/lib/utils";
@@ -74,9 +75,14 @@ export default function Search() {
const searchTorrents = async () => {
if (!query) return;
const localResults = await triblerService.searchTorrentsLocal(query);
- setTorrents(filterDuplicates(localResults, 'infohash'));
+ if (!(localResults === undefined) && !isErrorDict(localResults)) {
+ // Don't bother the user on error, just try again later.
+ setTorrents(filterDuplicates(localResults, 'infohash'));
+ }
const remoteQuery = await triblerService.searchTorrentsRemote(query, false);
- setRequest(remoteQuery.request_uuid);
+ if (!(remoteQuery === undefined) && !isErrorDict(remoteQuery)) {
+ setRequest(remoteQuery.request_uuid);
+ }
}
searchTorrents();
}, [query]);
diff --git a/src/tribler/ui/src/pages/Settings/Anonymity.tsx b/src/tribler/ui/src/pages/Settings/Anonymity.tsx
index 49869b4920..ef06c1f6eb 100644
--- a/src/tribler/ui/src/pages/Settings/Anonymity.tsx
+++ b/src/tribler/ui/src/pages/Settings/Anonymity.tsx
@@ -1,7 +1,9 @@
import SaveButton from "./SaveButton";
+import toast from 'react-hot-toast';
import { Slider } from "@/components/ui/slider";
import { Settings } from "@/models/settings.model";
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -11,7 +13,16 @@ export default function Anonimity() {
const [settings, setSettings] = useState();
if (!settings) {
- (async () => { setSettings(await triblerService.getSettings()) })();
+ (async () => {
+ const response = await triblerService.getSettings();
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorGetSettings")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorGetSettings")} ${response.error}`);
+ } else {
+ setSettings(response);
+ }
+ })();
return null;
}
@@ -48,8 +59,14 @@ export default function Anonimity() {
{
- if (settings)
- await triblerService.setSettings(settings);
+ if (settings){
+ const response = await triblerService.setSettings(settings);
+ if (response === undefined) {
+ toast.error(`${t("ToastErrorSetSettings")} ${t("ToastErrorGenNetworkErr")}`);
+ } else if (isErrorDict(response)){
+ toast.error(`${t("ToastErrorSetSettings")} ${response.error}`);
+ }
+ }
}}
/>
diff --git a/src/tribler/ui/src/pages/Settings/Bandwidth.tsx b/src/tribler/ui/src/pages/Settings/Bandwidth.tsx
index 9cbecfa2fc..c3f3120d0c 100644
--- a/src/tribler/ui/src/pages/Settings/Bandwidth.tsx
+++ b/src/tribler/ui/src/pages/Settings/Bandwidth.tsx
@@ -2,8 +2,10 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Settings } from "@/models/settings.model";
import { triblerService } from "@/services/tribler.service";
+import { isErrorDict } from "@/services/reporting";
import { useState } from "react";
import { useTranslation } from "react-i18next";
+import toast from 'react-hot-toast';
import SaveButton from "./SaveButton";
@@ -12,7 +14,16 @@ export default function Bandwith() {
const [settings, setSettings] = useState