Compare commits

...

13 Commits

Author SHA1 Message Date
shamoon
f6b1304e22 Merge pull request #749 from benphelps/fix-docker-log-error
Fix: remove error on no discovered services
2022-12-30 20:32:11 -08:00
Michael Shamoon
ee729a7e6a remove error on no discovered services 2022-12-30 20:31:25 -08:00
Michael Shamoon
bc7937db71 omada widget cleanup 2022-12-29 00:25:50 -08:00
shamoon
0e1aeaf54c Merge pull request #719 from benphelps/docker-server-failovers
Fix: Handle docker server failures if others succeed
2022-12-28 18:40:08 -08:00
shamoon
2e8717247d Merge pull request #745 from benphelps/fix-version-check-cache
Fix: version check caching
2022-12-28 18:38:50 -08:00
Michael Shamoon
d17a17bd3c Use server-side endpoint to properly cache GH release data 2022-12-28 18:33:14 -08:00
Michael Shamoon
0afc1b96f1 CPU / memory / disk usage bars start from 0
Closes #737
2022-12-28 16:21:04 -08:00
Michael Shamoon
5fbc6702bc Prevent blocking error on GH releases failure
Closes #738
2022-12-28 16:17:49 -08:00
Nonoss117
75455a23e2 Translated using Weblate (French)
Currently translated at 100.0% (288 of 288 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-12-27 12:50:18 +01:00
gallegonovato
2aed46671f Translated using Weblate (Spanish)
Currently translated at 100.0% (288 of 288 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-12-27 12:50:18 +01:00
shamoon
88934ec39a Correct debug messages in Pyload widget
Closes #733
2022-12-26 06:07:43 -08:00
shamoon
21c0c687cd Update README.md 2022-12-26 01:17:54 -08:00
Michael Shamoon
6b90d3ef28 Handle docker server failures if others succeed 2022-12-22 21:16:52 -08:00
12 changed files with 83 additions and 71 deletions

View File

@@ -45,15 +45,17 @@
- 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, Prowlarr, Bazarr, Lidarr, Emby, Jellyfin, Tautulli (Plex) - Sonarr, Radarr, Readarr, Prowlarr, Bazarr, Lidarr, Emby, Jellyfin, Tautulli, Plex and more
- Ombi, Overseerr, Jellyseerr, Jackett, NZBGet, SABnzbd, ruTorrent, Transmission, qBittorrent - Ombi, Overseerr, Jellyseerr, Jackett, NZBGet, SABnzbd, ruTorrent, Transmission, qBittorrent and more
- Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentik, Proxmox - Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentik, Proxmox and more
- Information Providers - Information Providers
- Coin Market Cap, Mastodon - Coin Market Cap, Mastodon and more
- Information & Utility Widgets - Information & Utility Widgets
- System Stats (Disk, CPU, Memory) - System Stats (Disk, CPU, Memory)
- Weather via [OpenWeatherMap](https://openweathermap.org/) or [Open-Meteo](https://open-meteo.com/) - Weather via [OpenWeatherMap](https://openweathermap.org/) or [Open-Meteo](https://open-meteo.com/)
- Search Bar - Web Search Bar
- UniFi Console, Glances and more
- Instant "Quick-launch" search
- Customizable - Customizable
- 21 theme colors with light and dark mode support - 21 theme colors with light and dark mode support
- Background image support - Background image support
@@ -63,7 +65,7 @@
If you have any questions, suggestions, or general issues, please start a discussion on the [Discussions](https://github.com/benphelps/homepage/discussions) page. If you have any questions, suggestions, or general issues, please start a discussion on the [Discussions](https://github.com/benphelps/homepage/discussions) page.
If you have a more specific issue, please open an issue on the [Issues](https://github.com/benphelps/homepage/issues) page. For bug reports, please open an issue on the [Issues](https://github.com/benphelps/homepage/issues) page.
## Getting Started ## Getting Started

View File

@@ -394,14 +394,14 @@
"numberOfLeases": "Alquileres" "numberOfLeases": "Alquileres"
}, },
"xteve": { "xteve": {
"streams_all": "All Streams", "streams_all": "Todas las corrientes",
"streams_active": "Active Streams", "streams_active": "Corrientes activas",
"streams_xepg": "XEPG Channels" "streams_xepg": "Canales XEPG"
}, },
"opnsense": { "opnsense": {
"cpu": "CPU Load", "cpu": "Carga de la CPU",
"memory": "Active Memory", "memory": "Memoria activa",
"wanUpload": "WAN Upload", "wanUpload": "Carga WAN",
"wanDownload": "WAN Download" "wanDownload": "Descargar WAN"
} }
} }

View File

@@ -399,9 +399,9 @@
"streams_xepg": "Canal XEPG" "streams_xepg": "Canal XEPG"
}, },
"opnsense": { "opnsense": {
"cpu": "CPU Load", "cpu": "Charge CPU",
"memory": "Active Memory", "memory": "Mém. Utilisée",
"wanUpload": "WAN Upload", "wanUpload": "WAN Envoi",
"wanDownload": "WAN Download" "wanDownload": "WAN Récep."
} }
} }

