Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94a518f4a8 | ||
|
|
e56dccc7f1 | ||
|
|
adf601c572 | ||
|
|
00eb768cdf | ||
|
|
b8d00f24f7 | ||
|
|
031f11c5c1 | ||
|
|
1ffe7da9b7 | ||
|
|
292c6dd6b0 | ||
|
|
6f5a0ea6b8 | ||
|
|
f44b564bb5 | ||
|
|
b380fd2306 | ||
|
|
528ce1a5fe | ||
|
|
593d8a1cbc | ||
|
|
8f6ea7b49f | ||
|
|
01d005dbbe | ||
|
|
45fb059e71 | ||
|
|
40a11bd9ce | ||
|
|
935f97415f | ||
|
|
969dab9971 | ||
|
|
193b548d0b | ||
|
|
6dacd1bdd6 | ||
|
|
6a0a872790 | ||
|
|
fa71586a50 | ||
|
|
65c5828456 | ||
|
|
892c68856f | ||
|
|
ea96999377 | ||
|
|
a9fb458f19 |
@@ -1,7 +1,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="images/homepage-dark.png">
|
<source media="(prefers-color-scheme: dark)" srcset="images/banner_light@2x.png">
|
||||||
<img src="images/homepage-light.png" width="65%">
|
<img src="images/banner_dark@2x.png" width="65%">
|
||||||
</picture>
|
</picture>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
- Images built for AMD64 (x86_64), ARM64, ARMv7 and ARMv6
|
- Images built for AMD64 (x86_64), ARM64, ARMv7 and ARMv6
|
||||||
- Supports all Raspberry Pi's, most SBCs & Apple Silicon
|
- Supports all Raspberry Pi's, most SBCs & Apple Silicon
|
||||||
- Full i18n support with automatic language detection
|
- Full i18n support with automatic language detection
|
||||||
- Translantions for Chinese, Dutch, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romainian, Russian, Spanish, Swedish and Yue
|
- Translantions for Chinese, Dutch, Finnish, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romainian, Russian, Spanish, Swedish and Yue
|
||||||
- Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/)
|
- Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/)
|
||||||
- Service & Web Bookmarks
|
- Service & Web Bookmarks
|
||||||
- Docker Integration
|
- Docker Integration
|
||||||
@@ -156,6 +156,7 @@ Huge thanks to the all the contributors who have helped make this project what i
|
|||||||
- [josways](https://github.com/benphelps/homepage/commits?author=josways) - Baidu search provider
|
- [josways](https://github.com/benphelps/homepage/commits?author=josways) - Baidu search provider
|
||||||
- [mauricio-kalil](https://github.com/benphelps/homepage/commits?author=mauricio-kalil) - Portuguese (Brazil)
|
- [mauricio-kalil](https://github.com/benphelps/homepage/commits?author=mauricio-kalil) - Portuguese (Brazil)
|
||||||
- [modem7](https://github.com/benphelps/homepage/commits?author=modem7) - Impvoed Docker Image
|
- [modem7](https://github.com/benphelps/homepage/commits?author=modem7) - Impvoed Docker Image
|
||||||
|
- [MountainGod2](https://github.com/benphelps/homepage/discussions/243) - Homepage Logo
|
||||||
- [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos
|
- [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos
|
||||||
- [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6
|
- [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6
|
||||||
- [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration
|
- [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration
|
||||||
@@ -186,3 +187,4 @@ Huge thanks to the all the contributors who have helped make this project what i
|
|||||||
- Sascha Jelinek - German
|
- Sascha Jelinek - German
|
||||||
- [ShlomiPorush](https://github.com/benphelps/homepage/commits?author=ShlomiPorush) - Hebrew
|
- [ShlomiPorush](https://github.com/benphelps/homepage/commits?author=ShlomiPorush) - Hebrew
|
||||||
- [SuperDOS](https://github.com/benphelps/homepage/commits?author=SuperDOS) - Swedish
|
- [SuperDOS](https://github.com/benphelps/homepage/commits?author=SuperDOS) - Swedish
|
||||||
|
- [kaihu](https://github.com/benphelps/homepage/commits?author=kaihu) - Finnish
|
||||||
|
|||||||
BIN
images/banner_dark@2x.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
images/banner_light@2x.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 465 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 15 KiB |
BIN
public/homepage.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -174,5 +174,11 @@
|
|||||||
"users": "Usuaris",
|
"users": "Usuaris",
|
||||||
"loginsLast24H": "Inicis de sessió (24h)",
|
"loginsLast24H": "Inicis de sessió (24h)",
|
||||||
"failedLoginsLast24H": "Errors d'inici de sessió (24h)"
|
"failedLoginsLast24H": "Errors d'inici de sessió (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"vms": "VMs",
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Benutzer",
|
"users": "Benutzer",
|
||||||
"loginsLast24H": "Anmeldungen (24h)",
|
"loginsLast24H": "Anmeldungen (24h)",
|
||||||
"failedLoginsLast24H": "fehlerhafte Anmeldungen (24h)"
|
"failedLoginsLast24H": "fehlerhafte Anmeldungen (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,5 +185,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Usuarios",
|
"users": "Usuarios",
|
||||||
"loginsLast24H": "Inicios de sesión (24h)",
|
"loginsLast24H": "Inicios de sesión (24h)",
|
||||||
"failedLoginsLast24H": "Inicios de sesión fallidos (24h)"
|
"failedLoginsLast24H": "Inicios de sesión fallidos (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,178 +1,184 @@
|
|||||||
{
|
{
|
||||||
"widget": {
|
"widget": {
|
||||||
"missing_type": "Missing Widget Type: {{type}}",
|
"missing_type": "Puuttuva härpäkkeen tyyppi: {{type}}",
|
||||||
"api_error": "API Error",
|
"api_error": "API-virhe",
|
||||||
"status": "Status"
|
"status": "Tila"
|
||||||
},
|
},
|
||||||
"weather": {
|
"weather": {
|
||||||
"current": "Current Location",
|
"current": "Nykyinen sijainti",
|
||||||
"allow": "Click to allow",
|
"allow": "Klikkaa salliaksesi",
|
||||||
"updating": "Updating",
|
"updating": "Päivitetään",
|
||||||
"wait": "Please wait"
|
"wait": "Odota, ole hyvä"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Search…"
|
"placeholder": "Hae…"
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"total": "Total",
|
"total": "Yhteensä",
|
||||||
"free": "Free",
|
"free": "Vapaana",
|
||||||
"used": "Used",
|
"used": "Käytetty",
|
||||||
"load": "Load"
|
"load": "Kuorma"
|
||||||
},
|
},
|
||||||
"docker": {
|
"docker": {
|
||||||
"rx": "RX",
|
"rx": "RX",
|
||||||
"tx": "TX",
|
"tx": "TX",
|
||||||
"mem": "MEM",
|
"mem": "RAM",
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"offline": "Offline"
|
"offline": "Offline"
|
||||||
},
|
},
|
||||||
"emby": {
|
"emby": {
|
||||||
"playing": "Playing",
|
"playing": "Toistaa",
|
||||||
"transcoding": "Transcoding",
|
"transcoding": "Transkoodaa",
|
||||||
"bitrate": "Bitrate",
|
"bitrate": "Bittinopeus",
|
||||||
"no_active": "No Active Streams"
|
"no_active": "Ei aktiivisia striimejä"
|
||||||
},
|
},
|
||||||
"tautulli": {
|
"tautulli": {
|
||||||
"playing": "Playing",
|
"playing": "Toistaa",
|
||||||
"transcoding": "Transcoding",
|
"transcoding": "Transkoodaa",
|
||||||
"bitrate": "Bitrate",
|
"bitrate": "Bittinopeus",
|
||||||
"no_active": "No Active Streams"
|
"no_active": "Ei aktiivisia striimejä"
|
||||||
},
|
},
|
||||||
"nzbget": {
|
"nzbget": {
|
||||||
"rate": "Rate",
|
"rate": "Nopeus",
|
||||||
"remaining": "Remaining",
|
"remaining": "Jäljellä",
|
||||||
"downloaded": "Downloaded"
|
"downloaded": "Ladattu"
|
||||||
},
|
},
|
||||||
"sabnzbd": {
|
"sabnzbd": {
|
||||||
"rate": "Rate",
|
"rate": "Nopeus",
|
||||||
"queue": "Queue",
|
"queue": "Jono",
|
||||||
"timeleft": "Time Left"
|
"timeleft": "Aikaa jäljellä"
|
||||||
},
|
},
|
||||||
"rutorrent": {
|
"rutorrent": {
|
||||||
"active": "Active",
|
"active": "Aktiivinen",
|
||||||
"upload": "Upload",
|
"upload": "Lähetys",
|
||||||
"download": "Download"
|
"download": "Lataus"
|
||||||
},
|
},
|
||||||
"transmission": {
|
"transmission": {
|
||||||
"download": "Download",
|
"download": "Lataus",
|
||||||
"upload": "Upload",
|
"upload": "Lähetys",
|
||||||
"leech": "Leech",
|
"leech": "Lataajia",
|
||||||
"seed": "Seed"
|
"seed": "Lähettäjiä"
|
||||||
},
|
},
|
||||||
"qbittorrent": {
|
"qbittorrent": {
|
||||||
"download": "Download",
|
"download": "Lataus",
|
||||||
"upload": "Upload",
|
"upload": "Lähetys",
|
||||||
"leech": "Leech",
|
"leech": "Lataajia",
|
||||||
"seed": "Seed"
|
"seed": "Lähettäjiä"
|
||||||
},
|
},
|
||||||
"sonarr": {
|
"sonarr": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Haluttu",
|
||||||
"queued": "Queued",
|
"queued": "Jonossa",
|
||||||
"series": "Series"
|
"series": "Sarja"
|
||||||
},
|
},
|
||||||
"radarr": {
|
"radarr": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Haluttu",
|
||||||
"queued": "Queued",
|
"queued": "Jonossa",
|
||||||
"movies": "Movies"
|
"movies": "Elokuvia"
|
||||||
},
|
},
|
||||||
"lidarr": {
|
"lidarr": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Haluttu",
|
||||||
"queued": "Queued",
|
"queued": "Jonossa",
|
||||||
"albums": "Albums"
|
"albums": "Albumeja"
|
||||||
},
|
},
|
||||||
"readarr": {
|
"readarr": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Haluttu",
|
||||||
"queued": "Queued",
|
"queued": "Jonossa",
|
||||||
"books": "Books"
|
"books": "Kirjoja"
|
||||||
},
|
},
|
||||||
"bazarr": {
|
"bazarr": {
|
||||||
"missingEpisodes": "Missing Episodes",
|
"missingEpisodes": "Puuttuvia jaksoja",
|
||||||
"missingMovies": "Missing Movies"
|
"missingMovies": "Puuttuvia elokuvia"
|
||||||
},
|
},
|
||||||
"ombi": {
|
"ombi": {
|
||||||
"pending": "Pending",
|
"pending": "Vireillä",
|
||||||
"approved": "Approved",
|
"approved": "Hyväksytty",
|
||||||
"available": "Available"
|
"available": "Saatavilla"
|
||||||
},
|
},
|
||||||
"jellyseerr": {
|
"jellyseerr": {
|
||||||
"pending": "Pending",
|
"pending": "Vireillä",
|
||||||
"approved": "Approved",
|
"approved": "Hyväksytty",
|
||||||
"available": "Available"
|
"available": "Saatavilla"
|
||||||
},
|
},
|
||||||
"overseerr": {
|
"overseerr": {
|
||||||
"pending": "Pending",
|
"pending": "Vireillä",
|
||||||
"approved": "Approved",
|
"approved": "Hyväksytty",
|
||||||
"available": "Available"
|
"available": "Saatavilla"
|
||||||
},
|
},
|
||||||
"pihole": {
|
"pihole": {
|
||||||
"queries": "Queries",
|
"queries": "Kyselyjä",
|
||||||
"blocked": "Blocked",
|
"blocked": "Estetty",
|
||||||
"gravity": "Gravity"
|
"gravity": "Vakavuus"
|
||||||
},
|
},
|
||||||
"adguard": {
|
"adguard": {
|
||||||
"queries": "Queries",
|
"queries": "Kyselyjä",
|
||||||
"blocked": "Blocked",
|
"blocked": "Estetty",
|
||||||
"filtered": "Filtered",
|
"filtered": "Suodatettu",
|
||||||
"latency": "Latency"
|
"latency": "Viive"
|
||||||
},
|
},
|
||||||
"speedtest": {
|
"speedtest": {
|
||||||
"upload": "Upload",
|
"upload": "Lähetys",
|
||||||
"download": "Download",
|
"download": "Lataus",
|
||||||
"ping": "Ping"
|
"ping": "Viive"
|
||||||
},
|
},
|
||||||
"portainer": {
|
"portainer": {
|
||||||
"running": "Running",
|
"running": "Käynnissä",
|
||||||
"stopped": "Stopped",
|
"stopped": "Pysäytetty",
|
||||||
"total": "Total"
|
"total": "Yhteensä"
|
||||||
},
|
},
|
||||||
"traefik": {
|
"traefik": {
|
||||||
"routers": "Routers",
|
"routers": "Reitittimiä",
|
||||||
"services": "Services",
|
"services": "Palveluja",
|
||||||
"middleware": "Middleware"
|
"middleware": "Middlewareja"
|
||||||
},
|
},
|
||||||
"npm": {
|
"npm": {
|
||||||
"enabled": "Enabled",
|
"enabled": "Käytössä",
|
||||||
"disabled": "Disabled",
|
"disabled": "Poissa käytöstä",
|
||||||
"total": "Total"
|
"total": "Yhteensä"
|
||||||
},
|
},
|
||||||
"coinmarketcap": {
|
"coinmarketcap": {
|
||||||
"configure": "Configure one or more crypto currencies to track",
|
"configure": "Määritä yksi tai useampi kryptovaluutta seurattavaksi",
|
||||||
"1hour": "1 Hour",
|
"1hour": "1 tunti",
|
||||||
"1day": "1 Day",
|
"1day": "1 päivä",
|
||||||
"7days": "7 Days",
|
"7days": "7 päivää",
|
||||||
"30days": "30 Days"
|
"30days": "30 päivää"
|
||||||
},
|
},
|
||||||
"gotify": {
|
"gotify": {
|
||||||
"apps": "Applications",
|
"apps": "Sovelluksia",
|
||||||
"clients": "Clients",
|
"clients": "Asiakasohjelmia",
|
||||||
"messages": "Messages"
|
"messages": "Viestejä"
|
||||||
},
|
},
|
||||||
"prowlarr": {
|
"prowlarr": {
|
||||||
"enableIndexers": "Indexers",
|
"enableIndexers": "Indeksoijia",
|
||||||
"numberOfGrabs": "Grabs",
|
"numberOfGrabs": "Nappauksia",
|
||||||
"numberOfQueries": "Queries",
|
"numberOfQueries": "Hakuja",
|
||||||
"numberOfFailGrabs": "Fail Grabs",
|
"numberOfFailGrabs": "Epäonnistuneita nappauksia",
|
||||||
"numberOfFailQueries": "Fail Queries"
|
"numberOfFailQueries": "Epäonnistuneita kyselyjä"
|
||||||
},
|
},
|
||||||
"jackett": {
|
"jackett": {
|
||||||
"configured": "Configured",
|
"configured": "Määritettyjä",
|
||||||
"errored": "Errored"
|
"errored": "Virheellisiä"
|
||||||
},
|
},
|
||||||
"strelaysrv": {
|
"strelaysrv": {
|
||||||
"numActiveSessions": "Sessions",
|
"numActiveSessions": "Istuntoja",
|
||||||
"numConnections": "Connections",
|
"numConnections": "Yhteyksiä",
|
||||||
"dataRelayed": "Relayed",
|
"dataRelayed": "Välitetty",
|
||||||
"transferRate": "Rate"
|
"transferRate": "Nopeus"
|
||||||
},
|
},
|
||||||
"mastodon": {
|
"mastodon": {
|
||||||
"user_count": "Users",
|
"user_count": "Käyttäjiä",
|
||||||
"status_count": "Posts",
|
"status_count": "Kirjoituksia",
|
||||||
"domain_count": "Domains"
|
"domain_count": "Verkkotunnuksia"
|
||||||
},
|
},
|
||||||
"authentik": {
|
"authentik": {
|
||||||
"users": "Users",
|
"users": "Käyttäjiä",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Kirjautumisia (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Epäonnistuneita kirjautumisia (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Utilisateurs",
|
"users": "Utilisateurs",
|
||||||
"loginsLast24H": "Cnx. (24h)",
|
"loginsLast24H": "Cnx. (24h)",
|
||||||
"failedLoginsLast24H": "Cnx. échouées (24h)"
|
"failedLoginsLast24H": "Cnx. échouées (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,5 +185,11 @@
|
|||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)",
|
"failedLoginsLast24H": "Failed Logins (24h)",
|
||||||
"users": "Users"
|
"users": "Users"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Utilizatori",
|
"users": "Utilizatori",
|
||||||
"loginsLast24H": "Autentificări (24h)",
|
"loginsLast24H": "Autentificări (24h)",
|
||||||
"failedLoginsLast24H": "Conectări eșuate (24h)"
|
"failedLoginsLast24H": "Conectări eșuate (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"vms": "VMs",
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "用戶",
|
"users": "用戶",
|
||||||
"loginsLast24H": "登錄( 24小时)",
|
"loginsLast24H": "登錄( 24小时)",
|
||||||
"failedLoginsLast24H": "登錄失敗( 24鐘頭)"
|
"failedLoginsLast24H": "登錄失敗( 24鐘頭)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,5 +174,11 @@
|
|||||||
"users": "Users",
|
"users": "Users",
|
||||||
"loginsLast24H": "Logins (24h)",
|
"loginsLast24H": "Logins (24h)",
|
||||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/mstile-144x144.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/mstile-310x150.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/mstile-310x310.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/mstile-70x70.png
Normal file
|
After Width: | Height: | Size: 958 B |
23
public/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M1682 5688 c-141 -24 -253 -147 -287 -316 -5 -24 -10 -3658 -5 -4029
|
||||||
|
0 -33 0 -33 168 108 92 77 178 151 192 162 14 12 75 64 136 114 62 51 114 99
|
||||||
|
116 105 2 7 4 209 3 449 l-1 436 276 0 c151 0 277 -4 280 -8 3 -4 5 -98 4
|
||||||
|
-208 0 -111 2 -201 4 -201 3 0 117 95 132 110 3 3 73 61 155 130 168 140 167
|
||||||
|
139 250 209 51 43 62 48 75 37 13 -12 524 -441 654 -550 28 -22 68 -57 91 -76
|
||||||
|
23 -19 66 -55 95 -80 30 -25 86 -72 125 -105 38 -33 140 -118 225 -190 85 -71
|
||||||
|
166 -139 180 -151 14 -13 70 -60 124 -106 55 -46 133 -111 174 -145 41 -35 77
|
||||||
|
-61 80 -59 4 2 7 607 7 1344 -1 1197 1 1341 15 1344 8 2 163 3 343 3 l328 0
|
||||||
|
-4 685 c-1 377 -6 695 -9 706 -26 90 -93 186 -161 233 -86 58 30 55 -1924 55
|
||||||
|
-991 1 -1819 -2 -1841 -6z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -3,9 +3,11 @@ import List from "components/bookmarks/list";
|
|||||||
|
|
||||||
export default function BookmarksGroup({ group }) {
|
export default function BookmarksGroup({ group }) {
|
||||||
return (
|
return (
|
||||||
<div key={group.name} className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1 p-1">
|
<div key={group.name} className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1">
|
||||||
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2>
|
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2>
|
||||||
<ErrorBoundary><List bookmarks={group.bookmarks} /></ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
<List bookmarks={group.bookmarks} />
|
||||||
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
export default class ErrorBoundary extends React.Component {
|
export default class ErrorBoundary extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -10,8 +10,8 @@ export default class ErrorBoundary extends React.Component {
|
|||||||
// Catch errors in any components below and re-render with error message
|
// Catch errors in any components below and re-render with error message
|
||||||
this.setState({
|
this.setState({
|
||||||
error,
|
error,
|
||||||
errorInfo
|
errorInfo,
|
||||||
})
|
});
|
||||||
|
|
||||||
// You can also log error messages to an error reporting service here
|
// You can also log error messages to an error reporting service here
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@@ -23,11 +23,10 @@ export default class ErrorBoundary extends React.Component {
|
|||||||
if (errorInfo) {
|
if (errorInfo) {
|
||||||
// Error path
|
// Error path
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="inline-block text-sm bg-rose-100 text-rose-900 dark:bg-rose-900 dark:text-rose-100 rounded-md p-2 m-1">
|
||||||
<h2>Something went wrong.</h2>
|
<div className="font-medium mb-1">Something went wrong.</div>
|
||||||
<details style={{ whiteSpace: 'pre-wrap' }}>
|
<details className="text-xs font-mono whitespace-pre">
|
||||||
{error && error.toString()}
|
<summary>{error && error.toString()}</summary>
|
||||||
<br />
|
|
||||||
{errorInfo.componentStack}
|
{errorInfo.componentStack}
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,4 +37,4 @@ export default class ErrorBoundary extends React.Component {
|
|||||||
const { children } = this.props;
|
const { children } = this.props;
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
114
src/components/favicon.jsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/* eslint-disable @next/next/no-img-element */
|
||||||
|
/* eslint-disable jsx-a11y/alt-text */
|
||||||
|
import { useRef, useEffect, useContext } from "react";
|
||||||
|
|
||||||
|
import themes from "utils/styles/themes";
|
||||||
|
import { ColorContext } from "utils/contexts/color";
|
||||||
|
|
||||||
|
export function Svg({ svgRef = null }) {
|
||||||
|
const { color } = useContext(ColorContext);
|
||||||
|
|
||||||
|
const { iconStart, iconEnd } = themes[color];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
style={{
|
||||||
|
enableBackground: "new 0 0 1024 1024",
|
||||||
|
}}
|
||||||
|
xmlSpace="preserve"
|
||||||
|
className="w-full h-full"
|
||||||
|
>
|
||||||
|
<style>
|
||||||
|
{
|
||||||
|
".st0{display:none}.st3{stroke-linecap:square}.st3,.st4{fill:none;stroke:#fff;stroke-miterlimit:10}.st6{display:inline;fill:#333}.st7{fill:#fff}"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<g id="Icon">
|
||||||
|
<path
|
||||||
|
d="M771.9 191c27.7 0 50.1 26.5 50.1 59.3v186.4l-100.2.3V250.3c0-32.8 22.4-59.3 50.1-59.3z"
|
||||||
|
style={{
|
||||||
|
fill: iconStart,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<linearGradient
|
||||||
|
id="homepage_favicon_gradient"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1={200.746}
|
||||||
|
y1={225.015}
|
||||||
|
x2={764.986}
|
||||||
|
y2={789.255}
|
||||||
|
>
|
||||||
|
<stop
|
||||||
|
offset={0}
|
||||||
|
style={{
|
||||||
|
stopColor: iconStart,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset={1}
|
||||||
|
style={{
|
||||||
|
stopColor: iconEnd,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<path
|
||||||
|
d="M721.8 250.3c0-32.7 22.4-59.3 50.1-59.3H253.1c-27.7 0-50.1 26.5-50.1 59.3v582.2l90.2-75.7-.1-130.3H375v61.8l88-73.8 258.8 217.9V250.6"
|
||||||
|
style={{
|
||||||
|
fill: "url(#homepage_favicon_gradient})",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Favicon() {
|
||||||
|
const svgRef = useRef();
|
||||||
|
const imgRef = useRef();
|
||||||
|
const canvasRef = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const svg = svgRef.current;
|
||||||
|
const img = imgRef.current;
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
|
||||||
|
if (!svg || !img || !canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xml = new XMLSerializer().serializeToString(svg);
|
||||||
|
|
||||||
|
const svg64 = Buffer.from(xml).toString("base64");
|
||||||
|
const b64Start = "data:image/svg+xml;base64,";
|
||||||
|
|
||||||
|
// prepend a "header"
|
||||||
|
const image64 = b64Start + svg64;
|
||||||
|
|
||||||
|
// set it as the source of the img element
|
||||||
|
img.onload = () => {
|
||||||
|
// draw the image onto the canvas
|
||||||
|
canvas.getContext("2d").drawImage(img, 0, 0);
|
||||||
|
// canvas.width = 256;
|
||||||
|
// canvas.height = 256;
|
||||||
|
|
||||||
|
const link = window.document.createElement("link");
|
||||||
|
link.type = "image/x-icon";
|
||||||
|
link.rel = "shortcut icon";
|
||||||
|
link.href = canvas.toDataURL("image/x-icon");
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = image64;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="hidden">
|
||||||
|
<Svg svgRef={svgRef} />
|
||||||
|
<img width={64} height={64} ref={imgRef} />
|
||||||
|
<canvas width={64} height={64} ref={canvasRef} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import ErrorBoundary from "components/errorboundry";
|
|
||||||
import List from "components/services/list";
|
import List from "components/services/list";
|
||||||
|
|
||||||
export default function ServicesGroup({ services, layout }) {
|
export default function ServicesGroup({ services, layout }) {
|
||||||
@@ -13,7 +12,7 @@ export default function ServicesGroup({ services, layout }) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
|
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
|
||||||
<ErrorBoundary><List services={services.services} layout={layout} /></ErrorBoundary>
|
<List services={services.services} layout={layout} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import ErrorBoundary from "components/errorboundry";
|
|
||||||
import Item from "components/services/item";
|
import Item from "components/services/item";
|
||||||
|
|
||||||
const columnMap = [
|
const columnMap = [
|
||||||
@@ -24,7 +23,7 @@ export default function List({ services, layout }) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{services.map((service) => (
|
{services.map((service) => (
|
||||||
<ErrorBoundary key={service.name}><Item key={service.name} service={service} /></ErrorBoundary>
|
<Item key={service.name} service={service} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
|
import ErrorBoundary from "components/errorboundry";
|
||||||
import components from "widgets/components";
|
import components from "widgets/components";
|
||||||
|
|
||||||
export default function Widget({ service }) {
|
export default function Widget({ service }) {
|
||||||
@@ -8,7 +9,11 @@ export default function Widget({ service }) {
|
|||||||
const ServiceWidget = components[service.widget.type];
|
const ServiceWidget = components[service.widget.type];
|
||||||
|
|
||||||
if (ServiceWidget) {
|
if (ServiceWidget) {
|
||||||
return <ServiceWidget service={service} />;
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ServiceWidget service={service} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
56
src/components/widgets/logo/logo.jsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
export default function Logo() {
|
||||||
|
return (
|
||||||
|
<div className="w-12 h-12 flex flex-row items-center align-middle mr-3 self-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
style={{
|
||||||
|
enableBackground: "new 0 0 1024 1024",
|
||||||
|
}}
|
||||||
|
xmlSpace="preserve"
|
||||||
|
className="w-full h-full"
|
||||||
|
>
|
||||||
|
<style>
|
||||||
|
{
|
||||||
|
".st0{display:none}.st3{stroke-linecap:square}.st3,.st4{fill:none;stroke:#fff;stroke-miterlimit:10}.st6{display:inline;fill:#333}.st7{fill:#fff}"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<g id="Icon">
|
||||||
|
<path
|
||||||
|
d="M771.9 191c27.7 0 50.1 26.5 50.1 59.3v186.4l-100.2.3V250.3c0-32.8 22.4-59.3 50.1-59.3z"
|
||||||
|
style={{
|
||||||
|
fill: "rgba(var(--color-logo-start))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<linearGradient
|
||||||
|
id="homepage_logo_gradient"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1={200.746}
|
||||||
|
y1={225.015}
|
||||||
|
x2={764.986}
|
||||||
|
y2={789.255}
|
||||||
|
>
|
||||||
|
<stop
|
||||||
|
offset={0}
|
||||||
|
style={{
|
||||||
|
stopColor: "rgba(var(--color-logo-start))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset={1}
|
||||||
|
style={{
|
||||||
|
stopColor: "rgba(var(--color-logo-stop))",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<path
|
||||||
|
d="M721.8 250.3c0-32.7 22.4-59.3 50.1-59.3H253.1c-27.7 0-50.1 26.5-50.1 59.3v582.2l90.2-75.7-.1-130.3H375v61.8l88-73.8 258.8 217.9V250.6"
|
||||||
|
style={{
|
||||||
|
fill: "url(#homepage_logo_gradient)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,9 +16,9 @@ function Widget({ options }) {
|
|||||||
|
|
||||||
if (error || data?.cod === 401 || data?.error) {
|
if (error || data?.cod === 401 || data?.error) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
<div className="flex flex-col justify-center first:ml-auto ml-4 mr-2">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="hidden sm:flex flex-col items-center">
|
||||||
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
||||||
<div className="flex flex-col ml-3 text-left">
|
<div className="flex flex-col ml-3 text-left">
|
||||||
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span>
|
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span>
|
||||||
@@ -32,9 +32,9 @@ function Widget({ options }) {
|
|||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
<div className="flex flex-col justify-center first:ml-auto ml-4 mr-2">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="hidden sm:flex flex-col items-center">
|
||||||
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col ml-3 text-left">
|
<div className="flex flex-col ml-3 text-left">
|
||||||
@@ -49,9 +49,9 @@ function Widget({ options }) {
|
|||||||
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
|
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
<div className="flex flex-col justify-center first:ml-auto ml-2 mr-2">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="hidden sm:flex flex-col items-center">
|
||||||
<Icon
|
<Icon
|
||||||
condition={data.weather[0].id}
|
condition={data.weather[0].id}
|
||||||
timeOfDay={data.dt > data.sys.sunrise && data.dt < data.sys.sundown ? "day" : "night"}
|
timeOfDay={data.dt > data.sys.sunrise && data.dt < data.sys.sundown ? "day" : "night"}
|
||||||
@@ -102,9 +102,13 @@ export default function OpenWeatherMap({ options }) {
|
|||||||
|
|
||||||
if (!location) {
|
if (!location) {
|
||||||
return (
|
return (
|
||||||
<button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center first:ml-0 ml-4">
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => requestLocation()}
|
||||||
|
className="flex flex-col justify-center first:ml-auto ml-4 mr-2"
|
||||||
|
>
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="hidden sm:flex flex-col items-center">
|
||||||
{requesting ? (
|
{requesting ? (
|
||||||
<MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
|
<MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Memory from "./memory";
|
|||||||
export default function Resources({ options }) {
|
export default function Resources({ options }) {
|
||||||
const { expanded } = options;
|
const { expanded } = options;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap">
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap">
|
||||||
<div className="flex flex-row self-center flex-wrap justify-between">
|
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||||
{options.cpu && <Cpu expanded={expanded} />}
|
{options.cpu && <Cpu expanded={expanded} />}
|
||||||
{options.memory && <Memory expanded={expanded} />}
|
{options.memory && <Memory expanded={expanded} />}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export default function Search({ options }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow first:ml-0 ml-4" onSubmit={handleSubmit}>
|
<form className="flex-col relative h-8 my-4 min-w-fit grow first:ml-0 ml-4" onSubmit={handleSubmit}>
|
||||||
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" />
|
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function Widget({ options }) {
|
|||||||
|
|
||||||
if (error || data?.error) {
|
if (error || data?.error) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
<div className="flex flex-col justify-center first:ml-0 ml-4 mr-2">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
||||||
@@ -32,7 +32,7 @@ function Widget({ options }) {
|
|||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
<div className="flex flex-col justify-center first:ml-0 ml-4 mr-2">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
||||||
@@ -49,7 +49,7 @@ function Widget({ options }) {
|
|||||||
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
|
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
<div className="flex flex-col justify-center first:ml-0 ml-4 mr-2">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} />
|
<Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} />
|
||||||
@@ -103,7 +103,11 @@ export default function WeatherApi({ options }) {
|
|||||||
|
|
||||||
if (!location) {
|
if (!location) {
|
||||||
return (
|
return (
|
||||||
<button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center first:ml-0 ml-4">
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => requestLocation()}
|
||||||
|
className="flex flex-col justify-center first:ml-0 ml-4 mr-2"
|
||||||
|
>
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
{requesting ? (
|
{requesting ? (
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
import ErrorBoundary from "components/errorboundry";
|
||||||
|
|
||||||
const widgetMappings = {
|
const widgetMappings = {
|
||||||
weatherapi: dynamic(() => import("components/widgets/weather/weather")),
|
weatherapi: dynamic(() => import("components/widgets/weather/weather")),
|
||||||
openweathermap: dynamic(() => import("components/widgets/openweathermap/weather")),
|
openweathermap: dynamic(() => import("components/widgets/openweathermap/weather")),
|
||||||
@@ -7,13 +9,18 @@ const widgetMappings = {
|
|||||||
search: dynamic(() => import("components/widgets/search/search")),
|
search: dynamic(() => import("components/widgets/search/search")),
|
||||||
greeting: dynamic(() => import("components/widgets/greeting/greeting")),
|
greeting: dynamic(() => import("components/widgets/greeting/greeting")),
|
||||||
datetime: dynamic(() => import("components/widgets/datetime/datetime")),
|
datetime: dynamic(() => import("components/widgets/datetime/datetime")),
|
||||||
|
logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Widget({ widget }) {
|
export default function Widget({ widget }) {
|
||||||
const InfoWidget = widgetMappings[widget.type];
|
const InfoWidget = widgetMappings[widget.type];
|
||||||
|
|
||||||
if (InfoWidget) {
|
if (InfoWidget) {
|
||||||
return <InfoWidget options={widget.options} />;
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<InfoWidget options={widget.options} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ export default function Document() {
|
|||||||
name="description"
|
name="description"
|
||||||
content="A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations."
|
content="A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations."
|
||||||
/>
|
/>
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=4" />
|
||||||
|
<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="manifest" href="/site.webmanifest?v=4" />
|
||||||
|
<link rel="mask-icon" href="/safari-pinned-tab.svg?v=4" color="#1e9cd7" />
|
||||||
</Head>
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
<Main />
|
<Main />
|
||||||
|
|||||||
14
src/pages/api/theme.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import checkAndCopyConfig, { getSettings } from "utils/config/config";
|
||||||
|
|
||||||
|
export default function handler({ res }) {
|
||||||
|
checkAndCopyConfig("settings.yaml");
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
const color = settings.color || "slate";
|
||||||
|
const theme = settings.theme || "dark";
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
color,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
}
|
||||||
29
src/pages/browserconfig.xml.jsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { getSettings } from "utils/config/config";
|
||||||
|
import themes from "utils/styles/themes";
|
||||||
|
|
||||||
|
export async function getServerSideProps({ res }) {
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
const color = settings.color || "slate";
|
||||||
|
const theme = settings.theme || "dark";
|
||||||
|
|
||||||
|
const xml = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png?v=2"/>
|
||||||
|
<TileColor>${themes[color][theme]}</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>`;
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "text/xml");
|
||||||
|
res.write(xml);
|
||||||
|
res.end();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BrowserConfig() {}
|
||||||
@@ -8,7 +8,6 @@ import { useEffect, useContext, useState } from "react";
|
|||||||
import { BiError } from "react-icons/bi";
|
import { BiError } from "react-icons/bi";
|
||||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
|
|
||||||
import ErrorBoundary from "components/errorboundry";
|
|
||||||
import ServicesGroup from "components/services/group";
|
import ServicesGroup from "components/services/group";
|
||||||
import BookmarksGroup from "components/bookmarks/group";
|
import BookmarksGroup from "components/bookmarks/group";
|
||||||
import Widget from "components/widgets/widget";
|
import Widget from "components/widgets/widget";
|
||||||
@@ -20,6 +19,8 @@ import { ColorContext } from "utils/contexts/color";
|
|||||||
import { ThemeContext } from "utils/contexts/theme";
|
import { ThemeContext } from "utils/contexts/theme";
|
||||||
import { SettingsContext } from "utils/contexts/settings";
|
import { SettingsContext } from "utils/contexts/settings";
|
||||||
import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
|
import { bookmarksResponse, servicesResponse, widgetsResponse } from "utils/config/api-response";
|
||||||
|
import ErrorBoundary from "components/errorboundry";
|
||||||
|
import themes from "utils/styles/themes";
|
||||||
|
|
||||||
const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
|
const ThemeToggle = dynamic(() => import("components/toggles/theme"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -145,11 +146,20 @@ function Index({ initialSettings, fallback }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SWRConfig value={{ fallback, fetcher: (resource, init) => fetch(resource, init).then((res) => res.json()) }}>
|
<SWRConfig value={{ fallback, fetcher: (resource, init) => fetch(resource, init).then((res) => res.json()) }}>
|
||||||
<Home initialSettings={initialSettings} />
|
<ErrorBoundary>
|
||||||
|
<Home initialSettings={initialSettings} />
|
||||||
|
</ErrorBoundary>
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const headerStyles = {
|
||||||
|
boxed:
|
||||||
|
"m-4 mb-0 sm:m-8 sm:mb-0 rounded-md shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 dark:bg-white/5 p-3",
|
||||||
|
underlined: "m-4 mb-0 sm:m-8 sm:mb-1 border-b-2 pb-4 border-theme-800 dark:border-theme-200/50",
|
||||||
|
clean: "m-4 mb-0 sm:m-8 sm:mb-0",
|
||||||
|
};
|
||||||
|
|
||||||
function Home({ initialSettings }) {
|
function Home({ initialSettings }) {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const { theme, setTheme } = useContext(ThemeContext);
|
const { theme, setTheme } = useContext(ThemeContext);
|
||||||
@@ -183,23 +193,37 @@ function Home({ initialSettings }) {
|
|||||||
<Head>
|
<Head>
|
||||||
<title>{initialSettings.title || "Homepage"}</title>
|
<title>{initialSettings.title || "Homepage"}</title>
|
||||||
{initialSettings.base && <base href={initialSettings.base} />}
|
{initialSettings.base && <base href={initialSettings.base} />}
|
||||||
{initialSettings.favicon && <link rel="icon" href={initialSettings.favicon} />}
|
{initialSettings.favicon ? (
|
||||||
|
<link rel="icon" href={initialSettings.favicon} />
|
||||||
|
) : (
|
||||||
|
<link rel="shortcut icon" href="/homepage.ico" />
|
||||||
|
)}
|
||||||
|
<meta
|
||||||
|
name="msapplication-TileColor"
|
||||||
|
content={themes[initialSettings.color || "slate"][initialSettings.theme || "dark"]}
|
||||||
|
/>
|
||||||
|
<meta name="theme-color" content={themes[initialSettings.color || "slate"][initialSettings.theme || "dark"]} />
|
||||||
</Head>
|
</Head>
|
||||||
<div className="relative container m-auto flex flex-col justify-between z-10">
|
<div className="relative container m-auto flex flex-col justify-between z-10">
|
||||||
<div className="flex flex-row flex-wrap m-8 pb-4 mt-10 border-b-2 border-theme-800 dark:border-theme-200 justify-between">
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"flex flex-row flex-wrap justify-between",
|
||||||
|
headerStyles[initialSettings.headerStyle || "underlined"]
|
||||||
|
)}
|
||||||
|
>
|
||||||
{widgets && (
|
{widgets && (
|
||||||
<>
|
<>
|
||||||
{widgets
|
{widgets
|
||||||
.filter((widget) => !rightAlignedWidgets.includes(widget.type))
|
.filter((widget) => !rightAlignedWidgets.includes(widget.type))
|
||||||
.map((widget, i) => (
|
.map((widget, i) => (
|
||||||
<ErrorBoundary key={i}><Widget key={i} widget={widget} /></ErrorBoundary>
|
<Widget key={i} widget={widget} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="ml-4 flex flex-wrap basis-full grow sm:basis-auto justify-between md:justify-end mt-2 md:mt-0">
|
<div className="m-auto sm:ml-2 flex flex-wrap grow sm:basis-auto justify-between md:justify-end">
|
||||||
{widgets
|
{widgets
|
||||||
.filter((widget) => rightAlignedWidgets.includes(widget.type))
|
.filter((widget) => rightAlignedWidgets.includes(widget.type))
|
||||||
.map((widget, i) => (
|
.map((widget, i) => (
|
||||||
<ErrorBoundary key={i}><Widget key={i} widget={widget} /></ErrorBoundary>
|
<Widget key={i} widget={widget} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -207,17 +231,17 @@ function Home({ initialSettings }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{services && (
|
{services && (
|
||||||
<div className="flex flex-wrap p-8 items-start">
|
<div className="flex flex-wrap p-4 sm:p-8 sm:pt-4 items-start pb-2">
|
||||||
{services.map((group) => (
|
{services.map((group) => (
|
||||||
<ErrorBoundary key={group.name}><ServicesGroup key={group.name} services={group} layout={initialSettings.layout?.[group.name]} /></ErrorBoundary>
|
<ServicesGroup key={group.name} services={group} layout={initialSettings.layout?.[group.name]} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{bookmarks && (
|
{bookmarks && (
|
||||||
<div className="grow flex flex-wrap pt-0 p-8">
|
<div className="grow flex flex-wrap pt-0 p-4 sm:p-8">
|
||||||
{bookmarks.map((group) => (
|
{bookmarks.map((group) => (
|
||||||
<ErrorBoundary key={group.name}><BookmarksGroup key={group.name} group={group} /></ErrorBoundary>
|
<BookmarksGroup key={group.name} group={group} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
42
src/pages/site.webmanifest.jsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import checkAndCopyConfig, { getSettings } from "utils/config/config";
|
||||||
|
import themes from "utils/styles/themes";
|
||||||
|
|
||||||
|
export async function getServerSideProps({ res }) {
|
||||||
|
checkAndCopyConfig("settings.yaml");
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
const color = settings.color || "slate";
|
||||||
|
const theme = settings.theme || "dark";
|
||||||
|
|
||||||
|
const manifest = {
|
||||||
|
name: "Homepage",
|
||||||
|
short_name: "Homepage",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/android-chrome-192x192.png?v=2",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/android-chrome-512x512.png?v=2",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
theme_color: themes[color][theme],
|
||||||
|
background_color: themes[color][theme],
|
||||||
|
display: "standalone",
|
||||||
|
};
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "application/manifest+json");
|
||||||
|
res.write(JSON.stringify(manifest));
|
||||||
|
res.end();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Webmanifest() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -39,7 +39,6 @@ body {
|
|||||||
|
|
||||||
#page_container {
|
#page_container {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow: overlay;
|
|
||||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
--color-700: 40 40 40;
|
--color-700: 40 40 40;
|
||||||
--color-800: 255 255 255;
|
--color-800: 255 255 255;
|
||||||
--color-900: 255 255 255;
|
--color-900: 255 255 255;
|
||||||
|
|
||||||
|
--color-logo-start: 128 128 128 / 20%;
|
||||||
|
--color-logo-stop: 128 128 128 / 40%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-slate {
|
.theme-slate {
|
||||||
@@ -22,6 +25,9 @@
|
|||||||
--color-700: 51 65 85;
|
--color-700: 51 65 85;
|
||||||
--color-800: 30 41 59;
|
--color-800: 30 41 59;
|
||||||
--color-900: 15 23 42;
|
--color-900: 15 23 42;
|
||||||
|
|
||||||
|
--color-logo-start: 148 163 184;
|
||||||
|
--color-logo-stop: 51 65 85;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-gray {
|
.theme-gray {
|
||||||
@@ -35,6 +41,9 @@
|
|||||||
--color-700: 55 65 81;
|
--color-700: 55 65 81;
|
||||||
--color-800: 31 41 55;
|
--color-800: 31 41 55;
|
||||||
--color-900: 17 24 39;
|
--color-900: 17 24 39;
|
||||||
|
|
||||||
|
--color-logo-start: 156 163 175;
|
||||||
|
--color-logo-stop: 55 65 81;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-zinc {
|
.theme-zinc {
|
||||||
@@ -48,6 +57,9 @@
|
|||||||
--color-700: 63 63 70;
|
--color-700: 63 63 70;
|
||||||
--color-800: 39 39 42;
|
--color-800: 39 39 42;
|
||||||
--color-900: 24 24 27;
|
--color-900: 24 24 27;
|
||||||
|
|
||||||
|
--color-logo-start: 161 161 170;
|
||||||
|
--color-logo-stop: 63 63 70;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-neutral {
|
.theme-neutral {
|
||||||
@@ -61,6 +73,9 @@
|
|||||||
--color-700: 64 64 64;
|
--color-700: 64 64 64;
|
||||||
--color-800: 38 38 38;
|
--color-800: 38 38 38;
|
||||||
--color-900: 23 23 23;
|
--color-900: 23 23 23;
|
||||||
|
|
||||||
|
--color-logo-start: 163 163 163;
|
||||||
|
--color-logo-stop: 64 64 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-stone {
|
.theme-stone {
|
||||||
@@ -74,6 +89,9 @@
|
|||||||
--color-700: 68 64 60;
|
--color-700: 68 64 60;
|
||||||
--color-800: 41 37 36;
|
--color-800: 41 37 36;
|
||||||
--color-900: 28 25 23;
|
--color-900: 28 25 23;
|
||||||
|
|
||||||
|
--color-logo-start: 168 162 158;
|
||||||
|
--color-logo-stop: 68 64 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-red {
|
.theme-red {
|
||||||
@@ -87,6 +105,9 @@
|
|||||||
--color-700: 185 28 28;
|
--color-700: 185 28 28;
|
||||||
--color-800: 153 27 27;
|
--color-800: 153 27 27;
|
||||||
--color-900: 127 29 29;
|
--color-900: 127 29 29;
|
||||||
|
|
||||||
|
--color-logo-start: 248 113 113;
|
||||||
|
--color-logo-stop: 185 28 28;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-orange {
|
.theme-orange {
|
||||||
@@ -100,6 +121,9 @@
|
|||||||
--color-700: 194 65 12;
|
--color-700: 194 65 12;
|
||||||
--color-800: 154 52 18;
|
--color-800: 154 52 18;
|
||||||
--color-900: 124 45 18;
|
--color-900: 124 45 18;
|
||||||
|
|
||||||
|
--color-logo-start: 251 146 60;
|
||||||
|
--color-logo-stop: 194 65 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-amber {
|
.theme-amber {
|
||||||
@@ -113,6 +137,9 @@
|
|||||||
--color-700: 180 83 9;
|
--color-700: 180 83 9;
|
||||||
--color-800: 146 64 14;
|
--color-800: 146 64 14;
|
||||||
--color-900: 120 53 15;
|
--color-900: 120 53 15;
|
||||||
|
|
||||||
|
--color-logo-start: 251 191 36;
|
||||||
|
--color-logo-stop: 180 83 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-yellow {
|
.theme-yellow {
|
||||||
@@ -126,6 +153,9 @@
|
|||||||
--color-700: 161 98 7;
|
--color-700: 161 98 7;
|
||||||
--color-800: 133 77 14;
|
--color-800: 133 77 14;
|
||||||
--color-900: 113 63 18;
|
--color-900: 113 63 18;
|
||||||
|
|
||||||
|
--color-logo-start: 250 204 21;
|
||||||
|
--color-logo-stop: 161 98 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-lime {
|
.theme-lime {
|
||||||
@@ -139,6 +169,9 @@
|
|||||||
--color-700: 77 124 15;
|
--color-700: 77 124 15;
|
||||||
--color-800: 63 98 18;
|
--color-800: 63 98 18;
|
||||||
--color-900: 54 83 20;
|
--color-900: 54 83 20;
|
||||||
|
|
||||||
|
--color-logo-start: 163 230 53;
|
||||||
|
--color-logo-stop: 77 124 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-green {
|
.theme-green {
|
||||||
@@ -152,6 +185,9 @@
|
|||||||
--color-700: 21 128 61;
|
--color-700: 21 128 61;
|
||||||
--color-800: 22 101 52;
|
--color-800: 22 101 52;
|
||||||
--color-900: 20 83 45;
|
--color-900: 20 83 45;
|
||||||
|
|
||||||
|
--color-logo-start: 74 222 128;
|
||||||
|
--color-logo-stop: 21 128 61;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-emerald {
|
.theme-emerald {
|
||||||
@@ -165,6 +201,9 @@
|
|||||||
--color-700: 4 120 87;
|
--color-700: 4 120 87;
|
||||||
--color-800: 6 95 70;
|
--color-800: 6 95 70;
|
||||||
--color-900: 6 78 59;
|
--color-900: 6 78 59;
|
||||||
|
|
||||||
|
--color-logo-start: 52 211 153;
|
||||||
|
--color-logo-stop: 4 120 87;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-teal {
|
.theme-teal {
|
||||||
@@ -178,6 +217,9 @@
|
|||||||
--color-700: 15 118 110;
|
--color-700: 15 118 110;
|
||||||
--color-800: 17 94 89;
|
--color-800: 17 94 89;
|
||||||
--color-900: 19 78 74;
|
--color-900: 19 78 74;
|
||||||
|
|
||||||
|
--color-logo-start: 45 212 191;
|
||||||
|
--color-logo-stop: 15 118 110;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-cyan {
|
.theme-cyan {
|
||||||
@@ -191,6 +233,9 @@
|
|||||||
--color-700: 14 116 144;
|
--color-700: 14 116 144;
|
||||||
--color-800: 21 94 117;
|
--color-800: 21 94 117;
|
||||||
--color-900: 22 78 99;
|
--color-900: 22 78 99;
|
||||||
|
|
||||||
|
--color-logo-start: 34 211 238;
|
||||||
|
--color-logo-stop: 14 116 144;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-sky {
|
.theme-sky {
|
||||||
@@ -204,6 +249,9 @@
|
|||||||
--color-700: 3 105 161;
|
--color-700: 3 105 161;
|
||||||
--color-800: 7 89 133;
|
--color-800: 7 89 133;
|
||||||
--color-900: 12 74 110;
|
--color-900: 12 74 110;
|
||||||
|
|
||||||
|
--color-logo-start: 56 189 248;
|
||||||
|
--color-logo-stop: 3 105 161;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-blue {
|
.theme-blue {
|
||||||
@@ -217,6 +265,9 @@
|
|||||||
--color-700: 29 78 216;
|
--color-700: 29 78 216;
|
||||||
--color-800: 30 64 175;
|
--color-800: 30 64 175;
|
||||||
--color-900: 30 58 138;
|
--color-900: 30 58 138;
|
||||||
|
|
||||||
|
--color-logo-start: 96 165 250;
|
||||||
|
--color-logo-stop: 29 78 216;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-indigo {
|
.theme-indigo {
|
||||||
@@ -230,6 +281,9 @@
|
|||||||
--color-700: 67 56 202;
|
--color-700: 67 56 202;
|
||||||
--color-800: 55 48 163;
|
--color-800: 55 48 163;
|
||||||
--color-900: 49 46 129;
|
--color-900: 49 46 129;
|
||||||
|
|
||||||
|
--color-logo-start: 129 140 248;
|
||||||
|
--color-logo-stop: 67 56 202;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-violet {
|
.theme-violet {
|
||||||
@@ -243,6 +297,9 @@
|
|||||||
--color-700: 109 40 217;
|
--color-700: 109 40 217;
|
||||||
--color-800: 91 33 182;
|
--color-800: 91 33 182;
|
||||||
--color-900: 76 29 149;
|
--color-900: 76 29 149;
|
||||||
|
|
||||||
|
--color-logo-start: 167 139 250;
|
||||||
|
--color-logo-stop: 109 40 217;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-purple {
|
.theme-purple {
|
||||||
@@ -256,6 +313,9 @@
|
|||||||
--color-700: 126 34 206;
|
--color-700: 126 34 206;
|
||||||
--color-800: 107 33 168;
|
--color-800: 107 33 168;
|
||||||
--color-900: 88 28 135;
|
--color-900: 88 28 135;
|
||||||
|
|
||||||
|
--color-logo-start: 192 132 252;
|
||||||
|
--color-logo-stop: 126 34 206;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-fuchsia {
|
.theme-fuchsia {
|
||||||
@@ -269,6 +329,9 @@
|
|||||||
--color-700: 162 28 175;
|
--color-700: 162 28 175;
|
||||||
--color-800: 134 25 143;
|
--color-800: 134 25 143;
|
||||||
--color-900: 112 26 117;
|
--color-900: 112 26 117;
|
||||||
|
|
||||||
|
--color-logo-start: 232 121 249;
|
||||||
|
--color-logo-stop: 162 28 175;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-pink {
|
.theme-pink {
|
||||||
@@ -282,6 +345,9 @@
|
|||||||
--color-700: 190 24 93;
|
--color-700: 190 24 93;
|
||||||
--color-800: 157 23 77;
|
--color-800: 157 23 77;
|
||||||
--color-900: 131 24 67;
|
--color-900: 131 24 67;
|
||||||
|
|
||||||
|
--color-logo-start: 244 114 182;
|
||||||
|
--color-logo-stop: 190 24 93;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-rose {
|
.theme-rose {
|
||||||
@@ -295,4 +361,7 @@
|
|||||||
--color-700: 190 18 60;
|
--color-700: 190 18 60;
|
||||||
--color-800: 159 18 57;
|
--color-800: 159 18 57;
|
||||||
--color-900: 136 19 55;
|
--color-900: 136 19 55;
|
||||||
|
|
||||||
|
--color-logo-start: 251 113 133;
|
||||||
|
--color-logo-stop: 190 18 60;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export default async function credentialedProxyHandler(req, res) {
|
|||||||
headers["X-gotify-Key"] = `${widget.key}`;
|
headers["X-gotify-Key"] = `${widget.key}`;
|
||||||
} else if (widget.type === "authentik") {
|
} else if (widget.type === "authentik") {
|
||||||
headers.Authorization = `Bearer ${widget.key}`;
|
headers.Authorization = `Bearer ${widget.key}`;
|
||||||
|
} else if (widget.type === "proxmox") {
|
||||||
|
headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
|
||||||
} else {
|
} else {
|
||||||
headers["X-API-Key"] = `${widget.key}`;
|
headers["X-API-Key"] = `${widget.key}`;
|
||||||
}
|
}
|
||||||
|
|||||||
142
src/utils/styles/themes.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
const themes = {
|
||||||
|
white: {
|
||||||
|
light: "#ffffff",
|
||||||
|
iconStart: "#ffffff",
|
||||||
|
iconEnd: "#282828",
|
||||||
|
dark: "#ffffff",
|
||||||
|
},
|
||||||
|
slate: {
|
||||||
|
light: "#f8fafc",
|
||||||
|
iconStart: "#94a3b8",
|
||||||
|
iconEnd: "#334155",
|
||||||
|
dark: "#1e293b",
|
||||||
|
},
|
||||||
|
gray: {
|
||||||
|
light: "#f9fafb",
|
||||||
|
iconStart: "#9ca3af",
|
||||||
|
iconEnd: "#374151",
|
||||||
|
dark: "#1f2937",
|
||||||
|
},
|
||||||
|
zinc: {
|
||||||
|
light: "#fafafa",
|
||||||
|
iconStart: "#a1a1aa",
|
||||||
|
iconEnd: "#3f3f46",
|
||||||
|
dark: "#27272a",
|
||||||
|
},
|
||||||
|
neutral: {
|
||||||
|
light: "#fafafa",
|
||||||
|
iconStart: "#a3a3a3",
|
||||||
|
iconEnd: "#404040",
|
||||||
|
dark: "#262626",
|
||||||
|
},
|
||||||
|
stone: {
|
||||||
|
light: "#fafaf9",
|
||||||
|
iconStart: "#a8a29e",
|
||||||
|
iconEnd: "#44403c",
|
||||||
|
dark: "#292524",
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
light: "#fef2f2",
|
||||||
|
iconStart: "#f87171",
|
||||||
|
iconEnd: "#b91c1c",
|
||||||
|
dark: "#991b1b",
|
||||||
|
},
|
||||||
|
orange: {
|
||||||
|
light: "#fff7ed",
|
||||||
|
iconStart: "#fb923c",
|
||||||
|
iconEnd: "#c2410c",
|
||||||
|
dark: "#9a3412",
|
||||||
|
},
|
||||||
|
amber: {
|
||||||
|
light: "#fffbeb",
|
||||||
|
iconStart: "#fbbf24",
|
||||||
|
iconEnd: "#b45309",
|
||||||
|
dark: "#92400e",
|
||||||
|
},
|
||||||
|
yellow: {
|
||||||
|
light: "#fefce8",
|
||||||
|
iconStart: "#facc15",
|
||||||
|
iconEnd: "#a16207",
|
||||||
|
dark: "#854d0e",
|
||||||
|
},
|
||||||
|
lime: {
|
||||||
|
light: "#f7fee7",
|
||||||
|
iconStart: "#a3e635",
|
||||||
|
iconEnd: "#4d7c0f",
|
||||||
|
dark: "#3f6212",
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
light: "#f0fdf4",
|
||||||
|
iconStart: "#4ade80",
|
||||||
|
iconEnd: "#15803d",
|
||||||
|
dark: "#166534",
|
||||||
|
},
|
||||||
|
emerald: {
|
||||||
|
light: "#ecfdf5",
|
||||||
|
iconStart: "#34d399",
|
||||||
|
iconEnd: "#047857",
|
||||||
|
dark: "#065f46",
|
||||||
|
},
|
||||||
|
teal: {
|
||||||
|
light: "#f0fdfa",
|
||||||
|
iconStart: "#2dd4bf",
|
||||||
|
iconEnd: "#0f766e",
|
||||||
|
dark: "#115e59",
|
||||||
|
},
|
||||||
|
cyan: {
|
||||||
|
light: "#ecfeff",
|
||||||
|
iconStart: "#22d3ee",
|
||||||
|
iconEnd: "#0e7490",
|
||||||
|
dark: "#155e75",
|
||||||
|
},
|
||||||
|
sky: {
|
||||||
|
light: "#f0f9ff",
|
||||||
|
iconStart: "#38bdf8",
|
||||||
|
iconEnd: "#0369a1",
|
||||||
|
dark: "#075985",
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
light: "#eff6ff",
|
||||||
|
iconStart: "#60a5fa",
|
||||||
|
iconEnd: "#1d4ed8",
|
||||||
|
dark: "#1e40af",
|
||||||
|
},
|
||||||
|
indigo: {
|
||||||
|
light: "#eef2ff",
|
||||||
|
iconStart: "#818cf8",
|
||||||
|
iconEnd: "#4338ca",
|
||||||
|
dark: "#3730a3",
|
||||||
|
},
|
||||||
|
violet: {
|
||||||
|
light: "#f5f3ff",
|
||||||
|
iconStart: "#a78bfa",
|
||||||
|
iconEnd: "#6d28d9",
|
||||||
|
dark: "#5b21b6",
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
light: "#faf5ff",
|
||||||
|
iconStart: "#c084fc",
|
||||||
|
iconEnd: "#7e22ce",
|
||||||
|
dark: "#6b21a8",
|
||||||
|
},
|
||||||
|
fuchsia: {
|
||||||
|
light: "#fdf4ff",
|
||||||
|
iconStart: "#e879f9",
|
||||||
|
iconEnd: "#a21caf",
|
||||||
|
dark: "#86198f",
|
||||||
|
},
|
||||||
|
pink: {
|
||||||
|
light: "#fdf2f8",
|
||||||
|
iconStart: "#f472b6",
|
||||||
|
iconEnd: "#be185d",
|
||||||
|
dark: "#9d174d",
|
||||||
|
},
|
||||||
|
rose: {
|
||||||
|
light: "#fff1f2",
|
||||||
|
iconStart: "#fb7185",
|
||||||
|
iconEnd: "#be123c",
|
||||||
|
dark: "#9f1239",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default themes;
|
||||||
@@ -20,6 +20,7 @@ const components = {
|
|||||||
pihole: dynamic(() => import("./pihole/component")),
|
pihole: dynamic(() => import("./pihole/component")),
|
||||||
portainer: dynamic(() => import("./portainer/component")),
|
portainer: dynamic(() => import("./portainer/component")),
|
||||||
prowlarr: dynamic(() => import("./prowlarr/component")),
|
prowlarr: dynamic(() => import("./prowlarr/component")),
|
||||||
|
proxmox: dynamic(() => import("./proxmox/component")),
|
||||||
qbittorrent: dynamic(() => import("./qbittorrent/component")),
|
qbittorrent: dynamic(() => import("./qbittorrent/component")),
|
||||||
radarr: dynamic(() => import("./radarr/component")),
|
radarr: dynamic(() => import("./radarr/component")),
|
||||||
readarr: dynamic(() => import("./readarr/component")),
|
readarr: dynamic(() => import("./readarr/component")),
|
||||||
|
|||||||
53
src/widgets/proxmox/component.jsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
function calcRunning(total, current) {
|
||||||
|
return current.status === "running" ? total + 1 : total;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Component({ service }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { widget } = service;
|
||||||
|
|
||||||
|
const { data: clusterData, error: clusterError } = useWidgetAPI(widget, "cluster/resources");
|
||||||
|
|
||||||
|
if (clusterError) {
|
||||||
|
return <Container error={t("widget.api_error")} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clusterData || !clusterData.data) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="proxmox.vms" />
|
||||||
|
<Block label="proxmox.lxc" />
|
||||||
|
<Block label="proxmox.cpu" />
|
||||||
|
<Block label="proxmox.ram" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = clusterData ;
|
||||||
|
const vms = data.filter(item => item.type === "qemu") || [];
|
||||||
|
const lxc = data.filter(item => item.type === "lxc") || [];
|
||||||
|
const nodes = data.filter(item => item.type === "node") || [];
|
||||||
|
|
||||||
|
const runningVMs = vms.reduce(calcRunning, 0);
|
||||||
|
const runningLXC = lxc.reduce(calcRunning, 0);
|
||||||
|
|
||||||
|
// TODO: support more than one node
|
||||||
|
// TODO: better handling of cluster with zero nodes
|
||||||
|
const node = nodes.length > 0 ? nodes[0] : { cpu: 0.0, mem: 0, maxmem: 0 };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="proxmox.vms" value={`${runningVMs} / ${vms.length}`} />
|
||||||
|
<Block label="proxmox.lxc" value={`${runningLXC} / ${lxc.length}`} />
|
||||||
|
<Block label="proxmox.cpu" value={t("common.percent", { value: (node.cpu * 100) })} />
|
||||||
|
<Block label="proxmox.mem" value={t("common.percent", { value: ((node.mem / node.maxmem) * 100) })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/widgets/proxmox/widget.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}/api2/json/{endpoint}",
|
||||||
|
proxyHandler: credentialedProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
"cluster/resources": {
|
||||||
|
endpoint: "cluster/resources",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
||||||
@@ -15,6 +15,7 @@ import overseerr from "./overseerr/widget";
|
|||||||
import pihole from "./pihole/widget";
|
import pihole from "./pihole/widget";
|
||||||
import portainer from "./portainer/widget";
|
import portainer from "./portainer/widget";
|
||||||
import prowlarr from "./prowlarr/widget";
|
import prowlarr from "./prowlarr/widget";
|
||||||
|
import proxmox from "./proxmox/widget";
|
||||||
import qbittorrent from "./qbittorrent/widget";
|
import qbittorrent from "./qbittorrent/widget";
|
||||||
import radarr from "./radarr/widget";
|
import radarr from "./radarr/widget";
|
||||||
import readarr from "./readarr/widget";
|
import readarr from "./readarr/widget";
|
||||||
@@ -46,6 +47,7 @@ const widgets = {
|
|||||||
pihole,
|
pihole,
|
||||||
portainer,
|
portainer,
|
||||||
prowlarr,
|
prowlarr,
|
||||||
|
proxmox,
|
||||||
qbittorrent,
|
qbittorrent,
|
||||||
radarr,
|
radarr,
|
||||||
readarr,
|
readarr,
|
||||||
|
|||||||