Compare commits

..

1 Commits

Author SHA1 Message Date
shamoon
8887281246 Documentation: make docker.sock mount read-only in docs 2025-11-30 20:04:42 -08:00
69 changed files with 159 additions and 351 deletions

View File

@@ -38,4 +38,3 @@ What type of change does your PR introduce to Homepage?
- [ ] If applicable, I have reviewed the [feature / enhancement](https://gethomepage.dev/more/development/#new-feature-guidelines) and / or [service widget guidelines](https://gethomepage.dev/more/development/#service-widget-guidelines).
- [ ] I have checked that all code style checks pass using [pre-commit hooks](https://gethomepage.dev/more/development/#code-formatting-with-pre-commit-hooks) and [linting checks](https://gethomepage.dev/more/development/#code-linting).
- [ ] If applicable, I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
- [ ] In the description above I have disclosed the use of AI tools in the coding of this PR.

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: crowdin action
uses: crowdin/github-action@v2
with:

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Install python
uses: actions/setup-python@v6
@@ -62,7 +62,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Extract Docker metadata
id: meta

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Install python
uses: actions/setup-python@v6
with:
@@ -32,7 +32,7 @@ jobs:
needs:
- pre-commit
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -54,7 +54,7 @@ jobs:
needs:
- pre-commit
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Configure Git Credentials
run: |
git config user.name github-actions[bot]

View File

@@ -571,18 +571,3 @@ or per service widget (`services.yaml`) with:
```
If either value is set to true, the error message will be hidden.
## Disable Search Engine Indexing
You can request that search engines not to index your Homepage instance by enabling the `disableIndexing` setting.
```yaml
disableIndexing: true
```
When enabled, this will:
- Disallow all crawlers in `robots.txt`
- Add `<meta name="robots" content="noindex, nofollow">` tags to prevent indexing
By default this feature is disabled.

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 # (optional) For docker integrations
- /var/run/docker.sock:/var/run/docker.sock:ro # (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 # (optional) For docker integrations, see alternative methods
- /var/run/docker.sock:/var/run/docker.sock:ro # (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

@@ -8,9 +8,6 @@ Learn more about [Crowdsec](https://crowdsec.net).
See the [crowdsec docs](https://docs.crowdsec.net/docs/local_api/intro/#machines) for information about registering a machine,
in most instances you can use the default credentials (`/etc/crowdsec/local_api_credentials.yaml`).
!!! note
Without the `limit24h` option, the widget will fetch all alerts which is limited to 100 by the API to avoid performance issues.
Allowed fields: `["alerts", "bans"]`.
```yaml
@@ -19,5 +16,4 @@ widget:
url: http://crowdsechostorip:port
username: localhost # machine_id in crowdsec
password: password
limit24h: true # optional, limits alerts to last 24h. Default: false
```

View File

@@ -14,6 +14,4 @@ widget:
type: frigate
url: http://frigate.host.or.ip:port
enableRecentEvents: true # Optional, defaults to false
username: username # optional
password: password # optional
```

View File

@@ -15,7 +15,7 @@ See the [official docs](https://github.com/ghostfolio/ghostfolio#authorization-b
_Note that the Bearer token is valid for 6 months, after which a new one must be generated._
Allowed fields: `["gross_percent_today", "gross_percent_1y", "gross_percent_max", "net_worth"]`
Allowed fields: `["gross_percent_today", "gross_percent_1y", "gross_percent_max"]`
```yaml
widget:

View File

@@ -12,17 +12,11 @@ Learn more about [Gluetun](https://github.com/qdm12/gluetun).
Allowed fields: `["public_ip", "region", "country", "port_forwarded"]`.
Default fields: `["public_ip", "region", "country"]`.
To setup authentication, follow [the official Gluetun documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication). Note that to use the api key method, you must add the route `GET /v1/publicip/ip` to the `routes` array in your Gluetun config.toml. Similarly, if you want to include the `port_forwarded` field, you must add the route `GET /v1/openvpn/portforwarded` (or `/v1/portforward`) to your Gluetun config.toml.
| Gluetun Version | Homepage Widget Version |
| --------------- | ----------------------- |
| < 3.40.1 | 1 (default) |
| >= 3.40.1 | 2 |
To setup authentication, follow [the official Gluetun documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication). Note that to use the api key method, you must add the route `GET /v1/publicip/ip` to the `routes` array in your Gluetun config.toml. Similarly, if you want to include the `port_forwarded` field, you must add the route `GET /v1/openvpn/portforwarded` to your Gluetun config.toml.
```yaml
widget:
type: gluetun
url: http://gluetun.host.or.ip:port
key: gluetunkey # Not required if /v1/publicip/ip endpoint is configured with `auth = none`
version: 2 # optional, default is 1
```

View File

@@ -29,14 +29,14 @@
"next-i18next": "^12.1.0",
"ping": "^0.4.4",
"pretty-bytes": "^7.1.0",
"raw-body": "^3.0.2",
"raw-body": "^3.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.5.3",
"react-icons": "^5.4.0",
"recharts": "^3.1.2",
"swr": "^2.3.3",
"systeminformation": "^5.27.11",
"systeminformation": "^5.27.7",
"tough-cookie": "^6.0.0",
"urbackup-server-api": "^0.8.9",
"winston": "^3.17.0",
@@ -54,7 +54,7 @@
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.2.0",
"postcss": "^8.5.6",
"prettier": "^3.7.3",
"prettier": "^3.6.2",
"prettier-plugin-organize-imports": "^4.3.0",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4.0.9",

58
pnpm-lock.yaml generated
View File

@@ -63,8 +63,8 @@ importers:
specifier: ^7.1.0
version: 7.1.0
raw-body:
specifier: ^3.0.2
version: 3.0.2
specifier: ^3.0.1
version: 3.0.1
react:
specifier: ^18.3.1
version: 18.3.1
@@ -84,8 +84,8 @@ importers:
specifier: ^2.3.3
version: 2.3.3(react@18.3.1)
systeminformation:
specifier: ^5.27.11
version: 5.27.11
specifier: ^5.27.7
version: 5.27.7
tough-cookie:
specifier: ^6.0.0
version: 6.0.0
@@ -122,7 +122,7 @@ importers:
version: 6.10.2(eslint@9.25.1(jiti@2.6.1))
eslint-plugin-prettier:
specifier: ^5.5.4
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.7.3)
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.6.2)
eslint-plugin-react:
specifier: ^7.37.4
version: 7.37.4(eslint@9.25.1(jiti@2.6.1))
@@ -133,11 +133,11 @@ importers:
specifier: ^8.5.6
version: 8.5.6
prettier:
specifier: ^3.7.3
version: 3.7.3
specifier: ^3.6.2
version: 3.6.2
prettier-plugin-organize-imports:
specifier: ^4.3.0
version: 4.3.0(prettier@3.7.3)(typescript@5.7.3)
version: 4.3.0(prettier@3.6.2)(typescript@5.7.3)
tailwind-scrollbar:
specifier: ^4.0.2
version: 4.0.2(react@18.3.1)(tailwindcss@4.0.9)
@@ -1749,8 +1749,8 @@ packages:
http-cache-semantics@4.2.0:
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
http2-wrapper@2.2.1:
@@ -2371,8 +2371,8 @@ packages:
vue-tsc:
optional: true
prettier@3.7.3:
resolution: {integrity: sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==}
prettier@3.6.2:
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
engines: {node: '>=14'}
hasBin: true
@@ -2409,8 +2409,8 @@ packages:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
raw-body@3.0.1:
resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==}
engines: {node: '>= 0.10'}
react-dom@18.3.1:
@@ -2669,8 +2669,8 @@ packages:
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
stop-iteration-iterator@1.1.0:
@@ -2776,8 +2776,8 @@ packages:
resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
engines: {node: ^14.18.0 || >=16.0.0}
systeminformation@5.27.11:
resolution: {integrity: sha512-K3Lto/2m3K2twmKHdgx5B+0in9qhXK4YnoT9rIlgwN/4v7OV5c8IjbeAUkuky/6VzCQC7iKCAqi8rZathCdjHg==}
systeminformation@5.27.7:
resolution: {integrity: sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==}
engines: {node: '>=8.0.0'}
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
hasBin: true
@@ -4506,10 +4506,10 @@ snapshots:
safe-regex-test: 1.1.0
string.prototype.includes: 2.0.1
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.7.3):
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.6.2):
dependencies:
eslint: 9.25.1(jiti@2.6.1)
prettier: 3.7.3
prettier: 3.6.2
prettier-linter-helpers: 1.0.0
synckit: 0.11.11
optionalDependencies:
@@ -4850,12 +4850,12 @@ snapshots:
http-cache-semantics@4.2.0: {}
http-errors@2.0.1:
http-errors@2.0.0:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.2
statuses: 2.0.1
toidentifier: 1.0.1
http2-wrapper@2.2.1:
@@ -5416,12 +5416,12 @@ snapshots:
dependencies:
fast-diff: 1.3.0
prettier-plugin-organize-imports@4.3.0(prettier@3.7.3)(typescript@5.7.3):
prettier-plugin-organize-imports@4.3.0(prettier@3.6.2)(typescript@5.7.3):
dependencies:
prettier: 3.7.3
prettier: 3.6.2
typescript: 5.7.3
prettier@3.7.3: {}
prettier@3.6.2: {}
pretty-bytes@7.1.0: {}
@@ -5465,10 +5465,10 @@ snapshots:
quick-lru@5.1.1: {}
raw-body@3.0.2:
raw-body@3.0.1:
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
http-errors: 2.0.0
iconv-lite: 0.7.0
unpipe: 1.0.0
@@ -5780,7 +5780,7 @@ snapshots:
stack-trace@0.0.10: {}
statuses@2.0.2: {}
statuses@2.0.1: {}
stop-iteration-iterator@1.1.0:
dependencies:
@@ -5905,7 +5905,7 @@ snapshots:
dependencies:
'@pkgr/core': 0.2.9
systeminformation@5.27.11: {}
systeminformation@5.27.7: {}
tabbable@6.3.0: {}

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Vandag",
"gross_percent_1y": "Een jaar",
"gross_percent_max": "Alle tyd",
"net_worth": "Net Worth"
"gross_percent_max": "Alle tyd"
},
"audiobookshelf": {
"podcasts": "Podsendinge",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "سنة",
"gross_percent_max": "كل الوقت",
"net_worth": "Net Worth"
"gross_percent_max": "كل الوقت"
},
"audiobookshelf": {
"podcasts": "بودكاست",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Една година",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Подкасти",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Un any",
"gross_percent_max": "Sempre",
"net_worth": "Net Worth"
"gross_percent_max": "Sempre"
},
"audiobookshelf": {
"podcasts": "Pòdcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Jeden rok",
"gross_percent_max": "Za celou dobu",
"net_worth": "Net Worth"
"gross_percent_max": "Za celou dobu"
},
"audiobookshelf": {
"podcasts": "Podcasty",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Et År",
"gross_percent_max": "Altid",
"net_worth": "Net Worth"
"gross_percent_max": "Altid"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Heute",
"gross_percent_1y": "Ein Jahr",
"gross_percent_max": "Gesamt",
"net_worth": ""
"gross_percent_max": "Gesamt"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Ένας χρόνος",
"gross_percent_max": "Διαχρονικά",
"net_worth": "Net Worth"
"gross_percent_max": "Διαχρονικά"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Hoy",
"gross_percent_1y": "Un año",
"gross_percent_max": "Todo el tiempo",
"net_worth": "Net Worth"
"gross_percent_max": "Todo el tiempo"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -142,8 +142,8 @@
"connectionStatusDisconnected": "Déconnecté",
"connectionStatusConnected": "Connecté",
"uptime": "Démarré depuis",
"maxDown": "Réception max",
"maxUp": "Envoi max",
"maxDown": "Réception max.",
"maxUp": "Envoi max.",
"down": "Réception",
"up": "Envoi",
"received": "Reçu",
@@ -229,7 +229,7 @@
"seed": "En partage"
},
"develancacheui": {
"cachehitbytes": "Octets acquis du cache",
"cachehitbytes": "Cache Hit (B)",
"cachemissbytes": "Cache Miss (B)"
},
"downloadstation": {
@@ -294,7 +294,7 @@
"queries": "Requêtes",
"blocked": "Bloqué",
"blocked_percent": "% bloqué",
"gravity": "Listes dom. Bloqués"
"gravity": "Listes dom. bloqués"
},
"adguard": {
"queries": "Requêtes",
@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Aujourd'hui",
"gross_percent_1y": "Un an",
"gross_percent_max": "Depuis le début",
"net_worth": "Patrimoine net"
"gross_percent_max": "Depuis le début"
},
"audiobookshelf": {
"podcasts": "Podcasts",
@@ -1092,7 +1091,7 @@
"NEW_ARRAY": "Nouveau tableau",
"RECON_DISK": "Reconstruction du disque",
"DISABLE_DISK": "Disque désactivé",
"SWAP_DSBL": "Désactiver le swap",
"SWAP_DSBL": "Swap Disable",
"INVALID_EXPANSION": "Extension invalide",
"PARITY_NOT_BIGGEST": "La parité n'est pas la plus grande",
"TOO_MANY_MISSING_DISKS": "Trop de disques manquants",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "היום",
"gross_percent_1y": "שנה",
"gross_percent_max": "כל הזמן",
"net_worth": "Net Worth"
"gross_percent_max": "כל הזמן"
},
"audiobookshelf": {
"podcasts": "פודקאסטים",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Danas",
"gross_percent_1y": "Jedna godina",
"gross_percent_max": "Svo vrijeme",
"net_worth": "Neto vrijednost"
"gross_percent_max": "Svo vrijeme"
},
"audiobookshelf": {
"podcasts": "Podcasti",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Egy év",
"gross_percent_max": "Mindig",
"net_worth": "Net Worth"
"gross_percent_max": "Mindig"
},
"audiobookshelf": {
"podcasts": "Podcast",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Satu Tahun",
"gross_percent_max": "Sepanjang Masa",
"net_worth": "Net Worth"
"gross_percent_max": "Sepanjang Masa"
},
"audiobookshelf": {
"podcasts": "Podcast",

View File

@@ -168,7 +168,7 @@
"passes": "Tessere"
},
"tautulli": {
"playing": "In riproduzione",
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams",
@@ -313,7 +313,7 @@
"total": "Total"
},
"suwayomi": {
"download": "Scaricati",
"download": "Downloaded",
"nondownload": "Non Scaricato",
"read": "Read",
"unread": "Unread",
@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Un anno",
"gross_percent_max": "Sempre",
"net_worth": "Net Worth"
"gross_percent_max": "Sempre"
},
"audiobookshelf": {
"podcasts": "Podcast",

View File

@@ -69,7 +69,7 @@
"docker": {
"rx": "受信済み",
"tx": "送信済み",
"mem": "メモリ",
"mem": "MEM",
"cpu": "CPU",
"running": "起動中",
"offline": "オフライン",
@@ -83,7 +83,7 @@
"partial": "部分的"
},
"ping": {
"error": "エラー",
"error": "Error",
"ping": "Ping",
"down": "下へ",
"up": "稼働",
@@ -112,7 +112,7 @@
"offline_alt": "オフライン",
"online": "オンライン",
"total": "Total",
"unknown": "不明"
"unknown": "Unknown"
},
"evcc": {
"pv_power": "発電量",
@@ -223,8 +223,8 @@
"invalid": "無効"
},
"deluge": {
"download": "ダウンロード",
"upload": "アップロード",
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
@@ -233,8 +233,8 @@
"cachemissbytes": "キャッシュミスバイト"
},
"downloadstation": {
"download": "ダウンロード",
"upload": "アップロード",
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
@@ -251,7 +251,7 @@
"queued": "Queued",
"movies": "Movies",
"queue": "Queue",
"unknown": "不明"
"unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
@@ -692,8 +692,8 @@
},
"diskstation": {
"days": "Days",
"uptime": "稼働時間",
"volumeAvailable": "利用可能"
"uptime": "Uptime",
"volumeAvailable": "Available"
},
"mylar": {
"series": "Series",
@@ -754,13 +754,12 @@
"gatus": {
"up": "Sites Up",
"down": "Sites Down",
"uptime": "稼働時間"
"uptime": "Uptime"
},
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "1年",
"gross_percent_max": "全期間",
"net_worth": "Net Worth"
"gross_percent_max": "全期間"
},
"audiobookshelf": {
"podcasts": "ポッドキャスト",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "오늘",
"gross_percent_1y": "1년",
"gross_percent_max": "전체 기간",
"net_worth": "Net Worth"
"gross_percent_max": "전체 기간"
},
"audiobookshelf": {
"podcasts": "팟캐스트",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Satu tahun",
"gross_percent_max": "Sepanjang masa",
"net_worth": "Net Worth"
"gross_percent_max": "Sepanjang masa"
},
"audiobookshelf": {
"podcasts": "Podkas",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Vandaag",
"gross_percent_1y": "Een jaar",
"gross_percent_max": "Altijd",
"net_worth": "Net Worth"
"gross_percent_max": "Altijd"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Ett år",
"gross_percent_max": "Gjennom tidene",
"net_worth": "Net Worth"
"gross_percent_max": "Gjennom tidene"
},
"audiobookshelf": {
"podcasts": "Podkaster",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Dzisiaj",
"gross_percent_1y": "Rok",
"gross_percent_max": "Od początku",
"net_worth": "Net Worth"
"gross_percent_max": "Od początku"
},
"audiobookshelf": {
"podcasts": "Podcasty",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Um ano",
"gross_percent_max": "Desde Sempre",
"net_worth": "Net Worth"
"gross_percent_max": "Desde Sempre"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Hoje",
"gross_percent_1y": "Um ano",
"gross_percent_max": "Todo o tempo",
"net_worth": "Patrimônio Líquido"
"gross_percent_max": "Todo o tempo"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Un an",
"gross_percent_max": "Tot timpul",
"net_worth": "Net Worth"
"gross_percent_max": "Tot timpul"
},
"audiobookshelf": {
"podcasts": "Podcasturi",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Сегодня",
"gross_percent_1y": "Один год",
"gross_percent_max": "Все время",
"net_worth": "Net Worth"
"gross_percent_max": "Все время"
},
"audiobookshelf": {
"podcasts": "Подкасты",

View File

@@ -362,8 +362,8 @@
},
"trilium": {
"version": "Verzia",
"notesCount": "Poznámky",
"dbSize": "Veľkosť databázy",
"notesCount": "Notes",
"dbSize": "Database Size",
"unknown": "Neznáme"
},
"navidrome": {
@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Dnes",
"gross_percent_1y": "Jeden rok",
"gross_percent_max": "Za celý čas",
"net_worth": "Net Worth"
"gross_percent_max": "Za celý čas"
},
"audiobookshelf": {
"podcasts": "Podcasty",
@@ -787,7 +786,7 @@
"downloadCount": "Poradie",
"downloadBytesRemaining": "Zostávajúce",
"downloadTotalBytes": "Veľkosť",
"downloadSpeed": "Rýchlosť"
"downloadSpeed": "Speed"
},
"kavita": {
"seriesCount": "Series",
@@ -953,7 +952,7 @@
"loading": "Načítava sa",
"open": "Open - US Market",
"closed": "Closed - US Market",
"invalidConfiguration": "Neplatná konfigurácia"
"invalidConfiguration": "Invalid Configuration"
},
"frigate": {
"cameras": "Kamery",
@@ -1023,10 +1022,10 @@
"loading": "Načítava sa"
},
"gitlab": {
"groups": "Skupiny",
"issues": "Problémy",
"groups": "Groups",
"issues": "Issues",
"merges": "Merge Requests",
"projects": "Projekty"
"projects": "Projects"
},
"apcups": {
"status": "Stav",
@@ -1036,7 +1035,7 @@
},
"karakeep": {
"bookmarks": "Bookmarks",
"favorites": "Obľúbené",
"favorites": "Favorites",
"archived": "Archived",
"highlights": "Highlights",
"lists": "Zoznamy",
@@ -1066,13 +1065,13 @@
"komodo": {
"total": "Celkom",
"running": "Beží",
"stopped": "Zastavené",
"stopped": "Stopped",
"down": "Down",
"unhealthy": "Nezdravý",
"unknown": "Neznáme",
"servers": "Servery",
"servers": "Servers",
"stacks": "Stacks",
"containers": "Kontajnery"
"containers": "Containers"
},
"filebrowser": {
"available": "Dostupné",
@@ -1081,8 +1080,8 @@
},
"wallos": {
"activeSubscriptions": "Subscriptions",
"thisMonthlyCost": "Tento mesiac",
"nextMonthlyCost": "Ďalší mesiac",
"thisMonthlyCost": "This Month",
"nextMonthlyCost": "Next Month",
"previousMonthlyCost": "Prev. Month",
"nextRenewingSubscription": "Next Payment"
},

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Eno leto",
"gross_percent_max": "Celoten čas",
"net_worth": "Net Worth"
"gross_percent_max": "Celoten čas"
},
"audiobookshelf": {
"podcasts": "Podcasti",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Данас",
"gross_percent_1y": "Једна година",
"gross_percent_max": "Све време",
"net_worth": "Нето вредност"
"gross_percent_max": "Све време"
},
"audiobookshelf": {
"podcasts": "Подкасти",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Bugün",
"gross_percent_1y": "Bir yıl",
"gross_percent_max": "Tüm zaman",
"net_worth": "Net Worth"
"gross_percent_max": "Tüm zaman"
},
"audiobookshelf": {
"podcasts": "Podcast",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "Один рік",
"gross_percent_max": "Весь час",
"net_worth": "Net Worth"
"gross_percent_max": "Весь час"
},
"audiobookshelf": {
"podcasts": "Подкасти",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time",
"net_worth": "Net Worth"
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "一年",
"gross_percent_max": "所有時間",
"net_worth": "Net Worth"
"gross_percent_max": "所有時間"
},
"audiobookshelf": {
"podcasts": "播客",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "一年",
"gross_percent_max": "所有时间",
"net_worth": "Net Worth"
"gross_percent_max": "所有时间"
},
"audiobookshelf": {
"podcasts": "播客",

View File

@@ -759,8 +759,7 @@
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "一年",
"gross_percent_max": "所有時間",
"net_worth": "Net Worth"
"gross_percent_max": "所有時間"
},
"audiobookshelf": {
"podcasts": "Podcasts",

View File

@@ -14,8 +14,6 @@ export default function Error({ error }) {
if (typeof error === "string") {
error = { message: error }; // eslint-disable-line no-param-reassign
} else if (typeof error === "number") {
error = { message: `Error ${error}` }; // eslint-disable-line no-param-reassign
}
if (error?.data?.error) {

View File

@@ -400,7 +400,6 @@ function Home({ initialSettings }) {
"A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations."
}
/>
{settings.disableIndexing && <meta name="robots" content="noindex, nofollow" />}
{settings.base && <base href={settings.base} />}
{settings.favicon ? (
<>

View File

@@ -1,19 +0,0 @@
import { getSettings } from "utils/config/config";
export async function getServerSideProps({ res }) {
const settings = getSettings();
const content = ["User-agent: *", !!settings.disableIndexing ? "Disallow: /" : "Allow: /"].join("\n");
res.setHeader("Content-Type", "text/plain");
res.write(content);
res.end();
return {
props: {},
};
}
export default function RobotsTxt() {
// placeholder component
return null;
}

View File

@@ -279,9 +279,6 @@ export function cleanServiceGroups(groups) {
slugs,
symbols,
// crowdsec
limit24h,
// customapi
mappings,
display,
@@ -476,10 +473,6 @@ export function cleanServiceGroups(groups) {
if (defaultinterval) widget.defaultinterval = defaultinterval;
}
if (limit24h !== undefined) {
widget.limit24h = !!limit24h;
}
if (type === "docker") {
if (server) widget.server = server;
if (container) widget.container = container;
@@ -563,7 +556,6 @@ export function cleanServiceGroups(groups) {
"speedtest",
"wgeasy",
"grafana",
"gluetun",
].includes(type)
) {
if (version) widget.version = parseInt(version, 10);

View File

@@ -9,7 +9,7 @@ export default function Component({ service }) {
const { widget } = service;
const { data: alerts, error: alertsError } = useWidgetAPI(widget, !!widget.limit24h ? "alerts24h" : "alerts");
const { data: alerts, error: alertsError } = useWidgetAPI(widget, "alerts");
const { data: bans, error: bansError } = useWidgetAPI(widget, "bans");
if (alertsError || bansError) {

View File

@@ -9,9 +9,6 @@ const widget = {
alerts: {
endpoint: "alerts",
},
alerts24h: {
endpoint: "alerts?limit=0&since=24h",
},
bans: {
endpoint: "alerts?decision_type=ban&origin=crowdsec&has_active_decision=1",
},

View File

@@ -166,11 +166,7 @@ export default function Component({ service }) {
refreshInterval: Math.max(1000, refreshInterval),
});
// if mappings includes an error field and the data contains an error field then show data even if there is an error
const mappingsIncludesError = Array.isArray(mappings) && mappings.find((mapping) => mapping.field === "error");
const errorIsData = customData && typeof customData === "object" && "error" in customData;
if (customError && !(mappingsIncludesError && errorIsData)) {
if (customError) {
return <Container service={service} error={customError} />;
}

View File

@@ -1,95 +0,0 @@
import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
import { asJson, formatApiCall, sanitizeErrorURL } from "utils/proxy/api-helpers";
import { addCookieToJar } from "utils/proxy/cookie-jar";
import { httpProxy } from "utils/proxy/http";
import widgets from "widgets/widgets";
const proxyName = "frigateProxyHandler";
const logger = createLogger(proxyName);
export default async function frigateProxyHandler(req, res, map) {
const { group, service, endpoint, index } = req.query;
if (group && service) {
const widget = await getServiceWidget(group, service, index);
if (!widgets?.[widget.type]?.api) {
return res.status(403).json({ error: "Service does not support API calls" });
}
if (widget) {
const url = formatApiCall(widgets[widget.type].api, { endpoint, ...widget });
const params = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
let [status, , data] = await httpProxy(url, params);
if (status === 401 && widget.username && widget.password) {
const loginUrl = `${widget.url}/api/login`;
logger.debug("Attempting login to Frigate at %s", loginUrl);
const [loginStatus, , , loginResponseHeaders] = await httpProxy(loginUrl, {
method: "POST",
body: JSON.stringify({ user: widget.username, password: widget.password }),
headers: {
"Content-Type": "application/json",
},
});
if (loginStatus !== 200) {
logger.error("HTTP Error %d calling %s", loginStatus, sanitizeErrorURL(loginUrl));
return res.status(status).json({
error: {
message: `HTTP Error ${status} while trying to login to Frigate`,
url: sanitizeErrorURL(url),
},
});
}
addCookieToJar(url, loginResponseHeaders);
// Retry original request with cookie set
[status, , data] = await httpProxy(url, params);
}
if (status >= 400) {
logger.error("HTTP Error %d calling %s", status, sanitizeErrorURL(url));
return res.status(status).json({
error: {
message: `HTTP Error ${status} from Frigate`,
url: sanitizeErrorURL(url),
},
});
}
data = asJson(data);
if (endpoint == "stats") {
res.status(status).send({
num_cameras: data?.cameras !== undefined ? Object.keys(data?.cameras).length : 0,
uptime: data?.service?.uptime,
version: data?.service.version,
});
} else if (endpoint == "events") {
return res.status(status).send(
data.slice(0, 5).map((event) => ({
id: event.id,
camera: event.camera,
label: event.label,
start_time: new Date(event.start_time * 1000),
thumbnail: event.thumbnail,
score: event.data.score,
type: event.data.type,
})),
);
}
}
}
logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group);
return res.status(400).json({ error: "Invalid proxy service type" });
}

View File

@@ -1,12 +1,37 @@
import frigateProxyHandler from "./proxy";
import { asJson } from "utils/proxy/api-helpers";
import genericProxyHandler from "utils/proxy/handlers/generic";
const widget = {
api: "{url}/api/{endpoint}",
proxyHandler: frigateProxyHandler,
proxyHandler: genericProxyHandler,
mappings: {
stats: { endpoint: "stats" },
events: { endpoint: "events" },
stats: {
endpoint: "stats",
map: (data) => {
const jsonData = asJson(data);
return {
num_cameras: jsonData?.cameras !== undefined ? Object.keys(jsonData?.cameras).length : 0,
uptime: jsonData?.service?.uptime,
version: jsonData?.service.version,
};
},
},
events: {
endpoint: "events",
map: (data) =>
asJson(data)
.slice(0, 5)
.map((event) => ({
id: event.id,
camera: event.camera,
label: event.label,
start_time: new Date(event.start_time * 1000),
thumbnail: event.thumbnail,
score: event.data.score,
type: event.data.type,
})),
},
},
};

View File

@@ -20,15 +20,13 @@ function getPerformancePercent(t, performanceRange) {
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const includeNetWorth = widget.fields?.includes("net_worth");
const { data: performanceToday, error: ghostfolioErrorToday } = useWidgetAPI(widget, "today");
const { data: performanceYear, error: ghostfolioErrorYear } = useWidgetAPI(widget, "year");
const { data: performanceMax, error: ghostfolioErrorMax } = useWidgetAPI(widget, "max");
const { data: userInfo, error: ghostfolioErrorUserInfo } = useWidgetAPI(widget, includeNetWorth ? "userInfo" : "");
if (ghostfolioErrorToday || ghostfolioErrorYear || ghostfolioErrorMax || ghostfolioErrorUserInfo) {
const finalError = ghostfolioErrorToday ?? ghostfolioErrorYear ?? ghostfolioErrorMax ?? ghostfolioErrorUserInfo;
if (ghostfolioErrorToday || ghostfolioErrorYear || ghostfolioErrorMax) {
const finalError = ghostfolioErrorToday ?? ghostfolioErrorYear ?? ghostfolioErrorMax;
return <Container service={service} error={finalError} />;
}
@@ -36,13 +34,12 @@ export default function Component({ service }) {
return <Container service={service} error={performanceToday} />;
}
if (!performanceToday || !performanceYear || !performanceMax || (includeNetWorth && !userInfo)) {
if (!performanceToday || !performanceYear || !performanceMax) {
return (
<Container service={service}>
<Block label="ghostfolio.gross_percent_today" />
<Block label="ghostfolio.gross_percent_1y" />
<Block label="ghostfolio.gross_percent_max" />
{includeNetWorth && <Block label="ghostfolio.net_worth" />}
</Container>
);
}
@@ -52,12 +49,6 @@ export default function Component({ service }) {
<Block label="ghostfolio.gross_percent_today" value={getPerformancePercent(t, performanceToday)} />
<Block label="ghostfolio.gross_percent_1y" value={getPerformancePercent(t, performanceYear)} />
<Block label="ghostfolio.gross_percent_max" value={getPerformancePercent(t, performanceMax)} />
{includeNetWorth && (
<Block
label="ghostfolio.net_worth"
value={`${performanceToday.performance.currentNetWorth.toFixed(2)} ${userInfo?.settings?.currency ?? ""}`}
/>
)}
</Container>
);
}

View File

@@ -1,21 +1,18 @@
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = {
api: "{url}/api/{endpoint}",
api: "{url}/api/v2/portfolio/performance?range={endpoint}",
proxyHandler: credentialedProxyHandler,
mappings: {
today: {
endpoint: "v2/portfolio/performance?range=1d",
endpoint: "1d",
},
year: {
endpoint: "v2/portfolio/performance?range=1y",
endpoint: "1y",
},
max: {
endpoint: "v2/portfolio/performance?range=max",
},
userInfo: {
endpoint: "v1/user",
endpoint: "max",
},
},
};

View File

@@ -12,8 +12,10 @@ export default function Component({ service }) {
const { data: gluetunData, error: gluetunError } = useWidgetAPI(widget, "ip");
const includePF = widget.fields.includes("port_forwarded");
const pfEndpoint = widget.version > 1 ? "port_forwarded_v2" : "port_forwarded";
const { data: portForwardedData, error: portForwardedError } = useWidgetAPI(widget, includePF ? pfEndpoint : "");
const { data: portForwardedData, error: portForwardedError } = useWidgetAPI(
widget,
includePF ? "port_forwarded" : "",
);
if (gluetunError || (includePF && portForwardedError)) {
return <Container service={service} error={gluetunError || portForwardedError} />;

View File

@@ -13,10 +13,6 @@ const widget = {
endpoint: "openvpn/portforwarded",
validate: ["port"],
},
port_forwarded_v2: {
endpoint: "portforward",
validate: ["port"],
},
},
};

View File

@@ -24,9 +24,9 @@ export default function Component({ service }) {
if (!data || (data && data.length === 0)) {
return (
<Container service={service}>
<Block label="myspeed.ping" />
<Block label="myspeed.download" />
<Block label="myspeed.upload" />
<Block label="myspeed.ping" />
</Container>
);
}