Merge branch 'benphelps:main' into main

This commit is contained in:
Karl0ss 2023-07-06 07:58:51 +01:00 committed by GitHub
commit dd2b7df350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 597 additions and 208 deletions

1
package-lock.json generated
View File

@ -21,7 +21,6 @@
"minecraft-ping-js": "^1.0.2",
"next": "^12.3.1",
"next-i18next": "^12.0.1",
"osx-temperature-sensor": "*",
"pretty-bytes": "^6.0.0",
"raw-body": "^2.5.1",
"react": "^18.2.0",

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadCount": "Queue Count",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadCount": "Queue Count",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

4
public/locales/en/common.json Executable file → Normal file
View File

@ -659,5 +659,9 @@
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size",
"downloadSpeed": "Speed"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -646,9 +646,13 @@
"down_alerts": "Alertas"
},
"jdownloader": {
"downloadCount": "Recuento de las colas",
"downloadSpeed": "Velocidad de Descarga",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
"downloadCount": "Cola",
"downloadSpeed": "Velocidad",
"downloadBytesRemaining": "Restante",
"downloadTotalBytes": "Tamaño"
},
"kavita": {
"seriesCount": "Serie",
"totalFiles": "Archivos"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -5,8 +5,8 @@
"status": "Statut",
"information": "Information",
"url": "URL",
"raw_error": "Raw Error",
"response_data": "Response Data"
"raw_error": "Erreur brute",
"response_data": "Données de réponse"
},
"search": {
"placeholder": "Recherche…"
@ -578,7 +578,7 @@
"homeassistant": {
"people_home": "People Home",
"lights_on": "Lumières allumées",
"switches_on": "Switches On"
"switches_on": "Commutateur On"
},
"freshrss": {
"unread": "Non lu",
@ -648,7 +648,11 @@
"jdownloader": {
"downloadCount": "Total en attente",
"downloadSpeed": "Vitesse de téléchargement",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
"downloadBytesRemaining": "Restant",
"downloadTotalBytes": "Taille"
},
"kavita": {
"seriesCount": "Séries",
"totalFiles": "Fichiers"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -126,21 +126,21 @@
"wanted": "Zatraženo",
"queued": "U redu čekanja",
"series": "Serije",
"unknown": "Unknown",
"queue": "Queue"
"unknown": "Nepoznato",
"queue": "Red čekanja"
},
"radarr": {
"wanted": "Zatraženo",
"queued": "U redu čekanja",
"movies": "Filmovi",
"missing": "Nedostaje",
"queue": "Queue",
"unknown": "Unknown"
"queue": "Red čekanja",
"unknown": "Nepoznato"
},
"lidarr": {
"wanted": "Zatraženo",
"queued": "U redu čekanja",
"artists": "Artists"
"artists": "Umjetnici"
},
"readarr": {
"wanted": "Zatraženo",
@ -646,9 +646,13 @@
"down_alerts": "Obavijest o rušenju"
},
"jdownloader": {
"downloadCount": "Queue Count",
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
"downloadCount": "Red čekanja",
"downloadSpeed": "Brzina",
"downloadBytesRemaining": "Preostalo",
"downloadTotalBytes": "Veličina"
},
"kavita": {
"seriesCount": "Serije",
"totalFiles": "Datoteke"
}
}

View File

@ -67,10 +67,10 @@
"transcoding": "Átkódolás",
"bitrate": "Bitráta",
"no_active": "Nincs aktív lejátszás",
"movies": "Movies",
"series": "Series",
"episodes": "Episodes",
"songs": "Songs"
"movies": "Film",
"series": "Sorozat",
"episodes": "Epizód",
"songs": "Zeneszám"
},
"tautulli": {
"playing": "Lejátszás folyamatban",
@ -254,16 +254,16 @@
"diffsDetected": "Diffs Detected"
},
"wmo": {
"0-day": "Sunny",
"0-night": "Clear",
"0-day": "Napos",
"0-night": "Derült",
"3-day": "Cloudy",
"3-night": "Cloudy",
"45-day": "Foggy",
"53-day": "Drizzle",
"56-night": "Light Freezing Drizzle",
"57-day": "Freezing Drizzle",
"1-day": "Mainly Sunny",
"1-night": "Mainly Clear",
"1-day": "Többnyire napos",
"1-night": "Többnyire derült",
"2-day": "Partly Cloudy",
"2-night": "Partly Cloudy",
"45-night": "Foggy",
@ -373,7 +373,7 @@
"hd": "HD"
},
"ping": {
"error": "Error",
"error": "Hiba",
"ping": "Ping"
},
"scrutiny": {
@ -570,10 +570,10 @@
"gross_percent_max": "All time"
},
"audiobookshelf": {
"podcasts": "Podcasts",
"books": "Books",
"podcastsDuration": "Duration",
"booksDuration": "Duration"
"podcasts": "Podcast",
"books": "Könyv",
"podcastsDuration": "Időtartam",
"booksDuration": "Időtartam"
},
"homeassistant": {
"people_home": "People Home",
@ -650,5 +650,9 @@
"downloadCount": "Queue Count",
"downloadTotalBytes": "Size",
"downloadBytesRemaining": "Remaining"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -7,12 +7,12 @@
"rx": "RX",
"error": "Errore",
"unknown": "Sconosciuto",
"running": "Running",
"starting": "Starting",
"running": "In esecuzione",
"starting": "In avvio",
"unhealthy": "Unhealthy",
"not_found": "Not Found",
"exited": "Exited",
"partial": "Partial",
"not_found": "Non trovato",
"exited": "Uscito",
"partial": "Parziale",
"healthy": "Healthy"
},
"emby": {
@ -20,10 +20,10 @@
"transcoding": "Transcodifica",
"bitrate": "Bitrate",
"no_active": "Nessuno Stream Attivo",
"movies": "Movies",
"series": "Series",
"episodes": "Episodes",
"songs": "Songs"
"movies": "Film",
"series": "Serie",
"episodes": "Episodi",
"songs": "Canzoni"
},
"tautulli": {
"playing": "In riproduzione",
@ -82,16 +82,16 @@
"series": "Serie",
"wanted": "Richiesti",
"queued": "In coda",
"queue": "Queue",
"unknown": "Unknown"
"queue": "Coda",
"unknown": "Sconosciuto"
},
"radarr": {
"wanted": "Richiesti",
"queued": "In coda",
"movies": "Film",
"missing": "Mancanti",
"queue": "Queue",
"unknown": "Unknown"
"queue": "Coda",
"unknown": "Sconosciuto"
},
"readarr": {
"wanted": "Richiesti",
@ -112,7 +112,7 @@
"queries": "Richieste",
"blocked": "Bloccati",
"gravity": "Severità",
"blocked_percent": "Blocked %"
"blocked_percent": "Bloccato %"
},
"npm": {
"enabled": "Attivi",
@ -175,9 +175,9 @@
"missingMovies": "Film Mancanti"
},
"lidarr": {
"wanted": "Mancanti",
"wanted": "Richiesto",
"queued": "In coda",
"artists": "Artists"
"artists": "Artisti"
},
"adguard": {
"queries": "Interrogazioni",
@ -228,13 +228,13 @@
"devices": "Dispositivi",
"lan_devices": "Dispositivi LAN",
"wlan_devices": "Dispositivi WLAN",
"empty_data": "Subsystem status unknown"
"empty_data": "Stato del sottosistema sconosciuto"
},
"plex": {
"streams": "Trasmissioni attive",
"movies": "Film",
"tv": "Programma televisivo",
"albums": "Albums"
"albums": "Album"
},
"glances": {
"cpu": "CPU",
@ -243,11 +243,11 @@
"uptime": "UP",
"days": "d",
"hours": "h",
"load": "Load",
"warn": "Warn",
"total": "Total",
"free": "Free",
"used": "Used"
"load": "Carico",
"warn": "Avviso",
"total": "Totale",
"free": "Libero",
"used": "Usato"
},
"changedetectionio": {
"totalObserved": "Totale Osservato",
@ -314,9 +314,9 @@
"quicklaunch": {
"bookmark": "Segnalibro",
"service": "Servizio",
"search": "Search",
"custom": "Custom",
"visit": "Visit",
"search": "Cerca",
"custom": "Personalizzato",
"visit": "Visita",
"url": "URL"
},
"homebridge": {
@ -327,7 +327,7 @@
"child_bridges": "Child Bridges",
"child_bridges_status": "{{ok}}/{{total}}",
"up": "Up",
"pending": "Pending",
"pending": "In attesa",
"down": "Down"
},
"autobrr": {
@ -432,7 +432,7 @@
"cpuLoad": "Carico della CPU",
"memoryUsed": "Memoria Utilizzata",
"uptime": "Tempo di attività",
"numberOfLeases": "Lease"
"numberOfLeases": "Rilasci"
},
"xteve": {
"streams_all": "Tutti gli stream",
@ -440,215 +440,219 @@
"streams_xepg": "Canali XEPG"
},
"opnsense": {
"cpu": "Carico CPU",
"cpu": "Carico della CPU",
"memory": "Memoria in uso",
"wanUpload": "WAN Upload",
"wanDownload": "WAN Download"
},
"moonraker": {
"printer_state": "Printer State",
"print_status": "Print Status",
"print_progress": "Progress",
"layers": "Layers"
"printer_state": "Stato stampante",
"print_status": "Stato Stampante",
"print_progress": "Avanzamento",
"layers": "Livelli"
},
"medusa": {
"wanted": "Wanted",
"queued": "Queued",
"series": "Series"
"wanted": "Richiesto",
"queued": "In coda",
"series": "Serie"
},
"octoprint": {
"printer_state": "Status",
"printer_state": "Stato",
"temp_tool": "Tool temp",
"temp_bed": "Bed temp",
"job_completion": "Completion"
"job_completion": "Completamento"
},
"cloudflared": {
"origin_ip": "Origin IP",
"status": "Status"
"origin_ip": "IP sorgente",
"status": "Stato"
},
"proxmoxbackupserver": {
"datastore_usage": "Datastore",
"failed_tasks_24h": "Failed Tasks 24h",
"failed_tasks_24h": "Attività Non Riuscite 24h",
"cpu_usage": "CPU",
"memory_usage": "Memory"
"memory_usage": "Memoria"
},
"immich": {
"users": "Users",
"photos": "Photos",
"videos": "Videos",
"storage": "Storage"
"users": "Utenti",
"photos": "Foto",
"videos": "Video",
"storage": "Memoria"
},
"uptimekuma": {
"up": "Sites Up",
"down": "Sites Down",
"up": "Siti On",
"down": "Siti Down",
"uptime": "Uptime",
"incident": "Incident",
"incident": "Incidente",
"m": "m"
},
"komga": {
"libraries": "Libraries",
"series": "Series",
"books": "Books"
"libraries": "Librerie",
"series": "Serie",
"books": "Libri"
},
"mylar": {
"series": "Series",
"issues": "Issues",
"wanted": "Wanted"
"series": "Serie",
"issues": "Problemi",
"wanted": "Richiesto"
},
"photoprism": {
"albums": "Albums",
"photos": "Photos",
"videos": "Videos",
"people": "People"
"albums": "Album",
"photos": "Foto",
"videos": "Video",
"people": "Persone"
},
"diskstation": {
"days": "Days",
"days": "Giorni",
"uptime": "Uptime",
"volumeAvailable": "Available"
"volumeAvailable": "Disponibile"
},
"fileflows": {
"queue": "Queue",
"processing": "Processing",
"processed": "Processed",
"time": "Time"
"queue": "Coda",
"processing": "In Lavorazione",
"processed": "Elaborato",
"time": "Tempo"
},
"grafana": {
"dashboards": "Dashboards",
"datasources": "Data Sources",
"totalalerts": "Total Alerts",
"alertstriggered": "Alerts Triggered"
"datasources": "Origine dei Dati",
"totalalerts": "Avvisi Totali",
"alertstriggered": "Avvisi Attivati"
},
"nextcloud": {
"memoryusage": "Memory Usage",
"cpuload": "Cpu Load",
"freespace": "Free Space",
"activeusers": "Active Users",
"numfiles": "Files",
"numshares": "Shared Items"
"memoryusage": "Uso della Memoria",
"cpuload": "Carico della CPU",
"freespace": "Spazio Libero",
"activeusers": "Utenti Attivi",
"numfiles": "File",
"numshares": "Oggetti Condivisi"
},
"kopia": {
"status": "Status",
"size": "Size",
"lastrun": "Last Run",
"nextrun": "Next Run",
"failed": "Failed"
"status": "Stato",
"size": "Dimensione",
"lastrun": "Ultima esecuzione",
"nextrun": "Prossima esecuzione",
"failed": "Fallito"
},
"unmanic": {
"active_workers": "Active Workers",
"total_workers": "Total Workers",
"records_total": "Queue Length"
"active_workers": "Lavoratori Attivi",
"total_workers": "Lavoratori Totali",
"records_total": "Lunghezza della Coda"
},
"healthchecks": {
"new": "New",
"new": "Nuovo",
"up": "Online",
"grace": "In Grace Period",
"grace": "Periodo di Tolleranza",
"down": "Offline",
"paused": "Paused",
"status": "Status",
"last_ping": "Last Ping",
"never": "No pings yet"
"paused": "In Pausa",
"status": "Stato",
"last_ping": "Ultimo Ping",
"never": "Ancora nessun ping"
},
"pterodactyl": {
"servers": "Servers",
"nodes": "Nodes"
"servers": "Server",
"nodes": "Nodi"
},
"prometheus": {
"targets_up": "Targets Up",
"targets_down": "Targets Down",
"targets_total": "Total Targets"
"targets_total": "Targets Totali"
},
"minecraft": {
"players": "Players",
"version": "Version",
"status": "Status",
"players": "Giocatori",
"version": "Versione",
"status": "Stato",
"up": "Online",
"down": "Offline"
},
"ghostfolio": {
"gross_percent_today": "Today",
"gross_percent_1y": "One year",
"gross_percent_max": "All time"
"gross_percent_today": "Oggi",
"gross_percent_1y": "Un anno",
"gross_percent_max": "Sempre"
},
"audiobookshelf": {
"podcasts": "Podcasts",
"books": "Books",
"podcastsDuration": "Duration",
"booksDuration": "Duration"
"podcasts": "Podcast",
"books": "Libri",
"podcastsDuration": "Durata",
"booksDuration": "Durata"
},
"homeassistant": {
"people_home": "People Home",
"lights_on": "Lights On",
"switches_on": "Switches On"
"people_home": "Persone a Casa",
"lights_on": "Luci Accese",
"switches_on": "Switch Accesi"
},
"freshrss": {
"subscriptions": "Subscriptions",
"unread": "Unread"
"subscriptions": "Iscrizioni",
"unread": "Non letto"
},
"channelsdvrserver": {
"shows": "Shows",
"recordings": "Recordings",
"scheduled": "Scheduled",
"passes": "Passes"
"shows": "Spettacoli",
"recordings": "Registrazioni",
"scheduled": "Programmati",
"passes": "Tessere"
},
"whatsupdocker": {
"monitoring": "Monitoring",
"updates": "Updates"
"monitoring": "Monitoraggio",
"updates": "Aggiornamenti"
},
"tailscale": {
"never": "Never",
"address": "Address",
"expires": "Expires",
"last_seen": "Last Seen",
"now": "Now",
"never": "Mai",
"address": "Indirizzo",
"expires": "Scade",
"last_seen": "Ultima visualizzazione",
"now": "Adesso",
"years": "{{number}}y",
"weeks": "{{number}}w",
"hours": "{{number}}h",
"minutes": "{{number}}m",
"seconds": "{{number}}s",
"ago": "{{value}} Ago",
"ago": "{{value}} Fa",
"days": "{{number}}d"
},
"qnap": {
"cpuUsage": "CPU Usage",
"memUsage": "MEM Usage",
"systemTempC": "System Temp",
"poolUsage": "Pool Usage",
"volumeUsage": "Volume Usage",
"invalid": "Invalid"
"cpuUsage": "Utilizzo CPU",
"memUsage": "Utilizzo MEM",
"systemTempC": "Temp sistema",
"poolUsage": "Utilizzo Pool",
"volumeUsage": "Utilizzo Volume",
"invalid": "Invalido"
},
"pfsense": {
"load": "Load Avg",
"memory": "Mem Usage",
"wanStatus": "WAN Status",
"load": "Carico Medio",
"memory": "Uso Memoria",
"wanStatus": "Stato WAN",
"up": "Up",
"down": "Down",
"temp": "Temp",
"disk": "Disk Usage",
"wanIP": "WAN IP"
"temp": "Temperatura",
"disk": "Uso Disco",
"wanIP": "IP WAN"
},
"caddy": {
"upstreams": "Upstreams",
"requests": "Current requests",
"requests_failed": "Failed requests"
"upstreams": "Upstream",
"requests": "Richieste correnti",
"requests_failed": "Richieste fallite"
},
"evcc": {
"pv_power": "Production",
"battery_soc": "Battery",
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"pv_power": "Produzione",
"battery_soc": "Batteria",
"grid_power": "Griglia",
"home_power": "Consumo",
"charge_power": "Caricatore",
"watt_hour": "Wh"
},
"pialert": {
"total": "Total",
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
"total": "Totali",
"connected": "Connesso",
"new_devices": "Nuovi Dispositivi",
"down_alerts": "Avvisi di Disservizio"
},
"jdownloader": {
"downloadCount": "Queue Count",
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
"downloadCount": "Coda",
"downloadSpeed": "Velocità Download",
"downloadBytesRemaining": "Residuo",
"downloadTotalBytes": "Dimensione"
},
"kavita": {
"seriesCount": "Serie",
"totalFiles": "File"
}
}

