Merge branch 'benphelps:main' into main
This commit is contained in:
commit
5644cf89ab
@ -32,7 +32,8 @@
|
|||||||
"shvl": "^3.0.0",
|
"shvl": "^3.0.0",
|
||||||
"swr": "^1.3.0",
|
"swr": "^1.3.0",
|
||||||
"tough-cookie": "^4.1.2",
|
"tough-cookie": "^4.1.2",
|
||||||
"winston": "^3.8.2"
|
"winston": "^3.8.2",
|
||||||
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
|||||||
@ -40,6 +40,7 @@ specifiers:
|
|||||||
tough-cookie: ^4.1.2
|
tough-cookie: ^4.1.2
|
||||||
typescript: ^4.8.3
|
typescript: ^4.8.3
|
||||||
winston: ^3.8.2
|
winston: ^3.8.2
|
||||||
|
xml-js: ^1.6.11
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@headlessui/react': 1.7.2_biqbaboplfbrettd7655fr4n2y
|
'@headlessui/react': 1.7.2_biqbaboplfbrettd7655fr4n2y
|
||||||
@ -65,6 +66,7 @@ dependencies:
|
|||||||
swr: 1.3.0_react@18.2.0
|
swr: 1.3.0_react@18.2.0
|
||||||
tough-cookie: 4.1.2
|
tough-cookie: 4.1.2
|
||||||
winston: 3.8.2
|
winston: 3.8.2
|
||||||
|
xml-js: 1.6.11
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
|
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
|
||||||
@ -2554,6 +2556,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/sax/1.2.4:
|
||||||
|
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/scheduler/0.23.0:
|
/scheduler/0.23.0:
|
||||||
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3000,6 +3006,13 @@ packages:
|
|||||||
/wrappy/1.0.2:
|
/wrappy/1.0.2:
|
||||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
|
/xml-js/1.6.11:
|
||||||
|
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
sax: 1.2.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/xtend/4.0.2:
|
/xtend/4.0.2:
|
||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|||||||
205
public/locales/bg/common.json
Normal file
205
public/locales/bg/common.json
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
{
|
||||||
|
"sabnzbd": {
|
||||||
|
"queue": "Опашка",
|
||||||
|
"timeleft": "Оставащо Време",
|
||||||
|
"rate": "Rate"
|
||||||
|
},
|
||||||
|
"rutorrent": {
|
||||||
|
"active": "Акитивен",
|
||||||
|
"upload": "Споделяне",
|
||||||
|
"download": "Сваляне"
|
||||||
|
},
|
||||||
|
"widget": {
|
||||||
|
"missing_type": "Липсваща приставка: {{type}}",
|
||||||
|
"api_error": "API Грешка",
|
||||||
|
"status": "Статус"
|
||||||
|
},
|
||||||
|
"weather": {
|
||||||
|
"current": "Текущо местоположение",
|
||||||
|
"allow": "Разреши",
|
||||||
|
"updating": "Обновяване",
|
||||||
|
"wait": "Моля изчакайте"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Търсене…"
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"total": "Общо",
|
||||||
|
"free": "Свободни",
|
||||||
|
"used": "Заети",
|
||||||
|
"load": "Натоварване"
|
||||||
|
},
|
||||||
|
"unifi": {
|
||||||
|
"users": "Потребители",
|
||||||
|
"uptime": "Активен от",
|
||||||
|
"days": "Дни",
|
||||||
|
"wan": "WAN",
|
||||||
|
"lan_users": "LAN Потребители",
|
||||||
|
"wlan_users": "WLAN Потребители",
|
||||||
|
"up": "UP",
|
||||||
|
"down": "DOWN",
|
||||||
|
"wait": "Моля изчакайте"
|
||||||
|
},
|
||||||
|
"docker": {
|
||||||
|
"offline": "Изключен",
|
||||||
|
"rx": "RX",
|
||||||
|
"tx": "TX",
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU"
|
||||||
|
},
|
||||||
|
"emby": {
|
||||||
|
"playing": "Възпроизвежда",
|
||||||
|
"transcoding": "Конвертира",
|
||||||
|
"bitrate": "Bitrate",
|
||||||
|
"no_active": "Няма активни потоци"
|
||||||
|
},
|
||||||
|
"tautulli": {
|
||||||
|
"playing": "Възпроизвежда",
|
||||||
|
"transcoding": "Конвертира",
|
||||||
|
"bitrate": "Честота",
|
||||||
|
"no_active": "Няма активни потоци"
|
||||||
|
},
|
||||||
|
"nzbget": {
|
||||||
|
"rate": "Rate",
|
||||||
|
"remaining": "Остава",
|
||||||
|
"downloaded": "Изтеглени"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Активни Потоци",
|
||||||
|
"movies": "Филми",
|
||||||
|
"tv": "Сериали"
|
||||||
|
},
|
||||||
|
"transmission": {
|
||||||
|
"download": "Сваляне",
|
||||||
|
"upload": "Upload",
|
||||||
|
"leech": "Leech",
|
||||||
|
"seed": "Seed"
|
||||||
|
},
|
||||||
|
"qbittorrent": {
|
||||||
|
"download": "Download",
|
||||||
|
"upload": "Upload",
|
||||||
|
"leech": "Leech",
|
||||||
|
"seed": "Seed"
|
||||||
|
},
|
||||||
|
"sonarr": {
|
||||||
|
"wanted": "Wanted",
|
||||||
|
"queued": "Queued",
|
||||||
|
"series": "Series"
|
||||||
|
},
|
||||||
|
"radarr": {
|
||||||
|
"wanted": "Wanted",
|
||||||
|
"queued": "Queued",
|
||||||
|
"movies": "Movies"
|
||||||
|
},
|
||||||
|
"lidarr": {
|
||||||
|
"wanted": "Wanted",
|
||||||
|
"queued": "Queued",
|
||||||
|
"albums": "Albums"
|
||||||
|
},
|
||||||
|
"readarr": {
|
||||||
|
"wanted": "Wanted",
|
||||||
|
"queued": "Queued",
|
||||||
|
"books": "Books"
|
||||||
|
},
|
||||||
|
"bazarr": {
|
||||||
|
"missingEpisodes": "Missing Episodes",
|
||||||
|
"missingMovies": "Missing Movies"
|
||||||
|
},
|
||||||
|
"ombi": {
|
||||||
|
"pending": "Pending",
|
||||||
|
"approved": "Approved",
|
||||||
|
"available": "Available"
|
||||||
|
},
|
||||||
|
"jellyseerr": {
|
||||||
|
"pending": "Pending",
|
||||||
|
"approved": "Approved",
|
||||||
|
"available": "Available"
|
||||||
|
},
|
||||||
|
"overseerr": {
|
||||||
|
"pending": "Pending",
|
||||||
|
"approved": "Approved",
|
||||||
|
"available": "Available"
|
||||||
|
},
|
||||||
|
"pihole": {
|
||||||
|
"queries": "Queries",
|
||||||
|
"blocked": "Blocked",
|
||||||
|
"gravity": "Gravity"
|
||||||
|
},
|
||||||
|
"adguard": {
|
||||||
|
"queries": "Queries",
|
||||||
|
"blocked": "Blocked",
|
||||||
|
"filtered": "Filtered",
|
||||||
|
"latency": "Latency"
|
||||||
|
},
|
||||||
|
"speedtest": {
|
||||||
|
"upload": "Upload",
|
||||||
|
"download": "Download",
|
||||||
|
"ping": "Ping"
|
||||||
|
},
|
||||||
|
"portainer": {
|
||||||
|
"running": "Running",
|
||||||
|
"stopped": "Stopped",
|
||||||
|
"total": "Total"
|
||||||
|
},
|
||||||
|
"traefik": {
|
||||||
|
"routers": "Routers",
|
||||||
|
"services": "Services",
|
||||||
|
"middleware": "Middleware"
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"total": "Total"
|
||||||
|
},
|
||||||
|
"coinmarketcap": {
|
||||||
|
"configure": "Configure one or more crypto currencies to track",
|
||||||
|
"1hour": "1 Hour",
|
||||||
|
"1day": "1 Day",
|
||||||
|
"7days": "7 Days",
|
||||||
|
"30days": "30 Days"
|
||||||
|
},
|
||||||
|
"gotify": {
|
||||||
|
"apps": "Applications",
|
||||||
|
"clients": "Clients",
|
||||||
|
"messages": "Messages"
|
||||||
|
},
|
||||||
|
"prowlarr": {
|
||||||
|
"enableIndexers": "Indexers",
|
||||||
|
"numberOfGrabs": "Grabs",
|
||||||
|
"numberOfQueries": "Queries",
|
||||||
|
"numberOfFailGrabs": "Fail Grabs",
|
||||||
|
"numberOfFailQueries": "Fail Queries"
|
||||||
|
},
|
||||||
|
"authentik": {
|
||||||
|
"loginsLast24H": "Logins (24h)",
|
||||||
|
"users": "Users",
|
||||||
|
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||||
|
},
|
||||||
|
"jackett": {
|
||||||
|
"configured": "Configured",
|
||||||
|
"errored": "Errored"
|
||||||
|
},
|
||||||
|
"strelaysrv": {
|
||||||
|
"numActiveSessions": "Sessions",
|
||||||
|
"numConnections": "Connections",
|
||||||
|
"dataRelayed": "Relayed",
|
||||||
|
"transferRate": "Rate"
|
||||||
|
},
|
||||||
|
"mastodon": {
|
||||||
|
"user_count": "Users",
|
||||||
|
"status_count": "Posts",
|
||||||
|
"domain_count": "Domains"
|
||||||
|
},
|
||||||
|
"proxmox": {
|
||||||
|
"mem": "MEM",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"lxc": "LXC",
|
||||||
|
"vms": "VMs"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,11 @@
|
|||||||
"remaining": "Remaining",
|
"remaining": "Remaining",
|
||||||
"downloaded": "Downloaded"
|
"downloaded": "Downloaded"
|
||||||
},
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
"sabnzbd": {
|
"sabnzbd": {
|
||||||
"rate": "Rate",
|
"rate": "Rate",
|
||||||
"queue": "Queue",
|
"queue": "Queue",
|
||||||
@ -202,5 +207,10 @@
|
|||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"lxc": "LXC",
|
"lxc": "LXC",
|
||||||
"vms": "VMs"
|
"vms": "VMs"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"wlan_users": "WLAN Users",
|
"wlan_users": "WLAN Users",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"wan": "WAN",
|
"wan": "WAN",
|
||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN"
|
"down": "DOWN"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"wait": "Please wait",
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -182,14 +182,24 @@
|
|||||||
"vms": "VMs"
|
"vms": "VMs"
|
||||||
},
|
},
|
||||||
"unifi": {
|
"unifi": {
|
||||||
"users": "Users",
|
"users": "Utilisateurs",
|
||||||
"uptime": "System Uptime",
|
"uptime": "Disponibilité du système",
|
||||||
"days": "Days",
|
"days": "Jours",
|
||||||
"wan": "WAN",
|
"wan": "WAN",
|
||||||
"lan_users": "LAN Users",
|
"lan_users": "Utilisateurs LAN",
|
||||||
"wlan_users": "WLAN Users",
|
"wlan_users": "Utilisateurs WLAN",
|
||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
|
"wait": "Merci de patienter"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Flux actif",
|
||||||
|
"movies": "Films",
|
||||||
|
"tv": "Séries TV"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"wlan_users": "WLAN Users",
|
"wlan_users": "WLAN Users",
|
||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN"
|
"down": "DOWN"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -202,5 +202,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,9 +31,9 @@
|
|||||||
"placeholder": "వెతకండి…"
|
"placeholder": "వెతకండి…"
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
"cpu": "సీ పి యూ",
|
"cpu": "సీపియూ",
|
||||||
"total": "మొత్తం",
|
"total": "మొత్తం",
|
||||||
"free": "ఉచిత",
|
"free": "మిగిలింది",
|
||||||
"used": "ఉపయోగించబడిన",
|
"used": "ఉపయోగించబడిన",
|
||||||
"load": "లోడ్"
|
"load": "లోడ్"
|
||||||
},
|
},
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"rx": "RX",
|
"rx": "RX",
|
||||||
"tx": "TX",
|
"tx": "TX",
|
||||||
"mem": "MEM",
|
"mem": "MEM",
|
||||||
"cpu": "CPU",
|
"cpu": "సీపియూ",
|
||||||
"offline": "ఆఫ్లైన్"
|
"offline": "ఆఫ్లైన్"
|
||||||
},
|
},
|
||||||
"emby": {
|
"emby": {
|
||||||
@ -128,8 +128,8 @@
|
|||||||
"ping": "పింగ్"
|
"ping": "పింగ్"
|
||||||
},
|
},
|
||||||
"portainer": {
|
"portainer": {
|
||||||
"running": "నడుస్తోంది",
|
"running": "నడుస్తున్నవి",
|
||||||
"stopped": "ఆగిపోయింది",
|
"stopped": "ఆగిపోయినవి",
|
||||||
"total": "మొత్తం"
|
"total": "మొత్తం"
|
||||||
},
|
},
|
||||||
"traefik": {
|
"traefik": {
|
||||||
@ -139,7 +139,7 @@
|
|||||||
},
|
},
|
||||||
"npm": {
|
"npm": {
|
||||||
"enabled": "ప్రారంభించబడింది",
|
"enabled": "ప్రారంభించబడింది",
|
||||||
"disabled": "Disabled",
|
"disabled": "డిసేబ్లెడ్",
|
||||||
"total": "మొత్తం"
|
"total": "మొత్తం"
|
||||||
},
|
},
|
||||||
"coinmarketcap": {
|
"coinmarketcap": {
|
||||||
@ -177,19 +177,29 @@
|
|||||||
},
|
},
|
||||||
"proxmox": {
|
"proxmox": {
|
||||||
"mem": "MEM",
|
"mem": "MEM",
|
||||||
"cpu": "CPU",
|
"cpu": "సీపియూ",
|
||||||
"lxc": "LXC",
|
"lxc": "LXC",
|
||||||
"vms": "VMs"
|
"vms": "విఎంలు"
|
||||||
},
|
},
|
||||||
"unifi": {
|
"unifi": {
|
||||||
"users": "Users",
|
"users": "వినియోగదారులు",
|
||||||
"uptime": "System Uptime",
|
"uptime": "సిస్టమ్ సమయము",
|
||||||
"days": "Days",
|
"days": "రోజులు",
|
||||||
"wan": "WAN",
|
"wan": "WAN",
|
||||||
"lan_users": "LAN Users",
|
"lan_users": "LAN వినియోగదారులు",
|
||||||
"wlan_users": "WLAN Users",
|
"wlan_users": "WLAN వినియోగదారులు",
|
||||||
"up": "UP",
|
"up": "అప్",
|
||||||
"down": "DOWN",
|
"down": "డౌన్",
|
||||||
|
"wait": "దయచేసి వేచి ఉండండి"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "యాక్టివ్ స్ట్రీమ్లు",
|
||||||
|
"movies": "సినిమాలు",
|
||||||
|
"tv": "దూరదర్శిని కార్యక్రమాలు"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,7 +92,7 @@
|
|||||||
"current": "当前定位",
|
"current": "当前定位",
|
||||||
"allow": "点击并允许",
|
"allow": "点击并允许",
|
||||||
"updating": "更新中",
|
"updating": "更新中",
|
||||||
"wait": "请稍后"
|
"wait": "请稍候"
|
||||||
},
|
},
|
||||||
"overseerr": {
|
"overseerr": {
|
||||||
"pending": "待办",
|
"pending": "待办",
|
||||||
@ -190,6 +190,16 @@
|
|||||||
"wlan_users": "WLAN Users",
|
"wlan_users": "WLAN Users",
|
||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
|
"wait": "请稍候"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -191,5 +191,15 @@
|
|||||||
"up": "UP",
|
"up": "UP",
|
||||||
"down": "DOWN",
|
"down": "DOWN",
|
||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
|
},
|
||||||
|
"plex": {
|
||||||
|
"streams": "Active Streams",
|
||||||
|
"movies": "Movies",
|
||||||
|
"tv": "TV Shows"
|
||||||
|
},
|
||||||
|
"glances": {
|
||||||
|
"cpu": "CPU",
|
||||||
|
"mem": "MEM",
|
||||||
|
"wait": "Please wait"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,19 +9,37 @@ import Docker from "widgets/docker/component";
|
|||||||
import { SettingsContext } from "utils/contexts/settings";
|
import { SettingsContext } from "utils/contexts/settings";
|
||||||
|
|
||||||
function resolveIcon(icon) {
|
function resolveIcon(icon) {
|
||||||
if (icon.startsWith("http")) {
|
// direct or relative URLs
|
||||||
return icon;
|
if (icon.startsWith("http") || icon.startsWith("/")) {
|
||||||
|
return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icon.startsWith("/")) {
|
// mdi- prefixed, material design icons
|
||||||
return icon;
|
if (icon.startsWith("mdi-")) {
|
||||||
|
const iconName = icon.replace("mdi-", "").replace(".svg", "");
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
|
||||||
|
mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
|
||||||
|
WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icon.endsWith(".png")) {
|
// fallback to dashboard-icons
|
||||||
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
|
const iconName = icon.replace(".png", "");
|
||||||
}
|
return (
|
||||||
|
<Image
|
||||||
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`;
|
src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
alt="logo"
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Item({ service }) {
|
export default function Item({ service }) {
|
||||||
@ -57,12 +75,10 @@ export default function Item({ service }) {
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="flex-shrink-0 flex items-center justify-center w-12 "
|
className="flex-shrink-0 flex items-center justify-center w-12 "
|
||||||
>
|
>
|
||||||
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" />
|
{resolveIcon(service.icon)}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-shrink-0 flex items-center justify-center w-12 ">
|
<div className="flex-shrink-0 flex items-center justify-center w-12 ">{resolveIcon(service.icon)}</div>
|
||||||
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" />
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{hasLink ? (
|
{hasLink ? (
|
||||||
|
|||||||
110
src/components/widgets/glances/glances.jsx
Normal file
110
src/components/widgets/glances/glances.jsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
import { BiError } from "react-icons/bi";
|
||||||
|
import { FaMemory } from "react-icons/fa";
|
||||||
|
import { FiCpu } from "react-icons/fi";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
|
import UsageBar from "../resources/usage-bar";
|
||||||
|
|
||||||
|
export default function Widget({ options }) {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
|
const { data, error } = useSWR(
|
||||||
|
`/api/widgets/glances?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`, {
|
||||||
|
refreshInterval: 1500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error || data?.error) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
||||||
|
<div className="flex flex-row items-center justify-end">
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
|
||||||
|
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5 text-xs">
|
||||||
|
{t("glances.wait")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UsageBar percent="0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5 text-xs">
|
||||||
|
{t("glances.wait")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UsageBar percent="0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{options.label && (
|
||||||
|
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
|
||||||
|
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">
|
||||||
|
{t("common.number", {
|
||||||
|
value: data.cpu,
|
||||||
|
style: "unit",
|
||||||
|
unit: "percent",
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="pr-1">{t("glances.cpu")}</div>
|
||||||
|
</div>
|
||||||
|
<UsageBar percent={data.cpu} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||||
|
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||||
|
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||||
|
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||||
|
<div className="pl-0.5">
|
||||||
|
{t("common.number", {
|
||||||
|
value: data.mem,
|
||||||
|
style: "unit",
|
||||||
|
unit: "percent",
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="pr-1">{t("glances.mem")}</div>
|
||||||
|
</div>
|
||||||
|
<UsageBar percent={data.mem} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{options.label && (
|
||||||
|
<div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ const widgetMappings = {
|
|||||||
datetime: dynamic(() => import("components/widgets/datetime/datetime")),
|
datetime: dynamic(() => import("components/widgets/datetime/datetime")),
|
||||||
logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }),
|
logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }),
|
||||||
unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")),
|
unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")),
|
||||||
|
glances: dynamic(() => import("components/widgets/glances/glances")),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Widget({ widget }) {
|
export default function Widget({ widget }) {
|
||||||
|
|||||||
52
src/pages/api/widgets/glances.js
Normal file
52
src/pages/api/widgets/glances.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
import { getSettings } from "utils/config/config";
|
||||||
|
|
||||||
|
const logger = createLogger("glances");
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
const { id } = req.query;
|
||||||
|
|
||||||
|
let errorMessage;
|
||||||
|
|
||||||
|
let instanceID = "glances";
|
||||||
|
if (id) { // multiple instances
|
||||||
|
instanceID = id;
|
||||||
|
}
|
||||||
|
const settings = getSettings();
|
||||||
|
const instanceSettings = settings[instanceID];
|
||||||
|
if (!instanceSettings) {
|
||||||
|
errorMessage = id ? `There is no glances section with id '${id}' in settings.yaml` : "There is no glances section in settings.yaml";
|
||||||
|
logger.error(errorMessage);
|
||||||
|
return res.status(400).json({ error: errorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = instanceSettings?.url;
|
||||||
|
if (!url) {
|
||||||
|
errorMessage = "Missing Glances URL";
|
||||||
|
logger.error(errorMessage);
|
||||||
|
return res.status(400).json({ error: errorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = `${url}/api/3/quicklook`;
|
||||||
|
const headers = {
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
};
|
||||||
|
if (instanceSettings.username && instanceSettings.password) {
|
||||||
|
headers.Authorization = `Basic ${Buffer.from(`${instanceSettings.username}:${instanceSettings.password}`).toString("base64")}`
|
||||||
|
}
|
||||||
|
const params = { method: "GET", headers };
|
||||||
|
|
||||||
|
const [status, contentType, data] = await httpProxy(apiUrl, params);
|
||||||
|
|
||||||
|
if (status === 401) {
|
||||||
|
logger.error("Authorization failure getting data from glances API. Data: %s", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.error("HTTP %d getting data from glances API. Data: %s", status, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType) res.setHeader("Content-Type", contentType);
|
||||||
|
return res.status(status).send(data);
|
||||||
|
}
|
||||||
@ -1,33 +1,13 @@
|
|||||||
import checkAndCopyConfig, { getSettings } from "utils/config/config";
|
import checkAndCopyConfig, { getSettings } from "utils/config/config";
|
||||||
import themes from "utils/styles/themes";
|
import themes from "utils/styles/themes";
|
||||||
import { servicesResponse, bookmarksResponse } from "utils/config/api-response";
|
|
||||||
|
|
||||||
export async function getServerSideProps({ res }) {
|
export async function getServerSideProps({ res }) {
|
||||||
checkAndCopyConfig("settings.yaml");
|
checkAndCopyConfig("settings.yaml");
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
const services = await servicesResponse();
|
|
||||||
const bookmarks = await bookmarksResponse();
|
|
||||||
|
|
||||||
const color = settings.color || "slate";
|
const color = settings.color || "slate";
|
||||||
const theme = settings.theme || "dark";
|
const theme = settings.theme || "dark";
|
||||||
|
|
||||||
const serviceShortcuts = services.map((group) =>
|
|
||||||
group.services.map((service) => ({
|
|
||||||
name: service.name,
|
|
||||||
url: service.href,
|
|
||||||
description: service.description,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const bookmarkShortcuts = bookmarks.map((group) =>
|
|
||||||
group.bookmarks.map((service) => ({
|
|
||||||
name: service.name,
|
|
||||||
url: service.href,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const shortcuts = [...serviceShortcuts, ...bookmarkShortcuts].flat();
|
|
||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
name: settings.title || "Homepage",
|
name: settings.title || "Homepage",
|
||||||
short_name: settings.title || "Homepage",
|
short_name: settings.title || "Homepage",
|
||||||
@ -43,7 +23,6 @@ export async function getServerSideProps({ res }) {
|
|||||||
type: "image/png",
|
type: "image/png",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
shortcuts,
|
|
||||||
theme_color: themes[color][theme],
|
theme_color: themes[color][theme],
|
||||||
background_color: themes[color][theme],
|
background_color: themes[color][theme],
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
|
|||||||
@ -18,6 +18,7 @@ const components = {
|
|||||||
ombi: dynamic(() => import("./ombi/component")),
|
ombi: dynamic(() => import("./ombi/component")),
|
||||||
overseerr: dynamic(() => import("./overseerr/component")),
|
overseerr: dynamic(() => import("./overseerr/component")),
|
||||||
pihole: dynamic(() => import("./pihole/component")),
|
pihole: dynamic(() => import("./pihole/component")),
|
||||||
|
plex: dynamic(() => import("./plex/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")),
|
proxmox: dynamic(() => import("./proxmox/component")),
|
||||||
|
|||||||
38
src/widgets/plex/component.jsx
Normal file
38
src/widgets/plex/component.jsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
|
import Block from "components/services/widget/block";
|
||||||
|
import Container from "components/services/widget/container";
|
||||||
|
import { formatProxyUrl } from "utils/proxy/api-helpers";
|
||||||
|
|
||||||
|
export default function Component({ service }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { widget } = service;
|
||||||
|
|
||||||
|
const { data: plexData, error: plexAPIError } = useSWR(formatProxyUrl(widget, "unified"), {
|
||||||
|
refreshInterval: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (plexAPIError) {
|
||||||
|
return <Container error={t("widget.api_error")} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plexData) {
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="plex.streams" />
|
||||||
|
<Block label="plex.movies" />
|
||||||
|
<Block label="plex.tv" />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<Block label="plex.streams" value={t("common.number", { value: plexData.streams })} />
|
||||||
|
<Block label="plex.movies" value={t("common.number", { value: plexData.movies })} />
|
||||||
|
<Block label="plex.tv" value={t("common.number", { value: plexData.tv })} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
src/widgets/plex/proxy.js
Normal file
110
src/widgets/plex/proxy.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
|
import cache from "memory-cache";
|
||||||
|
import { xml2json } from "xml-js";
|
||||||
|
|
||||||
|
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||||
|
import { httpProxy } from "utils/proxy/http";
|
||||||
|
import getServiceWidget from "utils/config/service-helpers";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
import widgets from "widgets/widgets";
|
||||||
|
|
||||||
|
const proxyName = "plexProxyHandler";
|
||||||
|
const librariesCacheKey = `${proxyName}__libraries`;
|
||||||
|
const moviesCacheKey = `${proxyName}__movies`;
|
||||||
|
const tvCacheKey = `${proxyName}__tv`;
|
||||||
|
const logger = createLogger(proxyName);
|
||||||
|
|
||||||
|
async function getWidget(req) {
|
||||||
|
const { group, service } = req.query;
|
||||||
|
|
||||||
|
if (!group || !service) {
|
||||||
|
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (!widget) {
|
||||||
|
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchFromPlexAPI(endpoint, widget) {
|
||||||
|
const api = widgets?.[widget.type]?.api;
|
||||||
|
if (!api) {
|
||||||
|
return [403, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(formatApiCall(api, { endpoint, ...widget }));
|
||||||
|
|
||||||
|
const [status, contentType, data] = await httpProxy(url);
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
logger.error("HTTP %d communicating with Plex. Data: %s", status, data.toString());
|
||||||
|
return [status, data.toString()];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dataDecoded = xml2json(data.toString(), { compact: true });
|
||||||
|
return [status, JSON.parse(dataDecoded), contentType];
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Error decoding Plex API data. Data: %s", data.toString());
|
||||||
|
return [status, null];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function plexProxyHandler(req, res) {
|
||||||
|
const widget = await getWidget(req);
|
||||||
|
if (!widget) {
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Getting streams from Plex API");
|
||||||
|
let streams;
|
||||||
|
let [status, apiData] = await fetchFromPlexAPI("/status/sessions", widget);
|
||||||
|
if (apiData && apiData.MediaContainer) {
|
||||||
|
streams = apiData.MediaContainer._attributes.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
let libraries = cache.get(librariesCacheKey);
|
||||||
|
if (libraries === null) {
|
||||||
|
logger.debug("Getting libraries from Plex API");
|
||||||
|
[status, apiData] = await fetchFromPlexAPI("/library/sections", widget);
|
||||||
|
if (apiData && apiData.MediaContainer) {
|
||||||
|
libraries = apiData.MediaContainer.Directory;
|
||||||
|
cache.put(librariesCacheKey, libraries, 1000 * 60 * 60 * 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let movies = cache.get(moviesCacheKey);
|
||||||
|
let tv = cache.get(tvCacheKey);
|
||||||
|
if (movies === null || tv === null) {
|
||||||
|
movies = 0;
|
||||||
|
tv = 0;
|
||||||
|
logger.debug("Getting movie + tv counts from Plex API");
|
||||||
|
libraries.filter(l => ["movie", "show"].includes(l._attributes.type)).forEach(async (library) => {
|
||||||
|
[status, apiData] = await fetchFromPlexAPI(`/library/sections/${library._attributes.key}/all`, widget);
|
||||||
|
if (apiData && apiData.MediaContainer) {
|
||||||
|
const size = parseInt(apiData.MediaContainer._attributes.size, 10);
|
||||||
|
if (library._attributes.type === "movie") {
|
||||||
|
movies += size;
|
||||||
|
} else if (library._attributes.type === "show") {
|
||||||
|
tv += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache.put(tvCacheKey, tv, 1000 * 60 * 10);
|
||||||
|
cache.put(moviesCacheKey, movies, 1000 * 60 * 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
streams,
|
||||||
|
tv,
|
||||||
|
movies
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.status(status).send(data);
|
||||||
|
}
|
||||||
14
src/widgets/plex/widget.js
Normal file
14
src/widgets/plex/widget.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import plexProxyHandler from "./proxy";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "{url}{endpoint}?X-Plex-Token={key}",
|
||||||
|
proxyHandler: plexProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
unified: {
|
||||||
|
endpoint: "/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
||||||
@ -13,6 +13,7 @@ import nzbget from "./nzbget/widget";
|
|||||||
import ombi from "./ombi/widget";
|
import ombi from "./ombi/widget";
|
||||||
import overseerr from "./overseerr/widget";
|
import overseerr from "./overseerr/widget";
|
||||||
import pihole from "./pihole/widget";
|
import pihole from "./pihole/widget";
|
||||||
|
import plex from "./plex/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 proxmox from "./proxmox/widget";
|
||||||
@ -46,6 +47,7 @@ const widgets = {
|
|||||||
ombi,
|
ombi,
|
||||||
overseerr,
|
overseerr,
|
||||||
pihole,
|
pihole,
|
||||||
|
plex,
|
||||||
portainer,
|
portainer,
|
||||||
prowlarr,
|
prowlarr,
|
||||||
proxmox,
|
proxmox,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user