View File

@@ -3,8 +3,6 @@ import useSWR from "swr";
import { compareVersions } from "compare-versions"; import { compareVersions } from "compare-versions";
import { MdNewReleases } from "react-icons/md"; import { MdNewReleases } from "react-icons/md";
import cachedFetch from "utils/proxy/cached-fetch";
export default function Version() { export default function Version() {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
@@ -12,9 +10,7 @@ export default function Version() {
const revision = process.env.NEXT_PUBLIC_REVISION?.length ? process.env.NEXT_PUBLIC_REVISION : "dev"; const revision = process.env.NEXT_PUBLIC_REVISION?.length ? process.env.NEXT_PUBLIC_REVISION : "dev";
const version = process.env.NEXT_PUBLIC_VERSION?.length ? process.env.NEXT_PUBLIC_VERSION : "dev"; const version = process.env.NEXT_PUBLIC_VERSION?.length ? process.env.NEXT_PUBLIC_VERSION : "dev";
const cachedFetcher = (resource) => cachedFetch(resource, 5); const { data: releaseData } = useSWR("/api/releases");
const { data: releaseData } = useSWR("https://api.github.com/repos/benphelps/homepage/releases", cachedFetcher);
// use Intl.DateTimeFormat to format the date // use Intl.DateTimeFormat to format the date
const formatDate = (date) => { const formatDate = (date) => {
@@ -48,7 +44,7 @@ export default function Version() {
</span> </span>
{version === "main" || version === "dev" || version === "nightly" {version === "main" || version === "dev" || version === "nightly"
? null ? null
: releaseData && : releaseData && latestRelease &&
compareVersions(latestRelease.tag_name, version) > 0 && ( compareVersions(latestRelease.tag_name, version) > 0 && (
<a <a
href={latestRelease.html_url} href={latestRelease.html_url}

View File

@@ -38,7 +38,7 @@ export default function Cpu({ expanded }) {
<div className="pr-1">{t("resources.load")}</div> <div className="pr-1">{t("resources.load")}</div>
</div> </div>
)} )}
<UsageBar percent={100} /> <UsageBar percent={0} />
</div> </div>
</div> </div>
); );

View File

@@ -38,7 +38,7 @@ export default function Disk({ options, expanded }) {
<div className="pr-1">{t("resources.total")}</div> <div className="pr-1">{t("resources.total")}</div>
</span> </span>
)} )}
<UsageBar percent={100} /> <UsageBar percent={0} />
</div> </div>
</div> </div>
); );

View File

@@ -38,7 +38,7 @@ export default function Memory({ expanded }) {
<div className="pr-1">{t("resources.total")}</div> <div className="pr-1">{t("resources.total")}</div>
</span> </span>
)} )}
<UsageBar percent={100} /> <UsageBar percent={0} />
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,6 @@
import cachedFetch from "utils/proxy/cached-fetch";
export default async function handler(req, res) {
const releasesURL = "https://api.github.com/repos/benphelps/homepage/releases";
return res.send(await cachedFetch(releasesURL, 5));
}

View File