View File

@ -239,7 +239,7 @@
"queries": "クエリ",
"blocked": "ブロック中",
"gravity": "グラビティ",
"blocked_percent": "Blocked %"
"blocked_percent": "ブロック %"
},
"adguard": {
"queries": "クエリ",
@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -659,5 +659,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"totalFiles": "Files",
"seriesCount": "Series"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"totalFiles": "Files",
"seriesCount": "Series"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -646,9 +646,13 @@
"down_alerts": "Сповіщення про збій"
},
"jdownloader": {
"downloadCount": "Всього в черзі",
"downloadSpeed": "Швидкість завантаження",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
"downloadCount": "Черга",
"downloadSpeed": "Швидкість",
"downloadBytesRemaining": "Залишилося",
"downloadTotalBytes": "Розмір"
},
"kavita": {
"seriesCount": "Серій",
"totalFiles": "Файлів"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -632,7 +632,7 @@
"requests_failed": "失败请求"
},
"evcc": {
"pv_power": "Production",
"pv_power": "正式环境",
"battery_soc": "Battery",
"grid_power": "Grid",
"home_power": "Consumption",
@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -650,5 +650,9 @@
"downloadSpeed": "Download Speed",
"downloadBytesRemaining": "Remaining",
"downloadTotalBytes": "Size"
},
"kavita": {
"seriesCount": "Series",
"totalFiles": "Files"
}
}

