Compare commits
3 commits
main
...
feature/ne
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bd0ecbfc5f | ||
![]() |
19afb2ed4a | ||
![]() |
27bf2cb739 |
74 changed files with 2768 additions and 2479 deletions
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -25,7 +25,7 @@ What type of change does your PR introduce to Homepage?
|
|||
|
||||
## Checklist:
|
||||
|
||||
- [ ] If applicable, I have added corresponding documentation changes.
|
||||
- [ ] If applicable, I have reviewed the [feature](https://gethomepage.dev/latest/more/development/#new-feature-guidelines) and / or [service widget guidelines](https://gethomepage.dev/latest/more/development/#service-widget-guidelines).
|
||||
- [ ] I have checked that all code style checks pass using [pre-commit hooks](https://gethomepage.dev/latest/more/development/#code-formatting-with-pre-commit-hooks) and [linting checks](https://gethomepage.dev/latest/more/development/#code-linting).
|
||||
- [ ] If adding a service widget or a change that requires it, I have added corresponding documentation changes.
|
||||
- [ ] If adding a new widget I have reviewed the [guidelines](https://gethomepage.dev/latest/more/development/#service-widget-guidelines).
|
||||
- [ ] I have checked that all code style checks pass using pre-commit hooks and linting checks with `pnpm lint` (see development guidelines).
|
||||
- [ ] If applicable, I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
|
||||
|
|
31
.github/workflows/crowdin.yml
vendored
31
.github/workflows/crowdin.yml
vendored
|
@ -1,31 +0,0 @@
|
|||
name: Crowdin Action
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '2 */12 * * *'
|
||||
push:
|
||||
paths: [
|
||||
'/public/locales/en/**',
|
||||
]
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
name: Crowdin Sync
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
crowdin_branch_name: main
|
||||
localization_branch_name: l10n_main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
8
.github/workflows/docker-publish.yml
vendored
8
.github/workflows/docker-publish.yml
vendored
|
@ -9,9 +9,7 @@ on:
|
|||
schedule:
|
||||
- cron: '20 0 * * *'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- feature/**
|
||||
branches: [ "main" ]
|
||||
# Publish semver tags as releases.
|
||||
tags: [ 'v*.*.*' ]
|
||||
paths-ignore:
|
||||
|
@ -41,7 +39,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
-
|
||||
|
@ -119,7 +117,7 @@ jobs:
|
|||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' && !(github.event_name == 'push' && startsWith(github.ref, 'refs/heads/feature')) }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
|
|
6
.github/workflows/docs-publish.yml
vendored
6
.github/workflows/docs-publish.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
-
|
||||
|
@ -42,7 +42,7 @@ jobs:
|
|||
- pre-commit
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
|
@ -67,7 +67,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
preserve_hierarchy: true
|
||||
files:
|
||||
- source: /public/locales/en/*.json
|
||||
translation: /public/locales/%osx_locale%/%original_file_name%
|
||||
|
|
|
@ -153,7 +153,7 @@ labels:
|
|||
- homepage.widget.fields=["field1","field2"] # optional
|
||||
```
|
||||
|
||||
You can add specify fields for e.g. the [CustomAPI](../widgets/services/customapi.md) widget by using array-style dot notation:
|
||||
You can add specify fields for e.g. the [CustomAPI](/widgets/services/customapi) widget by using array-style dot notation:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
|
@ -201,22 +201,6 @@ In order to detect every service within the Docker swarm it is necessary that se
|
|||
...
|
||||
```
|
||||
|
||||
## Multiple Homepage Instances
|
||||
|
||||
The optional field `instanceName` can be configured in [settings.md](settings.md#instance-name) to differentiate between multiple homepage instances.
|
||||
|
||||
To limit a label to an instance, insert `.instance.{{instanceName}}` after the `homepage` prefix.
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- homepage.group=Media
|
||||
- homepage.name=Emby
|
||||
- homepage.icon=emby.png
|
||||
- homepage.instance.internal.href=http://emby.lan/
|
||||
- homepage.instance.public.href=https://emby.mydomain.com/
|
||||
- homepage.description=Media server
|
||||
```
|
||||
|
||||
## Ordering
|
||||
|
||||
As of v0.6.4 discovered services can include an optional `weight` field to determine sorting such that:
|
||||
|
|
|
@ -229,28 +229,6 @@ disableCollapse: true
|
|||
|
||||
By default the feature is enabled.
|
||||
|
||||
### Use Equal Height Cards
|
||||
|
||||
You can enable equal height cards for groups of services, this will make all cards in a row the same height.
|
||||
|
||||
Global setting in `settings.yaml`:
|
||||
|
||||
```yaml
|
||||
useEqualHeights: true
|
||||
```
|
||||
|
||||
Per layout group in `settings.yaml`:
|
||||
|
||||
```yaml
|
||||
useEqualHeights: false
|
||||
layout:
|
||||
...
|
||||
Group Name:
|
||||
useEqualHeights: true # overrides global setting
|
||||
```
|
||||
|
||||
By default the feature is disabled
|
||||
|
||||
## Header Style
|
||||
|
||||
There are currently 4 options for header styles, you can see each one below.
|
||||
|
@ -426,16 +404,6 @@ or per-service (`services.yaml`) with:
|
|||
|
||||
If you have both set, the per-service settings take precedence.
|
||||
|
||||
## Instance Name
|
||||
|
||||
Name used by automatic docker service discovery to differentiate between multiple homepage instances.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
instanceName: public
|
||||
```
|
||||
|
||||
## Hide Widget Error Messages
|
||||
|
||||
Hide the visible API error messages either globally in `settings.yaml`:
|
||||
|
|
|
@ -39,16 +39,11 @@ Once installed, hooks will run when you commit. If the formatting isn't quite ri
|
|||
|
||||
See the [pre-commit documentation](https://pre-commit.com/#install) to get started.
|
||||
|
||||
## New Feature Guidelines
|
||||
|
||||
- New features should be linked to an existing feature request with at least 5 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of features that might only benefit a small number of users.
|
||||
- If you have ideas for a larger feature, please open a discussion first.
|
||||
|
||||
## Service Widget Guidelines
|
||||
|
||||
To ensure cohesiveness of various widgets, the following should be used as a guide for developing new widgets:
|
||||
|
||||
- Please only submit widgets that have been requested and have at least 5 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users.
|
||||
- Please only submit widgets that have been requested and have at least 5 'up-votes'
|
||||
- Widgets should be only one row of blocks
|
||||
- Widgets should be no more than 4 blocks wide
|
||||
- Minimize the number of API calls
|
||||
|
|
|
@ -6,10 +6,6 @@ hide:
|
|||
- navigation
|
||||
---
|
||||
|
||||
## Introducing the Homepage AI Bot
|
||||
|
||||
Thanks to the generous folks at [Glime](https://glimelab.ai), Homepage is now equipped with a pretty helpful AI-powered bot. The bot has full knowledge of our docs, GitHub issues and discussions and great at answering specific questions about setting up your Homepage. To use the bot, just hit the 'Ask AI' button on any page in our docs or check out the [#ai-support channel on Discord](https://discord.com/channels/1019316731635834932/1177885603552038993)!
|
||||
|
||||
## General Troubleshooting Tips
|
||||
|
||||
- For API errors, clicking the "API Error Information" button in the widget will usually show some helpful information as to whether the issue is reaching the service host, an authentication issue, etc.
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
var glimeScript;
|
||||
var glimeStyles = [];
|
||||
document$.subscribe(function () {
|
||||
if (!glimeScript) {
|
||||
glimeScript = document.createElement("script");
|
||||
glimeScript.setAttribute("src", "https://cdn.glimelab.ai/widget/1.0.0/widget.js");
|
||||
glimeScript.setAttribute("onload", "onGlimeLoad()");
|
||||
document.head.appendChild(glimeScript);
|
||||
} else {
|
||||
var newGlimeStyle = document.createElement("style");
|
||||
document.head.appendChild(newGlimeStyle);
|
||||
var i = 0;
|
||||
glimeStyles.forEach((rule) => {
|
||||
newGlimeStyle.sheet.insertRule(rule.cssText, i);
|
||||
i++;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onGlimeLoad = () => {
|
||||
window.glime.init("Bl3mlvfCnTnRm5");
|
||||
setTimeout(() => {
|
||||
const sheets = document.styleSheets;
|
||||
[...sheets].forEach((sheet) => {
|
||||
if (!sheet.href) {
|
||||
[...sheet.cssRules].forEach((rule) => {
|
||||
if (!rule || rule.href || !rule.selectorText) return;
|
||||
if (rule.selectorText.indexOf(".css-") === 0 || rule.selectorText.indexOf("glime") > -1) {
|
||||
glimeStyles.push(rule);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
};
|
|
@ -18,7 +18,3 @@
|
|||
border-color: var(--md-default-bg-color--lighter);
|
||||
}
|
||||
}
|
||||
|
||||
#glimeRoot * {
|
||||
font-family: var(--md-text-font) !important;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ Homepage has two types of widgets: info and service. Below we'll cover each type
|
|||
|
||||
## Service Widgets
|
||||
|
||||
Service widgets are used to display the status of a service, often a web service or API. Services (and their widgets) are defined in your `services.yaml` file. Here's an example:
|
||||
Service widgets are used to display the status of a service, often a web service or API. Services (and their widgets) are defined in your `services.yml` file. Here's an example:
|
||||
|
||||
```yaml
|
||||
- Plex:
|
||||
|
@ -24,7 +24,7 @@ Service widgets are used to display the status of a service, often a web service
|
|||
|
||||
## Info Widgets
|
||||
|
||||
Info widgets are used to display information in the header, often about your system or environment. Info widgets are defined your `widgets.yaml` file. Here's an example:
|
||||
Info widgets are used to display information in the header, often about your system or environment. Info widgets are defined your `widgets.yml` file. Here's an example:
|
||||
|
||||
```yaml
|
||||
- openmeteo:
|
||||
|
|
|
@ -26,12 +26,4 @@ It can show the aggregate metrics and/or the individual node metrics.
|
|||
- node2
|
||||
```
|
||||
|
||||
The Longhorn URL and credentials are stored in the `providers` section of the `settings.yaml`. e.g.:
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
longhorn:
|
||||
username: "longhorn-username" # optional
|
||||
password: "very-secret-longhorn-password" # optional
|
||||
url: https://longhorn.aesop.network
|
||||
```
|
||||
The Longhorn URL and credentials are stored in the `providers` section of the `settings.yaml`.
|
||||
|
|
|
@ -15,20 +15,13 @@ widget:
|
|||
firstDayInWeek: sunday # optional - defaults to monday
|
||||
view: monthly # optional - possible values monthly, agenda
|
||||
maxEvents: 10 # optional - defaults to 10
|
||||
showTime: true # optional - show time for event happening today - defaults to false
|
||||
integrations: # optional
|
||||
- type: sonarr # active widget type that is currently enabled on homepage - possible values: radarr, sonarr, lidarr, readarr, ical
|
||||
- type: sonarr # active widget type that is currently enabled on homepage - possible values: radarr, sonarr, lidarr, readarr
|
||||
service_group: Media # group name where widget exists
|
||||
service_name: Sonarr # service name for that widget
|
||||
color: teal # optional - defaults to pre-defined color for the service (teal for sonarr)
|
||||
params: # optional - additional params for the service
|
||||
unmonitored: true # optional - defaults to false, used with *arr stack
|
||||
- type: ical # Show calendar events from another service
|
||||
url: https://domain.url/with/link/to.ics # URL with calendar events
|
||||
name: My Events # required - name for these calendar events
|
||||
color: zinc # optional - defaults to pre-defined color for the service (zinc for ical)
|
||||
params: # optional - additional params for the service
|
||||
showName: true # optional - show name before event title in event line - defaults to false
|
||||
```
|
||||
|
||||
## Agenda
|
||||
|
@ -40,8 +33,6 @@ widget:
|
|||
type: calendar
|
||||
view: agenda
|
||||
maxEvents: 10 # optional - defaults to 10
|
||||
showTime: true # optional - show time for event happening today - defaults to false
|
||||
previousDays: 3 # optional - shows events since three days ago - defaults to 0
|
||||
integrations: # same as in Monthly view example
|
||||
```
|
||||
|
||||
|
@ -50,7 +41,3 @@ widget:
|
|||
Currently integrated widgets are [sonarr](sonarr.md), [radarr](radarr.md), [lidarr](lidarr.md) and [readarr](readarr.md).
|
||||
|
||||
Supported colors can be found on [color palette](../../configs/settings.md#color-palette).
|
||||
|
||||
### iCal
|
||||
|
||||
This custom integration allows you to show events from any calendar that supports iCal format, for example, Google Calendar (go to `Settings`, select specific calendar, go to `Integrate calendar`, copy URL from `Public Address in iCal format`).
|
||||
|
|
|
@ -3,7 +3,7 @@ title: Calibre-web
|
|||
description: Calibre-web Widget Configuration
|
||||
---
|
||||
|
||||
**Note: widget requires calibre-web ≥ v0.6.21.**
|
||||
**Note: this widget requires a feature of calibre-web that has not yet been distributed in versioned release. The code is contained in ["nightly" lsio builds after 25/8/23](https://hub.docker.com/layers/linuxserver/calibre-web/nightly/images/sha256-b27cbe5d17503de38135d925e226eb3e5ba04c558dbc865dc85d77824d35d7e2) or running the calibre-web source code including commit [0499e57](https://github.com/janeczku/calibre-web/commit/0499e578cdd45db656da34cd2d7152c8d88ceb23).**
|
||||
|
||||
Allowed fields: `["books", "authors", "categories", "series"]`.
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
title: FRITZ!Box
|
||||
description: FRITZ!Box Widget Configuration
|
||||
---
|
||||
|
||||
Application access & UPnP must be activated on your device:
|
||||
|
||||
```
|
||||
Home Network > Network > Network Settings > Access Settings in the Home Network
|
||||
[x] Allow access for applications
|
||||
[x] Transmit status information over UPnP
|
||||
```
|
||||
|
||||
Credentials are not needed and, as such, you may want to consider using `http` instead of `https` as those requests are significantly faster.
|
||||
|
||||
Allowed fields (limited to a max of 4): `["connectionStatus", "upTime", "maxDown", "maxUp", "down", "up", "received", "sent", "externalIPAddress"]`.
|
||||
|
||||
```yaml
|
||||
widget:
|
||||
type: fritzbox
|
||||
url: http://192.168.178.1
|
||||
```
|
|
@ -3,14 +3,12 @@ title: Gluetun
|
|||
description: Gluetun Widget Configuration
|
||||
---
|
||||
|
||||
!!! note
|
||||
|
||||
Requires [HTTP control server options](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md) to be enabled. By default this runs on port `8000`.
|
||||
Requires [HTTP control server options](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md) to be enabled.
|
||||
|
||||
Allowed fields: `["public_ip", "region", "country"]`.
|
||||
|
||||
```yaml
|
||||
widget:
|
||||
type: gluetun
|
||||
url: http://gluetun.host.or.ip:port
|
||||
url: http://gluetun.host.or.ip
|
||||
```
|
||||
|
|
|
@ -27,7 +27,7 @@ widget:
|
|||
src: http://example.com
|
||||
classes: h-60 sm:h-60 md:h-60 lg:h-60 xl:h-60 2xl:h-72 # optional, use tailwind height classes, see https://tailwindcss.com/docs/height
|
||||
referrerPolicy: same-origin # optional, no default
|
||||
allowPolicy: autoplay; fullscreen; gamepad # optional, no default
|
||||
allowPolicy: autoplay fullscreen gamepad # optional, no default
|
||||
allowFullscreen: false # optional, default: true
|
||||
loadingStrategy: eager # optional, default: eager
|
||||
allowScrolling: no # optional, default: yes
|
||||
|
|
|
@ -53,7 +53,6 @@ nav:
|
|||
- widgets/services/fileflows.md
|
||||
- widgets/services/flood.md
|
||||
- widgets/services/freshrss.md
|
||||
- widgets/services/fritzbox.md
|
||||
- widgets/services/gamedig.md
|
||||
- widgets/services/ghostfolio.md
|
||||
- widgets/services/glances.md
|
||||
|
@ -192,8 +191,6 @@ theme:
|
|||
|
||||
extra_css:
|
||||
- "stylesheets/extra.css"
|
||||
extra_javascript:
|
||||
- "scripts/extra.js"
|
||||
|
||||
extra:
|
||||
version:
|
||||
|
|
|
@ -4,6 +4,7 @@ const { i18n } = require("./next-i18next.config");
|
|||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
output: "standalone",
|
||||
swcMinify: false,
|
||||
images: {
|
||||
domains: ["cdn.jsdelivr.net"],
|
||||
unoptimized: true,
|
||||
|
|
1030
package-lock.json
generated
1030
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,6 @@
|
|||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.2",
|
||||
"@kubernetes/client-node": "^0.17.1",
|
||||
"cal-parser": "^1.0.2",
|
||||
"classnames": "^2.3.2",
|
||||
"compare-versions": "^5.0.1",
|
||||
"dockerode": "^3.3.4",
|
||||
|
@ -24,7 +23,7 @@
|
|||
"luxon": "^3.4.3",
|
||||
"memory-cache": "^0.2.0",
|
||||
"minecraft-ping-js": "^1.0.2",
|
||||
"next": "^12.3.1",
|
||||
"next": "^14.0.2",
|
||||
"next-i18next": "^12.0.1",
|
||||
"ping": "^0.4.4",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
|
@ -46,7 +45,7 @@
|
|||
"autoprefixer": "^10.4.12",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-next": "^12.3.1",
|
||||
"eslint-config-next": "^14.0.2",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
|
|
1057
pnpm-lock.yaml
generated
1057
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -402,316 +402,316 @@
|
|||
},
|
||||
"wmo": {
|
||||
"0-day": "Sonnig",
|
||||
"0-night": "Helder",
|
||||
"1-day": "Hoofsaaklik sonnig",
|
||||
"1-night": "Hoofsaaklik Helder",
|
||||
"2-day": "Gedeeltelik Bewolk",
|
||||
"2-night": "Gedeeltelik Bewolk",
|
||||
"3-day": "Bewolk",
|
||||
"3-night": "Bewolk",
|
||||
"45-day": "Mistig",
|
||||
"45-night": "Mistig",
|
||||
"48-day": "Mistig",
|
||||
"48-night": "Mistig",
|
||||
"51-day": "Ligte Motrëen",
|
||||
"51-night": "Ligte Motrëen",
|
||||
"53-day": "Motrëen",
|
||||
"53-night": "Motrëen",
|
||||
"55-day": "Swaar Motrëen",
|
||||
"55-night": "Swaar Motrëen",
|
||||
"56-day": "Ligte Ysige Motreën",
|
||||
"56-night": "Ligte Ysige Motreën",
|
||||
"57-day": "Ysige Motreën",
|
||||
"57-night": "Ysige Motreën",
|
||||
"61-day": "Ligte Rëen",
|
||||
"61-night": "Ligte Rëen",
|
||||
"63-day": "Rëen",
|
||||
"63-night": "Rëen",
|
||||
"65-day": "Swaar Rëen",
|
||||
"65-night": "Swaar Rëen",
|
||||
"66-day": "Ysige Rëen",
|
||||
"66-night": "Ysige Rëen",
|
||||
"67-day": "Ysige Rëen",
|
||||
"67-night": "Ysige Rëen",
|
||||
"71-day": "Ligte Sneeu",
|
||||
"71-night": "Ligte Sneeu",
|
||||
"73-day": "Sneeu",
|
||||
"73-night": "Sneeu",
|
||||
"75-day": "Swaar Sneeu",
|
||||
"75-night": "Swaar Sneeu",
|
||||
"77-day": "Sneeu Korrels",
|
||||
"77-night": "Sneeu Korrels",
|
||||
"80-day": "Ligte Buie",
|
||||
"80-night": "Ligte Buie",
|
||||
"81-day": "Buie",
|
||||
"81-night": "Buie",
|
||||
"82-day": "Swaar Buie",
|
||||
"82-night": "Swaar Buie",
|
||||
"85-day": "Sneeu Buie",
|
||||
"85-night": "Sneeu Buie",
|
||||
"86-day": "Sneeu Buie",
|
||||
"86-night": "Sneeu Buie",
|
||||
"95-day": "Donderstorm",
|
||||
"95-night": "Donderstorm",
|
||||
"96-day": "Donderstorm Met Hael",
|
||||
"96-night": "Donderstorm Met Hael",
|
||||
"99-day": "Donderstorm Met Hael",
|
||||
"99-night": "Donderstorm Met Hael"
|
||||
"0-night": "Clear",
|
||||
"1-day": "Mainly Sunny",
|
||||
"1-night": "Mainly Clear",
|
||||
"2-day": "Partly Cloudy",
|
||||
"2-night": "Partly Cloudy",
|
||||
"3-day": "Cloudy",
|
||||
"3-night": "Cloudy",
|
||||
"45-day": "Foggy",
|
||||
"45-night": "Foggy",
|
||||
"48-day": "Foggy",
|
||||
"48-night": "Foggy",
|
||||
"51-day": "Light Drizzle",
|
||||
"51-night": "Light Drizzle",
|
||||
"53-day": "Drizzle",
|
||||
"53-night": "Drizzle",
|
||||
"55-day": "Heavy Drizzle",
|
||||
"55-night": "Heavy Drizzle",
|
||||
"56-day": "Light Freezing Drizzle",
|
||||
"56-night": "Light Freezing Drizzle",
|
||||
"57-day": "Freezing Drizzle",
|
||||
"57-night": "Freezing Drizzle",
|
||||
"61-day": "Light Rain",
|
||||
"61-night": "Light Rain",
|
||||
"63-day": "Rain",
|
||||
"63-night": "Rain",
|
||||
"65-day": "Heavy Rain",
|
||||
"65-night": "Heavy Rain",
|
||||
"66-day": "Freezing Rain",
|
||||
"66-night": "Freezing Rain",
|
||||
"67-day": "Freezing Rain",
|
||||
"67-night": "Freezing Rain",
|
||||
"71-day": "Light Snow",
|
||||
"71-night": "Light Snow",
|
||||
"73-day": "Snow",
|
||||
"73-night": "Snow",
|
||||
"75-day": "Heavy Snow",
|
||||
"75-night": "Heavy Snow",
|
||||
"77-day": "Snow Grains",
|
||||
"77-night": "Snow Grains",
|
||||
"80-day": "Light Showers",
|
||||
"80-night": "Light Showers",
|
||||
"81-day": "Showers",
|
||||
"81-night": "Showers",
|
||||
"82-day": "Heavy Showers",
|
||||
"82-night": "Heavy Showers",
|
||||
"85-day": "Snow Showers",
|
||||
"85-night": "Snow Showers",
|
||||
"86-day": "Snow Showers",
|
||||
"86-night": "Snow Showers",
|
||||
"95-day": "Thunderstorm",
|
||||
"95-night": "Thunderstorm",
|
||||
"96-day": "Thunderstorm With Hail",
|
||||
"96-night": "Thunderstorm With Hail",
|
||||
"99-day": "Thunderstorm With Hail",
|
||||
"99-night": "Thunderstorm With Hail"
|
||||
},
|
||||
"homebridge": {
|
||||
"available_update": "Stelsel",
|
||||
"updates": "Opdatering",
|
||||
"update_available": "Opdatering Beskikbaar",
|
||||
"up_to_date": "Op Datum",
|
||||
"child_bridges": "Kinderbrug",
|
||||
"available_update": "System",
|
||||
"updates": "Updates",
|
||||
"update_available": "Update Available",
|
||||
"up_to_date": "Up to Date",
|
||||
"child_bridges": "Child Bridges",
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Op",
|
||||
"pending": "Afwagtend",
|
||||
"down": "Af"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nuut",
|
||||
"new": "New",
|
||||
"up": "Aanlyn",
|
||||
"grace": "In Grasietydperk",
|
||||
"grace": "In Grace Period",
|
||||
"down": "Vanlyn",
|
||||
"paused": "Onderbreek",
|
||||
"paused": "Paused",
|
||||
"status": "Status",
|
||||
"last_ping": "Laaste Pieng",
|
||||
"never": "Nog geen pienge nie"
|
||||
"last_ping": "Last Ping",
|
||||
"never": "No pings yet"
|
||||
},
|
||||
"watchtower": {
|
||||
"containers_scanned": "Geskandeer",
|
||||
"containers_updated": "Opgedateer",
|
||||
"containers_failed": "Misluk"
|
||||
"containers_scanned": "Scanned",
|
||||
"containers_updated": "Updated",
|
||||
"containers_failed": "Failed"
|
||||
},
|
||||
"autobrr": {
|
||||
"approvedPushes": "Goedgekeur",
|
||||
"rejectedPushes": "Verwerp",
|
||||
"rejectedPushes": "Rejected",
|
||||
"filters": "Filters",
|
||||
"indexers": "Indekseerders"
|
||||
},
|
||||
"tubearchivist": {
|
||||
"downloads": "Tou",
|
||||
"videos": "Videos",
|
||||
"channels": "Kanale",
|
||||
"playlists": "Snitlyste"
|
||||
"channels": "Channels",
|
||||
"playlists": "Playlists"
|
||||
},
|
||||
"truenas": {
|
||||
"load": "Stelsellading",
|
||||
"uptime": "Optyd",
|
||||
"load": "System Load",
|
||||
"uptime": "Uptime",
|
||||
"alerts": "Waarskuwings",
|
||||
"time": "{{value, number(style: unit; unitDisplay: long;)}}"
|
||||
},
|
||||
"pyload": {
|
||||
"speed": "Spoed",
|
||||
"speed": "Speed",
|
||||
"active": "Aktief",
|
||||
"queue": "Tou",
|
||||
"total": "Totaal"
|
||||
},
|
||||
"gluetun": {
|
||||
"public_ip": "Publieke IP",
|
||||
"region": "Streek",
|
||||
"country": "Land"
|
||||
"public_ip": "Public IP",
|
||||
"region": "Region",
|
||||
"country": "Country"
|
||||
},
|
||||
"hdhomerun": {
|
||||
"channels": "Kanale",
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Geslaag",
|
||||
"failed": "Misluk",
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Onbekend"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inmandjie",
|
||||
"inbox": "Inbox",
|
||||
"total": "Totaal"
|
||||
},
|
||||
"nextdns": {
|
||||
"wait": "Wag Asseblief",
|
||||
"no_devices": "Geen Toesteldata Ontvang Nie"
|
||||
"no_devices": "No Device Data Received"
|
||||
},
|
||||
"mikrotik": {
|
||||
"cpuLoad": "SVE-lading",
|
||||
"memoryUsed": "Geheue Gebruik",
|
||||
"uptime": "Optyd",
|
||||
"cpuLoad": "CPU Load",
|
||||
"memoryUsed": "Memory Used",
|
||||
"uptime": "Uptime",
|
||||
"numberOfLeases": "Leases"
|
||||
},
|
||||
"xteve": {
|
||||
"streams_all": "Alle Strome",
|
||||
"streams_all": "All Streams",
|
||||
"streams_active": "Aktiewe Strome",
|
||||
"streams_xepg": "XEPG Kanale"
|
||||
"streams_xepg": "XEPG Channels"
|
||||
},
|
||||
"opendtu": {
|
||||
"yieldDay": "Vandag",
|
||||
"absolutePower": "Krag",
|
||||
"relativePower": "Krag %",
|
||||
"limit": "Limiet"
|
||||
"yieldDay": "Today",
|
||||
"absolutePower": "Power",
|
||||
"relativePower": "Power %",
|
||||
"limit": "Limit"
|
||||
},
|
||||
"opnsense": {
|
||||
"cpu": "SVE-lading",
|
||||
"memory": "Aktiewe Geheue",
|
||||
"wanUpload": "WAN Oplaai",
|
||||
"wanDownload": "WAN Aflaai"
|
||||
"cpu": "CPU Load",
|
||||
"memory": "Active Memory",
|
||||
"wanUpload": "WAN Upload",
|
||||
"wanDownload": "WAN Download"
|
||||
},
|
||||
"moonraker": {
|
||||
"printer_state": "Staat van Bladsydrukker",
|
||||
"print_status": "Staat Van Druk",
|
||||
"print_progress": "Vordering",
|
||||
"layers": "Lae"
|
||||
"printer_state": "Printer State",
|
||||
"print_status": "Print Status",
|
||||
"print_progress": "Progress",
|
||||
"layers": "Layers"
|
||||
},
|
||||
"octoprint": {
|
||||
"printer_state": "Status",
|
||||
"temp_tool": "Gereedskap Temperatuur",
|
||||
"temp_bed": "Bed Temperatuur",
|
||||
"job_completion": "Afhandeling"
|
||||
"temp_tool": "Tool temp",
|
||||
"temp_bed": "Bed temp",
|
||||
"job_completion": "Completion"
|
||||
},
|
||||
"cloudflared": {
|
||||
"origin_ip": "Oorsprong IP",
|
||||
"origin_ip": "Origin IP",
|
||||
"status": "Status"
|
||||
},
|
||||
"pfsense": {
|
||||
"load": "Las Gem",
|
||||
"memory": "Mem Gebruik",
|
||||
"load": "Load Avg",
|
||||
"memory": "Mem Usage",
|
||||
"wanStatus": "WAN Status",
|
||||
"up": "Op",
|
||||
"down": "Af",
|
||||
"temp": "Temp",
|
||||
"disk": "Skyfgebruik",
|
||||
"disk": "Disk Usage",
|
||||
"wanIP": "WAN IP"
|
||||
},
|
||||
"proxmoxbackupserver": {
|
||||
"datastore_usage": "Datastoor",
|
||||
"failed_tasks_24h": "Mislukte Take 24h",
|
||||
"datastore_usage": "Datastore",
|
||||
"failed_tasks_24h": "Failed Tasks 24h",
|
||||
"cpu_usage": "SVE",
|
||||
"memory_usage": "Geheue"
|
||||
"memory_usage": "Memory"
|
||||
},
|
||||
"immich": {
|
||||
"users": "Gebruikers",
|
||||
"photos": "Foto's",
|
||||
"photos": "Photos",
|
||||
"videos": "Videos",
|
||||
"storage": "Bergplek"
|
||||
"storage": "Storage"
|
||||
},
|
||||
"uptimekuma": {
|
||||
"up": "Werwe Op",
|
||||
"down": "Werwe Af",
|
||||
"uptime": "Optyd",
|
||||
"incident": "Voorval",
|
||||
"up": "Sites Up",
|
||||
"down": "Sites Down",
|
||||
"uptime": "Uptime",
|
||||
"incident": "Incident",
|
||||
"m": "m"
|
||||
},
|
||||
"atsumeru": {
|
||||
"series": "Reekse",
|
||||
"archives": "Argiewe",
|
||||
"chapters": "Hoofstukke",
|
||||
"categories": "Kategorieë"
|
||||
"archives": "Archives",
|
||||
"chapters": "Chapters",
|
||||
"categories": "Categories"
|
||||
},
|
||||
"komga": {
|
||||
"libraries": "Biblioteke",
|
||||
"libraries": "Libraries",
|
||||
"series": "Reekse",
|
||||
"books": "Boeke"
|
||||
},
|
||||
"diskstation": {
|
||||
"days": "Daë",
|
||||
"uptime": "Optyd",
|
||||
"uptime": "Uptime",
|
||||
"volumeAvailable": "Beskikbaar"
|
||||
},
|
||||
"mylar": {
|
||||
"series": "Reekse",
|
||||
"issues": "Kwessies",
|
||||
"issues": "Issues",
|
||||
"wanted": "Gesoek"
|
||||
},
|
||||
"photoprism": {
|
||||
"albums": "Albums",
|
||||
"photos": "Foto's",
|
||||
"photos": "Photos",
|
||||
"videos": "Videos",
|
||||
"people": "Mense"
|
||||
"people": "People"
|
||||
},
|
||||
"fileflows": {
|
||||
"queue": "Tou",
|
||||
"processing": "Verwerking",
|
||||
"processed": "Verwerk",
|
||||
"time": "Tyd"
|
||||
"time": "Time"
|
||||
},
|
||||
"grafana": {
|
||||
"dashboards": "Dashboards",
|
||||
"datasources": "Databronne",
|
||||
"totalalerts": "Totale Waarskuwings",
|
||||
"alertstriggered": "Waarskuwings Geaktiveer"
|
||||
"datasources": "Data Sources",
|
||||
"totalalerts": "Total Alerts",
|
||||
"alertstriggered": "Alerts Triggered"
|
||||
},
|
||||
"nextcloud": {
|
||||
"cpuload": "Cpu Las",
|
||||
"memoryusage": "Geheuegebruik",
|
||||
"freespace": "Gratis Spasie",
|
||||
"activeusers": "Aktiewe Gebruikers",
|
||||
"numfiles": "Lêers",
|
||||
"numshares": "Gedeelde Items"
|
||||
"cpuload": "Cpu Load",
|
||||
"memoryusage": "Memory Usage",
|
||||
"freespace": "Free Space",
|
||||
"activeusers": "Active Users",
|
||||
"numfiles": "Files",
|
||||
"numshares": "Shared Items"
|
||||
},
|
||||
"kopia": {
|
||||
"status": "Status",
|
||||
"size": "Grootte",
|
||||
"lastrun": "Laaste Iterasie",
|
||||
"nextrun": "Volgende Iterasie",
|
||||
"failed": "Misluk"
|
||||
"size": "Size",
|
||||
"lastrun": "Last Run",
|
||||
"nextrun": "Next Run",
|
||||
"failed": "Failed"
|
||||
},
|
||||
"unmanic": {
|
||||
"active_workers": "Aktiewe Werkers",
|
||||
"total_workers": "Totale Werkers",
|
||||
"records_total": "Toulengte"
|
||||
"active_workers": "Active Workers",
|
||||
"total_workers": "Total Workers",
|
||||
"records_total": "Queue Length"
|
||||
},
|
||||
"pterodactyl": {
|
||||
"servers": "Bedieners",
|
||||
"nodes": "Nodusse"
|
||||
"servers": "Servers",
|
||||
"nodes": "Nodes"
|
||||
},
|
||||
"prometheus": {
|
||||
"targets_up": "Teikens Op",
|
||||
"targets_down": "Teikens Af",
|
||||
"targets_total": "Totale Teikens"
|
||||
"targets_up": "Targets Up",
|
||||
"targets_down": "Targets Down",
|
||||
"targets_total": "Total Targets"
|
||||
},
|
||||
"ghostfolio": {
|
||||
"gross_percent_today": "Vandag",
|
||||
"gross_percent_1y": "Een jaar",
|
||||
"gross_percent_max": "Alle tyd"
|
||||
"gross_percent_today": "Today",
|
||||
"gross_percent_1y": "One year",
|
||||
"gross_percent_max": "All time"
|
||||
},
|
||||
"audiobookshelf": {
|
||||
"podcasts": "Podsendinge",
|
||||
"podcasts": "Podcasts",
|
||||
"books": "Boeke",
|
||||
"podcastsDuration": "Duur",
|
||||
"booksDuration": "Duur"
|
||||
"podcastsDuration": "Duration",
|
||||
"booksDuration": "Duration"
|
||||
},
|
||||
"homeassistant": {
|
||||
"people_home": "Mense Tuis",
|
||||
"lights_on": "Ligte Aan",
|
||||
"switches_on": "Skakels Aan"
|
||||
"people_home": "People Home",
|
||||
"lights_on": "Lights On",
|
||||
"switches_on": "Switches On"
|
||||
},
|
||||
"whatsupdocker": {
|
||||
"monitoring": "Monitering",
|
||||
"updates": "Opdatering"
|
||||
"monitoring": "Monitoring",
|
||||
"updates": "Updates"
|
||||
},
|
||||
"calibreweb": {
|
||||
"books": "Boeke",
|
||||
"authors": "Skrywers",
|
||||
"categories": "Kategorieë",
|
||||
"authors": "Authors",
|
||||
"categories": "Categories",
|
||||
"series": "Reekse"
|
||||
},
|
||||
"jdownloader": {
|
||||
"downloadCount": "Tou",
|
||||
"downloadBytesRemaining": "Oorblywende",
|
||||
"downloadTotalBytes": "Grootte",
|
||||
"downloadSpeed": "Spoed"
|
||||
"downloadTotalBytes": "Size",
|
||||
"downloadSpeed": "Speed"
|
||||
},
|
||||
"kavita": {
|
||||
"seriesCount": "Reekse",
|
||||
"totalFiles": "Lêers"
|
||||
"totalFiles": "Files"
|
||||
},
|
||||
"azuredevops": {
|
||||
"result": "Uitslag",
|
||||
"result": "Result",
|
||||
"status": "Status",
|
||||
"buildId": "Bou ID",
|
||||
"succeeded": "Suksesvol",
|
||||
"notStarted": "Nie Begin Nie",
|
||||
"failed": "Misluk",
|
||||
"canceled": "Gekanselleer",
|
||||
"inProgress": "Besig",
|
||||
"totalPrs": "Totale PRs",
|
||||
"buildId": "Build ID",
|
||||
"succeeded": "Succeeded",
|
||||
"notStarted": "Not Started",
|
||||
"failed": "Failed",
|
||||
"canceled": "Canceled",
|
||||
"inProgress": "In Progress",
|
||||
"totalPrs": "Total PRs",
|
||||
"myPrs": "My PRs",
|
||||
"approved": "Goedgekeur"
|
||||
},
|
||||
|
@ -719,52 +719,52 @@
|
|||
"status": "Status",
|
||||
"online": "Aanlyn",
|
||||
"offline": "Vanlyn",
|
||||
"name": "Naam",
|
||||
"map": "Kaart",
|
||||
"currentPlayers": "Huidige Spelers",
|
||||
"name": "Name",
|
||||
"map": "Map",
|
||||
"currentPlayers": "Current players",
|
||||
"players": "Spelers",
|
||||
"maxPlayers": "Maks spelers",
|
||||
"maxPlayers": "Max players",
|
||||
"bots": "Bots",
|
||||
"ping": "Pieng"
|
||||
},
|
||||
"urbackup": {
|
||||
"ok": "Ok",
|
||||
"errored": "Foute",
|
||||
"noRecent": "Verouderd",
|
||||
"totalUsed": "Gebruikte Bergplek"
|
||||
"errored": "Errors",
|
||||
"noRecent": "Out of Date",
|
||||
"totalUsed": "Used Storage"
|
||||
},
|
||||
"mealie": {
|
||||
"recipes": "Resepte",
|
||||
"recipes": "Recipes",
|
||||
"users": "Gebruikers",
|
||||
"categories": "Kategorieë",
|
||||
"tags": "Merkers"
|
||||
"categories": "Categories",
|
||||
"tags": "Tags"
|
||||
},
|
||||
"openmediavault": {
|
||||
"downloading": "Aflaai",
|
||||
"downloading": "Downloading",
|
||||
"total": "Totaal",
|
||||
"running": "Lopend",
|
||||
"stopped": "Gestop",
|
||||
"passed": "Geslaag",
|
||||
"failed": "Misluk"
|
||||
"passed": "Passed",
|
||||
"failed": "Failed"
|
||||
},
|
||||
"uptimerobot": {
|
||||
"status": "Status",
|
||||
"uptime": "Optyd",
|
||||
"lastDown": "Laaste Stilstand",
|
||||
"downDuration": "Stilstand Duur",
|
||||
"sitesUp": "Werwe Op",
|
||||
"sitesDown": "Werwe Af",
|
||||
"paused": "Onderbreek",
|
||||
"notyetchecked": "Nog Nie Nagegaan Nie",
|
||||
"uptime": "Uptime",
|
||||
"lastDown": "Last Downtime",
|
||||
"downDuration": "Downtime Duration",
|
||||
"sitesUp": "Sites Up",
|
||||
"sitesDown": "Sites Down",
|
||||
"paused": "Paused",
|
||||
"notyetchecked": "Not Yet Checked",
|
||||
"up": "Op",
|
||||
"seemsdown": "Lyk Af",
|
||||
"seemsdown": "Seems Down",
|
||||
"down": "Af",
|
||||
"unknown": "Onbekend"
|
||||
},
|
||||
"calendar": {
|
||||
"inCinemas": "In fliekteaters",
|
||||
"physicalRelease": "Fisiese Vrylating",
|
||||
"digitalRelease": "Digitale Vrylating",
|
||||
"noEventsToday": "Geen gebeure vir vandag nie!"
|
||||
"inCinemas": "In cinemas",
|
||||
"physicalRelease": "Physical release",
|
||||
"digitalRelease": "Digital release",
|
||||
"noEventsToday": "No events for today!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
"api_error": "API خطأ",
|
||||
"information": "معلومات",
|
||||
"status": "الحالة",
|
||||
"url": "الرابط",
|
||||
"raw_error": "خطأ خام",
|
||||
"url": "URL",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "بيانات الاستجابة"
|
||||
},
|
||||
"weather": {
|
||||
|
@ -38,118 +38,118 @@
|
|||
"free": "متاح",
|
||||
"used": "مستخدم",
|
||||
"load": "الضغط",
|
||||
"temp": "مؤقت",
|
||||
"max": "الحد الأقصى",
|
||||
"uptime": "تعمل",
|
||||
"months": "ش",
|
||||
"days": "ي",
|
||||
"hours": "س",
|
||||
"minutes": "د"
|
||||
"temp": "TEMP",
|
||||
"max": "Max",
|
||||
"uptime": "UP",
|
||||
"months": "mo",
|
||||
"days": "d",
|
||||
"hours": "h",
|
||||
"minutes": "m"
|
||||
},
|
||||
"unifi": {
|
||||
"users": "المستخدمون",
|
||||
"uptime": "مدة تشغيل النظام",
|
||||
"days": "أيام",
|
||||
"wan": "الشبكة الواسعة",
|
||||
"lan": "الشبكة المحلية",
|
||||
"wlan": "الشبكة المحلية اللاسلكية",
|
||||
"wan": "WAN",
|
||||
"lan": "LAN",
|
||||
"wlan": "WLAN",
|
||||
"devices": "الأجهزة",
|
||||
"lan_devices": "LAN أجهزة",
|
||||
"wlan_devices": "WLAN أجهزة",
|
||||
"lan_users": "LAN مستخدمين",
|
||||
"wlan_users": "WLAN مستخدمين",
|
||||
"up": "تعمل",
|
||||
"up": "UP",
|
||||
"down": "لا يعمل",
|
||||
"wait": "الرجاء الإنتظار",
|
||||
"empty_data": "حالة النظام الفرعي غير معروفة"
|
||||
"empty_data": "Subsystem status unknown"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "استقبال",
|
||||
"tx": "ارسال",
|
||||
"rx": "RX",
|
||||
"tx": "TX",
|
||||
"mem": "الذاكرة",
|
||||
"cpu": "المعالج",
|
||||
"running": "قيد التشغيل",
|
||||
"running": "Running",
|
||||
"offline": "غير متصل",
|
||||
"error": "خطأ",
|
||||
"unknown": "مجهول",
|
||||
"healthy": "سليم",
|
||||
"starting": "يبدأ التشغيل",
|
||||
"unhealthy": "غير صحّي",
|
||||
"not_found": "غير موجود",
|
||||
"exited": "خرجت",
|
||||
"partial": "جزئي"
|
||||
"healthy": "Healthy",
|
||||
"starting": "Starting",
|
||||
"unhealthy": "Unhealthy",
|
||||
"not_found": "Not Found",
|
||||
"exited": "Exited",
|
||||
"partial": "Partial"
|
||||
},
|
||||
"ping": {
|
||||
"error": "خطأ",
|
||||
"ping": "بينغ",
|
||||
"down": "لا يعمل",
|
||||
"up": "يعمل",
|
||||
"not_available": "غير مُـتوفـّر"
|
||||
"ping": "Ping",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "Not Available"
|
||||
},
|
||||
"siteMonitor": {
|
||||
"http_status": "حالة HTTP",
|
||||
"http_status": "HTTP status",
|
||||
"error": "خطأ",
|
||||
"response": "الرد",
|
||||
"down": "لا يعمل",
|
||||
"up": "يعمل",
|
||||
"not_available": "غير مُـتوفـّر"
|
||||
"response": "Response",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "Not Available"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "يعمل الآن",
|
||||
"transcoding": "التحويل",
|
||||
"bitrate": "معدل البت",
|
||||
"no_active": "لا يوجد بث نشط",
|
||||
"movies": "أفلام",
|
||||
"series": "مسلسلات",
|
||||
"episodes": "حلقات",
|
||||
"songs": "أغاني"
|
||||
"no_active": "No Active Streams",
|
||||
"movies": "Movies",
|
||||
"series": "Series",
|
||||
"episodes": "Episodes",
|
||||
"songs": "Songs"
|
||||
},
|
||||
"evcc": {
|
||||
"pv_power": "إنتاج",
|
||||
"battery_soc": "البطارية",
|
||||
"grid_power": "شبكة",
|
||||
"home_power": "الاستهلاك",
|
||||
"charge_power": "شاحن",
|
||||
"watt_hour": "واط ساعة"
|
||||
"pv_power": "Production",
|
||||
"battery_soc": "Battery",
|
||||
"grid_power": "Grid",
|
||||
"home_power": "Consumption",
|
||||
"charge_power": "Charger",
|
||||
"watt_hour": "Wh"
|
||||
},
|
||||
"flood": {
|
||||
"download": "التنزيل",
|
||||
"upload": "التحميل",
|
||||
"leech": "القرناء",
|
||||
"seed": "البذور"
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"freshrss": {
|
||||
"subscriptions": "الاشتراكات",
|
||||
"unread": "غير مقروءة"
|
||||
"subscriptions": "Subscriptions",
|
||||
"unread": "Unread"
|
||||
},
|
||||
"caddy": {
|
||||
"upstreams": "تدفق",
|
||||
"requests": "طلبات الحالية",
|
||||
"requests_failed": "طلبات فشلت"
|
||||
"upstreams": "Upstreams",
|
||||
"requests": "Current requests",
|
||||
"requests_failed": "Failed requests"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "مجموع الملاحظات",
|
||||
"diffsDetected": "الاختلافات المكتشفة"
|
||||
"diffsDetected": "Diffs Detected"
|
||||
},
|
||||
"channelsdvrserver": {
|
||||
"shows": "برامج",
|
||||
"recordings": "التسجيلات",
|
||||
"scheduled": "مجدولة",
|
||||
"passes": "تمريرات"
|
||||
"shows": "Shows",
|
||||
"recordings": "Recordings",
|
||||
"scheduled": "Scheduled",
|
||||
"passes": "Passes"
|
||||
},
|
||||
"tautulli": {
|
||||
"playing": "يعمل الآن",
|
||||
"transcoding": "التحويل",
|
||||
"bitrate": "معدل البت",
|
||||
"no_active": "لا يوجد بث نشط",
|
||||
"plex_connection_error": "تحقق من الاتصال بـ Plex"
|
||||
"no_active": "No Active Streams",
|
||||
"plex_connection_error": "Check Plex Connection"
|
||||
},
|
||||
"omada": {
|
||||
"connectedAp": "المتصلة APs",
|
||||
"activeUser": "الأجهزة النشطة",
|
||||
"alerts": "تنبيهات",
|
||||
"connectedGateway": "البوابات المتصلة",
|
||||
"connectedSwitches": "مفاتيح التبديل المتصلة"
|
||||
"connectedGateway": "Connected gateways",
|
||||
"connectedSwitches": "Connected switches"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "معدل",
|
||||
|
@ -157,9 +157,9 @@
|
|||
"downloaded": "مُنزل"
|
||||
},
|
||||
"plex": {
|
||||
"streams": "بث نشيطٌ",
|
||||
"albums": "ألبومات",
|
||||
"movies": "أفلام",
|
||||
"streams": "Active Streams",
|
||||
"albums": "Albums",
|
||||
"movies": "Movies",
|
||||
"tv": "مسلسلات"
|
||||
},
|
||||
"sabnzbd": {
|
||||
|
@ -175,39 +175,39 @@
|
|||
"transmission": {
|
||||
"download": "التنزيل",
|
||||
"upload": "التحميل",
|
||||
"leech": "القرناء",
|
||||
"seed": "البذور"
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"qbittorrent": {
|
||||
"download": "التنزيل",
|
||||
"upload": "التحميل",
|
||||
"leech": "القرناء",
|
||||
"seed": "البذور"
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"qnap": {
|
||||
"cpuUsage": "استهلاك المعالج",
|
||||
"memUsage": "استخدام الذاكرة العشوائية",
|
||||
"systemTempC": "درجة حرارة النظام",
|
||||
"poolUsage": "استخدام التجمع",
|
||||
"volumeUsage": "استخدام حجم القرص",
|
||||
"invalid": "غير صحيح"
|
||||
"cpuUsage": "CPU Usage",
|
||||
"memUsage": "MEM Usage",
|
||||
"systemTempC": "System Temp",
|
||||
"poolUsage": "Pool Usage",
|
||||
"volumeUsage": "Volume Usage",
|
||||
"invalid": "Invalid"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "التنزيل",
|
||||
"upload": "التحميل",
|
||||
"leech": "القرناء",
|
||||
"seed": "البذور"
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"downloadstation": {
|
||||
"download": "التنزيل",
|
||||
"upload": "التحميل",
|
||||
"leech": "القرناء",
|
||||
"seed": "البذور"
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"sonarr": {
|
||||
"wanted": "مطلوب",
|
||||
"queued": "في الإنتظار",
|
||||
"series": "مسلسلات",
|
||||
"series": "Series",
|
||||
"queue": "إنتظار",
|
||||
"unknown": "مجهول"
|
||||
},
|
||||
|
@ -215,14 +215,14 @@
|
|||
"wanted": "مطلوب",
|
||||
"missing": "مفقود",
|
||||
"queued": "في الإنتظار",
|
||||
"movies": "أفلام",
|
||||
"movies": "Movies",
|
||||
"queue": "إنتظار",
|
||||
"unknown": "مجهول"
|
||||
},
|
||||
"lidarr": {
|
||||
"wanted": "مطلوب",
|
||||
"queued": "في الإنتظار",
|
||||
"artists": "فنانين"
|
||||
"artists": "Artists"
|
||||
},
|
||||
"readarr": {
|
||||
"wanted": "مطلوب",
|
||||
|
@ -251,14 +251,14 @@
|
|||
},
|
||||
"pialert": {
|
||||
"total": "المجموع",
|
||||
"connected": "متصل",
|
||||
"new_devices": "أجهزة جديدة",
|
||||
"down_alerts": "تنبيهات تعطل الخوادم"
|
||||
"connected": "Connected",
|
||||
"new_devices": "New Devices",
|
||||
"down_alerts": "Down Alerts"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "الاستعلامات",
|
||||
"blocked": "محظور",
|
||||
"blocked_percent": "تم حظر %",
|
||||
"blocked_percent": "Blocked %",
|
||||
"gravity": "الجاذبية"
|
||||
},
|
||||
"adguard": {
|
||||
|
@ -270,26 +270,26 @@
|
|||
"speedtest": {
|
||||
"upload": "التحميل",
|
||||
"download": "التنزيل",
|
||||
"ping": "بينغ"
|
||||
"ping": "Ping"
|
||||
},
|
||||
"portainer": {
|
||||
"running": "قيد التشغيل",
|
||||
"running": "Running",
|
||||
"stopped": "متوقف",
|
||||
"total": "المجموع"
|
||||
},
|
||||
"tailscale": {
|
||||
"address": "عنوان",
|
||||
"expires": "تنتهي",
|
||||
"never": "مطلقاً",
|
||||
"last_seen": "آخر ظهور",
|
||||
"now": "الآن",
|
||||
"years": "{{number}}س",
|
||||
"weeks": "{{number}}أ",
|
||||
"days": "{{number}}ي",
|
||||
"hours": "{{number}}س",
|
||||
"minutes": "{{number}}د",
|
||||
"seconds": "{{number}}ث",
|
||||
"ago": "منذ {{value}}"
|
||||
"address": "Address",
|
||||
"expires": "Expires",
|
||||
"never": "Never",
|
||||
"last_seen": "Last Seen",
|
||||
"now": "Now",
|
||||
"years": "{{number}}y",
|
||||
"weeks": "{{number}}w",
|
||||
"days": "{{number}}d",
|
||||
"hours": "{{number}}h",
|
||||
"minutes": "{{number}}m",
|
||||
"seconds": "{{number}}s",
|
||||
"ago": "{{value}} Ago"
|
||||
},
|
||||
"tdarr": {
|
||||
"queue": "إنتظار",
|
||||
|
@ -303,7 +303,7 @@
|
|||
"middleware": "الوسيطة"
|
||||
},
|
||||
"navidrome": {
|
||||
"nothing_streaming": "لا يوجد بث نشط",
|
||||
"nothing_streaming": "No Active Streams",
|
||||
"please_wait": "الرجاء الإنتظار"
|
||||
},
|
||||
"npm": {
|
||||
|
@ -316,7 +316,7 @@
|
|||
"1hour": "١ ساعة",
|
||||
"1day": "١ يوم",
|
||||
"7days": "٧ أيام",
|
||||
"30days": "30 يوماً"
|
||||
"30days": "٣٠ يوم"
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "التطبيقات",
|
||||
|
@ -325,41 +325,41 @@
|
|||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "مفهرسات",
|
||||
"numberOfGrabs": "مساكات",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "الاستعلامات",
|
||||
"numberOfFailGrabs": "إخفاقات في الالتقاط",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "فشل الاستعلامات"
|
||||
},
|
||||
"jackett": {
|
||||
"configured": "مهيأ",
|
||||
"configured": "Configured",
|
||||
"errored": "خطأ"
|
||||
},
|
||||
"strelaysrv": {
|
||||
"numActiveSessions": "الجلسات",
|
||||
"numConnections": "التوصيلات",
|
||||
"dataRelayed": "منقول(ة)",
|
||||
"dataRelayed": "Relayed",
|
||||
"transferRate": "معدل"
|
||||
},
|
||||
"mastodon": {
|
||||
"user_count": "المستخدمون",
|
||||
"status_count": "منشورات",
|
||||
"domain_count": "مجالات"
|
||||
"status_count": "Posts",
|
||||
"domain_count": "Domains"
|
||||
},
|
||||
"medusa": {
|
||||
"wanted": "مطلوب",
|
||||
"queued": "في الإنتظار",
|
||||
"series": "مسلسلات"
|
||||
"series": "Series"
|
||||
},
|
||||
"minecraft": {
|
||||
"players": "مشغلات",
|
||||
"version": "الإصدار",
|
||||
"players": "Players",
|
||||
"version": "Version",
|
||||
"status": "الحالة",
|
||||
"up": "مُتّصل",
|
||||
"up": "Online",
|
||||
"down": "غير متصل"
|
||||
},
|
||||
"miniflux": {
|
||||
"read": "قراءة",
|
||||
"unread": "غير مقروءة"
|
||||
"unread": "Unread"
|
||||
},
|
||||
"authentik": {
|
||||
"users": "المستخدمون",
|
||||
|
@ -369,36 +369,36 @@
|
|||
"proxmox": {
|
||||
"mem": "الذاكرة",
|
||||
"cpu": "المعالج",
|
||||
"lxc": "حاويات لينكس",
|
||||
"vms": "أجهزة ظاهرية"
|
||||
"lxc": "LXC",
|
||||
"vms": "VMs"
|
||||
},
|
||||
"glances": {
|
||||
"cpu": "المعالج",
|
||||
"load": "الضغط",
|
||||
"wait": "الرجاء الإنتظار",
|
||||
"temp": "مؤقت",
|
||||
"_temp": "درجة الحرارة",
|
||||
"warn": "تنبية",
|
||||
"uptime": "تعمل",
|
||||
"temp": "TEMP",
|
||||
"_temp": "Temp",
|
||||
"warn": "Warn",
|
||||
"uptime": "UP",
|
||||
"total": "المجموع",
|
||||
"free": "متاح",
|
||||
"used": "مستخدم",
|
||||
"days": "ي",
|
||||
"hours": "س",
|
||||
"crit": "حساس",
|
||||
"days": "d",
|
||||
"hours": "h",
|
||||
"crit": "Crit",
|
||||
"read": "قراءة",
|
||||
"write": "الكتابة",
|
||||
"gpu": "كرت الشاشة",
|
||||
"mem": "الذاكرة",
|
||||
"swap": "ذاكرة سواب"
|
||||
"write": "Write",
|
||||
"gpu": "GPU",
|
||||
"mem": "Mem",
|
||||
"swap": "Swap"
|
||||
},
|
||||
"quicklaunch": {
|
||||
"bookmark": "مفضلة",
|
||||
"service": "خدمة",
|
||||
"search": "البحث",
|
||||
"custom": "مُخصّص",
|
||||
"visit": "زيارة",
|
||||
"url": "الرابط"
|
||||
"search": "Search",
|
||||
"custom": "Custom",
|
||||
"visit": "Visit",
|
||||
"url": "URL"
|
||||
},
|
||||
"wmo": {
|
||||
"0-day": "مشمس",
|
||||
|
@ -463,24 +463,24 @@
|
|||
"updates": "تحديثات",
|
||||
"update_available": "تحديث متاح",
|
||||
"up_to_date": "حتى الآن",
|
||||
"child_bridges": "الجسور الأطفال",
|
||||
"child_bridges": "Child Bridges",
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "يعمل",
|
||||
"up": "Up",
|
||||
"pending": "معلق",
|
||||
"down": "لا يعمل"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "جديد(ة)",
|
||||
"up": "مُتّصل",
|
||||
"grace": "في فترة السماح",
|
||||
"new": "New",
|
||||
"up": "Online",
|
||||
"grace": "In Grace Period",
|
||||
"down": "غير متصل",
|
||||
"paused": "متوقف",
|
||||
"paused": "Paused",
|
||||
"status": "الحالة",
|
||||
"last_ping": "آخر Ping",
|
||||
"never": "لا توجد بنغات بعد"
|
||||
"last_ping": "Last Ping",
|
||||
"never": "No pings yet"
|
||||
},
|
||||
"watchtower": {
|
||||
"containers_scanned": "مفحوصة",
|
||||
"containers_scanned": "Scanned",
|
||||
"containers_updated": "محدث",
|
||||
"containers_failed": "فشل"
|
||||
},
|
||||
|
@ -515,7 +515,7 @@
|
|||
},
|
||||
"hdhomerun": {
|
||||
"channels": "القنوات",
|
||||
"hd": "جودة HD"
|
||||
"hd": "HD"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "إجتاز",
|
||||
|
@ -534,18 +534,18 @@
|
|||
"cpuLoad": "حمل المعالج",
|
||||
"memoryUsed": "الذاكرة الستخدمة",
|
||||
"uptime": "مدة التشغيل",
|
||||
"numberOfLeases": "إيجارات"
|
||||
"numberOfLeases": "Leases"
|
||||
},
|
||||
"xteve": {
|
||||
"streams_all": "جميع البث",
|
||||
"streams_active": "بث نشيطٌ",
|
||||
"streams_all": "All Streams",
|
||||
"streams_active": "Active Streams",
|
||||
"streams_xepg": "XEPG قنوات"
|
||||
},
|
||||
"opendtu": {
|
||||
"yieldDay": "اليوم",
|
||||
"absolutePower": "القوة",
|
||||
"relativePower": "قوة %",
|
||||
"limit": "الحد الأقصى"
|
||||
"yieldDay": "Today",
|
||||
"absolutePower": "Power",
|
||||
"relativePower": "Power %",
|
||||
"limit": "Limit"
|
||||
},
|
||||
"opnsense": {
|
||||
"cpu": "حمل المعالج",
|
||||
|
@ -566,47 +566,47 @@
|
|||
"job_completion": "إتمام"
|
||||
},
|
||||
"cloudflared": {
|
||||
"origin_ip": "IP الأصل",
|
||||
"origin_ip": "Origin IP",
|
||||
"status": "الحالة"
|
||||
},
|
||||
"pfsense": {
|
||||
"load": "معدل التحميل",
|
||||
"memory": "استخدام الذاكرة العشوائية",
|
||||
"wanStatus": "حالة الشبكة الواسعة",
|
||||
"up": "يعمل",
|
||||
"down": "لا يعمل",
|
||||
"temp": "درجة الحرارة",
|
||||
"disk": "استخدام القرص",
|
||||
"wanIP": "IP الشبكة الواسعة"
|
||||
"load": "Load Avg",
|
||||
"memory": "Mem Usage",
|
||||
"wanStatus": "WAN Status",
|
||||
"up": "Up",
|
||||
"down": "Down",
|
||||
"temp": "Temp",
|
||||
"disk": "Disk Usage",
|
||||
"wanIP": "WAN IP"
|
||||
},
|
||||
"proxmoxbackupserver": {
|
||||
"datastore_usage": "مخزن البيانات",
|
||||
"failed_tasks_24h": "المهام الفاشلة 24 ساعة",
|
||||
"datastore_usage": "Datastore",
|
||||
"failed_tasks_24h": "Failed Tasks 24h",
|
||||
"cpu_usage": "المعالج",
|
||||
"memory_usage": "الذاكرة"
|
||||
"memory_usage": "Memory"
|
||||
},
|
||||
"immich": {
|
||||
"users": "المستخدمون",
|
||||
"photos": "الصور",
|
||||
"photos": "Photos",
|
||||
"videos": "الفيديوهات",
|
||||
"storage": "التخزين"
|
||||
"storage": "Storage"
|
||||
},
|
||||
"uptimekuma": {
|
||||
"up": "المواقع تعمل",
|
||||
"down": "مواقع لا تعمل",
|
||||
"up": "Sites Up",
|
||||
"down": "Sites Down",
|
||||
"uptime": "مدة التشغيل",
|
||||
"incident": "حادثة",
|
||||
"m": "د"
|
||||
"incident": "Incident",
|
||||
"m": "m"
|
||||
},
|
||||
"atsumeru": {
|
||||
"series": "مسلسلات",
|
||||
"archives": "الأرشيف",
|
||||
"chapters": "الفصول",
|
||||
"categories": "التصنيفات"
|
||||
"series": "Series",
|
||||
"archives": "Archives",
|
||||
"chapters": "Chapters",
|
||||
"categories": "Categories"
|
||||
},
|
||||
"komga": {
|
||||
"libraries": "المكتبات",
|
||||
"series": "مسلسلات",
|
||||
"libraries": "Libraries",
|
||||
"series": "Series",
|
||||
"books": "كتب"
|
||||
},
|
||||
"diskstation": {
|
||||
|
@ -615,134 +615,134 @@
|
|||
"volumeAvailable": "متاح"
|
||||
},
|
||||
"mylar": {
|
||||
"series": "مسلسلات",
|
||||
"issues": "المُشكِلات",
|
||||
"series": "Series",
|
||||
"issues": "Issues",
|
||||
"wanted": "مطلوب"
|
||||
},
|
||||
"photoprism": {
|
||||
"albums": "ألبومات",
|
||||
"photos": "الصور",
|
||||
"albums": "Albums",
|
||||
"photos": "Photos",
|
||||
"videos": "الفيديوهات",
|
||||
"people": "أشخاص"
|
||||
"people": "People"
|
||||
},
|
||||
"fileflows": {
|
||||
"queue": "إنتظار",
|
||||
"processing": "معالجة",
|
||||
"processed": "معالجة",
|
||||
"time": "الوقت"
|
||||
"time": "Time"
|
||||
},
|
||||
"grafana": {
|
||||
"dashboards": "لوحات المعلومات",
|
||||
"datasources": "مصادر البيانات",
|
||||
"totalalerts": "إجمالي التنبيهات",
|
||||
"alertstriggered": "تنبيهات مفعلة"
|
||||
"dashboards": "Dashboards",
|
||||
"datasources": "Data Sources",
|
||||
"totalalerts": "Total Alerts",
|
||||
"alertstriggered": "Alerts Triggered"
|
||||
},
|
||||
"nextcloud": {
|
||||
"cpuload": "حمل المعالج",
|
||||
"memoryusage": "استخدام الذاكرة",
|
||||
"freespace": "مساحة فارغة",
|
||||
"activeusers": "مستخدمين نشطين",
|
||||
"numfiles": "ملفات",
|
||||
"numshares": "عناصر مشتركة"
|
||||
"cpuload": "Cpu Load",
|
||||
"memoryusage": "Memory Usage",
|
||||
"freespace": "Free Space",
|
||||
"activeusers": "Active Users",
|
||||
"numfiles": "Files",
|
||||
"numshares": "Shared Items"
|
||||
},
|
||||
"kopia": {
|
||||
"status": "الحالة",
|
||||
"size": "حجم",
|
||||
"lastrun": "آخر تشغيل",
|
||||
"nextrun": "التشغيل التالي",
|
||||
"size": "Size",
|
||||
"lastrun": "Last Run",
|
||||
"nextrun": "Next Run",
|
||||
"failed": "فشل"
|
||||
},
|
||||
"unmanic": {
|
||||
"active_workers": "العمال النشطون",
|
||||
"total_workers": "مجموع العمال",
|
||||
"records_total": "طول الصف"
|
||||
"active_workers": "Active Workers",
|
||||
"total_workers": "Total Workers",
|
||||
"records_total": "Queue Length"
|
||||
},
|
||||
"pterodactyl": {
|
||||
"servers": "السيرفرات",
|
||||
"nodes": "عقد"
|
||||
"servers": "Servers",
|
||||
"nodes": "Nodes"
|
||||
},
|
||||
"prometheus": {
|
||||
"targets_up": "أهداف تعمل",
|
||||
"targets_down": "الأهداف لا تعمل",
|
||||
"targets_total": "الأهداف الإجمالية"
|
||||
"targets_up": "Targets Up",
|
||||
"targets_down": "Targets Down",
|
||||
"targets_total": "Total Targets"
|
||||
},
|
||||
"ghostfolio": {
|
||||
"gross_percent_today": "اليوم",
|
||||
"gross_percent_1y": "سنة",
|
||||
"gross_percent_max": "كل الوقت"
|
||||
"gross_percent_today": "Today",
|
||||
"gross_percent_1y": "One year",
|
||||
"gross_percent_max": "All time"
|
||||
},
|
||||
"audiobookshelf": {
|
||||
"podcasts": "بودكاست",
|
||||
"podcasts": "Podcasts",
|
||||
"books": "كتب",
|
||||
"podcastsDuration": "المدة",
|
||||
"booksDuration": "المدة"
|
||||
"podcastsDuration": "Duration",
|
||||
"booksDuration": "Duration"
|
||||
},
|
||||
"homeassistant": {
|
||||
"people_home": "أشخاص في المنزل",
|
||||
"lights_on": "أضواء مضاءة",
|
||||
"switches_on": "مفاتيح قيد التشغيل"
|
||||
"people_home": "People Home",
|
||||
"lights_on": "Lights On",
|
||||
"switches_on": "Switches On"
|
||||
},
|
||||
"whatsupdocker": {
|
||||
"monitoring": "المراقبة",
|
||||
"monitoring": "Monitoring",
|
||||
"updates": "تحديثات"
|
||||
},
|
||||
"calibreweb": {
|
||||
"books": "كتب",
|
||||
"authors": "المؤلفون",
|
||||
"categories": "التصنيفات",
|
||||
"series": "مسلسلات"
|
||||
"authors": "Authors",
|
||||
"categories": "Categories",
|
||||
"series": "Series"
|
||||
},
|
||||
"jdownloader": {
|
||||
"downloadCount": "إنتظار",
|
||||
"downloadBytesRemaining": "متبقي",
|
||||
"downloadTotalBytes": "حجم",
|
||||
"downloadTotalBytes": "Size",
|
||||
"downloadSpeed": "السرعة"
|
||||
},
|
||||
"kavita": {
|
||||
"seriesCount": "مسلسلات",
|
||||
"totalFiles": "ملفات"
|
||||
"seriesCount": "Series",
|
||||
"totalFiles": "Files"
|
||||
},
|
||||
"azuredevops": {
|
||||
"result": "نتيجة",
|
||||
"result": "Result",
|
||||
"status": "الحالة",
|
||||
"buildId": "معرف البناء",
|
||||
"succeeded": "تم بنجاح",
|
||||
"notStarted": "لم يبدأ",
|
||||
"buildId": "Build ID",
|
||||
"succeeded": "Succeeded",
|
||||
"notStarted": "Not Started",
|
||||
"failed": "فشل",
|
||||
"canceled": "ملغى",
|
||||
"inProgress": "قيد التنفيذ",
|
||||
"totalPrs": "المجموع الكلي للPRs",
|
||||
"myPrs": "الPRs الشخصية",
|
||||
"canceled": "Canceled",
|
||||
"inProgress": "In Progress",
|
||||
"totalPrs": "Total PRs",
|
||||
"myPrs": "My PRs",
|
||||
"approved": "مصدق"
|
||||
},
|
||||
"gamedig": {
|
||||
"status": "الحالة",
|
||||
"online": "مُتّصل",
|
||||
"online": "Online",
|
||||
"offline": "غير متصل",
|
||||
"name": "الاسم",
|
||||
"map": "خريطة",
|
||||
"currentPlayers": "المشغلات الحالية",
|
||||
"players": "مشغلات",
|
||||
"maxPlayers": "الحد الأقصى للمشغلات",
|
||||
"bots": "بوتات",
|
||||
"ping": "بينغ"
|
||||
"name": "Name",
|
||||
"map": "Map",
|
||||
"currentPlayers": "Current players",
|
||||
"players": "Players",
|
||||
"maxPlayers": "Max players",
|
||||
"bots": "Bots",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"urbackup": {
|
||||
"ok": "تمام",
|
||||
"errored": "أخطاء",
|
||||
"noRecent": "غير محدّث",
|
||||
"totalUsed": "التخزين المستخدم"
|
||||
"ok": "Ok",
|
||||
"errored": "Errors",
|
||||
"noRecent": "Out of Date",
|
||||
"totalUsed": "Used Storage"
|
||||
},
|
||||
"mealie": {
|
||||
"recipes": "وصفات",
|
||||
"recipes": "Recipes",
|
||||
"users": "المستخدمون",
|
||||
"categories": "التصنيفات",
|
||||
"tags": "التصنيفات"
|
||||
"categories": "Categories",
|
||||
"tags": "Tags"
|
||||
},
|
||||
"openmediavault": {
|
||||
"downloading": "جاري التنزيل",
|
||||
"downloading": "Downloading",
|
||||
"total": "المجموع",
|
||||
"running": "قيد التشغيل",
|
||||
"running": "Running",
|
||||
"stopped": "متوقف",
|
||||
"passed": "إجتاز",
|
||||
"failed": "فشل"
|
||||
|
@ -750,21 +750,21 @@
|
|||
"uptimerobot": {
|
||||
"status": "الحالة",
|
||||
"uptime": "مدة التشغيل",
|
||||
"lastDown": "فترة التعطّل الأخيرة",
|
||||
"downDuration": "مدة التعطل",
|
||||
"sitesUp": "المواقع تعمل",
|
||||
"sitesDown": "مواقع لا تعمل",
|
||||
"paused": "متوقف",
|
||||
"notyetchecked": "لم يتم التحقق بعد",
|
||||
"up": "يعمل",
|
||||
"seemsdown": "يبدو أنه معطل",
|
||||
"down": "لا يعمل",
|
||||
"lastDown": "Last Downtime",
|
||||
"downDuration": "Downtime Duration",
|
||||
"sitesUp": "Sites Up",
|
||||
"sitesDown": "Sites Down",
|
||||
"paused": "Paused",
|
||||
"notyetchecked": "Not Yet Checked",
|
||||
"up": "Up",
|
||||
"seemsdown": "Seems Down",
|
||||
"down": "Down",
|
||||
"unknown": "مجهول"
|
||||
},
|
||||
"calendar": {
|
||||
"inCinemas": "في دور السينما",
|
||||
"physicalRelease": "الإصدار المادي",
|
||||
"digitalRelease": "الإصدار الرقمي",
|
||||
"noEventsToday": "لا توجد أحداث اليوم!"
|
||||
"inCinemas": "In cinemas",
|
||||
"physicalRelease": "Physical release",
|
||||
"digitalRelease": "Digital release",
|
||||
"noEventsToday": "No events for today!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,24 +122,6 @@
|
|||
"subscriptions": "Abonnements",
|
||||
"unread": "Ungelesen"
|
||||
},
|
||||
"fritzbox": {
|
||||
"connectionStatus": "Status",
|
||||
"connectionStatusUnconfigured": "Unkonfiguriert",
|
||||
"connectionStatusConnecting": "Verbinde",
|
||||
"connectionStatusAuthenticating": "Authenifiziere",
|
||||
"connectionStatusPendingDisconnect": "Anstehende Trennung",
|
||||
"connectionStatusDisconnecting": "Trenne",
|
||||
"connectionStatusDisconnected": "Getrennt",
|
||||
"connectionStatusConnected": "Verbunden",
|
||||
"uptime": "Betriebszeit",
|
||||
"maxDown": "Max. Down",
|
||||
"maxUp": "Max. Up",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"received": "Empfangen",
|
||||
"sent": "Gesendet",
|
||||
"externalIPAddress": "Ext. IP"
|
||||
},
|
||||
"caddy": {
|
||||
"upstreams": "Upstreams",
|
||||
"requests": "Aktuelle Anfragen",
|
||||
|
@ -147,7 +129,7 @@
|
|||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "Gesamt beobachtet",
|
||||
"diffsDetected": "Erkannte Änderungen"
|
||||
"diffsDetected": "Erkannte Differenzen"
|
||||
},
|
||||
"channelsdvrserver": {
|
||||
"shows": "Serien",
|
||||
|
@ -346,7 +328,7 @@
|
|||
"numberOfGrabs": "Abrufungen",
|
||||
"numberOfQueries": "Anfragen",
|
||||
"numberOfFailGrabs": "Fehlgeschlagene Abrufungen",
|
||||
"numberOfFailQueries": "Fehlgeschlagene Anfragen"
|
||||
"numberOfFailQueries": "Fehlgeschlagene Abfragen"
|
||||
},
|
||||
"jackett": {
|
||||
"configured": "Konfiguriert",
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
},
|
||||
"unifi": {
|
||||
"users": "Users",
|
||||
"uptime": "Uptime",
|
||||
"uptime": "System Uptime",
|
||||
"days": "Days",
|
||||
"wan": "WAN",
|
||||
"lan": "LAN",
|
||||
|
@ -122,24 +122,6 @@
|
|||
"subscriptions": "Subscriptions",
|
||||
"unread": "Unread"
|
||||
},
|
||||
"fritzbox": {
|
||||
"connectionStatus": "Status",
|
||||
"connectionStatusUnconfigured": "Unconfigured",
|
||||
"connectionStatusConnecting": "Connecting",
|
||||
"connectionStatusAuthenticating": "Authenticating",
|
||||
"connectionStatusPendingDisconnect": "Pending Disconnect",
|
||||
"connectionStatusDisconnecting": "Disconnecting",
|
||||
"connectionStatusDisconnected": "Disconnected",
|
||||
"connectionStatusConnected": "Connected",
|
||||
"uptime": "Uptime",
|
||||
"maxDown": "Max. Down",
|
||||
"maxUp": "Max. Up",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"received": "Received",
|
||||
"sent": "Sent",
|
||||
"externalIPAddress": "Ext. IP"
|
||||
},
|
||||
"caddy": {
|
||||
"upstreams": "Upstreams",
|
||||
"requests": "Current requests",
|
||||
|
@ -783,7 +765,6 @@
|
|||
"inCinemas": "In cinemas",
|
||||
"physicalRelease": "Physical release",
|
||||
"digitalRelease": "Digital release",
|
||||
"noEventsToday": "No events for today!",
|
||||
"noEventsFound": "No events found"
|
||||
"noEventsToday": "No events for today!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
"flood": {
|
||||
"download": "Descarga",
|
||||
"upload": "Subida",
|
||||
"leech": "Descargas",
|
||||
"leech": "Depender",
|
||||
"seed": "Semillas"
|
||||
},
|
||||
"freshrss": {
|
||||
|
@ -175,13 +175,13 @@
|
|||
"transmission": {
|
||||
"download": "Descarga",
|
||||
"upload": "Subida",
|
||||
"leech": "Descargas",
|
||||
"leech": "Depender",
|
||||
"seed": "Semillas"
|
||||
},
|
||||
"qbittorrent": {
|
||||
"download": "Descarga",
|
||||
"upload": "Subida",
|
||||
"leech": "Descargas",
|
||||
"leech": "Depender",
|
||||
"seed": "Semillas"
|
||||
},
|
||||
"qnap": {
|
||||
|
@ -195,13 +195,13 @@
|
|||
"deluge": {
|
||||
"download": "Descarga",
|
||||
"upload": "Subida",
|
||||
"leech": "Descargas",
|
||||
"leech": "Depender",
|
||||
"seed": "Semillas"
|
||||
},
|
||||
"downloadstation": {
|
||||
"download": "Descarga",
|
||||
"upload": "Subida",
|
||||
"leech": "Descargas",
|
||||
"leech": "Depender",
|
||||
"seed": "Semillas"
|
||||
},
|
||||
"sonarr": {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"widget": {
|
||||
"missing_type": "Tipo del Widget Mancante: {{type}}",
|
||||
"api_error": "Errore API",
|
||||
"information": "Informazioni",
|
||||
"information": "Informazione",
|
||||
"status": "Stato",
|
||||
"url": "URL",
|
||||
"raw_error": "Errore non processato",
|
||||
|
@ -87,9 +87,9 @@
|
|||
"not_available": "Non disponibile"
|
||||
},
|
||||
"siteMonitor": {
|
||||
"http_status": "Stato HTTP",
|
||||
"http_status": "HTTP status",
|
||||
"error": "Errore",
|
||||
"response": "Risposta",
|
||||
"response": "Response",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "Non disponibile"
|
||||
|
@ -115,7 +115,7 @@
|
|||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "In download",
|
||||
"leech": "In scaricamento",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"freshrss": {
|
||||
|
@ -128,7 +128,7 @@
|
|||
"requests_failed": "Richieste fallite"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "Totale Osservati",
|
||||
"totalObserved": "Totale Osservato",
|
||||
"diffsDetected": "Differenze Rilevate"
|
||||
},
|
||||
"channelsdvrserver": {
|
||||
|
@ -160,7 +160,7 @@
|
|||
"streams": "Trasmissioni attive",
|
||||
"albums": "Album",
|
||||
"movies": "Film",
|
||||
"tv": "Programmi televisivi"
|
||||
"tv": "Programma televisivo"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rapporto",
|
||||
|
@ -175,13 +175,13 @@
|
|||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "In download",
|
||||
"leech": "In scaricamento",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"qbittorrent": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "In download",
|
||||
"leech": "In scaricamento",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"qnap": {
|
||||
|
@ -195,13 +195,13 @@
|
|||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "In download",
|
||||
"leech": "In scaricamento",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"downloadstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "In download",
|
||||
"leech": "In scaricamento",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"sonarr": {
|
||||
|
@ -283,10 +283,10 @@
|
|||
"never": "Mai",
|
||||
"last_seen": "Ultima visualizzazione",
|
||||
"now": "Adesso",
|
||||
"years": "{{number}}a",
|
||||
"weeks": "{{number}}st",
|
||||
"days": "{{number}}g",
|
||||
"hours": "{{number}}o",
|
||||
"years": "{{number}}y",
|
||||
"weeks": "{{number}}w",
|
||||
"days": "{{number}}d",
|
||||
"hours": "{{number}}h",
|
||||
"minutes": "{{number}}m",
|
||||
"seconds": "{{number}}s",
|
||||
"ago": "{{value}} Fa"
|
||||
|
@ -342,7 +342,7 @@
|
|||
},
|
||||
"mastodon": {
|
||||
"user_count": "Utenti",
|
||||
"status_count": "Messaggi",
|
||||
"status_count": "Posts",
|
||||
"domain_count": "Domini"
|
||||
},
|
||||
"medusa": {
|
||||
|
@ -764,7 +764,7 @@
|
|||
"calendar": {
|
||||
"inCinemas": "Al cinema",
|
||||
"physicalRelease": "Release fisici",
|
||||
"digitalRelease": "Versione digitale",
|
||||
"noEventsToday": "Nessun evento per oggi!"
|
||||
"digitalRelease": "Digital release",
|
||||
"noEventsToday": "No events for today!"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -59,7 +59,7 @@
|
|||
"lan_users": "LAN uporabniki",
|
||||
"wlan_users": "WLAN uporabniki",
|
||||
"up": "Gor",
|
||||
"down": "DOL",
|
||||
"down": "Dol",
|
||||
"wait": "Prosimo počakajte",
|
||||
"empty_data": "Neznani status podsistema"
|
||||
},
|
||||
|
@ -82,17 +82,17 @@
|
|||
"ping": {
|
||||
"error": "Napaka",
|
||||
"ping": "Ping",
|
||||
"down": "Nepovezan",
|
||||
"up": "Povezan",
|
||||
"not_available": "Ni na voljo"
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "Not Available"
|
||||
},
|
||||
"siteMonitor": {
|
||||
"http_status": "HTTP status",
|
||||
"error": "Napaka",
|
||||
"response": "Odgovor",
|
||||
"down": "Nepovezan",
|
||||
"up": "Povezan",
|
||||
"not_available": "Ni na voljo"
|
||||
"response": "Response",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "Not Available"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Predvaja",
|
||||
|
@ -465,9 +465,9 @@
|
|||
"up_to_date": "Posodobljeno",
|
||||
"child_bridges": "Otroški mostovi",
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Povezan",
|
||||
"up": "Up",
|
||||
"pending": "V teku",
|
||||
"down": "Nepovezan"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nov",
|
||||
|
@ -542,9 +542,9 @@
|
|||
"streams_xepg": "XEPG kanali"
|
||||
},
|
||||
"opendtu": {
|
||||
"yieldDay": "Danes",
|
||||
"absolutePower": "Napajanje",
|
||||
"relativePower": "Napajanje %",
|
||||
"yieldDay": "Today",
|
||||
"absolutePower": "Power",
|
||||
"relativePower": "Power %",
|
||||
"limit": "Limit"
|
||||
},
|
||||
"opnsense": {
|
||||
|
@ -572,9 +572,9 @@
|
|||
"pfsense": {
|
||||
"load": "Povp. obremenitev",
|
||||
"memory": "Poraba spomina",
|
||||
"wanStatus": "WAN status",
|
||||
"up": "Povezan",
|
||||
"down": "Nepovezan",
|
||||
"wanStatus": "WAN Status",
|
||||
"up": "Up",
|
||||
"down": "Down",
|
||||
"temp": "Temp",
|
||||
"disk": "Poraba diska",
|
||||
"wanIP": "WAN IP"
|
||||
|
@ -667,7 +667,7 @@
|
|||
"targets_total": "Skupaj tarč"
|
||||
},
|
||||
"ghostfolio": {
|
||||
"gross_percent_today": "Danes",
|
||||
"gross_percent_today": "Today",
|
||||
"gross_percent_1y": "Eno leto",
|
||||
"gross_percent_max": "Celoten čas"
|
||||
},
|
||||
|
@ -750,21 +750,21 @@
|
|||
"uptimerobot": {
|
||||
"status": "Stanje",
|
||||
"uptime": "Čas delovanja",
|
||||
"lastDown": "Zadnjič nepovezan",
|
||||
"downDuration": "Dolžina izpada",
|
||||
"lastDown": "Last Downtime",
|
||||
"downDuration": "Downtime Duration",
|
||||
"sitesUp": "Deluje",
|
||||
"sitesDown": "Ne deluje",
|
||||
"paused": "Pavziran",
|
||||
"notyetchecked": "Še nepreverjeno",
|
||||
"up": "Povezan",
|
||||
"seemsdown": "Ne deluje",
|
||||
"down": "Nepovezan",
|
||||
"notyetchecked": "Not Yet Checked",
|
||||
"up": "Up",
|
||||
"seemsdown": "Seems Down",
|
||||
"down": "Down",
|
||||
"unknown": "Neznano"
|
||||
},
|
||||
"calendar": {
|
||||
"inCinemas": "V kinu",
|
||||
"physicalRelease": "Fizična izdaja",
|
||||
"digitalRelease": "Digitalna izdaja",
|
||||
"noEventsToday": "Za danes ni dogodkov!"
|
||||
"inCinemas": "In cinemas",
|
||||
"physicalRelease": "Physical release",
|
||||
"digitalRelease": "Digital release",
|
||||
"noEventsToday": "No events for today!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,8 +51,8 @@
|
|||
"uptime": "系統運作時間",
|
||||
"days": "天",
|
||||
"wan": "WAN",
|
||||
"lan": "區域網路",
|
||||
"wlan": "無線區域網路",
|
||||
"lan": "LAN",
|
||||
"wlan": "WLAN",
|
||||
"devices": "設備",
|
||||
"lan_devices": "有線設備",
|
||||
"wlan_devices": "無線設備",
|
||||
|
@ -81,18 +81,18 @@
|
|||
},
|
||||
"ping": {
|
||||
"error": "錯誤",
|
||||
"ping": "延遲",
|
||||
"ping": "Ping",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "不可用"
|
||||
"not_available": "Not Available"
|
||||
},
|
||||
"siteMonitor": {
|
||||
"http_status": "HTTP 狀態",
|
||||
"http_status": "HTTP status",
|
||||
"error": "錯誤",
|
||||
"response": "回應",
|
||||
"response": "Response",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "不可用"
|
||||
"not_available": "Not Available"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "正在播放",
|
||||
|
@ -270,7 +270,7 @@
|
|||
"speedtest": {
|
||||
"upload": "上傳速率",
|
||||
"download": "下載速率",
|
||||
"ping": "延遲"
|
||||
"ping": "Ping"
|
||||
},
|
||||
"portainer": {
|
||||
"running": "執行中",
|
||||
|
@ -542,7 +542,7 @@
|
|||
"streams_xepg": "XEPG頻道"
|
||||
},
|
||||
"opendtu": {
|
||||
"yieldDay": "今日",
|
||||
"yieldDay": "Today",
|
||||
"absolutePower": "Power",
|
||||
"relativePower": "Power %",
|
||||
"limit": "Limit"
|
||||
|
@ -667,7 +667,7 @@
|
|||
"targets_total": "目標總數"
|
||||
},
|
||||
"ghostfolio": {
|
||||
"gross_percent_today": "今日",
|
||||
"gross_percent_today": "Today",
|
||||
"gross_percent_1y": "一年",
|
||||
"gross_percent_max": "所有時間"
|
||||
},
|
||||
|
@ -725,10 +725,10 @@
|
|||
"players": "玩家",
|
||||
"maxPlayers": "玩家數上限",
|
||||
"bots": "機器人",
|
||||
"ping": "延遲"
|
||||
"ping": "Ping"
|
||||
},
|
||||
"urbackup": {
|
||||
"ok": "確定",
|
||||
"ok": "Ok",
|
||||
"errored": "錯誤",
|
||||
"noRecent": "已過時",
|
||||
"totalUsed": "已使用空間"
|
||||
|
@ -765,6 +765,6 @@
|
|||
"inCinemas": "In cinemas",
|
||||
"physicalRelease": "Physical release",
|
||||
"digitalRelease": "Digital release",
|
||||
"noEventsToday": "今日無事件"
|
||||
"noEventsToday": "No events for today!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext } from "react";
|
||||
import Image from "next/future/image";
|
||||
import Image from "next/image";
|
||||
|
||||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import { ThemeContext } from "utils/contexts/theme";
|
||||
|
|
|
@ -6,7 +6,7 @@ import { MdKeyboardArrowDown } from "react-icons/md";
|
|||
import List from "components/services/list";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
|
||||
export default function ServicesGroup({ group, services, layout, fiveColumns, disableCollapse, useEqualHeights }) {
|
||||
export default function ServicesGroup({ group, services, layout, fiveColumns, disableCollapse }) {
|
||||
const panel = useRef();
|
||||
|
||||
return (
|
||||
|
@ -62,7 +62,7 @@ export default function ServicesGroup({ group, services, layout, fiveColumns, di
|
|||
}}
|
||||
>
|
||||
<Disclosure.Panel className="transition-all overflow-hidden duration-300 ease-out" ref={panel} static>
|
||||
<List group={group} services={services.services} layout={layout} useEqualHeights={useEqualHeights} />
|
||||
<List group={group} services={services.services} layout={layout} />
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
|
|
|
@ -12,7 +12,7 @@ import Kubernetes from "widgets/kubernetes/component";
|
|||
import { SettingsContext } from "utils/contexts/settings";
|
||||
import ResolvedIcon from "components/resolvedicon";
|
||||
|
||||
export default function Item({ service, group, useEqualHeights }) {
|
||||
export default function Item({ service, group }) {
|
||||
const hasLink = service.href && service.href !== "#";
|
||||
const { settings } = useContext(SettingsContext);
|
||||
const showStats = service.showStats === false ? false : settings.showStats;
|
||||
|
@ -37,8 +37,7 @@ export default function Item({ service, group, useEqualHeights }) {
|
|||
className={classNames(
|
||||
settings.cardBlur !== undefined && `backdrop-blur${settings.cardBlur.length ? "-" : ""}${settings.cardBlur}`,
|
||||
hasLink && "cursor-pointer",
|
||||
useEqualHeights && "h-[calc(100%-0.5rem)]",
|
||||
"transition-all mb-2 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative overflow-clip service-card",
|
||||
"transition-all h-15 mb-2 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative overflow-clip service-card",
|
||||
)}
|
||||
>
|
||||
<div className="flex select-none z-0 service-title">
|
||||
|
|
|
@ -4,7 +4,7 @@ import { columnMap } from "../../utils/layout/columns";
|
|||
|
||||
import Item from "components/services/item";
|
||||
|
||||
export default function List({ group, services, layout, useEqualHeights }) {
|
||||
export default function List({ group, services, layout }) {
|
||||
return (
|
||||
<ul
|
||||
className={classNames(
|
||||
|
@ -13,12 +13,7 @@ export default function List({ group, services, layout, useEqualHeights }) {
|
|||
)}
|
||||
>
|
||||
{services.map((service) => (
|
||||
<Item
|
||||
key={service.container ?? service.app ?? service.name}
|
||||
service={service}
|
||||
group={group}
|
||||
useEqualHeights={layout?.useEqualHeights ?? useEqualHeights}
|
||||
/>
|
||||
<Item key={service.container ?? service.app ?? service.name} service={service} group={group} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ColorProvider } from "utils/contexts/color";
|
|||
import { ThemeProvider } from "utils/contexts/theme";
|
||||
import { SettingsProvider } from "utils/contexts/settings";
|
||||
import { TabProvider } from "utils/contexts/tab";
|
||||
import { EventProvider } from "utils/contexts/calendar";
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
|
@ -31,7 +32,9 @@ function MyApp({ Component, pageProps }) {
|
|||
<ThemeProvider>
|
||||
<SettingsProvider>
|
||||
<TabProvider>
|
||||
<Component {...pageProps} />
|
||||
<EventProvider>
|
||||
<Component {...pageProps} />
|
||||
</EventProvider>
|
||||
</TabProvider>
|
||||
</SettingsProvider>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -10,6 +10,7 @@ export default function Document() {
|
|||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="/site.webmanifest?v=4" crossOrigin="use-credentials" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg?v=4" color="#1e9cd7" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
|
|
|
@ -223,10 +223,7 @@ function Home({ initialSettings }) {
|
|||
useEffect(() => {
|
||||
function handleKeyDown(e) {
|
||||
if (e.target.tagName === "BODY" || e.target.id === "inner_wrapper") {
|
||||
if (
|
||||
(e.key.length === 1 && e.key.match(/(\w|\s)/g) && !(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) ||
|
||||
(e.key === "v" && (e.ctrlKey || e.metaKey))
|
||||
) {
|
||||
if (e.key.length === 1 && e.key.match(/(\w|\s)/g) && !(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
|
||||
setSearching(true);
|
||||
} else if (e.key === "Escape") {
|
||||
setSearchString("");
|
||||
|
@ -307,7 +304,6 @@ function Home({ initialSettings }) {
|
|||
layout={settings.layout?.[group.name]}
|
||||
fiveColumns={settings.fiveColumns}
|
||||
disableCollapse={settings.disableCollapse}
|
||||
useEqualHeights={settings.useEqualHeights}
|
||||
/>
|
||||
) : (
|
||||
<BookmarksGroup
|
||||
|
@ -356,7 +352,6 @@ function Home({ initialSettings }) {
|
|||
settings.layout,
|
||||
settings.fiveColumns,
|
||||
settings.disableCollapse,
|
||||
settings.useEqualHeights,
|
||||
settings.cardBlur,
|
||||
initialSettings.layout,
|
||||
]);
|
||||
|
@ -368,8 +363,8 @@ function Home({ initialSettings }) {
|
|||
{settings.base && <base href={settings.base} />}
|
||||
{settings.favicon ? (
|
||||
<>
|
||||
<link rel="icon" href={settings.favicon} />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={settings.favicon} />
|
||||
<link rel="icon" href={settings.favicon} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
@ -377,7 +372,6 @@ function Home({ initialSettings }) {
|
|||
<link rel="shortcut icon" href="/homepage.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=4" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=4" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg?v=4" color="#1e9cd7" />
|
||||
</>
|
||||
)}
|
||||
<meta name="msapplication-TileColor" content={themes[settings.color || "slate"][settings.theme || "dark"]} />
|
||||
|
@ -493,7 +487,7 @@ export default function Wrapper({ initialSettings, fallback }) {
|
|||
rgb(var(--bg-color) / ${opacityValue}),
|
||||
rgb(var(--bg-color) / ${opacityValue})
|
||||
),
|
||||
url('${backgroundImage}')`;
|
||||
url(${backgroundImage})`;
|
||||
wrappedStyle.backgroundPosition = "center";
|
||||
wrappedStyle.backgroundSize = "cover";
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://gethomepage.dev/latest/configs/service-widgets
|
||||
# https://gethomepage.dev/latest/configs/widgets
|
||||
|
||||
- resources:
|
||||
cpu: true
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
--color-logo-stop: 128 128 128 / 40%;
|
||||
}
|
||||
|
||||
.theme-white .bg-theme-100\/20:not([class^="backdrop-blur"]),
|
||||
.theme-white .dark\:bg-white\/5:not([class^="backdrop-blur"]) {
|
||||
.theme-white .bg-theme-100\/20,
|
||||
.theme-white .dark\:bg-white\/5 {
|
||||
background-color: rgb(245, 245, 245);
|
||||
}
|
||||
|
||||
.theme-white .bg-theme-100\/20:hover:not([class^="backdrop-blur"]),
|
||||
.theme-white .dark\:bg-white\/5:hover:not([class^="backdrop-blur"]) {
|
||||
.theme-white .bg-theme-100\/20:hover,
|
||||
.theme-white .dark\:bg-white\/5:hover {
|
||||
background-color: rgb(250, 250, 250);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import Docker from "dockerode";
|
|||
import { CustomObjectsApi, NetworkingV1Api, ApiextensionsV1Api } from "@kubernetes/client-node";
|
||||
|
||||
import createLogger from "utils/logger";
|
||||
import checkAndCopyConfig, { CONF_DIR, getSettings, substituteEnvironmentVars } from "utils/config/config";
|
||||
import checkAndCopyConfig, { CONF_DIR, substituteEnvironmentVars } from "utils/config/config";
|
||||
import getDockerArguments from "utils/config/docker";
|
||||
import getKubeConfig from "utils/config/kubernetes";
|
||||
import * as shvl from "utils/config/shvl";
|
||||
|
@ -59,8 +59,6 @@ export async function servicesFromDocker() {
|
|||
return [];
|
||||
}
|
||||
|
||||
const { instanceName } = getSettings();
|
||||
|
||||
const serviceServers = await Promise.all(
|
||||
Object.keys(servers).map(async (serverName) => {
|
||||
try {
|
||||
|
@ -84,13 +82,6 @@ export async function servicesFromDocker() {
|
|||
|
||||
Object.keys(containerLabels).forEach((label) => {
|
||||
if (label.startsWith("homepage.")) {
|
||||
let value = label.replace("homepage.", "");
|
||||
if (instanceName && value.startsWith(`instance.${instanceName}.`)) {
|
||||
value = value.replace(`instance.${instanceName}.`, "");
|
||||
} else if (value.startsWith("instance.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!constructedService) {
|
||||
constructedService = {
|
||||
container: containerName.replace(/^\//, ""),
|
||||
|
@ -98,7 +89,11 @@ export async function servicesFromDocker() {
|
|||
type: "service",
|
||||
};
|
||||
}
|
||||
shvl.set(constructedService, value, substituteEnvironmentVars(containerLabels[label]));
|
||||
shvl.set(
|
||||
constructedService,
|
||||
label.replace("homepage.", ""),
|
||||
substituteEnvironmentVars(containerLabels[label]),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -257,7 +252,7 @@ export async function servicesFromKubernetes() {
|
|||
constructedService.external =
|
||||
String(ingress.metadata.annotations[`${ANNOTATION_BASE}/external`]).toLowerCase() === "true";
|
||||
}
|
||||
if (ingress.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`] !== undefined) {
|
||||
if (ingress.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`]) {
|
||||
constructedService.podSelector = ingress.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`];
|
||||
}
|
||||
if (ingress.metadata.annotations[`${ANNOTATION_BASE}/ping`]) {
|
||||
|
@ -336,89 +331,48 @@ export function cleanServiceGroups(groups) {
|
|||
|
||||
if (cleanedService.widget) {
|
||||
// whitelisted set of keys to pass to the frontend
|
||||
// alphabetical, grouped by widget(s)
|
||||
const {
|
||||
// all widgets
|
||||
type, // all widgets
|
||||
fields,
|
||||
hideErrors,
|
||||
type,
|
||||
|
||||
// azuredevops
|
||||
repositoryId,
|
||||
userEmail,
|
||||
|
||||
// calendar
|
||||
firstDayInWeek,
|
||||
integrations,
|
||||
maxEvents,
|
||||
showTime,
|
||||
previousDays,
|
||||
view,
|
||||
|
||||
// coinmarketcap
|
||||
currency,
|
||||
defaultinterval,
|
||||
slugs,
|
||||
symbols,
|
||||
|
||||
// customapi
|
||||
mappings,
|
||||
|
||||
// diskstation
|
||||
volume,
|
||||
|
||||
// docker
|
||||
server, // docker widget
|
||||
container,
|
||||
server,
|
||||
|
||||
// emby, jellyfin
|
||||
enableBlocks,
|
||||
enableNowPlaying,
|
||||
|
||||
// glances
|
||||
chart,
|
||||
metric,
|
||||
pointsLimit,
|
||||
|
||||
// glances, customapi, iframe
|
||||
refreshInterval,
|
||||
|
||||
// iframe
|
||||
allowFullscreen,
|
||||
allowPolicy,
|
||||
allowScrolling,
|
||||
classes,
|
||||
loadingStrategy,
|
||||
referrerPolicy,
|
||||
src,
|
||||
|
||||
// kopia
|
||||
snapshotHost,
|
||||
snapshotPath,
|
||||
|
||||
// kubernetes
|
||||
currency, // coinmarketcap widget
|
||||
symbols,
|
||||
slugs,
|
||||
defaultinterval,
|
||||
site, // unifi widget
|
||||
namespace, // kubernetes widget
|
||||
app,
|
||||
namespace,
|
||||
podSelector,
|
||||
|
||||
// mjpeg
|
||||
wan, // opnsense widget, pfsense widget
|
||||
enableBlocks, // emby/jellyfin
|
||||
enableNowPlaying,
|
||||
volume, // diskstation widget,
|
||||
enableQueue, // sonarr/radarr
|
||||
node, // Proxmox
|
||||
snapshotHost, // kopia
|
||||
snapshotPath,
|
||||
userEmail, // azuredevops
|
||||
repositoryId,
|
||||
metric, // glances
|
||||
chart, // glances
|
||||
stream, // mjpeg
|
||||
fit,
|
||||
stream,
|
||||
|
||||
// openmediavault
|
||||
method,
|
||||
|
||||
// opnsense, pfsense
|
||||
wan,
|
||||
|
||||
// proxmox
|
||||
node,
|
||||
|
||||
// sonarr, radarr
|
||||
enableQueue,
|
||||
|
||||
// unifi
|
||||
site,
|
||||
method, // openmediavault widget
|
||||
mappings, // customapi widget
|
||||
refreshInterval,
|
||||
integrations, // calendar widget
|
||||
firstDayInWeek,
|
||||
view,
|
||||
maxEvents,
|
||||
src, // iframe widget
|
||||
classes,
|
||||
referrerPolicy,
|
||||
allowPolicy,
|
||||
allowFullscreen,
|
||||
loadingStrategy,
|
||||
allowScrolling,
|
||||
} = cleanedService.widget;
|
||||
|
||||
let fieldsList = fields;
|
||||
|
@ -500,8 +454,6 @@ export function cleanServiceGroups(groups) {
|
|||
} else {
|
||||
cleanedService.widget.chart = true;
|
||||
}
|
||||
if (refreshInterval) cleanedService.widget.refreshInterval = refreshInterval;
|
||||
if (pointsLimit) cleanedService.widget.pointsLimit = pointsLimit;
|
||||
}
|
||||
if (type === "mjpeg") {
|
||||
if (stream) cleanedService.widget.stream = stream;
|
||||
|
@ -519,8 +471,6 @@ export function cleanServiceGroups(groups) {
|
|||
if (firstDayInWeek) cleanedService.widget.firstDayInWeek = firstDayInWeek;
|
||||
if (view) cleanedService.widget.view = view;
|
||||
if (maxEvents) cleanedService.widget.maxEvents = maxEvents;
|
||||
if (previousDays) cleanedService.widget.previousDays = previousDays;
|
||||
if (showTime) cleanedService.widget.showTime = showTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
15
src/utils/contexts/calendar.jsx
Normal file
15
src/utils/contexts/calendar.jsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createContext, useState, useMemo } from "react";
|
||||
|
||||
export const EventContext = createContext();
|
||||
|
||||
export function EventProvider({ initialEvent, children }) {
|
||||
const [events, setEvents] = useState({});
|
||||
|
||||
if (initialEvent) {
|
||||
setEvents(initialEvent);
|
||||
}
|
||||
|
||||
const value = useMemo(() => ({ events, setEvents }), [events]);
|
||||
|
||||
return <EventContext.Provider value={value}>{children}</EventContext.Provider>;
|
||||
}
|
|
@ -1,11 +1,45 @@
|
|||
import { useContext, useState } from "react";
|
||||
import { DateTime } from "luxon";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { IoMdCheckmarkCircleOutline } from "react-icons/io";
|
||||
|
||||
import Event from "./event";
|
||||
import { EventContext } from "../../utils/contexts/calendar";
|
||||
|
||||
export default function Agenda({ service, colorVariants, events, showDate }) {
|
||||
export function Event({ event, colorVariants, showDate = false }) {
|
||||
const [hover, setHover] = useState(false);
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row text-theme-700 dark:text-theme-200 items-center text-xs text-left h-5 rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1"
|
||||
onMouseEnter={() => setHover(!hover)}
|
||||
onMouseLeave={() => setHover(!hover)}
|
||||
>
|
||||
<span className="ml-2 w-10">
|
||||
<span>
|
||||
{showDate &&
|
||||
event.date.setLocale(i18n.language).startOf("day").toLocaleString({ month: "short", day: "numeric" })}
|
||||
</span>
|
||||
</span>
|
||||
<span className="ml-2 h-2 w-2">
|
||||
<span className={classNames("block w-2 h-2 rounded", colorVariants[event.color] ?? "gray")} />
|
||||
</span>
|
||||
<div className="ml-2 h-5 text-left relative truncate" style={{ width: "70%" }}>
|
||||
<div className="absolute mt-0.5 text-xs">{hover && event.additional ? event.additional : event.title}</div>
|
||||
</div>
|
||||
{event.isCompleted && (
|
||||
<span className="text-xs mr-1 ml-auto z-10">
|
||||
<IoMdCheckmarkCircleOutline />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Agenda({ service, colorVariants, showDate }) {
|
||||
const { widget } = service;
|
||||
const { events } = useContext(EventContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!showDate) {
|
||||
|
@ -14,9 +48,7 @@ export default function Agenda({ service, colorVariants, events, showDate }) {
|
|||
|
||||
const eventsArray = Object.keys(events)
|
||||
.filter(
|
||||
(eventKey) =>
|
||||
showDate.minus({ days: widget?.previousDays ?? 0 }).startOf("day").ts <=
|
||||
events[eventKey].date?.startOf("day").ts,
|
||||
(eventKey) => showDate.startOf("day").toUnixInteger() <= events[eventKey].date?.startOf("day").toUnixInteger(),
|
||||
)
|
||||
.map((eventKey) => events[eventKey])
|
||||
.sort((a, b) => a.date - b.date)
|
||||
|
@ -25,8 +57,10 @@ export default function Agenda({ service, colorVariants, events, showDate }) {
|
|||
if (!eventsArray.length) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className="pl-2 pr-2">
|
||||
<div className={classNames("flex flex-col", !eventsArray.length && !events.length && "animate-pulse")}>
|
||||
<div className="p-2 ">
|
||||
<div
|
||||
className={classNames("flex flex-col pt-1 pb-1", !eventsArray.length && !events.length && "animate-pulse")}
|
||||
>
|
||||
<Event
|
||||
key="no-event"
|
||||
event={{
|
||||
|
@ -46,17 +80,16 @@ export default function Agenda({ service, colorVariants, events, showDate }) {
|
|||
const eventsByDay = days.map((d) => eventsArray.filter((e) => e.date.startOf("day").ts === d));
|
||||
|
||||
return (
|
||||
<div className="pl-1 pr-1 pb-1">
|
||||
<div className={classNames("flex flex-col", !eventsArray.length && !events.length && "animate-pulse")}>
|
||||
<div className="p-2">
|
||||
<div className={classNames("flex flex-col pt-1 pb-1", !eventsArray.length && !events.length && "animate-pulse")}>
|
||||
{eventsByDay.map((eventsDay, i) => (
|
||||
<div key={days[i]}>
|
||||
{eventsDay.map((event, j) => (
|
||||
<Event
|
||||
key={`event-agenda-${event.title}-${event.date}-${event.additional}`}
|
||||
key={`event${event.title}-${event.date}`}
|
||||
event={event}
|
||||
colorVariants={colorVariants}
|
||||
showDate={j === 0}
|
||||
showTime={widget?.showTime && event.date.startOf("day").ts === showDate.startOf("day").ts}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -40,7 +40,6 @@ export default function Component({ service }) {
|
|||
const { widget } = service;
|
||||
const { i18n } = useTranslation();
|
||||
const [showDate, setShowDate] = useState(null);
|
||||
const [events, setEvents] = useState({});
|
||||
const currentDate = DateTime.now().setLocale(i18n.language).startOf("day");
|
||||
const { settings } = useContext(SettingsContext);
|
||||
|
||||
|
@ -70,9 +69,9 @@ export default function Component({ service }) {
|
|||
?.filter((integration) => integration?.type)
|
||||
.map((integration) => ({
|
||||
service: dynamic(() => import(`./integrations/${integration.type}`)),
|
||||
widget: { ...widget, ...integration },
|
||||
widget: integration,
|
||||
})) ?? [],
|
||||
[widget],
|
||||
[widget.integrations],
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -81,14 +80,13 @@ export default function Component({ service }) {
|
|||
<div className="sticky top-0">
|
||||
{integrations.map((integration) => {
|
||||
const Integration = integration.service;
|
||||
const key = `integration-${integration.widget.type}-${integration.widget.service_name}-${integration.widget.service_group}-${integration.widget.name}`;
|
||||
const key = integration.widget.type + integration.widget.service_name + integration.widget.service_group;
|
||||
|
||||
return (
|
||||
<Integration
|
||||
key={key}
|
||||
config={integration.widget}
|
||||
params={params}
|
||||
setEvents={setEvents}
|
||||
hideErrors={settings.hideErrors}
|
||||
className="fixed bottom-0 left-0 bg-red-500 w-screen h-12"
|
||||
/>
|
||||
|
@ -97,10 +95,8 @@ export default function Component({ service }) {
|
|||
</div>
|
||||
{(!widget?.view || widget?.view === "monthly") && (
|
||||
<Monthly
|
||||
key={`monthly-${showDate?.toFormat("yyyy-MM-dd")}`}
|
||||
service={service}
|
||||
colorVariants={colorVariants}
|
||||
events={events}
|
||||
showDate={showDate}
|
||||
setShowDate={setShowDate}
|
||||
className="flex"
|
||||
|
@ -108,10 +104,8 @@ export default function Component({ service }) {
|
|||
)}
|
||||
{widget?.view === "agenda" && (
|
||||
<Agenda
|
||||
key={`agenda-${showDate?.toFormat("yyyy-MM-dd")}`}
|
||||
service={service}
|
||||
colorVariants={colorVariants}
|
||||
events={events}
|
||||
showDate={showDate}
|
||||
setShowDate={setShowDate}
|
||||
className="flex"
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { DateTime } from "luxon";
|
||||
import classNames from "classnames";
|
||||
import { IoMdCheckmarkCircleOutline } from "react-icons/io";
|
||||
|
||||
export default function Event({ event, colorVariants, showDate = false, showTime = false, showDateColumn = true }) {
|
||||
const [hover, setHover] = useState(false);
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row text-theme-700 dark:text-theme-200 items-center text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1"
|
||||
onMouseEnter={() => setHover(!hover)}
|
||||
onMouseLeave={() => setHover(!hover)}
|
||||
key={`event-${event.title}-${event.date}-${event.additional}`}
|
||||
>
|
||||
{showDateColumn && (
|
||||
<span className="ml-2 w-10">
|
||||
<span>
|
||||
{(showDate || showTime) &&
|
||||
event.date
|
||||
.setLocale(i18n.language)
|
||||
.toLocaleString(showTime ? DateTime.TIME_24_SIMPLE : { month: "short", day: "numeric" })}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<span className="ml-2 h-2 w-2">
|
||||
<span className={classNames("block w-2 h-2 rounded", colorVariants[event.color] ?? "gray")} />
|
||||
</span>
|
||||
<div className="ml-2 h-5 text-left relative truncate" style={{ width: "70%" }}>
|
||||
<div className="absolute mt-0.5 text-xs">{hover && event.additional ? event.additional : event.title}</div>
|
||||
</div>
|
||||
{event.isCompleted && (
|
||||
<span className="text-xs mr-1 ml-auto z-10">
|
||||
<IoMdCheckmarkCircleOutline />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
import { DateTime } from "luxon";
|
||||
import { parseString } from "cal-parser";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import useWidgetAPI from "../../../utils/proxy/use-widget-api";
|
||||
import Error from "../../../components/services/widget/error";
|
||||
|
||||
export default function Integration({ config, params, setEvents, hideErrors }) {
|
||||
const { t } = useTranslation();
|
||||
const { data: icalData, error: icalError } = useWidgetAPI(config, config.name, {
|
||||
refreshInterval: 300000, // 5 minutes
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let parsedIcal;
|
||||
|
||||
if (!icalError && icalData && !icalData.error) {
|
||||
parsedIcal = parseString(icalData.data);
|
||||
if (parsedIcal.events.length === 0) {
|
||||
icalData.error = { message: `'${config.name}': ${t("calendar.noEventsFound")}` };
|
||||
}
|
||||
}
|
||||
|
||||
if (icalError || !parsedIcal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventsToAdd = {};
|
||||
const events = parsedIcal?.getEventsBetweenDates(
|
||||
DateTime.fromISO(params.start).toJSDate(),
|
||||
DateTime.fromISO(params.end).toJSDate(),
|
||||
);
|
||||
|
||||
events?.forEach((event) => {
|
||||
let title = `${event?.summary?.value}`;
|
||||
if (config?.params?.showName) {
|
||||
title = `${config.name}: ${title}`;
|
||||
}
|
||||
|
||||
event.matchingDates.forEach((date) => {
|
||||
eventsToAdd[event?.uid?.value] = {
|
||||
title,
|
||||
date: DateTime.fromJSDate(date),
|
||||
color: config?.color ?? "zinc",
|
||||
isCompleted: DateTime.fromJSDate(date) < DateTime.now(),
|
||||
additional: event.location?.value,
|
||||
type: "ical",
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
setEvents((prevEvents) => ({ ...prevEvents, ...eventsToAdd }));
|
||||
}, [icalData, icalError, config, params, setEvents, t]);
|
||||
|
||||
const error = icalError ?? icalData?.error;
|
||||
return error && !hideErrors && <Error error={{ message: `${config.type}: ${error.message ?? error}` }} />;
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import { DateTime } from "luxon";
|
||||
import { useEffect } from "react";
|
||||
import { useContext, useEffect } from "react";
|
||||
|
||||
import useWidgetAPI from "../../../utils/proxy/use-widget-api";
|
||||
import { EventContext } from "../../../utils/contexts/calendar";
|
||||
import Error from "../../../components/services/widget/error";
|
||||
|
||||
export default function Integration({ config, params, setEvents, hideErrors = false }) {
|
||||
export default function Integration({ config, params, hideErrors = false }) {
|
||||
const { setEvents } = useContext(EventContext);
|
||||
const { data: lidarrData, error: lidarrError } = useWidgetAPI(config, "calendar", {
|
||||
...params,
|
||||
includeArtist: "false",
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { DateTime } from "luxon";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useContext } from "react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import useWidgetAPI from "../../../utils/proxy/use-widget-api";
|
||||
import { EventContext } from "../../../utils/contexts/calendar";
|
||||
import Error from "../../../components/services/widget/error";
|
||||
|
||||
export default function Integration({ config, params, setEvents, hideErrors = false }) {
|
||||
export default function Integration({ config, params, hideErrors = false }) {
|
||||
const { t } = useTranslation();
|
||||
const { setEvents } = useContext(EventContext);
|
||||
const { data: radarrData, error: radarrError } = useWidgetAPI(config, "calendar", {
|
||||
...params,
|
||||
...(config?.params ?? {}),
|
||||
|
@ -23,35 +25,27 @@ export default function Integration({ config, params, setEvents, hideErrors = fa
|
|||
const physicalTitle = `${event.title} - ${t("calendar.physicalRelease")}`;
|
||||
const digitalTitle = `${event.title} - ${t("calendar.digitalRelease")}`;
|
||||
|
||||
if (event.inCinemas) {
|
||||
eventsToAdd[cinemaTitle] = {
|
||||
title: cinemaTitle,
|
||||
date: DateTime.fromISO(event.inCinemas),
|
||||
color: config?.color ?? "amber",
|
||||
isCompleted: event.hasFile,
|
||||
additional: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (event.physicalRelease) {
|
||||
eventsToAdd[physicalTitle] = {
|
||||
title: physicalTitle,
|
||||
date: DateTime.fromISO(event.physicalRelease),
|
||||
color: config?.color ?? "cyan",
|
||||
isCompleted: event.hasFile,
|
||||
additional: "",
|
||||
};
|
||||
}
|
||||
|
||||
if (event.digitalRelease) {
|
||||
eventsToAdd[digitalTitle] = {
|
||||
title: digitalTitle,
|
||||
date: DateTime.fromISO(event.digitalRelease),
|
||||
color: config?.color ?? "emerald",
|
||||
isCompleted: event.hasFile,
|
||||
additional: "",
|
||||
};
|
||||
}
|
||||
eventsToAdd[cinemaTitle] = {
|
||||
title: cinemaTitle,
|
||||
date: DateTime.fromISO(event.inCinemas),
|
||||
color: config?.color ?? "amber",
|
||||
isCompleted: event.isAvailable,
|
||||
additional: "",
|
||||
};
|
||||
eventsToAdd[physicalTitle] = {
|
||||
title: physicalTitle,
|
||||
date: DateTime.fromISO(event.physicalRelease),
|
||||
color: config?.color ?? "cyan",
|
||||
isCompleted: event.isAvailable,
|
||||
additional: "",
|
||||
};
|
||||
eventsToAdd[digitalTitle] = {
|
||||
title: digitalTitle,
|
||||
date: DateTime.fromISO(event.digitalRelease),
|
||||
color: config?.color ?? "emerald",
|
||||
isCompleted: event.isAvailable,
|
||||
additional: "",
|
||||
};
|
||||
});
|
||||
|
||||
setEvents((prevEvents) => ({ ...prevEvents, ...eventsToAdd }));
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { DateTime } from "luxon";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useContext } from "react";
|
||||
|
||||
import useWidgetAPI from "../../../utils/proxy/use-widget-api";
|
||||
import { EventContext } from "../../../utils/contexts/calendar";
|
||||
import Error from "../../../components/services/widget/error";
|
||||
|
||||
export default function Integration({ config, params, setEvents, hideErrors = false }) {
|
||||
export default function Integration({ config, params, hideErrors = false }) {
|
||||
const { setEvents } = useContext(EventContext);
|
||||
const { data: readarrData, error: readarrError } = useWidgetAPI(config, "calendar", {
|
||||
...params,
|
||||
includeAuthor: "true",
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { DateTime } from "luxon";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useContext } from "react";
|
||||
|
||||
import useWidgetAPI from "../../../utils/proxy/use-widget-api";
|
||||
import { EventContext } from "../../../utils/contexts/calendar";
|
||||
import Error from "../../../components/services/widget/error";
|
||||
|
||||
export default function Integration({ config, params, setEvents, hideErrors = false }) {
|
||||
export default function Integration({ config, params, hideErrors = false }) {
|
||||
const { setEvents } = useContext(EventContext);
|
||||
const { data: sonarrData, error: sonarrError } = useWidgetAPI(config, "calendar", {
|
||||
...params,
|
||||
includeSeries: "true",
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { useMemo } from "react";
|
||||
import { useContext, useMemo } from "react";
|
||||
import { DateTime, Info } from "luxon";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { IoMdCheckmarkCircleOutline } from "react-icons/io";
|
||||
|
||||
import Event from "./event";
|
||||
import { EventContext } from "../../utils/contexts/calendar";
|
||||
|
||||
const cellStyle = "relative w-10 flex items-center justify-center flex-col";
|
||||
const monthButton = "pl-6 pr-6 ml-2 mr-2 hover:bg-theme-100/20 dark:hover:bg-white/5 rounded-md cursor-pointer";
|
||||
|
@ -31,11 +32,11 @@ export function Day({ weekNumber, weekday, events, colorVariants, showDate, setS
|
|||
|
||||
// selected same day style
|
||||
style +=
|
||||
displayDate.startOf("day").ts === showDate.startOf("day").ts
|
||||
displayDate.toFormat("MM-dd-yyyy") === showDate.toFormat("MM-dd-yyyy")
|
||||
? "text-black-500 bg-theme-100/20 dark:bg-white/10 rounded-md "
|
||||
: "";
|
||||
|
||||
if (displayDate.startOf("day").ts === currentDate.startOf("day").ts) {
|
||||
if (displayDate.toFormat("MM-dd-yyyy") === currentDate.toFormat("MM-dd-yyyy")) {
|
||||
// today style
|
||||
style += "text-black-500 bg-theme-100/20 dark:bg-black/20 rounded-md ";
|
||||
} else {
|
||||
|
@ -60,7 +61,7 @@ export function Day({ weekNumber, weekday, events, colorVariants, showDate, setS
|
|||
.slice(0, 4)
|
||||
.map((event) => (
|
||||
<span
|
||||
key={`${event.date.ts}+${event.color}-${event.title}-${event.additional}`}
|
||||
key={event.date.toLocaleString() + event.color + event.title}
|
||||
className={classNames("inline-flex h-1 w-1 m-0.5 rounded", colorVariants[event.color] ?? "gray")}
|
||||
/>
|
||||
))}
|
||||
|
@ -69,6 +70,25 @@ export function Day({ weekNumber, weekday, events, colorVariants, showDate, setS
|
|||
);
|
||||
}
|
||||
|
||||
export function Event({ event }) {
|
||||
return (
|
||||
<div
|
||||
key={event.title}
|
||||
className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1"
|
||||
>
|
||||
<span className="absolute left-2 text-left text-xs mt-[2px] truncate text-ellipsis" style={{ width: "96%" }}>
|
||||
{event.title}
|
||||
{event.additional ? ` - ${event.additional}` : ""}
|
||||
</span>
|
||||
{event.isCompleted && (
|
||||
<span className="text-right text-xs flex justify-end mr-1 mt-1 z-10 ">
|
||||
<IoMdCheckmarkCircleOutline />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const dayInWeekId = {
|
||||
monday: 1,
|
||||
tuesday: 2,
|
||||
|
@ -79,9 +99,10 @@ const dayInWeekId = {
|
|||
sunday: 7,
|
||||
};
|
||||
|
||||
export default function Monthly({ service, colorVariants, events, showDate, setShowDate }) {
|
||||
export default function Monthly({ service, colorVariants, showDate, setShowDate }) {
|
||||
const { widget } = service;
|
||||
const { i18n } = useTranslation();
|
||||
const { events } = useContext(EventContext);
|
||||
const currentDate = DateTime.now().setLocale(i18n.language).startOf("day");
|
||||
|
||||
const dayNames = Info.weekdays("short", { locale: i18n.language });
|
||||
|
@ -140,7 +161,7 @@ export default function Monthly({ service, colorVariants, events, showDate, setS
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div className="pl-1 pr-1 pb-1 w-full">
|
||||
<div className="p-2 w-full">
|
||||
<div className="flex justify-between flex-wrap">
|
||||
{dayNames.map((name) => (
|
||||
<span key={name} className={classNames(cellStyle)} style={{ width: "14%" }}>
|
||||
|
@ -151,7 +172,7 @@ export default function Monthly({ service, colorVariants, events, showDate, setS
|
|||
|
||||
<div
|
||||
className={classNames(
|
||||
"flex justify-between flex-wrap pb-1",
|
||||
"flex justify-between flex-wrap",
|
||||
!eventsArray.length && widget?.integrations?.length && "animate-pulse",
|
||||
)}
|
||||
>
|
||||
|
@ -170,18 +191,12 @@ export default function Monthly({ service, colorVariants, events, showDate, setS
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col pt-1 pb-1">
|
||||
{eventsArray
|
||||
?.filter((event) => showDate.startOf("day").ts === event.date?.startOf("day").ts)
|
||||
?.filter((event) => showDate.startOf("day").toUnixInteger() === event.date?.startOf("day").toUnixInteger())
|
||||
.slice(0, widget?.maxEvents ?? 10)
|
||||
.map((event) => (
|
||||
<Event
|
||||
key={`event-monthly-${event.title}-${event.date}-${event.additional}`}
|
||||
event={event}
|
||||
colorVariants={colorVariants}
|
||||
showDateColumn={widget?.showTime ?? false}
|
||||
showTime={widget?.showTime && event.date.startOf("day").ts === showDate.startOf("day").ts}
|
||||
/>
|
||||
<Event key={`event${event.title}-${event.additional}`} event={event} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("calendarProxyHandler");
|
||||
|
||||
export default async function calendarProxyHandler(req, res) {
|
||||
const { group, service, endpoint } = req.query;
|
||||
|
||||
if (group && service) {
|
||||
const widget = await getServiceWidget(group, service);
|
||||
const integration = widget.integrations?.find((i) => i.name === endpoint);
|
||||
|
||||
if (integration) {
|
||||
if (!integration.url) {
|
||||
return res.status(403).json({ error: "No integration URL specified" });
|
||||
}
|
||||
|
||||
const [status, contentType, data] = await httpProxy(integration.url);
|
||||
|
||||
if (contentType) res.setHeader("Content-Type", contentType);
|
||||
|
||||
if (status !== 200) {
|
||||
logger.debug(`HTTTP ${status} retrieving data from integration URL ${integration.url} : ${data}`);
|
||||
return res.status(status).send(data);
|
||||
}
|
||||
|
||||
return res.status(status).json({ data: data.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(400).json({ error: "Invalid integration" });
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import calendarProxyHandler from "./proxy";
|
||||
|
||||
const widget = {
|
||||
api: "{url}",
|
||||
proxyHandler: calendarProxyHandler,
|
||||
};
|
||||
|
||||
export default widget;
|
|
@ -28,7 +28,7 @@ export default function Component({ service }) {
|
|||
let diffsDetected = 0;
|
||||
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (data[key].last_changed > 0 && data[key].last_checked === data[key].last_changed && !data[key].viewed) {
|
||||
if (data[key].last_changed > 0 && data[key].last_checked === data[key].last_changed) {
|
||||
diffsDetected += 1;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -27,7 +27,6 @@ const components = {
|
|||
fileflows: dynamic(() => import("./fileflows/component")),
|
||||
flood: dynamic(() => import("./flood/component")),
|
||||
freshrss: dynamic(() => import("./freshrss/component")),
|
||||
fritzbox: dynamic(() => import("./fritzbox/component")),
|
||||
gamedig: dynamic(() => import("./gamedig/component")),
|
||||
ghostfolio: dynamic(() => import("./ghostfolio/component")),
|
||||
glances: dynamic(() => import("./glances/component")),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import useSWR from "swr";
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import { calculateCPUPercent, calculateUsedMemory, calculateThroughput } from "./stats-helpers";
|
||||
import { calculateCPUPercent, calculateUsedMemory } from "./stats-helpers";
|
||||
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
|
@ -41,7 +41,7 @@ export default function Component({ service }) {
|
|||
);
|
||||
}
|
||||
|
||||
const { rxBytes, txBytes } = calculateThroughput(statsData.stats);
|
||||
const network = statsData.stats.networks?.eth0 || statsData.stats.networks?.network;
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
|
@ -49,10 +49,10 @@ export default function Component({ service }) {
|
|||
{statsData.stats.memory_stats.usage && (
|
||||
<Block label="docker.mem" value={t("common.bytes", { value: calculateUsedMemory(statsData.stats) })} />
|
||||
)}
|
||||
{statsData.stats.networks && (
|
||||
{network && (
|
||||
<>
|
||||
<Block label="docker.rx" value={t("common.bytes", { value: rxBytes })} />
|
||||
<Block label="docker.tx" value={t("common.bytes", { value: txBytes })} />
|
||||
<Block label="docker.rx" value={t("common.bytes", { value: network.rx_bytes })} />
|
||||
<Block label="docker.tx" value={t("common.bytes", { value: network.tx_bytes })} />
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
|
|
|
@ -16,18 +16,3 @@ export function calculateUsedMemory(stats) {
|
|||
stats.memory_stats.usage - (stats.memory_stats.total_inactive_file ?? stats.memory_stats.stats?.inactive_file ?? 0)
|
||||
);
|
||||
}
|
||||
|
||||
export function calculateThroughput(stats) {
|
||||
let rxBytes = 0;
|
||||
let txBytes = 0;
|
||||
if (stats.networks?.network) {
|
||||
rxBytes = stats.networks?.network.rx_bytes;
|
||||
txBytes = stats.networks?.network.tx_bytes;
|
||||
} else if (stats.networks && Array.isArray(Object.values(stats.networks))) {
|
||||
Object.values(stats.networks).forEach((containerInterface) => {
|
||||
rxBytes += containerInterface.rx_bytes;
|
||||
txBytes += containerInterface.tx_bytes;
|
||||
});
|
||||
}
|
||||
return { rxBytes, txBytes };
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
export const fritzboxDefaultFields = ["connectionStatus", "uptime", "maxDown", "maxUp"];
|
||||
|
||||
const formatUptime = (timestamp) => {
|
||||
const hours = Math.floor(timestamp / 3600);
|
||||
const minutes = Math.floor((timestamp % 3600) / 60);
|
||||
const seconds = timestamp % 60;
|
||||
|
||||
const hourDuration = hours > 0 ? `${hours}h` : "00h";
|
||||
const minDuration = minutes > 0 ? `${minutes}m` : "00m";
|
||||
const secDuration = seconds > 0 ? `${seconds}s` : "00s";
|
||||
|
||||
return hourDuration + minDuration + secDuration;
|
||||
};
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { data: fritzboxData, error: fritzboxError } = useWidgetAPI(widget, "status");
|
||||
|
||||
if (fritzboxError) {
|
||||
return <Container service={service} error={fritzboxError} />;
|
||||
}
|
||||
|
||||
// Default fields
|
||||
if (!widget.fields?.length > 0) {
|
||||
widget.fields = fritzboxDefaultFields;
|
||||
}
|
||||
const MAX_ALLOWED_FIELDS = 4;
|
||||
// Limits max number of displayed fields
|
||||
if (widget.fields?.length > MAX_ALLOWED_FIELDS) {
|
||||
widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
|
||||
}
|
||||
|
||||
if (!fritzboxData) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="fritzbox.connectionStatus" />
|
||||
<Block label="fritzbox.uptime" />
|
||||
<Block label="fritzbox.maxDown" />
|
||||
<Block label="fritzbox.maxUp" />
|
||||
<Block label="fritzbox.down" />
|
||||
<Block label="fritzbox.up" />
|
||||
<Block label="fritzbox.received" />
|
||||
<Block label="fritzbox.sent" />
|
||||
<Block label="fritzbox.externalIPAddress" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="fritzbox.connectionStatus" value={t(`fritzbox.connectionStatus${fritzboxData.connectionStatus}`)} />
|
||||
<Block label="fritzbox.uptime" value={formatUptime(fritzboxData.uptime)} />
|
||||
<Block label="fritzbox.maxDown" value={t("common.byterate", { value: fritzboxData.maxDown / 8, decimals: 1 })} />
|
||||
<Block label="fritzbox.maxUp" value={t("common.byterate", { value: fritzboxData.maxUp / 8, decimals: 1 })} />
|
||||
<Block label="fritzbox.down" value={t("common.byterate", { value: fritzboxData.down, decimals: 1 })} />
|
||||
<Block label="fritzbox.up" value={t("common.byterate", { value: fritzboxData.up, decimals: 1 })} />
|
||||
<Block label="fritzbox.received" value={t("common.bytes", { value: fritzboxData.received })} />
|
||||
<Block label="fritzbox.sent" value={t("common.bytes", { value: fritzboxData.sent })} />
|
||||
<Block label="fritzbox.externalIPAddress" value={fritzboxData.externalIPAddress} />
|
||||
</Container>
|
||||
);
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
import { xml2json } from "xml-js";
|
||||
|
||||
import { fritzboxDefaultFields } from "./component";
|
||||
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("fritzboxProxyHandler");
|
||||
|
||||
async function requestEndpoint(apiBaseUrl, service, action) {
|
||||
const servicePath = service === "WANIPConnection" ? "WANIPConn1" : "WANCommonIFC1";
|
||||
const params = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "text/xml; charset='utf-8'",
|
||||
SoapAction: `urn:schemas-upnp-org:service:${service}:1#${action}`,
|
||||
},
|
||||
body:
|
||||
"<?xml version='1.0' encoding='utf-8'?>" +
|
||||
"<s:Envelope s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/' xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>" +
|
||||
"<s:Body>" +
|
||||
`<u:${action} xmlns:u='urn:schemas-upnp-org:service:${service}:1' />` +
|
||||
"</s:Body>" +
|
||||
"</s:Envelope>",
|
||||
};
|
||||
const apiUrl = `${apiBaseUrl}/igdupnp/control/${servicePath}`;
|
||||
const [status, , data] = await httpProxy(apiUrl, params);
|
||||
if (status !== 200) {
|
||||
logger.debug(`HTTP ${status} performing SoapRequest for ${service}->${action}`, data);
|
||||
throw new Error(`Failed fetching '${action}'`);
|
||||
}
|
||||
const response = {};
|
||||
try {
|
||||
const jsonData = JSON.parse(xml2json(data));
|
||||
const responseElements = jsonData?.elements[0]?.elements[0]?.elements[0]?.elements || [];
|
||||
responseElements.forEach((element) => {
|
||||
response[element.name] = element.elements[0]?.text || "";
|
||||
});
|
||||
} catch (e) {
|
||||
logger.debug(`Failed parsing ${service}->${action} response:`, data);
|
||||
throw new Error(`Failed parsing '${action}' response`);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export default async function fritzboxProxyHandler(req, res) {
|
||||
const { group, service } = req.query;
|
||||
const serviceWidget = await getServiceWidget(group, service);
|
||||
|
||||
if (!serviceWidget) {
|
||||
res.status(500).json({ error: "Service widget not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!serviceWidget.url) {
|
||||
res.status(500).json({ error: "Service widget url not configured" });
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceWidgetUrl = new URL(serviceWidget.url);
|
||||
const port = serviceWidgetUrl.protocol === "https:" ? 49443 : 49000;
|
||||
const apiBaseUrl = `${serviceWidgetUrl.protocol}//${serviceWidgetUrl.hostname}:${port}`;
|
||||
|
||||
if (!serviceWidget.fields?.length > 0) {
|
||||
serviceWidget.fields = fritzboxDefaultFields;
|
||||
}
|
||||
const requestStatusInfo = ["connectionStatus", "uptime"].some((field) => serviceWidget.fields.includes(field));
|
||||
const requestLinkProperties = ["maxDown", "maxUp"].some((field) => serviceWidget.fields.includes(field));
|
||||
const requestAddonInfos = ["down", "up", "received", "sent"].some((field) => serviceWidget.fields.includes(field));
|
||||
const requestExternalIPAddress = ["externalIPAddress"].some((field) => serviceWidget.fields.includes(field));
|
||||
|
||||
await Promise.all([
|
||||
requestStatusInfo ? requestEndpoint(apiBaseUrl, "WANIPConnection", "GetStatusInfo") : null,
|
||||
requestLinkProperties ? requestEndpoint(apiBaseUrl, "WANCommonInterfaceConfig", "GetCommonLinkProperties") : null,
|
||||
requestAddonInfos ? requestEndpoint(apiBaseUrl, "WANCommonInterfaceConfig", "GetAddonInfos") : null,
|
||||
requestExternalIPAddress ? requestEndpoint(apiBaseUrl, "WANIPConnection", "GetExternalIPAddress") : null,
|
||||
])
|
||||
.then(([statusInfo, linkProperties, addonInfos, externalIPAddress]) => {
|
||||
res.status(200).json({
|
||||
connectionStatus: statusInfo?.NewConnectionStatus || "Unconfigured",
|
||||
uptime: statusInfo?.NewUptime || 0,
|
||||
maxDown: linkProperties?.NewLayer1DownstreamMaxBitRate || 0,
|
||||
maxUp: linkProperties?.NewLayer1UpstreamMaxBitRate || 0,
|
||||
down: addonInfos?.NewByteReceiveRate || 0,
|
||||
up: addonInfos?.NewByteSendRate || 0,
|
||||
received: addonInfos?.NewX_AVM_DE_TotalBytesReceived64 || 0,
|
||||
sent: addonInfos?.NewX_AVM_DE_TotalBytesSent64 || 0,
|
||||
externalIPAddress: externalIPAddress?.NewExternalIPAddress || null,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
res.status(500).json({ error: error.message });
|
||||
});
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import fritzboxProxyHandler from "./proxy";
|
||||
|
||||
const widget = {
|
||||
proxyHandler: fritzboxProxyHandler,
|
||||
};
|
||||
|
||||
export default widget;
|
|
@ -10,18 +10,17 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
|
|||
|
||||
const Chart = dynamic(() => import("../components/chart"), { ssr: false });
|
||||
|
||||
const defaultPointsLimit = 15;
|
||||
const defaultInterval = 1000;
|
||||
const pointsLimit = 15;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit } = widget;
|
||||
const { chart } = widget;
|
||||
|
||||
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
|
||||
|
||||
const { data, error } = useWidgetAPI(service.widget, "cpu", {
|
||||
refreshInterval: Math.max(defaultInterval, refreshInterval),
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
const { data: systemData, error: systemError } = useWidgetAPI(service.widget, "system");
|
||||
|
@ -36,7 +35,7 @@ export default function Component({ service }) {
|
|||
return newDataPoints;
|
||||
});
|
||||
}
|
||||
}, [data, pointsLimit]);
|
||||
}, [data]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
|
|
@ -10,13 +10,12 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
|
|||
|
||||
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
|
||||
|
||||
const defaultPointsLimit = 15;
|
||||
const defaultInterval = 1000;
|
||||
const pointsLimit = 15;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit } = widget;
|
||||
const { chart } = widget;
|
||||
const [, diskName] = widget.metric.split(":");
|
||||
|
||||
const [dataPoints, setDataPoints] = useState(
|
||||
|
@ -25,7 +24,7 @@ export default function Component({ service }) {
|
|||
const [ratePoints, setRatePoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit));
|
||||
|
||||
const { data, error } = useWidgetAPI(service.widget, "diskio", {
|
||||
refreshInterval: Math.max(defaultInterval, refreshInterval),
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
const calculateRates = (d) =>
|
||||
|
@ -46,7 +45,7 @@ export default function Component({ service }) {
|
|||
return newDataPoints;
|
||||
});
|
||||
}
|
||||
}, [data, diskName, pointsLimit]);
|
||||
}, [data, diskName]);
|
||||
|
||||
useEffect(() => {
|
||||
setRatePoints(calculateRates(dataPoints));
|
||||
|
|
|
@ -6,16 +6,14 @@ import Block from "../components/block";
|
|||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
const defaultInterval = 1000;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart, refreshInterval = defaultInterval } = widget;
|
||||
const { chart } = widget;
|
||||
const [, fsName] = widget.metric.split("fs:");
|
||||
|
||||
const { data, error } = useWidgetAPI(widget, "fs", {
|
||||
refreshInterval: Math.max(defaultInterval, refreshInterval),
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
|
|
|
@ -10,19 +10,18 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
|
|||
|
||||
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
|
||||
|
||||
const defaultPointsLimit = 15;
|
||||
const defaultInterval = 1000;
|
||||
const pointsLimit = 15;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit } = widget;
|
||||
const { chart } = widget;
|
||||
const [, gpuName] = widget.metric.split(":");
|
||||
|
||||
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ a: 0, b: 0 }, 0, pointsLimit));
|
||||
|
||||
const { data, error } = useWidgetAPI(widget, "gpu", {
|
||||
refreshInterval: Math.max(defaultInterval, refreshInterval),
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -40,7 +39,7 @@ export default function Component({ service }) {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [data, gpuName, pointsLimit]);
|
||||
}, [data, gpuName]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
|
|
@ -69,19 +69,16 @@ function Mem({ quicklookData, className = "" }) {
|
|||
);
|
||||
}
|
||||
|
||||
const defaultInterval = 1000;
|
||||
const defaultSystemInterval = 30000; // This data (OS, hostname, distribution) is usually super stable.
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { widget } = service;
|
||||
const { chart, refreshInterval = defaultInterval } = widget;
|
||||
const { chart } = widget;
|
||||
|
||||
const { data: quicklookData, errorL: quicklookError } = useWidgetAPI(service.widget, "quicklook", {
|
||||
refreshInterval,
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
const { data: systemData, errorL: systemError } = useWidgetAPI(service.widget, "system", {
|
||||
refreshInterval: defaultSystemInterval,
|
||||
refreshInterval: 30000,
|
||||
});
|
||||
|
||||
if (quicklookError) {
|
||||
|
|
|
@ -10,19 +10,17 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
|
|||
|
||||
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
|
||||
|
||||
const defaultPointsLimit = 15;
|
||||
const defaultInterval = (isChart) => (isChart ? 1000 : 5000);
|
||||
const pointsLimit = 15;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart } = widget;
|
||||
const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit } = widget;
|
||||
|
||||
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
|
||||
|
||||
const { data, error } = useWidgetAPI(service.widget, "mem", {
|
||||
refreshInterval: Math.max(defaultInterval(chart), refreshInterval),
|
||||
refreshInterval: chart ? 1000 : 5000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -35,7 +33,7 @@ export default function Component({ service }) {
|
|||
return newDataPoints;
|
||||
});
|
||||
}
|
||||
}, [data, pointsLimit]);
|
||||
}, [data]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
|
|
@ -10,21 +10,18 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
|
|||
|
||||
const ChartDual = dynamic(() => import("../components/chart_dual"), { ssr: false });
|
||||
|
||||
const defaultPointsLimit = 15;
|
||||
const defaultInterval = (isChart) => (isChart ? 1000 : 5000);
|
||||
const pointsLimit = 15;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart, metric } = widget;
|
||||
const { refreshInterval = defaultInterval(chart), pointsLimit = defaultPointsLimit } = widget;
|
||||
|
||||
const [, interfaceName] = metric.split(":");
|
||||
|
||||
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
|
||||
|
||||
const { data, error } = useWidgetAPI(widget, "network", {
|
||||
refreshInterval: Math.max(defaultInterval(chart), refreshInterval),
|
||||
refreshInterval: chart ? 1000 : 5000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -47,7 +44,7 @@ export default function Component({ service }) {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [data, interfaceName, pointsLimit]);
|
||||
}, [data, interfaceName]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
|
|
@ -17,15 +17,13 @@ const statusMap = {
|
|||
X: <ResolvedIcon icon="mdi-rhombus-outline" width={32} height={32} />, // dead
|
||||
};
|
||||
|
||||
const defaultInterval = 1000;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart, refreshInterval = defaultInterval } = widget;
|
||||
const { chart } = widget;
|
||||
|
||||
const { data, error } = useWidgetAPI(service.widget, "processlist", {
|
||||
refreshInterval: Math.max(defaultInterval, refreshInterval),
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
|
|
|
@ -10,19 +10,18 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
|
|||
|
||||
const Chart = dynamic(() => import("../components/chart"), { ssr: false });
|
||||
|
||||
const defaultPointsLimit = 15;
|
||||
const defaultInterval = 1000;
|
||||
const pointsLimit = 15;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
const { chart, refreshInterval = defaultInterval, pointsLimit = defaultPointsLimit } = widget;
|
||||
const { chart } = widget;
|
||||
const [, sensorName] = widget.metric.split(":");
|
||||
|
||||
const [dataPoints, setDataPoints] = useState(new Array(pointsLimit).fill({ value: 0 }, 0, pointsLimit));
|
||||
|
||||
const { data, error } = useWidgetAPI(service.widget, "sensors", {
|
||||
refreshInterval: Math.max(defaultInterval, refreshInterval),
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -36,7 +35,7 @@ export default function Component({ service }) {
|
|||
return newDataPoints;
|
||||
});
|
||||
}
|
||||
}, [data, sensorName, pointsLimit]);
|
||||
}, [data, sensorName]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
|
|
@ -40,17 +40,17 @@ export default function Component({ service }) {
|
|||
if (!data) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="healthchecks.status" />
|
||||
<Block label="healthchecks.last_ping" />
|
||||
<Block label={t("healthchecks.status")} />
|
||||
<Block label={t("healthchecks.last_ping")} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="healthchecks.status" value={t(`healthchecks.${data.status}`)} />
|
||||
<Block label={t("healthchecks.status")} value={t(`healthchecks.${data.status}`)} />
|
||||
<Block
|
||||
label="healthchecks.last_ping"
|
||||
label={t("healthchecks.last_ping")}
|
||||
value={data.last_ping ? formatDate(data.last_ping) : t("healthchecks.never")}
|
||||
/>
|
||||
</Container>
|
||||
|
|
|
@ -21,7 +21,7 @@ export default function Component({ service }) {
|
|||
return <Container service={service} error={statsError ?? statusError} />;
|
||||
}
|
||||
|
||||
if (statusData && !(statusData.status.includes("running") || statusData.status.includes("partial"))) {
|
||||
if (statusData && statusData.status !== "running") {
|
||||
return (
|
||||
<Container>
|
||||
<Block label={t("widget.status")} value={t("docker.offline")} />
|
||||
|
|
|
@ -29,7 +29,7 @@ export default function Component({ service }) {
|
|||
<Container service={service}>
|
||||
<Block label="strelaysrv.numActiveSessions" value={t("common.number", { value: statsData.numActiveSessions })} />
|
||||
<Block label="strelaysrv.numConnections" value={t("common.number", { value: statsData.numConnections })} />
|
||||
<Block label="strelaysrv.dataRelayed" value={t("common.bytes", { value: statsData.bytesProxied })} />
|
||||
<Block label={t("strelaysrv.dataRelayed")} value={t("common.bytes", { value: statsData.bytesProxied })} />
|
||||
<Block
|
||||
label="strelaysrv.transferRate"
|
||||
value={t("common.bitrate", { value: statsData.kbps10s1m5m15m30m60m[5] })}
|
||||
|
|
|
@ -6,7 +6,6 @@ import autobrr from "./autobrr/widget";
|
|||
import azuredevops from "./azuredevops/widget";
|
||||
import bazarr from "./bazarr/widget";
|
||||
import caddy from "./caddy/widget";
|
||||
import calendar from "./calendar/widget";
|
||||
import calibreweb from "./calibreweb/widget";
|
||||
import changedetectionio from "./changedetectionio/widget";
|
||||
import channelsdvrserver from "./channelsdvrserver/widget";
|
||||
|
@ -21,7 +20,6 @@ import evcc from "./evcc/widget";
|
|||
import fileflows from "./fileflows/widget";
|
||||
import flood from "./flood/widget";
|
||||
import freshrss from "./freshrss/widget";
|
||||
import fritzbox from "./fritzbox/widget";
|
||||
import gamedig from "./gamedig/widget";
|
||||
import ghostfolio from "./ghostfolio/widget";
|
||||
import glances from "./glances/widget";
|
||||
|
@ -123,7 +121,6 @@ const widgets = {
|
|||
fileflows,
|
||||
flood,
|
||||
freshrss,
|
||||
fritzbox,
|
||||
gamedig,
|
||||
ghostfolio,
|
||||
glances,
|
||||
|
@ -134,7 +131,6 @@ const widgets = {
|
|||
homeassistant,
|
||||
homebridge,
|
||||
healthchecks,
|
||||
ical: calendar,
|
||||
immich,
|
||||
jackett,
|
||||
jdownloader,
|
||||
|
|
Loading…
Add table
Reference in a new issue