Compare commits

..

1 Commits

Author SHA1 Message Date
Crowdin Bot
9ec2b1a669 New Crowdin translations by GitHub Action 2025-12-04 12:15:42 +00:00
61 changed files with 29 additions and 575 deletions

View File

@@ -54,7 +54,7 @@ RUN apk add --no-cache su-exec iputils-ping shadow
USER root
ENV NODE_ENV=production
ENV HOSTNAME=::
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
EXPOSE $PORT

View File

@@ -12,15 +12,6 @@ export PGID=${PGID:-0}
export HOMEPAGE_BUILDTIME=$(date +%s)
# Try IPv6 first (dual stack when available), but fall back to IPv4 if the bind fails
export HOSTNAME=${HOSTNAME:-::}
if [ "$HOSTNAME" = "::" ]; then
if ! node -e "const server = require('http').createServer(() => {}); const host = '::'; const port = process.env.PORT || 3000; server.once('error', (err) => { console.error('IPv6 bind failed:', err.message); process.exit(1); }); server.listen(port, host, () => server.close(() => process.exit(0)));"; then
echo "Falling back to IPv4 bind at 0.0.0.0"
export HOSTNAME=0.0.0.0
fi
fi
# Check ownership before chown
if [ -e /app/config ]; then
CURRENT_UID=$(stat -c %u /app/config)

View File

@@ -159,19 +159,6 @@ Widgets can tint their metric block text automatically based on rules defined al
Supported numeric operators for the `when` property are `gt`, `gte`, `lt`, `lte`, `eq`, `ne`, `between`, and `outside`. String rules support `equals`, `includes`, `startsWith`, `endsWith`, and `regex`. Each rule can be inverted with `negate: true`, and string rules may pass `caseSensitive: true` or custom regex `flags`. The highlight engine does its best to coerce formatted values, but you will get the most reliable results when you pass plain numbers or strings into `<Block>`.
#### Value Only Highlighting
You can optionally apply highlighting only to the value portion of a block (not the label) by setting `valueOnly: true` on the field configuration. This keeps the label visible while highlighting only the metric value itself.
```yaml
- Sonarr:
...
highlight:
queued:
valueOnly: true
...
```
## Descriptions
Services may have descriptions,

View File