View File

@ -1,13 +1,41 @@
import classNames from "classnames";
import { Disclosure, Transition } from '@headlessui/react';
import { MdKeyboardArrowDown } from "react-icons/md";
import ErrorBoundary from "components/errorboundry";
import List from "components/bookmarks/list";
export default function BookmarksGroup({ group }) {
export default function BookmarksGroup({ group, disableCollapse }) {
return (
<div key={group.name} className="flex-1">
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2>
<ErrorBoundary>
<List bookmarks={group.bookmarks} />
</ErrorBoundary>
<Disclosure defaultOpen>
{({ open }) => (
<>
<Disclosure.Button disabled={disableCollapse} className="flex w-full select-none items-center group">
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2>
<MdKeyboardArrowDown className={classNames(
disableCollapse ? 'hidden' : '',
'transition-opacity opacity-0 group-hover:opacity-100 ml-auto text-theme-800 dark:text-theme-300 text-xl',
open ? 'rotate-180 transform' : ''
)} />
</Disclosure.Button>
<Transition
enter="transition duration-200 ease-out"
enterFrom="transform scale-75 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-75 opacity-0"
>
<Disclosure.Panel>
<ErrorBoundary>
<List bookmarks={group.bookmarks} />
</ErrorBoundary>
</Disclosure.Panel>
</Transition>
</>
)}
</Disclosure>
</div>
);
}

View File

@ -1,9 +1,12 @@
import classNames from "classnames";
import { Disclosure, Transition } from '@headlessui/react';
import { MdKeyboardArrowDown } from "react-icons/md";
import List from "components/services/list";
import ResolvedIcon from "components/resolvedicon";
export default function ServicesGroup({ group, services, layout, fiveColumns }) {
export default function ServicesGroup({ group, services, layout, fiveColumns, disableCollapse }) {
return (
<div
key={services.name}
@ -13,15 +16,37 @@ export default function ServicesGroup({ group, services, layout, fiveColumns })
"flex-1 p-1"
)}
>
<div className="flex select-none items-center">
{layout?.icon &&
<div className="flex-shrink-0 mr-2 w-7 h-7">
<ResolvedIcon icon={layout.icon} />
</div>
}
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
</div>
<List group={group} services={services.services} layout={layout} />
<Disclosure defaultOpen>
{({ open }) => (
<>
<Disclosure.Button disabled={disableCollapse} className="flex w-full select-none items-center group">
{layout?.icon &&
<div className="flex-shrink-0 mr-2 w-7 h-7">
<ResolvedIcon icon={layout.icon} />
</div>
}
<h2 className="flex text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
<MdKeyboardArrowDown className={classNames(
disableCollapse ? 'hidden' : '',
'transition-opacity opacity-0 group-hover:opacity-100 ml-auto text-theme-800 dark:text-theme-300 text-xl',
open ? 'rotate-180 transform' : ''
)} />
</Disclosure.Button>
<Transition
enter="transition duration-200 ease-out"
enterFrom="transform scale-75 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-75 opacity-0"
>
<Disclosure.Panel>
<List group={group} services={services.services} layout={layout} />
</Disclosure.Panel>
</Transition>
</>
)}
</Disclosure>
</div>
);
}

