Compare commits

..

9 Commits

16 changed files with 72 additions and 41 deletions

View File

@@ -98,6 +98,8 @@ When the Kubernetes cluster connection has been properly configured, this servic
If you are using multiple instances of homepage, an `instance` annotation can be specified to limit services to a specific instance. If no instance is provided, the service will be visible on all instances.
If you have a single service that needs to be shown on multiple specific instances of homepage (but not on all of them), the service can be annotated by multiple `instance.name` annotations, where `name` can be the names of your specific multiple homepage instances. For example, a service that is annotated with `gethomepage.dev/instance.public: ""` and `gethomepage.dev/instance.internal: ""` will be shown on `public` and `internal` homepage instances.
### Traefik IngressRoute support
Homepage can also read ingresses defined using the Traefik IngressRoute custom resource definition. Due to the complex nature of Traefik routing rules, it is required for the `gethomepage.dev/href` annotation to be set:

View File

@@ -140,7 +140,7 @@
"connectionStatusPendingDisconnect": "Desconexión pendiente",
"connectionStatusDisconnecting": "Desconectando",
"connectionStatusDisconnected": "Desconectado",
"connectionStatusConnected": "Connected",
"connectionStatusConnected": "Conectado",
"uptime": "Tiempo activo",
"maxDown": "Descarga máxima",
"maxUp": "Subida máxima",
@@ -279,9 +279,9 @@
},
"netalertx": {
"total": "Total",
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
"connected": "Conectado",
"new_devices": "Nuevos dispositivos",
"down_alerts": "Alertas de caída"
},
"pihole": {
"queries": "Consultas",
@@ -544,7 +544,7 @@
"hdhomerun": {
"channels": "Canales",
"hd": "Alta definición",
"tunerCount": "Tuners",
"tunerCount": "Sintonizadores",
"channelNumber": "Canal",
"channelNetwork": "Red",
"signalStrength": "Intensidad",
@@ -827,7 +827,7 @@
},
"romm": {
"platforms": "Plataformas",
"totalRoms": "Total ROMs"
"totalRoms": "ROMs totales"
},
"netdata": {
"warnings": "Advertencias",
@@ -835,38 +835,38 @@
},
"plantit": {
"events": "Eventos",
"plants": "Plants",
"plants": "Plantas",
"photos": "Fotos",
"species": "Species"
"species": "Especies"
},
"gitea": {
"notifications": "Notificaciones",
"issues": "Números",
"pulls": "Pull Requests"
"pulls": "Solicitudes de cambios"
},
"stash": {
"scenes": "Scenes",
"scenesPlayed": "Scenes Played",
"playCount": "Total Plays",
"playDuration": "Time Watched",
"sceneSize": "Scenes Size",
"sceneDuration": "Scenes Duration",
"scenes": "Escenas",
"scenesPlayed": "Escenas reproducidas",
"playCount": "Reproducciones totales",
"playDuration": "Tiempo visto",
"sceneSize": "Tamaño de las escenas",
"sceneDuration": "Duración de las escenas",
"images": "Imágenes",
"imageSize": "Tamaño de imagen",
"galleries": "Galerías",
"performers": "Performers",
"studios": "Studios",
"performers": "Intérpretes",
"studios": "Estudios",
"movies": "Películas",
"tags": "Etiquetas",
"oCount": "O Count"
"oCount": "O cuenta"
},
"tandoor": {
"users": "Usuarios",
"recipes": "Recetas",
"keywords": "Keywords"
"keywords": "Palabras clave"
},
"homebox": {
"items": "Items",
"items": "Objetos",
"totalWithWarranty": "Con Garantía",
"locations": "Ubicaciones",
"labels": "Etiquetas",
@@ -875,10 +875,10 @@
},
"crowdsec": {
"alerts": "Alertas",
"bans": "Bans"
"bans": "Baneos"
},
"wgeasy": {
"connected": "Connected",
"connected": "Conectado",
"enabled": "Activado",
"disabled": "Desactivado",
"total": "Total"

View File

@@ -884,9 +884,9 @@
"total": "Total"
},
"swagdashboard": {
"proxied": "Proxied",
"auth": "With Auth",
"outdated": "Outdated",
"banned": "Banned"
"proxied": "Par proxy",
"auth": "Avec authentification",
"outdated": "Obsolète",
"banned": "Banni"
}
}

View File