@@ -50,9 +50,12 @@ export async function servicesResponse() {
try { try {
discoveredServices = cleanServiceGroups(await servicesFromDocker()); discoveredServices = cleanServiceGroups(await servicesFromDocker());
if (discoveredServices?.length === 0) {
console.debug("No containers were found with homepage labels.");
}
} catch (e) { } catch (e) {
console.error("Failed to discover services, please check docker.yaml for errors or remove example entries."); console.error("Failed to discover services, please check docker.yaml for errors or remove example entries.");
if (e) console.error(e); if (e) console.error(e.toString());
discoveredServices = []; discoveredServices = [];
} }
@@ -60,7 +63,7 @@ export async function servicesResponse() {
configuredServices = cleanServiceGroups(await servicesFromConfig()); configuredServices = cleanServiceGroups(await servicesFromConfig());
} catch (e) { } catch (e) {
console.error("Failed to load services.yaml, please check for errors"); console.error("Failed to load services.yaml, please check for errors");
if (e) console.error(e); if (e) console.error(e.toString());
configuredServices = []; configuredServices = [];
} }
@@ -68,7 +71,7 @@ export async function servicesResponse() {
initialSettings = await getSettings(); initialSettings = await getSettings();
} catch (e) { } catch (e) {
console.error("Failed to load settings.yaml, please check for errors"); console.error("Failed to load settings.yaml, please check for errors");
if (e) console.error(e); if (e) console.error(e.toString());
initialSettings = {}; initialSettings = {};
} }

View File