View File

@ -294,7 +294,13 @@ function Home({ initialSettings }) {
{services?.length > 0 && (
<div className="flex flex-wrap p-4 sm:p-8 sm:pt-4 items-start pb-2">
{services.map((group) => (
<ServicesGroup key={group.name} group={group.name} services={group} layout={initialSettings.layout?.[group.name]} fiveColumns={settings.fiveColumns} />
<ServicesGroup
key={group.name}
group={group.name}
services={group}
layout={initialSettings.layout?.[group.name]}
fiveColumns={settings.fiveColumns}
disableCollapse={settings.disableCollapse} />
))}
</div>
)}
@ -302,7 +308,10 @@ function Home({ initialSettings }) {
{bookmarks?.length > 0 && (
<div className={`grow flex flex-wrap pt-0 p-4 sm:p-8 gap-2 grid-cols-1 lg:grid-cols-2 lg:grid-cols-${Math.min(6, bookmarks.length)}`}>
{bookmarks.map((group) => (
<BookmarksGroup key={group.name} group={group} />
<BookmarksGroup
key={group.name}
group={group}
disableCollapse={settings.disableCollapse} />
))}
</div>
)}

View File

@ -291,8 +291,15 @@ export function cleanServiceGroups(groups) {
enableQueue, // sonarr/radarr
} = cleanedService.widget;
const fieldsList = typeof fields === 'string' ? JSON.parse(fields) : fields;
let fieldsList = fields;
if (typeof fields === 'string') {
try { JSON.parse(fields) }
catch (e) {
logger.error("Invalid fields list detected in config for service '%s'", service.name);
fieldsList = null;
}
}
cleanedService.widget = {
type,
fields: fieldsList || null,

View File

@ -1,6 +1,6 @@
/* eslint-disable prefer-promise-reject-errors */
/* eslint-disable no-param-reassign */
import { createUnzip } from "node:zlib";
import { createUnzip, constants as zlibConstants } from "node:zlib";
import { http, https } from "follow-redirects";
@ -34,7 +34,14 @@ function handleRequest(requestor, url, params) {
let responseContent = response;
if (contentEncoding === 'gzip' || contentEncoding === 'deflate') {
responseContent = createUnzip();
// https://github.com/request/request/blob/3c0cddc7c8eb60b470e9519da85896ed7ee0081e/request.js#L1018-L1025
// Be more lenient with decoding compressed responses, in case of invalid gzip responses that are still accepted
// by common browsers.
responseContent = createUnzip({
flush: zlibConstants.Z_SYNC_FLUSH,
finishFlush: zlibConstants.Z_SYNC_FLUSH
});
// zlib errors
responseContent.on("error", (e) => {
logger.error(e);
@ -103,6 +110,6 @@ export async function httpProxy(url, params = {}) {
constructedUrl.pathname
);
logger.error(err);
return [500, "application/json", { error: {message: err?.message ?? "Unknown error", url, rawError: err} }, null];
return [500, "application/json", { error: { message: err?.message ?? "Unknown error", url, rawError: err } }, null];
}
}

View File

@ -34,6 +34,7 @@ const components = {
jdownloader: dynamic(() => import("./jdownloader/component")),
jellyfin: dynamic(() => import("./emby/component")),
jellyseerr: dynamic(() => import("./jellyseerr/component")),
kavita: dynamic(() => import("./kavita/component")),
komga: dynamic(() => import("./komga/component")),
kopia: dynamic(() => import("./kopia/component")),
lidarr: dynamic(() => import("./lidarr/component")),
@ -94,4 +95,4 @@ const components = {
xteve: dynamic(() => import("./xteve/component")),
};
export default components;
export default components;

View File

@ -0,0 +1,33 @@
import { useTranslation } from "next-i18next";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: kavitaData, error: kavitaError } = useWidgetAPI(widget, "info");
if (kavitaError) {
return <Container service={service} error={kavitaError} />;
}
if (!kavitaData) {
return (
<Container service={service}>
<Block label="kavita.seriesCount" />
<Block label="kavita.totalFiles" />
</Container>
);
}
return (
<Container service={service}>
<Block label="kavita.seriesCount" value={t("common.number", { value: kavitaData.seriesCount })} />
<Block label="kavita.totalFiles" value={t("common.number", { value: kavitaData.totalFiles })} />
</Container>
);
}

View File

@ -0,0 +1,96 @@
import cache from "memory-cache";
import { httpProxy } from "utils/proxy/http";
import { formatApiCall } from "utils/proxy/api-helpers";
import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
import widgets from "widgets/widgets";
const proxyName = "kavitaProxyHandler";
const sessionTokenCacheKey = `${proxyName}__sessionToken`;
const logger = createLogger(proxyName);
async function login(widget, service) {
const endpoint = "Account/login";
const api = widgets?.[widget.type]?.api
const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget }));
const loginBody = { username: widget.username, password: widget.password };
const headers = { "Content-Type": "application/json", "accept": "text/plain" };
const [, , data,] = await httpProxy(loginUrl, {
method: "POST",
body: JSON.stringify(loginBody),
headers,
});
try {
const { token: accessToken } = JSON.parse(data.toString());
cache.put(`${sessionTokenCacheKey}.${service}`, accessToken);
return { accessToken };
} catch (e) {
logger.error("Unable to login to Kavita API: %s", e);
}
return { token: false };
}
async function apiCall(widget, endpoint, service) {
const key = `${sessionTokenCacheKey}.${service}`;
const headers = {
"content-type": "application/json",
"Authorization": `Bearer ${cache.get(key)}`,
}
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
const method = "GET";
let [status, contentType, data, responseHeaders] = await httpProxy(url, {
method,
headers,
});
if (status === 401 || status === 403) {
logger.debug("Kavita API rejected the request, attempting to obtain new session token");
const { accessToken } = await login(widget, service);
headers.Authorization = `Bearer ${accessToken}`;
// retry the request, now with the new session token
[status, contentType, data, responseHeaders] = await httpProxy(url, {
method,
headers,
});
}
if (status !== 200) {
logger.error("Error getting data from Kavita: %s status %d. Data: %s", url, status, data);
return { status, contentType, data: null, responseHeaders };
}
return { status, contentType, data: JSON.parse(data.toString()), responseHeaders };
}
export default async function KavitaProxyHandler(req, res) {
const { group, service } = req.query;
if (!group || !service) {
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
return res.status(400).json({ error: "Invalid proxy service type" });
}
const widget = await getServiceWidget(group, service);
if (!widget) {
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
return res.status(400).json({ error: "Invalid proxy service type" });
}
if (!cache.get(`${sessionTokenCacheKey}.${service}`)) {
await login(widget, service);
}
const { data: statsData } = await apiCall(widget, "Stats/server/stats", service);
return res.status(200).send({
seriesCount: statsData?.seriesCount,
totalFiles: statsData?.totalFiles
});
}

View File

@ -0,0 +1,13 @@
import kavitaProxyHandler from "./proxy";
const widget = {
api: "{url}/api/{endpoint}",
proxyHandler: kavitaProxyHandler,
mappings: {
info: {
endpoint: "/"
}
}
};
export default widget;

View File

@ -28,6 +28,7 @@ import immich from "./immich/widget";
import jackett from "./jackett/widget";
import jellyseerr from "./jellyseerr/widget";
import jdownloader from "./jdownloader/widget";
import kavita from "./kavita/widget";
import komga from "./komga/widget";
import kopia from "./kopia/widget";
import lidarr from "./lidarr/widget";
@ -104,7 +105,7 @@ const widgets = {
diskstation,
downloadstation,
emby,
evcc,
evcc,
fileflows,
flood,
freshrss,
@ -123,6 +124,7 @@ const widgets = {
jdrssdownloader,
jdownloader,
jellyseerr,
kavita,
komga,
kopia,
lidarr,

View File

@ -9,6 +9,11 @@ module.exports = {
"./src/components/**/*.{js,ts,jsx,tsx}",
"./src/widgets/**/*.{js,ts,jsx,tsx}",
],
variants: {
extend: {
display: ["group-hover"],
},
},
theme: {
extend: {
colors: {