@@ -1,9 +1,11 @@
import cachedFetch from "utils/proxy/cached-fetch";
import { getSettings } from "utils/config/config";
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
export default async function handler(req, res) {
const { latitude, longitude, units, provider, cache, lang } = req.query;
let { apiKey } = req.query;
const { latitude, longitude, units, provider, cache, lang, index } = req.query;
const privateWidgetOptions = await getPrivateWidgetOptions("openweathermap", index);
let { apiKey } = privateWidgetOptions;
if (!apiKey && !provider) {
return res.status(400).json({ error: "Missing API key or provider" });

View File

@@ -1,9 +1,11 @@
import cachedFetch from "utils/proxy/cached-fetch";
import { getSettings } from "utils/config/config";
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
export default async function handler(req, res) {
const { latitude, longitude, provider, cache, lang } = req.query;
let { apiKey } = req.query;
const { latitude, longitude, provider, cache, lang, index } = req.query;
const privateWidgetOptions = await getPrivateWidgetOptions("weatherapi", index);
let { apiKey } = privateWidgetOptions;
if (!apiKey && !provider) {
return res.status(400).json({ error: "Missing API key or provider" });

View File

@@ -254,7 +254,8 @@ export async function servicesFromKubernetes() {
ingress.metadata.annotations &&
ingress.metadata.annotations[`${ANNOTATION_BASE}/enabled`] === "true" &&
(!ingress.metadata.annotations[`${ANNOTATION_BASE}/instance`] ||
ingress.metadata.annotations[`${ANNOTATION_BASE}/instance`] === instanceName),
ingress.metadata.annotations[`${ANNOTATION_BASE}/instance`] === instanceName ||
`${ANNOTATION_BASE}/instance.${instanceName}` in ingress.metadata.annotations),
)
.map((ingress) => {
let constructedService = {

View File

@@ -32,7 +32,7 @@ export async function cleanWidgetGroups(widgets) {
const optionKeys = Object.keys(sanitizedOptions);
// delete private options from the sanitized options
["username", "password", "key"].forEach((pO) => {
["username", "password", "key", "apiKey"].forEach((pO) => {
if (optionKeys.includes(pO)) {
delete sanitizedOptions[pO];
}
@@ -57,7 +57,7 @@ export async function getPrivateWidgetOptions(type, widgetIndex) {
const widgets = await widgetsFromConfig();
const privateOptions = widgets.map((widget) => {
const { index, url, username, password, key } = widget.options;
const { index, url, username, password, key, apiKey } = widget.options;
return {
type: widget.type,
@@ -67,6 +67,7 @@ export async function getPrivateWidgetOptions(type, widgetIndex) {
username,
password,
key,
apiKey,
},
};
});

View File

@@ -31,6 +31,10 @@ export async function sendJsonRpcRequest(url, method, params, username, password
if (status === 200) {
const json = JSON.parse(data.toString());
if (json.id === null) {
json.id = 1;
}
// in order to get access to the underlying error object in the JSON response
// you must set `result` equal to undefined
if (json.error && json.result === null) {

View File

@@ -8,7 +8,7 @@ export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: resultData, error: resultError } = useWidgetAPI(widget, "result");
const { data: resultData, error: resultError } = useWidgetAPI(widget, "upstreams");
if (resultError) {
return <Container service={service} error={resultError} />;

View File

@@ -1,8 +1,14 @@
import genericProxyHandler from "utils/proxy/handlers/generic";
const widget = {
api: "{url}/reverse_proxy/upstreams",
api: "{url}/{endpoint}",
proxyHandler: genericProxyHandler,
mappings: {
upstreams: {
endpoint: "reverse_proxy/upstreams",
},
},
};
export default widget;

View File

@@ -65,7 +65,7 @@ export default function Component({ service }) {
return (
<Container service={service}>
<div className={classNames(service.description ? "-top-10" : "-top-8", "absolute right-1")}>
<div className={classNames(service.description ? "-top-10" : "-top-8", "absolute right-1 z-20")}>
<Dropdown options={dateRangeOptions} value={dateRange} setValue={setDateRange} />
</div>

View File

@@ -9,7 +9,7 @@ export default function Component({ service }) {
const { widget } = service;
const { data: omadaData, error: omadaAPIError } = useWidgetAPI(widget, {
const { data: omadaData, error: omadaAPIError } = useWidgetAPI(widget, "info", {
refreshInterval: 5000,
});

View File

@@ -2,6 +2,12 @@ import omadaProxyHandler from "./proxy";
const widget = {
proxyHandler: omadaProxyHandler,
mappings: {
info: {
endpoint: "api/info",
},
},
};
export default widget;

View File

@@ -77,7 +77,7 @@ async function fetchSystem(url) {
const systemResponse = JSON.parse(data.toString())[1];
const response = {
uptime: systemResponse.uptime,
cpuLoad: systemResponse.load[1],
cpuLoad: (systemResponse.load[1] / 65536.0).toFixed(2),
};
return [200, contentType, response];
}

View File

@@ -28,7 +28,7 @@ export default function Component({ service }) {
const enabled = infoData.filter((item) => item.enabled).length;
const disabled = infoData.length - enabled;
const connectionThreshold = widget.threshold ?? 2 * 60 * 1000;
const connectionThreshold = (widget.threshold ?? 2) * 60 * 1000;
const currentTime = new Date();
const connected = infoData.filter(
(item) => currentTime - new Date(item.latestHandshakeAt) < connectionThreshold,

View File

@@ -21,14 +21,21 @@ async function login(widget, service) {
});
try {
const connectSidCookie = responseHeaders["set-cookie"]
let connectSidCookie = responseHeaders["set-cookie"];
if (!connectSidCookie) {
const sid = cache.get(`${sessionSIDCacheKey}.${service}`);
if (sid) {
return sid;
}
}
connectSidCookie = connectSidCookie
.find((cookie) => cookie.startsWith("connect.sid="))
.split(";")[0]
.replace("connect.sid=", "");
cache.put(`${sessionSIDCacheKey}.${service}`, connectSidCookie);
return connectSidCookie;
} catch (e) {
logger.error(`Error logging into wg-easy`);
logger.error(`Error logging into wg-easy, error: ${e}`);
cache.del(`${sessionSIDCacheKey}.${service}`);
return null;
}