@@ -44,36 +44,41 @@ export async function servicesFromDocker() {
const serviceServers = await Promise.all( const serviceServers = await Promise.all(
Object.keys(servers).map(async (serverName) => { Object.keys(servers).map(async (serverName) => {
const docker = new Docker(getDockerArguments(serverName).conn); try {
const containers = await docker.listContainers({ const docker = new Docker(getDockerArguments(serverName).conn);
all: true, const containers = await docker.listContainers({
}); all: true,
// bad docker connections can result in a <Buffer ...> object?
// in any case, this ensures the result is the expected array
if (!Array.isArray(containers)) {
return [];
}
const discovered = containers.map((container) => {
let constructedService = null;
Object.keys(container.Labels).forEach((label) => {
if (label.startsWith("homepage.")) {
if (!constructedService) {
constructedService = {
container: container.Names[0].replace(/^\//, ""),
server: serverName,
};
}
shvl.set(constructedService, label.replace("homepage.", ""), container.Labels[label]);
}
}); });
return constructedService; // bad docker connections can result in a <Buffer ...> object?
}); // in any case, this ensures the result is the expected array
if (!Array.isArray(containers)) {
return [];
}
return { server: serverName, services: discovered.filter((filteredService) => filteredService) }; const discovered = containers.map((container) => {
let constructedService = null;
Object.keys(container.Labels).forEach((label) => {
if (label.startsWith("homepage.")) {
if (!constructedService) {
constructedService = {
container: container.Names[0].replace(/^\//, ""),
server: serverName,
};
}
shvl.set(constructedService, label.replace("homepage.", ""), container.Labels[label]);
}
});
return constructedService;
});
return { server: serverName, services: discovered.filter((filteredService) => filteredService) };
} catch (e) {
// a server failed, but others may succeed
return { server: serverName, services: [] };
}
}) })
); );

View File

@@ -42,9 +42,9 @@ export default async function omadaProxyHandler(req, res) {
if (widget) { if (widget) {
const {url} = widget; const { url } = widget;
const controllerInfoURL = `${widget.url}/api/info`; const controllerInfoURL = `${url}/api/info`;
let [status, contentType, data] = await httpProxy(controllerInfoURL, { let [status, contentType, data] = await httpProxy(controllerInfoURL, {
headers: { headers: {
@@ -77,13 +77,13 @@ export default async function omadaProxyHandler(req, res) {
switch (controllerVersionMajor) { switch (controllerVersionMajor) {
case 3: case 3:
loginUrl = `${widget.url}/api/user/login?ajax`; loginUrl = `${url}/api/user/login?ajax`;
break; break;
case 4: case 4:
loginUrl = `${widget.url}/api/v2/login`; loginUrl = `${url}/api/v2/login`;
break; break;
case 5: case 5:
loginUrl = `${widget.url}/${cId}/api/v2/login`; loginUrl = `${url}/${cId}/api/v2/login`;
break; break;
default: default:
break; break;
@@ -105,7 +105,7 @@ export default async function omadaProxyHandler(req, res) {
switch (controllerVersionMajor) { switch (controllerVersionMajor) {
case 3: case 3:
sitesUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; sitesUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
body = { body = {
"method": "getUserSites", "method": "getUserSites",
"params": { "params": {
@@ -115,10 +115,10 @@ export default async function omadaProxyHandler(req, res) {
method = "POST"; method = "POST";
break; break;
case 4: case 4:
sitesUrl = `${widget.url}/api/v2/sites?token=${token}&currentPage=1&currentPageSize=1000`; sitesUrl = `${url}/api/v2/sites?token=${token}&currentPage=1&currentPageSize=1000`;
break; break;
case 5: case 5:
sitesUrl = `${widget.url}/${cId}/api/v2/sites?token=${token}&currentPage=1&currentPageSize=1000`; sitesUrl = `${url}/${cId}/api/v2/sites?token=${token}&currentPage=1&currentPageSize=1000`;
break; break;
default: default:
break; break;
@@ -133,7 +133,7 @@ export default async function omadaProxyHandler(req, res) {
const sitesResponseData = JSON.parse(data); const sitesResponseData = JSON.parse(data);
if (sitesResponseData.errorCode > 0) { if (status !== 200 || sitesResponseData.errorCode > 0) {
logger.debug(`HTTTP ${status} getting sites list: ${sitesResponseData.msg}`); logger.debug(`HTTTP ${status} getting sites list: ${sitesResponseData.msg}`);
return res.status(status).json({error: {message: "Error getting sites list", url, data: sitesResponseData}}); return res.status(status).json({error: {message: "Error getting sites list", url, data: sitesResponseData}});
} }
@@ -143,7 +143,7 @@ export default async function omadaProxyHandler(req, res) {
sitesResponseData.result.data.find(s => s.name === widget.site); sitesResponseData.result.data.find(s => s.name === widget.site);
if (!site) { if (!site) {
return res.status(status).json({error: {message: `Site ${widget.site} is not found`, url, data}}); return res.status(status).json({error: {message: `Site ${widget.site} is not found`, url: sitesUrl, data}});
} }
let siteResponseData; let siteResponseData;
@@ -156,7 +156,7 @@ export default async function omadaProxyHandler(req, res) {
if (controllerVersionMajor === 3) { if (controllerVersionMajor === 3) {
// Omada v3 controller requires switching site // Omada v3 controller requires switching site
const switchUrl = `${widget.url}/web/v1/controller?ajax=&token=${token}`; const switchUrl = `${url}/web/v1/controller?ajax=&token=${token}`;
method = "POST"; method = "POST";
body = { body = {
method: "switchSite", method: "switchSite",
@@ -181,7 +181,7 @@ export default async function omadaProxyHandler(req, res) {
return res.status(status).json({error: {message: "Error switching site", url: switchUrl, data}}); return res.status(status).json({error: {message: "Error switching site", url: switchUrl, data}});
} }
const statsUrl = `${widget.url}/web/v1/controller?getGlobalStat=&token=${token}`; const statsUrl = `${url}/web/v1/controller?getGlobalStat=&token=${token}`;
[status, contentType, data] = await httpProxy(statsUrl, { [status, contentType, data] = await httpProxy(statsUrl, {
method, method,
params, params,

View File

@@ -84,9 +84,9 @@ export default async function pyloadProxyHandler(req, res) {
if (data?.error || status !== 200) { if (data?.error || status !== 200) {
try { try {
return res.status(status).send({error: {message: "HTTP error communicating with Plex API", data: Buffer.from(data).toString()}}); return res.status(status).send({error: {message: "HTTP error communicating with Pyload API", data: Buffer.from(data).toString()}});
} catch (e) { } catch (e) {
return res.status(status).send({error: {message: "HTTP error communicating with Plex API", data}}); return res.status(status).send({error: {message: "HTTP error communicating with Pyload API", data}});
} }
} }
@@ -95,7 +95,7 @@ export default async function pyloadProxyHandler(req, res) {
} }
} catch (e) { } catch (e) {
logger.error(e); logger.error(e);
return res.status(500).send({error: {message: `Error communicating with Plex API: ${e.toString()}`}}); return res.status(500).send({error: {message: `Error communicating with Pyload API: ${e.toString()}`}});
} }
return res.status(400).json({ error: 'Invalid proxy service type' }); return res.status(400).json({ error: 'Invalid proxy service type' });