@@ -14,7 +14,7 @@ services:
- 3000:3000
volumes:
- /path/to/config:/app/config # Make sure your local config directory exists
- /var/run/docker.sock:/var/run/docker.sock:ro # (optional) For docker integrations
- /var/run/docker.sock:/var/run/docker.sock # (optional) For docker integrations
environment:
HOMEPAGE_ALLOWED_HOSTS: gethomepage.dev # required, may need port. See gethomepage.dev/installation/#homepage_allowed_hosts
```
@@ -36,7 +36,7 @@ services:
- 3000:3000
volumes:
- /path/to/config:/app/config # Make sure your local config directory exists
- /var/run/docker.sock:/var/run/docker.sock:ro # (optional) For docker integrations, see alternative methods
- /var/run/docker.sock:/var/run/docker.sock # (optional) For docker integrations, see alternative methods
environment:
HOMEPAGE_ALLOWED_HOSTS: gethomepage.dev # required, may need port. See gethomepage.dev/installation/#homepage_allowed_hosts
PUID: $PUID

View File

@@ -68,7 +68,7 @@ All service widgets work essentially the same, that is, homepage makes a proxied
If, after correctly adding and mapping your custom icons via the [Icons](../configs/services.md#icons) instructions, you are still unable to see your icons please try recreating your container.
## Disabling IPv6 for http requests {#disabling-ipv6}
## Disabling IPv6
If you are having issues with certain widgets that are unable to reach public APIs (e.g. weather), in certain setups you may need to disable IPv6. You can set the environment variable `HOMEPAGE_PROXY_DISABLE_IPV6` to `true` to disable IPv6 for the homepage proxy.

View File

@@ -1,29 +0,0 @@
---
title: Pangolin
description: Pangolin Widget Configuration
---
Learn more about [Pangolin](https://github.com/fosrl/pangolin).
This widget shows sites (online/total), resources (healthy/total), targets (healthy/total), and traffic statistics for a Pangolin organization. A resource is considered healthy if at least one of its targets is healthy, or if it has no targets.
Allowed fields: `["sites", "resources", "targets", "traffic", "in", "out"]` (maximum of 4).
```yaml
widget:
type: pangolin
url: https://api.pangolin.net
key: your-api-key
org: your-org-id
```
Find your organization ID in the URL when logged in (e.g., `https://app.pangolin.net/{org-id}/...`).
## API Key Setup
Create an API key with the following permissions:
- **List Sites**
- **List Resources**
**Self-Hosted:** Enable the [Integration API](https://docs.pangolin.net/self-host/advanced/integration-api) in your Pangolin configuration before creating the key.

View File

@@ -122,7 +122,6 @@ nav:
- widgets/services/opnsense.md
- widgets/services/openwrt.md
- widgets/services/overseerr.md
- widgets/services/pangolin.md
- widgets/services/paperlessngx.md
- widgets/services/peanut.md
- widgets/services/pfsense.md

View File

@@ -599,15 +599,6 @@
"inbox": "Inmandjie",
"total": "Totaal"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Bronne",
"targets": "Teikens",
"traffic": "Verkeer",
"in": "In",
"out": "Uit"
},
"peanut": {
"battery_charge": "Batterylading",
"ups_load": "SVE-lading",

View File

@@ -599,15 +599,6 @@
"inbox": "صندوق الوارد",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "شحن البطارية",
"ups_load": "حمل UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Входящи",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Заряд на батерията",
"ups_load": "Натоварване на UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Safata d'entrada",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Càrrega de la bateria",
"ups_load": "Càrrega del SAI",

View File

@@ -599,15 +599,6 @@
"inbox": "Doručená pošta",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Úroveň baterie",
"ups_load": "Zítěž UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Indbakke",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Batteriniveau",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Posteingang",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Akkuladung",
"ups_load": "USV-Auslastung",

View File

@@ -599,15 +599,6 @@
"inbox": "Εισερχόμενα",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Bandeja de entrada",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Carga de la batería",
"ups_load": "Carga del UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Boîte de réception",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Charge de la batterie",
"ups_load": "Charge de lASI",

View File

@@ -599,15 +599,6 @@
"inbox": "תיבת דואר נכנס",
"total": "סה\"כ"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "טעינת סוללה",
"ups_load": "עומס UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Ulazni sandučić",
"total": "Ukupno"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Napunjenost baterije",
"ups_load": "UPS opterećenje",

View File

@@ -599,15 +599,6 @@
"inbox": "Beérkezett",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Akku töltöttsége",
"ups_load": "UPS terheltsége",

View File

@@ -599,15 +599,6 @@
"inbox": "Kotak Masuk",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Sisa Baterai",
"ups_load": "Beban UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "In arrivo",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Carica Batteria",
"ups_load": "Carico UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "受信トレイ",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "バッテリー充電",
"ups_load": "UPS 負荷",

View File

@@ -599,15 +599,6 @@
"inbox": "받은 편지함",
"total": "전체"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "배터리 충전",
"ups_load": "UPS 부하",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Peti Masuk",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Bateri dicas",
"ups_load": "Beban UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Postvak In",
"total": "Totaal"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Batterij opladen",
"ups_load": "UPS-belasting",

View File

@@ -599,15 +599,6 @@
"inbox": "Innboks",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Batteriladning",
"ups_load": "UPS last",

View File

@@ -599,15 +599,6 @@
"inbox": "Skrzynka odbiorcza",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Stan baterii",
"ups_load": "Obciążenie UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Caixa de entrada",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Carga da bateria",
"ups_load": "Carga da UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Caixa de entrada",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Carga da bateria",
"ups_load": "Carga do UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Încărcare Baterie",
"ups_load": "UPS Load",

View File

@@ -193,7 +193,7 @@
"tv": "Сериалы"
},
"sabnzbd": {
"rate": "",
"rate": "Rate",
"queue": "Очередь",
"timeleft": "Осталось"
},
@@ -599,15 +599,6 @@
"inbox": "Входящие",
"total": "Всего"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Заряд батареи",
"ups_load": "Нагрузка на UPS",
@@ -1098,7 +1089,7 @@
"unraid": {
"STARTED": "Started",
"STOPPED": "Stopped",
"NEW_ARRAY": "Новый массив",
"NEW_ARRAY": "New Array",
"RECON_DISK": "Reconstructing Disk",
"DISABLE_DISK": "Disk Disabled",
"SWAP_DSBL": "Swap Disable",
@@ -1107,8 +1098,8 @@
"TOO_MANY_MISSING_DISKS": "Too Many Missing Disks",
"NEW_DISK_TOO_SMALL": "New Disk Too Small",
"NO_DATA_DISKS": "No Data Disks",
"notifications": "Уведомления",
"status": "Статус",
"notifications": "Notifications",
"status": "Status",
"cpu": "CPU",
"memoryUsed": "Memory Used",
"memoryAvailable": "Memory Available",

View File

@@ -599,15 +599,6 @@
"inbox": "Schránka správ",
"total": "Celkom"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Nabitie batérie",
"ups_load": "Záťaž UPS",

View File

@@ -599,15 +599,6 @@
"inbox": "Prejeto",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Napolnjenost baterije",
"ups_load": "UPS obremenitev",

View File

@@ -599,15 +599,6 @@
"inbox": "Примљено",
"total": "Укупно"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Напуњеност батерије",
"ups_load": "Оптерећење УПС-а",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "Gelen Kutusu",
"total": "Toplam"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Pil Yüzdesi",
"ups_load": "UPS Yükü",

View File

@@ -599,15 +599,6 @@
"inbox": "Вхідні",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Заряд батареї",
"ups_load": "UPS завантаження",

View File

@@ -599,15 +599,6 @@
"inbox": "Inbox",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "Battery Charge",
"ups_load": "UPS Load",

View File

@@ -599,15 +599,6 @@
"inbox": "收件箱",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "充電",
"ups_load": "備用電源負載",

View File

@@ -599,15 +599,6 @@
"inbox": "收件箱",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "充电中",
"ups_load": "UPS 负载",

View File

@@ -599,15 +599,6 @@
"inbox": "收件箱",
"total": "Total"
},
"pangolin": {
"orgs": "Orgs",
"sites": "Sites",
"resources": "Resources",
"targets": "Targets",
"traffic": "Traffic",
"in": "In",
"out": "Out"
},
"peanut": {
"battery_charge": "充電",
"ups_load": "備用電源負載",

View File

@@ -32,8 +32,6 @@ export default function Block({ value, label, field }) {
return getHighlightClass(highlight.level, highlightConfig);
}, [highlight, highlightConfig]);
const applyToValueOnly = highlight?.valueOnly === true;
return (
<div
className={classNames(
@@ -46,11 +44,7 @@ export default function Block({ value, label, field }) {
data-highlight-source={highlight?.source}
>
<div className="font-thin text-sm">{value === undefined || value === null ? "-" : value}</div>
<div
className={classNames("font-bold text-xs uppercase", applyToValueOnly && "text-theme-700 dark:text-theme-200")}
>
{t(label)}
</div>
<div className="font-bold text-xs uppercase">{t(label)}</div>
</div>
);
}

View File

@@ -1,14 +1,4 @@
import {
Combobox,
ComboboxInput,
ComboboxOption,
ComboboxOptions,
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions,
Transition,
} from "@headlessui/react";
import { Combobox, Listbox, Transition } from "@headlessui/react";
import classNames from "classnames";
import { useTranslation } from "next-i18next";
import { Fragment, useEffect, useState } from "react";
@@ -168,7 +158,7 @@ export default function Search({ options }) {
<div className="flex-col relative h-8 my-4 min-w-fit z-20">
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" />
<Combobox value={query}>
<ComboboxInput
<Combobox.Input
type="text"
className="
overflow-hidden w-full h-full rounded-md
@@ -199,7 +189,7 @@ export default function Search({ options }) {
disabled={availableProviderIds?.length === 1}
>
<div>
<ListboxButton
<Listbox.Button
className="
absolute right-0.5 bottom-0.5 rounded-r-md px-4 py-2
text-white font-medium text-sm
@@ -208,7 +198,7 @@ export default function Search({ options }) {
>
<selectedProvider.icon className="text-white w-3 h-3" />
<span className="sr-only">{t("search.search")}</span>
</ListboxButton>
</Listbox.Button>
</div>
<Transition
as={Fragment}
@@ -219,7 +209,7 @@ export default function Search({ options }) {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<ListboxOptions
<Listbox.Options
className="absolute right-0 z-10 mt-1 origin-top-right rounded-md
bg-theme-100 dark:bg-theme-600 shadow-lg
ring-1 ring-black ring-opacity-5 focus:outline-hidden"
@@ -228,7 +218,7 @@ export default function Search({ options }) {
{availableProviderIds.map((providerId) => {
const p = searchProviders[providerId];
return (
<ListboxOption key={providerId} value={p} as={Fragment}>
<Listbox.Option key={providerId} value={p} as={Fragment}>
{({ active }) => (
<li
className={classNames(
@@ -239,23 +229,23 @@ export default function Search({ options }) {
<p.icon className="h-4 w-4 mx-4 my-2" />
</li>
)}
</ListboxOption>
</Listbox.Option>
);
})}
</div>
</ListboxOptions>
</Listbox.Options>
</Transition>
</Listbox>
{searchSuggestions[1]?.length > 0 && (
<ComboboxOptions className="mt-1 rounded-md bg-theme-50 dark:bg-theme-800 border border-theme-300 dark:border-theme-200/30 cursor-pointer shadow-lg">
<Combobox.Options className="mt-1 rounded-md bg-theme-50 dark:bg-theme-800 border border-theme-300 dark:border-theme-200/30 cursor-pointer shadow-lg">
<div className="p-1 bg-white/50 dark:bg-white/10 text-theme-900/90 dark:text-white/90 text-xs">
<ComboboxOption key={query} value={query} />
<Combobox.Option key={query} value={query} />
{searchSuggestions[1].map((suggestion) => (
<ComboboxOption
<Combobox.Option
key={suggestion}
value={suggestion}
onMouseDown={() => {
onClick={() => {
doSearch(suggestion);
}}
className="flex w-full"
@@ -276,10 +266,10 @@ export default function Search({ options }) {
</div>
);
}}
</ComboboxOption>
</Combobox.Option>
))}
</div>
</ComboboxOptions>
</Combobox.Options>
)}
</Combobox>
</div>

View File

@@ -4,7 +4,7 @@ export function middleware(req) {
// Check the Host header, if HOMEPAGE_ALLOWED_HOSTS is set
const host = req.headers.get("host");
const port = process.env.PORT || 3000;
let allowedHosts = [`localhost:${port}`, `127.0.0.1:${port}`, `[::1]:${port}`];
let allowedHosts = [`localhost:${port}`, `127.0.0.1:${port}`];
const allowAll = process.env.HOMEPAGE_ALLOWED_HOSTS === "*";
if (process.env.HOMEPAGE_ALLOWED_HOSTS) {
allowedHosts = allowedHosts.concat(process.env.HOMEPAGE_ALLOWED_HOSTS.split(","));

View File

@@ -111,7 +111,7 @@ export async function servicesFromDocker() {
};
}
let substitutedVal = substituteEnvironmentVars(containerLabels[label]);
if (value === "widget.version" || /^widgets\[\d+\]\.version$/.test(value)) {
if (value === "widget.version") {
substitutedVal = parseInt(substitutedVal, 10);
}
shvl.set(constructedService, value, substitutedVal);

View File

@@ -200,7 +200,7 @@ const ensureArray = (value) => {
};
const findHighlightLevel = (ruleSet, numericValue, stringValue) => {
const { numeric, string, valueOnly } = ruleSet;
const { numeric, string } = ruleSet;
if (numeric && numericValue !== undefined) {
const numericRules = ensureArray(numeric);
@@ -208,7 +208,7 @@ const findHighlightLevel = (ruleSet, numericValue, stringValue) => {
for (const candidate of numericCandidates) {
for (const rule of numericRules) {
if (rule?.level && evaluateNumericRule(candidate, rule)) {
return { level: rule.level, source: "numeric", rule, valueOnly };
return { level: rule.level, source: "numeric", rule };
}
}
}
@@ -218,7 +218,7 @@ const findHighlightLevel = (ruleSet, numericValue, stringValue) => {
const stringRules = ensureArray(string);
for (const rule of stringRules) {
if (rule?.level && evaluateStringRule(stringValue, rule)) {
return { level: rule.level, source: "string", rule, valueOnly };
return { level: rule.level, source: "string", rule };
}
}
}

View File

@@ -53,7 +53,6 @@ export default async function credentialedProxyHandler(req, res, map) {
"linkwarden",
"mealie",
"netalertx",
"pangolin",
"tailscale",
"tandoor",
"pterodactyl",

View File

@@ -111,7 +111,7 @@ export async function cachedRequest(url, duration = 5, ua = "homepage") {
export async function httpProxy(url, params = {}) {
const constructedUrl = new URL(url);
const disableIpv6 = process.env.HOMEPAGE_PROXY_DISABLE_IPV6 === "true";
const agentOptions = disableIpv6 ? { family: 4, autoSelectFamily: false } : { autoSelectFamilyAttemptTimeout: 500 };
const agentOptions = disableIpv6 ? { family: 4, autoSelectFamily: false } : {};
let request = null;
if (constructedUrl.protocol === "https:") {

View File

@@ -97,7 +97,6 @@ const components = {
openmediavault: dynamic(() => import("./openmediavault/component")),
openwrt: dynamic(() => import("./openwrt/component")),
paperlessngx: dynamic(() => import("./paperlessngx/component")),
pangolin: dynamic(() => import("./pangolin/component")),
pfsense: dynamic(() => import("./pfsense/component")),
photoprism: dynamic(() => import("./photoprism/component")),
proxmoxbackupserver: dynamic(() => import("./proxmoxbackupserver/component")),

View File

@@ -1,70 +0,0 @@
import Block from "components/services/widget/block";
import Container from "components/services/widget/container";
import { useTranslation } from "next-i18next";
import useWidgetAPI from "utils/proxy/use-widget-api";
const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
if (!widget.fields) {
widget.fields = ["sites", "resources", "targets", "traffic"];
} else if (widget.fields?.length > MAX_ALLOWED_FIELDS) {
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
}
const { data: sitesData, error: sitesError } = useWidgetAPI(widget, "sites");
const { data: resourcesData, error: resourcesError } = useWidgetAPI(widget, "resources");
if (sitesError || resourcesError) {
return <Container service={service} error={sitesError || resourcesError} />;
}
if (!sitesData || !resourcesData) {
return (
<Container service={service}>
<Block label="pangolin.sites" />
<Block label="pangolin.resources" />
<Block label="pangolin.targets" />
<Block label="pangolin.traffic" />
<Block label="pangolin.in" />
<Block label="pangolin.out" />
</Container>
);
}
const sites = sitesData.data?.sites || [];
const resources = resourcesData.data?.resources || [];
const sitesTotal = sites.length;
const sitesOnline = sites.filter((s) => s.online).length;
const resourcesTotal = resources.length;
const resourcesHealthy = resources.filter(
(r) => r.targets?.some((t) => t.healthStatus !== "unhealthy") || !r.targets?.length,
).length;
const targetsTotal = resources.reduce((sum, r) => sum + (r.targets?.length || 0), 0);
const targetsHealthy = resources.reduce(
(sum, r) => sum + (r.targets?.filter((t) => t.healthStatus !== "unhealthy").length || 0),
0,
);
const trafficIn = sites.reduce((sum, s) => sum + (s.megabytesIn || 0), 0) * 1_000_000;
const trafficOut = sites.reduce((sum, s) => sum + (s.megabytesOut || 0), 0) * 1_000_000;
const trafficTotal = trafficIn + trafficOut;
return (
<Container service={service}>
<Block label="pangolin.sites" value={`${sitesOnline} / ${sitesTotal}`} />
<Block label="pangolin.resources" value={`${resourcesHealthy} / ${resourcesTotal}`} />
<Block label="pangolin.targets" value={`${targetsHealthy} / ${targetsTotal}`} />
<Block label="pangolin.traffic" value={t("common.bytes", { value: trafficTotal })} />
<Block label="pangolin.in" value={t("common.bytes", { value: trafficIn })} />
<Block label="pangolin.out" value={t("common.bytes", { value: trafficOut })} />
</Container>
);
}

View File

@@ -1,17 +0,0 @@
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = {
api: "{url}/v1/{endpoint}",
proxyHandler: credentialedProxyHandler,
mappings: {
sites: {
endpoint: "org/{org}/sites",
},
resources: {
endpoint: "org/{org}/resources",
},
},
};
export default widget;

View File

@@ -87,7 +87,6 @@ import openmediavault from "./openmediavault/widget";
import openwrt from "./openwrt/widget";
import opnsense from "./opnsense/widget";
import overseerr from "./overseerr/widget";
import pangolin from "./pangolin/widget";
import paperlessngx from "./paperlessngx/widget";
import peanut from "./peanut/widget";
import pfsense from "./pfsense/widget";
@@ -238,7 +237,6 @@ const widgets = {
openmediavault,
openwrt,
paperlessngx,
pangolin,
peanut,
pfsense,
photoprism,