pull/141/head
Jason Fischer 2 years ago
commit 95b6ea0e23

@ -16,7 +16,7 @@
- Container status (Running / Stopped) & statistics (CPU, Memory, Network) - Container status (Running / Stopped) & statistics (CPU, Memory, Network)
- Automatic service discovery (via labels) - Automatic service discovery (via labels)
* Service Integration * Service Integration
- Sonarr, Radarr, Readarr, Emby, Jellyfin, Tautulli (Plex) - Sonarr, Radarr, Readarr, Prowlarr, Emby, Jellyfin, Tautulli (Plex)
- Ombi, Overseerr, Jellyseerr, NZBGet, SABnzbd, ruTorrent - Ombi, Overseerr, Jellyseerr, NZBGet, SABnzbd, ruTorrent
- Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager, Gotify - Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager, Gotify
* Information Providers * Information Providers
@ -78,6 +78,8 @@ pnpm install
pnpm build pnpm build
``` ```
If this is your first time starting, copy the `src/skeleton` directory to `config/` to populate initial example config files.
Finally, run the server: Finally, run the server:
```bash ```bash
@ -131,4 +133,4 @@ Huge thanks to the all the contributors who have helped make this project what i
* [Nonoss117](https://github.com/benphelps/homepage/commits?author=Nonoss117) - French Translation * [Nonoss117](https://github.com/benphelps/homepage/commits?author=Nonoss117) - French Translation
* [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos * [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos
* [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6 * [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6
* [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify Integration * [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration

@ -7,6 +7,7 @@ const nextConfig = {
domains: ["cdn.jsdelivr.net"], domains: ["cdn.jsdelivr.net"],
unoptimized: true, unoptimized: true,
}, },
experimental: { images: { allowFutureImage: true } }
}; };
module.exports = nextConfig; module.exports = nextConfig;

@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Gesamt", "total": "Gesamt",
"free": "Frei", "free": "Frei",
"used": "Gebraucht" "used": "Gebraucht",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
@ -114,5 +115,12 @@
"apps": "Applications", "apps": "Applications",
"clients": "Clients", "clients": "Clients",
"messages": "Messages" "messages": "Messages"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -27,7 +27,8 @@
"resources": { "resources": {
"total": "Total", "total": "Total",
"free": "Free", "free": "Free",
"used": "Used" "used": "Used",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@ -131,5 +132,12 @@
"apps": "Applications", "apps": "Applications",
"clients": "Clients", "clients": "Clients",
"messages": "Messages" "messages": "Messages"
},
"prowlarr":{
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Total", "total": "Total",
"free": "Libre", "free": "Libre",
"used": "Usado" "used": "Usado",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "Recibido", "rx": "Recibido",
@ -47,9 +48,9 @@
"movies": "Películas" "movies": "Películas"
}, },
"readarr": { "readarr": {
"wanted": "Wanted", "wanted": "Más deseado",
"queued": "Queued", "queued": "Puesto en cola",
"books": "Books" "books": "Libros"
}, },
"ombi": { "ombi": {
"pending": "Pendiente", "pending": "Pendiente",
@ -98,21 +99,28 @@
"available": "Disponible" "available": "Disponible"
}, },
"sabnzbd": { "sabnzbd": {
"rate": "Rate", "rate": "Tasa de descarga",
"queue": "Queue", "queue": "Puesto en cola",
"timeleft": "Time Left" "timeleft": "Tiempo Restante"
}, },
"nzbget": { "nzbget": {
"rate": "Rate", "rate": "Tasa de descarga",
"remaining": "Remaining", "remaining": "Restante",
"downloaded": "Downloaded" "downloaded": "Descargado"
}, },
"coinmarketcap": { "coinmarketcap": {
"configure": "Configure one or more crypto currencies to track" "configure": "Configurar una o varias criptomonedas para su seguimiento"
}, },
"gotify": { "gotify": {
"apps": "Applications", "apps": "Aplicaciones",
"clients": "Clients", "clients": "Clientes",
"messages": "Messages" "messages": "Mensajes"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Total", "total": "Total",
"free": "Libre", "free": "Libre",
"used": "Utilisée" "used": "Utilisé",
"load": "Charge"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
@ -32,53 +33,53 @@
"no_active": "Aucun flux actif" "no_active": "Aucun flux actif"
}, },
"rutorrent": { "rutorrent": {
"active": "Active", "active": "Actif",
"upload": "Téléverser", "upload": "Téléverser",
"download": "Télécharger" "download": "Télécharger"
}, },
"sonarr": { "sonarr": {
"wanted": "Recherchée", "wanted": "Demandé",
"queued": "En queue", "queued": "En queue",
"series": "Séries" "series": "Séries"
}, },
"radarr": { "radarr": {
"wanted": "Recherchée", "wanted": "Demandé",
"queued": "En queue", "queued": "En queue",
"movies": "Films" "movies": "Films"
}, },
"readarr": { "readarr": {
"wanted": "Wanted", "wanted": "Demandé",
"queued": "Queued", "queued": "En Queue",
"books": "Books" "books": "Livres"
}, },
"ombi": { "ombi": {
"pending": "En attente", "pending": "En attente",
"approved": "Approuvée", "approved": "Validé",
"available": "Disponible" "available": "Disponible"
}, },
"jellyseerr": { "jellyseerr": {
"pending": "En attente", "pending": "En attente",
"approved": "Approuvée", "approved": "Validé",
"available": "Disponible" "available": "Disponible"
}, },
"pihole": { "pihole": {
"queries": "Requêtes", "queries": "Requêtes",
"blocked": "Bloquée", "blocked": "Bloqué",
"gravity": "La gravité" "gravity": "La gravité"
}, },
"speedtest": { "speedtest": {
"upload": "Téléversement", "upload": "Téléversement",
"download": "Téléchargement", "download": "Téléchargement",
"ping": "Ping-ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
"running": "Fonctionnement", "running": "Démarré",
"stopped": "Arrêté", "stopped": "Arrêté",
"total": "Total" "total": "Total"
}, },
"traefik": { "traefik": {
"routers": "Routeurs", "routers": "Routeurs",
"services": "Prestations de service", "services": "Services",
"middleware": "Middleware" "middleware": "Middleware"
}, },
"npm": { "npm": {
@ -105,25 +106,32 @@
}, },
"overseerr": { "overseerr": {
"pending": "En attente", "pending": "En attente",
"approved": "Approuvée", "approved": "Validé",
"available": "Disponible" "available": "Disponible"
}, },
"sabnzbd": { "sabnzbd": {
"rate": "Rate", "rate": "Taux",
"queue": "Queue", "queue": "Queue",
"timeleft": "Time Left" "timeleft": "Temps restant"
}, },
"nzbget": { "nzbget": {
"remaining": "Remaining", "remaining": "Restant",
"downloaded": "Downloaded", "downloaded": "Téléchargé",
"rate": "Rate" "rate": "Évaluer"
}, },
"coinmarketcap": { "coinmarketcap": {
"configure": "Configure one or more crypto currencies to track" "configure": "Configurer une ou plusieurs crypto-monnaies à suivre"
}, },
"gotify": { "gotify": {
"apps": "Applications", "apps": "Applications",
"clients": "Clients", "clients": "Clients",
"messages": "Messages" "messages": "Messages"
},
"prowlarr": {
"enableIndexers": "Indexeurs",
"numberOfGrabs": "Capture",
"numberOfQueries": "Demandes",
"numberOfFailGrabs": "Capture échouée",
"numberOfFailQueries": "Demande échouée"
} }
} }

@ -44,7 +44,8 @@
"resources": { "resources": {
"total": "Totale", "total": "Totale",
"free": "Libero", "free": "Libero",
"used": "In utilizzo" "used": "In utilizzo",
"load": "Load"
}, },
"rutorrent": { "rutorrent": {
"active": "Attivo", "active": "Attivo",
@ -114,5 +115,12 @@
"apps": "Applications", "apps": "Applications",
"clients": "Clients", "clients": "Clients",
"messages": "Messages" "messages": "Messages"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Totalt", "total": "Totalt",
"free": "Ledig", "free": "Ledig",
"used": "Brukt" "used": "Brukt",
"load": "Last inn"
}, },
"docker": { "docker": {
"rx": "Mottatt", "rx": "Mottatt",
@ -23,13 +24,13 @@
"playing": "Spiller", "playing": "Spiller",
"transcoding": "Transkoding", "transcoding": "Transkoding",
"bitrate": "Bitrate", "bitrate": "Bitrate",
"no_active": "No Active Streams" "no_active": "Ingen aktive strømmer"
}, },
"tautulli": { "tautulli": {
"playing": "Spiller", "playing": "Spiller",
"transcoding": "Transkoding", "transcoding": "Transkoding",
"bitrate": "Bitrate", "bitrate": "Bitrate",
"no_active": "No Active Streams" "no_active": "Ingen aktive strømmer"
}, },
"rutorrent": { "rutorrent": {
"active": "Aktiv", "active": "Aktiv",
@ -93,26 +94,33 @@
"current": "Nåværende posisjon" "current": "Nåværende posisjon"
}, },
"overseerr": { "overseerr": {
"pending": "Pending", "pending": "Venter",
"approved": "Approved", "approved": "Godkjent",
"available": "Available" "available": "Tilgjengelig"
}, },
"sabnzbd": { "sabnzbd": {
"rate": "Rate", "rate": "Takt",
"queue": "Queue", "queue": "",
"timeleft": "Time Left" "timeleft": "Gjenstående tid"
}, },
"nzbget": { "nzbget": {
"rate": "Rate", "rate": "Takt",
"downloaded": "Downloaded", "downloaded": "Nedlastet",
"remaining": "Remaining" "remaining": "Gjenstående"
}, },
"coinmarketcap": { "coinmarketcap": {
"configure": "Configure one or more crypto currencies to track" "configure": "Sett opp én eller flere kryptovalutaer å holde øye med"
}, },
"gotify": { "gotify": {
"apps": "Applications", "apps": "Programmer",
"clients": "Clients", "clients": "Klienter",
"messages": "Messages" "messages": "Meldinger"
},
"prowlarr": {
"enableIndexers": "Indekserere",
"numberOfGrabs": "Hentninger",
"numberOfQueries": "Spørringer",
"numberOfFailGrabs": "Mislykkede hentinger",
"numberOfFailQueries": "Mislykkede spørringer"
} }
} }

