Compare commits

..

14 Commits

Author SHA1 Message Date
shamoon
4028194830 Bump version to 1.5.0 2025-09-22 08:18:10 -07:00
shamoon
4c04a7a45f Merge branch 'dev' 2025-09-22 08:17:08 -07:00
github-actions[bot]
ce344a9db5 New Crowdin translations by GitHub Action (#5800)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-09-22 07:57:35 -07:00
github-actions[bot]
8e90ece498 New Crowdin translations by GitHub Action (#5695)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-09-21 16:30:51 -07:00
shamoon
151ad552ca Fix: dont lose color when switching light / dark (#5796) 2025-09-21 16:29:37 -07:00
shamoon
251cb65e12 Enhancement: mobile QuickLaunch button (#5789) 2025-09-18 22:52:16 -07:00
Ariel David Buena
8ebd0d0b2e Documentation: fix typo (#5770) 2025-09-11 18:46:27 -07:00
shamoon
c20f71738b Change Crowdin project link in sponsors.md 2025-09-10 19:48:31 -07:00
shamoon
78b73e8166 Refactor basic auth header generation 2025-09-04 10:23:43 -07:00
dependabot[bot]
547ef0c4c5 Chore(deps): Bump actions/setup-python from 5 to 6 (#5746)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 16:55:44 +00:00
dependabot[bot]
11d148fff0 Chore(deps): Bump actions/setup-node from 4 to 5 (#5747)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 16:44:40 +00:00
dependabot[bot]
eb61d69626 Chore(deps): Bump actions/stale from 9 to 10 (#5745)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 16:29:39 +00:00
dependabot[bot]
876304cda5 Chore(deps): Bump actions/github-script from 7 to 8 (#5744)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 09:13:22 -07:00
shamoon
5fe5a3869e Chore: update mkdocs (#5708) 2025-08-23 16:21:30 -07:00
18 changed files with 923 additions and 869 deletions

View File

@@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v5
- name: Install python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -38,7 +38,7 @@ jobs:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: 20
cache: 'pnpm'
@@ -96,7 +96,7 @@ jobs:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: 20
cache: 'pnpm'

View File

@@ -19,7 +19,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v5
- name: Install python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: 3.x
- name: Check files
@@ -33,7 +33,7 @@ jobs:
- pre-commit
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
@@ -59,7 +59,7 @@ jobs:
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
- run: echo "cache_id=${{github.sha}}" >> $GITHUB_ENV

View File

@@ -18,7 +18,7 @@ jobs:
name: 'Stale'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
days-before-stale: 7
days-before-close: 14
@@ -57,7 +57,7 @@ jobs:
name: 'Close Answered Discussions'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@v8
with:
script: |
function sleep(ms) {
@@ -113,7 +113,7 @@ jobs:
name: 'Close Outdated Discussions'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@v8
with:
script: |
function sleep(ms) {
@@ -204,7 +204,7 @@ jobs:
name: 'Close Unsupported Feature Requests'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@v8
with:
script: |
function sleep(ms) {

View File

@@ -441,6 +441,7 @@ There are a few optional settings for the Quick Launch feature:
- `showSearchSuggestions`: show search suggestions for the internet search. If this is not specified then the setting will be inherited from the search widget. If it is not specified there either, it will default to false. For custom providers the `suggestionUrl` needs to be set in order for this to work.
- `provider`: search engine provider. If none is specified it will try to use the provider set for the Search Widget, if neither are present then internet search will be disabled.
- `hideVisitURL`: disable detecting and offering an option to open URLs. This is false by default, enabling the feature.
- `mobileButtonPosition`: enables and sets the position of the mobile quicklaunch button. Options are `top-left`, `top-right`, `bottom-left`, `bottom-right`. This is empty by default, disabling the feature.
```yaml
quicklaunch:

View File

@@ -28,7 +28,7 @@ These companies help the Homepage project by providing services, tools, and reso
</div>
<div style="margin-bottom: 16px;">
<a href="https://crowdin.com/project/homepage"><img src="https://support.crowdin.com/assets/logos/core-logo/png/crowdin-core-logo-cWhite.png" alt="Crowdin" style="max-width: 100%; height: 64px; display: block;" /></a>
<a href="https://crowdin.com/project/gethomepage"><img src="https://support.crowdin.com/assets/logos/core-logo/png/crowdin-core-logo-cWhite.png" alt="Crowdin" style="max-width: 100%; height: 64px; display: block;" /></a>
<p>
Crowdin provides the translation platform for the project. Making it easy to translate the project into multiple languages.
</p>

View File

@@ -32,7 +32,7 @@ More detail on configuring service widgets can be found in the [Service Widgets
## 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 in your `widgets.yaml` file. Here's an example:
```yaml
- openmeteo:

View File

@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "1.4.6",
"version": "1.5.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -276,7 +276,7 @@
"pending": "Afwagtend",
"approved": "Goedgekeur",
"available": "Beskikbaar",
"issues": "Open Issues"
"issues": "Oop Kwessies"
},
"overseerr": {
"pending": "Afwagtend",
@@ -1074,45 +1074,45 @@
"containers": "Houers"
},
"filebrowser": {
"available": "Available",
"used": "Used",
"total": "Total"
"available": "Beskikbaar",
"used": "Gebruik",
"total": "Totaal"
},
"wallos": {
"activeSubscriptions": "Subscriptions",
"thisMonthlyCost": "This Month",
"nextMonthlyCost": "Next Month",
"previousMonthlyCost": "Prev. Month",
"nextRenewingSubscription": "Next Payment"
"activeSubscriptions": "Intekeninge",
"thisMonthlyCost": "Hierdie Maand",
"nextMonthlyCost": "Volgende Maand",
"previousMonthlyCost": "Vorige Maand",
"nextRenewingSubscription": "Volgende paaiement"
},
"unraid": {
"STARTED": "Started",
"STOPPED": "Stopped",
"NEW_ARRAY": "New Array",
"RECON_DISK": "Reconstructing Disk",
"DISABLE_DISK": "Disk Disabled",
"SWAP_DSBL": "Swap Disable",
"INVALID_EXPANSION": "Invalid Expansion",
"PARITY_NOT_BIGGEST": "Parity Not Biggest",
"TOO_MANY_MISSING_DISKS": "Too Many Missing Disks",
"NEW_DISK_TOO_SMALL": "New Disk Too Small",
"NO_DATA_DISKS": "No Data Disks",
"notifications": "Notifications",
"STARTED": "Begin",
"STOPPED": "Gestop",
"NEW_ARRAY": "Nuwe Skikking",
"RECON_DISK": "Rekonstruksie van Skyf",
"DISABLE_DISK": "Skyf Gedeaktiveer",
"SWAP_DSBL": "Ruil Gedeaktiveer",
"INVALID_EXPANSION": "Ongeldige Uitbreiding",
"PARITY_NOT_BIGGEST": "Pariteit nie die grootste nie",
"TOO_MANY_MISSING_DISKS": "Te Veel Ontbrekende Skywe",
"NEW_DISK_TOO_SMALL": "Nuwe Skyf te Klein",
"NO_DATA_DISKS": "Geen Data Skywe",
"notifications": "Kennisgewings",
"status": "Status",
"cpu": "CPU",
"memoryUsed": "Memory Used",
"memoryAvailable": "Memory Available",
"arrayUsed": "Array Used",
"arrayFree": "Array Free",
"poolUsed": "{{pool}} Used",
"poolFree": "{{pool}} Free"
"cpu": "SVE",
"memoryUsed": "Geheue Gebruik",
"memoryAvailable": "Geheue Beskikbaar",
"arrayUsed": "Skikking Gebruik",
"arrayFree": "Skikking Vry",
"poolUsed": "{{pool}} Gebruik",
"poolFree": "{{pool}} Vry"
},
"backrest": {
"num_plans": "Plans",
"num_success_30": "Successes",
"num_failure_30": "Failures",
"num_success_latest": "Succeeding",
"num_failure_latest": "Failing",
"bytes_added_30": "Bytes Added"
"num_plans": "Planne",
"num_success_30": "Suksesse",
"num_failure_30": "Mislukkings",
"num_success_latest": "Slaag",
"num_failure_latest": "Mislukking",
"bytes_added_30": "Grepe bygevoeg"
}
}

View File

@@ -630,9 +630,9 @@
},
"opnsense": {
"cpu": "CPU-Last",
"memory": "Aktiver RAM",
"wanUpload": "WAN-Upload",
"wanDownload": "WAN-Download"
"memory": "RAM aktiv",
"wanUpload": "WAN Up",
"wanDownload": "WAN Down"
},
"moonraker": {
"printer_state": "Druckerstatus",
@@ -786,7 +786,7 @@
"downloadCount": "Warteschlange",
"downloadBytesRemaining": "Verbleibend",
"downloadTotalBytes": "Größe",
"downloadSpeed": "Geschwindigkeit"
"downloadSpeed": "Datenrate"
},
"kavita": {
"seriesCount": "Serien",

File diff suppressed because it is too large Load Diff

View File

@@ -241,16 +241,16 @@
"sonarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
"series": "Series",
"queue": "Queue",
"unknown": "Unknown"
"series": "Seriale",
"queue": "Kolejka",
"unknown": "Nieznany"
},
"radarr": {
"wanted": "Wanted",
"wanted": "Poszukiwane",
"missing": "Brakujące",
"queued": "Queued",
"movies": "Movies",
"queue": "Queue",
"queued": "W kolejce",
"movies": "Filmy",
"queue": "Kolejka",
"unknown": "Unknown"
},
"lidarr": {
@@ -273,16 +273,16 @@
"available": "Dostępne"
},
"jellyseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available",
"pending": "Oczekujące",
"approved": "Zaakceptowane",
"available": "Dostępne",
"issues": "Open Issues"
},
"overseerr": {
"pending": "Pending",
"pending": "Oczekujące",
"processing": "Przetwarzane",
"approved": "Approved",
"available": "Available"
"approved": "Zaakceptowane",
"available": "Dostępne"
},
"netalertx": {
"total": "Total",
@@ -297,8 +297,8 @@
"gravity": "Grawitacja"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"queries": "Zapytania",
"blocked": "Zablokowane",
"filtered": "Przefiltrowane",
"latency": "Opóźnienia"
},
@@ -313,7 +313,7 @@
"total": "Total"
},
"suwayomi": {
"download": "Downloaded",
"download": "Pobrano",
"nondownload": "Niepobrane",
"read": "Read",
"unread": "Unread",
@@ -367,7 +367,7 @@
"unknown": "Unknown"
},
"navidrome": {
"nothing_streaming": "No Active Streams",
"nothing_streaming": "Brak aktywnych strumieni",
"please_wait": "Proszę czekać"
},
"npm": {
@@ -426,26 +426,26 @@
"unread": "Unread"
},
"authentik": {
"users": "Users",
"users": "Użytkownicy",
"loginsLast24H": "Logowania (24h)",
"failedLoginsLast24H": "Nieudane logowania (24h)"
},
"proxmox": {
"mem": "MEM",
"mem": "RAM",
"cpu": "Procesor",
"lxc": "Kontenery LXC",
"vms": "Maszyn wirtualnych"
},
"glances": {
"cpu": "Procesor",
"load": "Load",
"load": "Obciążenie",
"wait": "Proszę czekać",
"temp": "TEMP",
"temp": "TEMP.",
"_temp": "Temperatura",
"warn": "Ostrzeżenie",
"uptime": "UP",
"total": "Total",
"free": "Free",
"free": "Wolne",
"used": "Used",
"days": "d",
"hours": "h",
@@ -471,57 +471,57 @@
"1-day": "Głównie słoneczny",
"1-night": "Głównie bezchmurny",
"2-day": "Częściowo pochmurnie",
"2-night": "Partly Cloudy",
"2-night": "Częściowo pochmurnie",
"3-day": "Pochmurnie",
"3-night": "Cloudy",
"3-night": "Pochmurnie",
"45-day": "Mgliście",
"45-night": "Foggy",
"48-day": "Foggy",
"48-night": "Foggy",
"45-night": "Mgliście",
"48-day": "Mgliście",
"48-night": "Mgliście",
"51-day": "Lekka mżawka",
"51-night": "Light Drizzle",
"51-night": "Lekka mżawka",
"53-day": "Mżawka",
"53-night": "Drizzle",
"53-night": "Mżawka",
"55-day": "Gęsta mżawka",
"55-night": "Heavy Drizzle",
"55-night": "Gęsta mżawka",
"56-day": "Lekko chłodna mżawka",
"56-night": "Light Freezing Drizzle",
"56-night": "Lekko chłodna mżawka",
"57-day": "Chłodna mżawka",
"57-night": "Freezing Drizzle",
"57-night": "Chłodna mżawka",
"61-day": "Lekki deszcz",
"61-night": "Light Rain",
"61-night": "Lekki deszcz",
"63-day": "Deszcz",
"63-night": "Rain",
"63-night": "Deszcz",
"65-day": "Ciężki deszcz",
"65-night": "Heavy Rain",
"65-night": "Ciężki deszcz",
"66-day": "Mroźny deszcz",
"66-night": "Freezing Rain",
"67-day": "Freezing Rain",
"67-night": "Freezing Rain",
"66-night": "Mroźny deszcz",
"67-day": "Mroźny deszcz",
"67-night": "Mroźny deszcz",
"71-day": "Lekki śnieg",
"71-night": "Light Snow",
"71-night": "Lekki śnieg",
"73-day": "Śnieg",
"73-night": "Snow",
"73-night": "Śnieg",
"75-day": "Ciężki śnieg",
"75-night": "Heavy Snow",
"75-night": "Ciężki śnieg",
"77-day": "Ziarnisty śnieg",
"77-night": "Snow Grains",
"77-night": "Ziarnisty śnieg",
"80-day": "Lekkie opady",
"80-night": "Light Showers",
"80-night": "Lekkie opady",
"81-day": "Opady",
"81-night": "Showers",
"81-night": "Opady",
"82-day": "Ciężkie opady",
"82-night": "Heavy Showers",
"82-night": "Ciężkie opady",
"85-day": "Opady śniegu",
"85-night": "Snow Showers",
"86-day": "Snow Showers",
"86-night": "Snow Showers",
"85-night": "Opady śniegu",
"86-day": "Opady śniegu",
"86-night": "Opady śniegu",
"95-day": "Burze z piorunami",
"95-night": "Thunderstorm",
"95-night": "Burze z piorunami",
"96-day": "Burza z gradobiciem",
"96-night": "Thunderstorm With Hail",
"99-day": "Thunderstorm With Hail",
"99-night": "Thunderstorm With Hail"
"96-night": "Burza z gradobiciem",
"99-day": "Burza z gradobiciem",
"99-night": "Burza z gradobiciem"
},
"homebridge": {
"available_update": "System",

View File

@@ -63,7 +63,7 @@
"wlan_users": "Usuários de WLAN",
"up": "UP",
"down": "Desligado",
"wait": "Please wait",
"wait": "Por favor, aguarde",
"empty_data": "Status do Subsistema desconhecido"
},
"docker": {
@@ -83,7 +83,7 @@
"partial": "Parcial"
},
"ping": {
"error": "Error",
"error": "Erro",
"ping": "Tempo de resposta",
"down": "Inativo",
"up": "Ativo",
@@ -91,11 +91,11 @@
},
"siteMonitor": {
"http_status": "Estado HTTP",
"error": "Error",
"error": "Erro",
"response": "Resposta",
"down": "Down",
"up": "Up",
"not_available": "Not Available"
"down": "Inativo",
"up": "Ativo",
"not_available": "Não Disponível"
},
"emby": {
"playing": "A reproduzir",
@@ -112,7 +112,7 @@
"offline_alt": "Offline",
"online": "Disponível",
"total": "Total",
"unknown": "Unknown"
"unknown": "Desconhecido"
},
"evcc": {
"pv_power": "Produção",
@@ -141,11 +141,11 @@
"connectionStatusDisconnecting": "Desconectando",
"connectionStatusDisconnected": "Desconectado",
"connectionStatusConnected": "Conectado",
"uptime": "Uptime",
"uptime": "Tempo ativo",
"maxDown": "Tempo de inatividade máximo",
"maxUp": "Máx. Acima",
"down": "Down",
"up": "Up",
"down": "Inativo",
"up": "Ativo",
"received": "Recebido",
"sent": "Enviado",
"externalIPAddress": "IP Externo",
@@ -168,10 +168,10 @@
"passes": "Passes"
},
"tautulli": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams",
"playing": "Tocando",
"transcoding": "Transcodificando",
"bitrate": "Taxa de bits",
"no_active": "Sem Streams Ativos",
"plex_connection_error": "Verifique a conexão do Plex"
},
"omada": {
@@ -189,28 +189,28 @@
"plex": {
"streams": "Streams Ativas",
"albums": "Álbuns",
"movies": "Movies",
"movies": "Filmes",
"tv": "Series de TV"
},
"sabnzbd": {
"rate": "Rate",
"rate": "Taxa",
"queue": "Fila",
"timeleft": "Tempo restante"
},
"rutorrent": {
"active": "Ativo",
"upload": "Upload",
"download": "Download"
"upload": "Carregar",
"download": "Descarregar"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"download": "Descarregar",
"upload": "Carregar",
"leech": "Leech",
"seed": "Seed"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"download": "Descarregar",
"upload": "Carregar",
"leech": "Leech",
"seed": "Seed"
},
@@ -223,8 +223,8 @@
"invalid": "Inválido"
},
"deluge": {
"download": "Download",
"upload": "Upload",
"download": "Descarregar",
"upload": "Carregar",
"leech": "Leech",
"seed": "Seed"
},
@@ -233,25 +233,25 @@
"cachemissbytes": "Bytes de Falha de Cache"
},
"downloadstation": {
"download": "Download",
"upload": "Upload",
"download": "Descarregar",
"upload": "Carregar",
"leech": "Leech",
"seed": "Seed"
},
"sonarr": {
"wanted": "Desejada",
"queued": "Em fila",
"series": "Series",
"queue": "Queue",
"unknown": "Unknown"
"series": "Séries",
"queue": "Fila",
"unknown": "Desconhecido"
},
"radarr": {
"wanted": "Wanted",
"missing": "Faltando",
"queued": "Queued",
"movies": "Movies",
"queue": "Queue",
"unknown": "Unknown"
"queued": "Em fila",
"movies": "Filmes",
"queue": "Fila",
"unknown": "Desconhecido"
},
"lidarr": {
"wanted": "Wanted",

View File

@@ -276,7 +276,7 @@
"pending": "На чекању",
"approved": "Одобрено",
"available": "Доступно",
"issues": "Open Issues"
"issues": "Отворених питања"
},
"overseerr": {
"pending": "На чекању",
@@ -1108,11 +1108,11 @@
"poolFree": "{{pool}} слободно"
},
"backrest": {
"num_plans": "Plans",
"num_success_30": "Successes",
"num_failure_30": "Failures",
"num_success_latest": "Succeeding",
"num_failure_latest": "Failing",
"bytes_added_30": "Bytes Added"
"num_plans": "Планови",
"num_success_30": "Успешно",
"num_failure_30": "Неуспешно",
"num_success_latest": "Успевајући",
"num_failure_latest": "Неуспешно",
"bytes_added_30": "Додати бајтови"
}
}

View File

@@ -367,7 +367,7 @@
"unknown": "Unknown"
},
"navidrome": {
"nothing_streaming": "No Active Streams",
"nothing_streaming": "",
"please_wait": "请等待"
},
"npm": {

View File

@@ -1,21 +1,37 @@
Babel==2.12.1
backrefs==5.9
cairocffi==1.7.1
CairoSVG==2.7.1
certifi==2023.7.22
cffi==1.17.1
cfgv==3.4.0
charset-normalizer==3.2.0
click==8.1.7
colorama==0.4.6
cssselect2==0.7.0
defusedxml==0.7.1
distlib==0.3.9
filelock==3.17.0
ghp-import==2.1.0
identify==2.6.7
idna==3.4
Jinja2==3.1.2
Markdown==3.4.4
MarkupSafe==2.1.3
mergedeep==1.3.4
mkdocs==1.6
mkdocs-material==9.5.26
mkdocs==1.6.0
mkdocs-get-deps==0.2.0
mkdocs-material==9.6.18
mkdocs-material-extensions==1.3.1
mkdocs-redirects==1.2.1
nodeenv==1.9.1
packaging==23.1
paginate==0.5.6
pathspec==0.11.2
pillow==10.4.0
platformdirs==3.10.0
pre-commit==3.5.0
pycparser==2.22
Pygments==2.16.1
pymdown-extensions==10.3
python-dateutil==2.8.2
@@ -24,8 +40,8 @@ pyyaml_env_tag==0.1
regex==2023.8.8
requests==2.31.0
six==1.16.0
tinycss2==1.4.0
urllib3==2.0.5
virtualenv==20.29.2
watchdog==3.0.0
pre-commit==3.5.0
mkdocs-material[imaging]==9.5.26
mkdocs-redirects==1.2.1
webencodings==0.5.1

View File

@@ -1,13 +1,21 @@
import classNames from "classnames";
import { useTranslation } from "next-i18next";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { FiSearch } from "react-icons/fi";
import useSWR from "swr";
import { SettingsContext } from "utils/contexts/settings";
import ResolvedIcon from "./resolvedicon";
import { getStoredProvider, searchProviders } from "./widgets/search/search";
export default function QuickLaunch({ servicesAndBookmarks, searchString, setSearchString, isOpen, close }) {
const MOBILE_BUTTON_POSITIONS = {
"top-left": "top-4 left-4",
"top-right": "top-4 right-4",
"bottom-left": "bottom-4 left-4",
"bottom-right": "bottom-4 right-4",
};
export default function QuickLaunch({ servicesAndBookmarks, searchString, setSearchString, isOpen, setSearching }) {
const { t } = useTranslation();
const { settings } = useContext(SettingsContext);
@@ -49,6 +57,10 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
);
}
let mobileButtonPosition = settings.quicklaunch?.mobileButtonPosition
? MOBILE_BUTTON_POSITIONS[settings.quicklaunch.mobileButtonPosition]
: null;
function openCurrentItem(newWindow) {
const result = results[currentItemIndex];
window.open(
@@ -59,13 +71,13 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
}
const closeAndReset = useCallback(() => {
close(false);
setSearching(false);
setTimeout(() => {
setSearchString("");
setCurrentItemIndex(null);
setSearchSuggestions([]);
}, 200); // delay a little for animations
}, [close, setSearchString, setCurrentItemIndex, setSearchSuggestions]);
}, [setSearching, setSearchString, setCurrentItemIndex, setSearchSuggestions]);
function handleSearchChange(event) {
const rawSearchString = event.target.value;
@@ -245,86 +257,98 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
}
return (
<div
className={classNames(
"relative z-40 ease-in-out duration-300 transition-opacity",
hidden && !isOpen && "hidden",
!hidden && isOpen && "opacity-100",
!isOpen && "opacity-0",
)}
role="dialog"
aria-modal="true"
>
<div className="fixed inset-0 bg-gray-500 opacity-50" />
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full min-w-full items-start justify-center text-center">
<dialog className="mt-[10%] mx-auto min-w-[90%] max-w-[90%] md:min-w-[40%] md:max-w-[40%] rounded-md p-0 block 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-50 dark:bg-theme-800">
<input
placeholder="Search"
className={classNames(
results.length > 0 && "rounded-t-md",
results.length === 0 && "rounded-md",
"w-full p-4 m-0 border-0 border-b border-slate-700 focus:border-slate-700 focus:outline-0 focus:ring-0 text-sm md:text-xl text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800",
)}
type="text"
autoCorrect="false"
ref={searchField}
value={searchString}
onChange={handleSearchChange}
onKeyDown={handleSearchKeyDown}
/>
{results.length > 0 && (
<ul className="max-h-[60vh] overflow-y-auto m-2">
{results.map((r, i) => (
<li key={[r.name, r.container, r.app, r.href].filter((s) => s).join("-")}>
<button
type="button"
data-index={i}
onMouseEnter={handleItemHover}
onClick={handleItemClick}
onKeyDown={handleItemKeyDown}
className={classNames(
"flex flex-row w-full items-center justify-between rounded-md text-sm md:text-xl py-2 px-4 cursor-pointer text-theme-700 dark:text-theme-200",
i === currentItemIndex && "bg-theme-300/50 dark:bg-theme-700/50",
)}
>
<div className="flex flex-row items-center mr-4 pointer-events-none">
{(r.icon || r.abbr) && (
<div className="w-5 text-xs mr-4">
{r.icon && <ResolvedIcon icon={r.icon} />}
{r.abbr && r.abbr}
</div>
<>
<div
className={classNames(
"relative z-40 ease-in-out duration-300 transition-opacity",
hidden && !isOpen && "hidden",
!hidden && isOpen && "opacity-100",
!isOpen && "opacity-0",
)}
role="dialog"
aria-modal="true"
>
<div className="fixed inset-0 bg-gray-500 opacity-50" />
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full min-w-full items-start justify-center text-center">
<dialog className="mt-[10%] mx-auto min-w-[90%] max-w-[90%] md:min-w-[40%] md:max-w-[40%] rounded-md p-0 block 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-50 dark:bg-theme-800">
<input
placeholder="Search"
className={classNames(
results.length > 0 && "rounded-t-md",
results.length === 0 && "rounded-md",
"w-full p-4 m-0 border-0 border-b border-slate-700 focus:border-slate-700 focus:outline-0 focus:ring-0 text-sm md:text-xl text-theme-700 dark:text-theme-200 bg-theme-60 dark:bg-theme-800",
)}
type="text"
autoCorrect="false"
ref={searchField}
value={searchString}
onChange={handleSearchChange}
onKeyDown={handleSearchKeyDown}
/>
{results.length > 0 && (
<ul className="max-h-[60vh] overflow-y-auto m-2">
{results.map((r, i) => (
<li key={[r.name, r.container, r.app, r.href].filter((s) => s).join("-")}>
<button
type="button"
data-index={i}
onMouseEnter={handleItemHover}
onClick={handleItemClick}
onKeyDown={handleItemKeyDown}
className={classNames(
"flex flex-row w-full items-center justify-between rounded-md text-sm md:text-xl py-2 px-4 cursor-pointer text-theme-700 dark:text-theme-200",
i === currentItemIndex && "bg-theme-300/50 dark:bg-theme-700/50",
)}
<div className="flex flex-col md:flex-row text-left items-baseline mr-4 pointer-events-none">
{r.type !== "searchSuggestion" && <span className="mr-4">{r.name}</span>}
{r.type === "searchSuggestion" && (
<div className="flex-nowrap">
<span className="whitespace-pre">
{r.name.indexOf(searchString) === 0 ? searchString : ""}
</span>
<span className="whitespace-pre opacity-50">
{r.name.indexOf(searchString) === 0 ? r.name.substring(searchString.length) : r.name}
</span>
>
<div className="flex flex-row items-center mr-4 pointer-events-none">
{(r.icon || r.abbr) && (
<div className="w-5 text-xs mr-4">
{r.icon && <ResolvedIcon icon={r.icon} />}
{r.abbr && r.abbr}
</div>
)}
{r.description && (
<span className="text-xs text-theme-600 text-light">
{searchDescriptions && r.priority < 2 ? highlightText(r.description) : r.description}
</span>
)}
<div className="flex flex-col md:flex-row text-left items-baseline mr-4 pointer-events-none">
{r.type !== "searchSuggestion" && <span className="mr-4">{r.name}</span>}
{r.type === "searchSuggestion" && (
<div className="flex-nowrap">
<span className="whitespace-pre">
{r.name.indexOf(searchString) === 0 ? searchString : ""}
</span>
<span className="whitespace-pre opacity-50">
{r.name.indexOf(searchString) === 0 ? r.name.substring(searchString.length) : r.name}
</span>
</div>
)}
{r.description && (
<span className="text-xs text-theme-600 text-light">
{searchDescriptions && r.priority < 2 ? highlightText(r.description) : r.description}
</span>
)}
</div>
</div>
</div>
<div className="text-xs text-theme-600 font-bold pointer-events-none">
{t(`quicklaunch.${r.type ? r.type.toLowerCase() : "bookmark"}`)}
</div>
</button>
</li>
))}
</ul>
)}
</dialog>
<div className="text-xs text-theme-600 font-bold pointer-events-none">
{t(`quicklaunch.${r.type ? r.type.toLowerCase() : "bookmark"}`)}
</div>
</button>
</li>
))}
</ul>
)}
</dialog>
</div>
</div>
</div>
</div>
{mobileButtonPosition && (
<button
type="button"
onClick={setSearching.bind(this, !isOpen)}
className={`fixed ${mobileButtonPosition} z-40 p-2 rounded-full sm:hidden text-theme-700 dark:text-theme-200 bg-theme-50 dark:bg-theme-800 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 transition-opacity duration-100`}
style={{ opacity: isOpen ? 0 : 1 }}
>
<FiSearch className="w-4 h-4" />
</button>
)}
</>
);
}

View File

@@ -432,7 +432,7 @@ function Home({ initialSettings }) {
searchString={searchString}
setSearchString={setSearchString}
isOpen={searching}
close={setSearching}
setSearching={setSearching}
/>
<div
id="information-widgets"
@@ -499,6 +499,7 @@ function Home({ initialSettings }) {
export default function Wrapper({ initialSettings, fallback }) {
const { theme } = useContext(ThemeContext);
const { color } = useContext(ColorContext);
let backgroundImage = "";
let opacity = initialSettings?.backgroundOpacity ?? 0;
let backgroundBlur = false;
@@ -527,14 +528,22 @@ export default function Wrapper({ initialSettings, fallback }) {
html.classList.toggle("dark", theme === "dark");
html.classList.add(theme === "dark" ? "scheme-dark" : "scheme-light");
html.classList.remove(...Array.from(html.classList).filter((cls) => cls.startsWith("theme-")));
html.classList.add(`theme-${initialSettings.color || "slate"}`);
const desiredThemeClass = `theme-${color || initialSettings.color || "slate"}`;
const themeClassesToRemove = Array.from(html.classList).filter(
(cls) => cls.startsWith("theme-") && cls !== desiredThemeClass,
);
if (themeClassesToRemove.length) {
html.classList.remove(...themeClassesToRemove);
}
if (!html.classList.contains(desiredThemeClass)) {
html.classList.add(desiredThemeClass);
}
// Remove any previously applied inline styles
body.style.backgroundImage = "";
body.style.backgroundColor = "";
body.style.backgroundAttachment = "";
}, [backgroundImage, opacity, theme, initialSettings.color]);
}, [backgroundImage, opacity, theme, color, initialSettings.color]);
return (
<>

View File

@@ -8,6 +8,10 @@ import widgets from "widgets/widgets";
const logger = createLogger("credentialedProxyHandler");
function basicAuthHeader(widget) {
return `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
}
export default async function credentialedProxyHandler(req, res, map) {
const { group, service, endpoint, index } = req.query;
@@ -61,7 +65,7 @@ export default async function credentialedProxyHandler(req, res, map) {
if (widget.key) {
headers.Authorization = `Bearer ${widget.key}`;
} else {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
headers.Authorization = basicAuthHeader(widget);
}
} else if (widget.type === "proxmox") {
headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
@@ -78,31 +82,31 @@ export default async function credentialedProxyHandler(req, res, map) {
if (widget.key) {
headers["NC-Token"] = `${widget.key}`;
} else {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
headers.Authorization = basicAuthHeader(widget);
}
} else if (widget.type === "paperlessngx") {
if (widget.key) {
headers.Authorization = `Token ${widget.key}`;
} else {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
headers.Authorization = basicAuthHeader(widget);
}
} else if (widget.type === "azuredevops") {
headers.Authorization = `Basic ${Buffer.from(`$:${widget.key}`).toString("base64")}`;
} else if (widget.type === "glances") {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
headers.Authorization = basicAuthHeader(widget);
} else if (widget.type === "plantit") {
headers.Key = `${widget.key}`;
} else if (widget.type === "myspeed") {
headers.Password = `${widget.password}`;
} else if (widget.type === "esphome") {
if (widget.username && widget.password) {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
headers.Authorization = basicAuthHeader(widget);
} else if (widget.key) {
headers.Cookie = `authenticated=${widget.key}`;
}
} else if (widget.type === "wgeasy") {
if (widget.username && widget.password) {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
headers.Authorization = basicAuthHeader(widget);
} else {
headers.Authorization = widget.password;
}