@ -7,7 +7,8 @@
"resources": { "resources": {
"total": "Totaal", "total": "Totaal",
"free": "Vrij", "free": "Vrij",
"used": "Gebruikt" "used": "Gebruikt",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@ -114,5 +115,12 @@
"apps": "Applications", "apps": "Applications",
"clients": "Clients", "clients": "Clients",
"messages": "Messages" "messages": "Messages"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Total", "total": "Total",
"free": "Livre", "free": "Livre",
"used": "Usada" "used": "Usada",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
@ -125,5 +126,12 @@
"apps": "Aplicações", "apps": "Aplicações",
"clients": "Clientes", "clients": "Clientes",
"messages": "Mensagens" "messages": "Mensagens"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Общий", "total": "Общий",
"free": "Свободно", "free": "Свободно",
"used": "Использовано" "used": "Использовано",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
@ -114,5 +115,12 @@
"apps": "Applications", "apps": "Applications",
"clients": "Clients", "clients": "Clients",
"messages": "Messages" "messages": "Messages"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Tổng", "total": "Tổng",
"free": "Dư", "free": "Dư",
"used": "Đã dùng" "used": "Đã dùng",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@ -114,5 +115,12 @@
"apps": "Applications", "apps": "Applications",
"clients": "Clients", "clients": "Clients",
"messages": "Messages" "messages": "Messages"
},
"prowlarr": {
"numberOfFailGrabs": "Fail Grabs",
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -8,31 +8,32 @@
"placeholder": "搜索…" "placeholder": "搜索…"
}, },
"resources": { "resources": {
"total": "全部的", "total": "共",
"free": "自由的", "free": "空闲",
"used": "用过的" "used": "已用",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "rx", "rx": "接收",
"tx": "TX", "tx": "发送",
"mem": "mem", "mem": "内存",
"cpu": "中央处理器", "cpu": "处理器",
"offline": "离线" "offline": "离线"
}, },
"emby": { "emby": {
"playing": "", "playing": "正在播放",
"transcoding": "转码", "transcoding": "转码",
"bitrate": "比特率", "bitrate": "比特率",
"no_active": "No Active Streams" "no_active": "暂无播放"
}, },
"tautulli": { "tautulli": {
"playing": "", "playing": "正在播放",
"transcoding": "转码", "transcoding": "转码",
"bitrate": "比特率", "bitrate": "比特率",
"no_active": "No Active Streams" "no_active": "暂无播放"
}, },
"rutorrent": { "rutorrent": {
"active": "积极的", "active": "活动中",
"upload": "上传", "upload": "上传",
"download": "下载" "download": "下载"
}, },
@ -42,18 +43,18 @@
"series": "系列" "series": "系列"
}, },
"radarr": { "radarr": {
"wanted": "通缉", "wanted": "订阅",
"queued": "队", "queued": "",
"movies": "电影" "movies": "电影"
}, },
"readarr": { "readarr": {
"wanted": "Wanted", "wanted": "订阅",
"queued": "Queued", "queued": "队列",
"books": "Books" "books": "书籍"
}, },
"ombi": { "ombi": {
"pending": "待办的", "pending": "待办的",
"approved": "得到正式认可的", "approved": "已批准",
"available": "可用的" "available": "可用的"
}, },
"jellyseerr": { "jellyseerr": {
@ -72,9 +73,9 @@
"ping": "ping" "ping": "ping"
}, },
"portainer": { "portainer": {
"running": "跑步", "running": "运行中",
"stopped": "停了下来", "stopped": "已停止",
"total": "全部的" "total": "总计"
}, },
"traefik": { "traefik": {
"routers": "路由器", "routers": "路由器",
@ -87,32 +88,39 @@
"total": "全部的" "total": "全部的"
}, },
"weather": { "weather": {
"current": "Current Location", "current": "当前位置",
"allow": "Click to allow", "allow": "点击并允许",
"updating": "Updating", "updating": "更新中",
"wait": "Please wait" "wait": "请等待"
}, },
"overseerr": { "overseerr": {
"pending": "Pending", "pending": "待办",
"approved": "Approved", "approved": "已批准",
"available": "Available" "available": "可用"
}, },
"sabnzbd": { "sabnzbd": {
"rate": "Rate", "rate": "速率",
"queue": "Queue", "queue": "队列",
"timeleft": "Time Left" "timeleft": "剩余时间"
}, },
"nzbget": { "nzbget": {
"rate": "Rate", "rate": "速率",
"remaining": "Remaining", "remaining": "剩余",
"downloaded": "Downloaded" "downloaded": "下载"
}, },
"coinmarketcap": { "coinmarketcap": {
"configure": "Configure one or more crypto currencies to track" "configure": "配置一个或多个需要追踪的加密"
}, },
"gotify": { "gotify": {
"apps": "Applications", "apps": "应用",
"clients": "Clients", "clients": "客户端",
"messages": "Messages" "messages": "信息"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
} }
} }

@ -6,7 +6,7 @@ export default function Item({ bookmark }) {
<button <button
type="button" type="button"
onClick={() => window.open(bookmark.href, "_blank").focus()} onClick={() => window.open(bookmark.href, "_blank").focus()}
className="w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/10 dark:bg-white/10 dark:hover:bg-white/20 backdrop-blur-md" className="w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/10 dark:bg-white/10 dark:hover:bg-white/20"
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 hover:text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md"> <div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 hover:text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">

@ -36,7 +36,7 @@ export default function Item({ service }) {
<div <div
className={`${ className={`${
hasLink ? "cursor-pointer " : " " hasLink ? "cursor-pointer " : " "
}transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-700/70 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/20 dark:bg-white/10 dark:hover:bg-white/20 backdrop-blur-md`} }transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-700/70 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/20 dark:bg-white/10 dark:hover:bg-white/20`}
> >
<div className="flex select-none"> <div className="flex select-none">
{service.icon && {service.icon &&

@ -21,6 +21,7 @@ import Npm from "./widgets/service/npm";
import Tautulli from "./widgets/service/tautulli"; import Tautulli from "./widgets/service/tautulli";
import CoinMarketCap from "./widgets/service/coinmarketcap"; import CoinMarketCap from "./widgets/service/coinmarketcap";
import Gotify from "./widgets/service/gotify"; import Gotify from "./widgets/service/gotify";
import Prowlarr from "./widgets/service/prowlarr";
const widgetMappings = { const widgetMappings = {
docker: Docker, docker: Docker,
@ -44,6 +45,7 @@ const widgetMappings = {
npm: Npm, npm: Npm,
tautulli: Tautulli, tautulli: Tautulli,
gotify: Gotify, gotify: Gotify,
prowlarr: Prowlarr
}; };
export default function Widget({ service }) { export default function Widget({ service }) {

@ -0,0 +1,55 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Prowlarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: indexersData, error: indexersError } = useSWR(formatApiUrl(config, "indexer"));
const { data: grabsData, error: grabsError } = useSWR(formatApiUrl(config, "indexerstats"));
if (indexersError || grabsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!indexersData || !grabsData) {
return (
<Widget>
<Block label={t("prowlarr.enableIndexers")} />
<Block label={t("prowlarr.numberOfGrabs")} />
<Block label={t("prowlarr.numberOfQueries")} />
<Block label={t("prowlarr.numberOfFailGrabs")} />
<Block label={t("prowlarr.numberOfFailQueries")} />
</Widget>
);
}
const indexers = indexersData?.filter((indexer) => indexer.enable === true);
let numberOfGrabs = 0
let numberOfQueries = 0
let numberOfFailedGrabs = 0
let numberOfFailedQueries = 0
grabsData?.indexers?.forEach(element => {
numberOfGrabs += element.numberOfGrabs;
numberOfQueries += element.numberOfQueries;
numberOfFailedGrabs += numberOfFailedGrabs + element.numberOfFailedGrabs;
numberOfFailedQueries += numberOfFailedQueries + element.numberOfFailedQueries;
});
return (
<Widget>
<Block label={t("prowlarr.enableIndexers")} value={indexers.length} />
<Block label={t("prowlarr.numberOfGrabs")} value={numberOfGrabs} />
<Block label={t("prowlarr.numberOfQueries")} value={numberOfQueries} />
<Block label={t("prowlarr.numberOfFailGrabs")} value={numberOfFailedGrabs} />
<Block label={t("prowlarr.numberOfFailQueries")} value={numberOfFailedQueries} />
</Widget>
);
}

@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Cpu() { export default function Cpu({ expanded }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, error } = useSWR(`/api/widgets/resources?type=cpu`, { const { data, error } = useSWR(`/api/widgets/resources?type=cpu`, {
@ -39,11 +39,29 @@ export default function Cpu() {
return ( return (
<div className="flex-none flex flex-row items-center mr-3 py-1.5"> <div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left font-mono min-w-[80px]"> <div className="flex flex-col ml-3 text-left min-w-[85px]">
<div className="text-theme-800 dark:text-theme-200 text-xs"> <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{t("common.number", { value: data.cpu.usage, style: "unit", unit: "percent", maximumFractionDigits: 0 })}{" "} <div className="pl-0.5">
{t("docker.cpu")} {t("common.number", {
value: data.cpu.usage,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
})}
</div>
<div className="pr-1">{t("docker.cpu")}</div>
</div> </div>
{expanded && (
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{t("common.number", {
value: data.cpu.load,
maximumFractionDigits: 2,
})}
</div>
<div className="pr-1">{t("resources.load")}</div>
</div>
)}
<UsageBar percent={percent} /> <UsageBar percent={percent} />
</div> </div>
</div> </div>

@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Disk({ options }) { export default function Disk({ options, expanded }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, error } = useSWR(`/api/widgets/resources?type=disk&target=${options.disk}`, { const { data, error } = useSWR(`/api/widgets/resources?type=disk&target=${options.disk}`, {
@ -37,15 +37,19 @@ export default function Disk({ options }) {
const percent = Math.round((data.drive.usedGb / data.drive.totalGb) * 100); const percent = Math.round((data.drive.usedGb / data.drive.totalGb) * 100);
return ( return (
<div className="flex-none flex flex-row items-center mr-3 py-1.5 group"> <div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left min-w-[80px]"> <div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs group-hover:hidden"> <span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })} {t("resources.free")} <div className="pl-0.5">{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })}</div>
</span> <div className="pr-1">{t("resources.free")}</div>
<span className="text-theme-800 dark:text-theme-200 text-xs hidden group-hover:block">
{t("common.bytes", { value: data.drive.totalGb * 1024 * 1024 * 1024 })} {t("resources.total")}
</span> </span>
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">{t("common.bytes", { value: data.drive.totalGb * 1024 * 1024 * 1024 })}</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={percent} /> <UsageBar percent={percent} />
</div> </div>
</div> </div>

@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Memory() { export default function Memory({ expanded }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, error } = useSWR(`/api/widgets/resources?type=memory`, { const { data, error } = useSWR(`/api/widgets/resources?type=memory`, {
@ -37,15 +37,27 @@ export default function Memory() {
const percent = Math.round((data.memory.usedMemMb / data.memory.totalMemMb) * 100); const percent = Math.round((data.memory.usedMemMb / data.memory.totalMemMb) * 100);
return ( return (
<div className="flex-none flex flex-row items-center mr-3 py-1.5 group"> <div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left min-w-[80px]"> <div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs group-hover:hidden"> <span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024 })} {t("resources.free")} <div className="pl-0.5">
</span> {t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 0, binary: true })}
<span className="text-theme-800 dark:text-theme-200 text-xs hidden group-hover:block"> </div>
{t("common.bytes", { value: data.memory.usedMemMb * 1024 * 1024 })} {t("resources.used")} <div className="pr-1">{t("resources.free")}</div>
</span> </span>
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{t("common.bytes", {
value: data.memory.totalMemMb * 1024 * 1024,
maximumFractionDigits: 0,
binary: true,
})}
</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={percent} /> <UsageBar percent={percent} />
</div> </div>
</div> </div>

@ -3,14 +3,16 @@ import Cpu from "./cpu";
import Memory from "./memory"; import Memory from "./memory";
export default function Resources({ options }) { export default function Resources({ options }) {
console.log(options);
const { expanded } = options;
return ( return (
<div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap"> <div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap">
<div className="flex flex-row self-center flex-wrap justify-between"> <div className="flex flex-row self-center flex-wrap justify-between">
{options.cpu && <Cpu />} {options.cpu && <Cpu expanded={expanded} />}
{options.memory && <Memory />} {options.memory && <Memory expanded={expanded} />}
{Array.isArray(options.disk) {Array.isArray(options.disk)
? options.disk.map((disk) => <Disk key={disk} options={{ disk }} />) ? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} />)
: options.disk && <Disk options={options} />} : options.disk && <Disk options={options} expanded={expanded} />}
</div> </div>
{options.label && ( {options.label && (
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div> <div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>

@ -1,6 +1,6 @@
export default function UsageBar({ percent }) { export default function UsageBar({ percent }) {
return ( return (
<div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-white/20 backdrop-blur-md"> <div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-white/20">
<div <div
className="bg-theme-800/70 h-1 rounded-full dark:bg-white/50" className="bg-theme-800/70 h-1 rounded-full dark:bg-white/50"
style={{ style={{

@ -62,8 +62,7 @@ export default function Search({ options }) {
bg-white/50 dark:bg-white/10 bg-white/50 dark:bg-white/10
focus:ring-theme-500 dark:focus:ring-white/50 focus:ring-theme-500 dark:focus:ring-white/50
focus:border-theme-500 dark:focus:border-white/50 focus:border-theme-500 dark:focus:border-white/50
border border-theme-300 dark:border-theme-200/50 border border-theme-300 dark:border-theme-200/50"
backdrop-blur-md"
placeholder={t("search.placeholder")} placeholder={t("search.placeholder")}
onChange={(s) => setQuery(s.currentTarget.value)} onChange={(s) => setQuery(s.currentTarget.value)}
required required

@ -24,6 +24,7 @@ const serviceProxyHandlers = {
overseerr: credentialedProxyHandler, overseerr: credentialedProxyHandler,
ombi: credentialedProxyHandler, ombi: credentialedProxyHandler,
coinmarketcap: credentialedProxyHandler, coinmarketcap: credentialedProxyHandler,
prowlarr: credentialedProxyHandler,
// super specific handlers // super specific handlers
rutorrent: rutorrentProxyHandler, rutorrent: rutorrentProxyHandler,
nzbget: nzbgetProxyHandler, nzbget: nzbgetProxyHandler,

@ -18,6 +18,7 @@ const formats = {
sabnzbd: `{url}/api/?apikey={key}&output=json&mode={endpoint}`, sabnzbd: `{url}/api/?apikey={key}&output=json&mode={endpoint}`,
coinmarketcap: `https://pro-api.coinmarketcap.com/{endpoint}`, coinmarketcap: `https://pro-api.coinmarketcap.com/{endpoint}`,
gotify: `{url}/{endpoint}`, gotify: `{url}/{endpoint}`,
prowlarr: `{url}/api/v1/{endpoint}`,
}; };
export function formatApiCall(api, args) { export function formatApiCall(api, args) {

Loading…
Cancel
Save