v0.10.6 : Reworks and QOL

This commit is contained in:
Thomas Camlong
2022-11-30 00:08:38 +09:00
committed by GitHub
89 changed files with 954 additions and 615 deletions

View File

@@ -1,2 +1,2 @@
export const REPO_URL = 'ajnart/homarr';
export const CURRENT_VERSION = 'v0.10.5';
export const CURRENT_VERSION = 'v0.10.6';

View File

@@ -1,6 +1,6 @@
{
"name": "homarr",
"version": "0.10.5",
"version": "0.10.6",
"description": "Homarr - A homepage for your server.",
"license": "MIT",
"repository": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Søg på nettet..."
}
},
"switched-to": "Skiftet til",
"searchEngines": {
"search": {
"name": "Web",
"description": "Søg ved hjælp af din søgemaskine (defineret i indstillinger)"
},
"youtube": {
"name": "YouTube",
"description": "Søg på YouTube"
},
"torrents": {
"name": "Torrents",
"description": "Søg efter torrents"
},
"overseerr": {
"name": "Overseerr",
"description": "Søg efter film og tv-serier ved hjælp af Overseerr (modul skal være aktiveret)"
}
},
"tip": "Du kan vælge søgefeltet med genvejen ",
"switchedSearchEngine": "Skiftede til søgning med {{searchEngine}}"
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Giver dig mulighed for at se din usenet (Sabnzbd eller NZBGet) kø og historik, pause og genoptage downloads"
"description": "Tillader dig at se din usenet (Sabnzbd eller NZBGet) kø og historie, pause og genoptage downloads"
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Das Internet durchsuchen..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Ermöglicht es Ihnen, Ihre Usenet-Warteschlange (Sabnzbd oder NZBGet) und den Verlauf anzuzeigen, Downloads anzuhalten und fortzusetzen"
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Search the web..."
}
},
"switched-to": "Switched to",
"searchEngines": {
"search": {
"name": "Web",
"description": "Search using your search engine (defined in settings)"
},
"youtube": {
"name": "Youtube",
"description": "Search on Youtube"
},
"torrents": {
"name": "Torrents",
"description": "Search for Torrents"
},
"overseerr": {
"name": "Overseerr",
"description": "Search for Movies and TV Shows using Overseerr (module must be enabled)"
}
},
"tip": "You can select the search bar with the shortcut ",
"switchedSearchEngine": "Switched to searching with {{searchEngine}}"
}

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Buscar en Internet..."
}
},
"switched-to": "Cambiado a",
"searchEngines": {
"search": {
"name": "Web",
"description": "Buscar usando tu motor de búsqueda (definido en ajustes)"
},
"youtube": {
"name": "Youtube",
"description": "Buscar en Youtube"
},
"torrents": {
"name": "Torrents",
"description": "Buscar Torrents"
},
"overseerr": {
"name": "Overseerr",
"description": "Buscar Películas y Series TV usando Overseerr (el módulo debe esta activado)"
}
},
"tip": "Puedes seleccionar la barra de búsqueda con el atajo ",
"switchedSearchEngine": "Cambiado a buscando con {{searchEngine}}"
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Le permite ver la cola y el historial de usenet (Sabnzbd o NZBGet), pausar y reanudar las descargas"
"description": "Te permite ver la cola e historial de tu usenet (Sabnzbd or NZBGet), pausar y reanudar descargas"
},
"card": {
"errors": {

View File

@@ -2,8 +2,8 @@
"accordions": {
"downloads": {
"text": "Vos téléchargements",
"torrents": "",
"usenet": ""
"torrents": "Vos téléchargements Torrents",
"usenet": "Vos téléchargements Usenet"
},
"others": {
"text": "Autres"

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Docker",
"description": ""
"description": "Vous permet de gérer facilement vos conteneurs docker"
},
"search": {
"placeholder": "Recherche par nom de conteneur ou d'image"
@@ -49,20 +49,20 @@
},
"actions": {
"start": {
"start": "",
"end": ""
"start": "Démarrage en cours",
"end": "Démarré"
},
"stop": {
"start": "",
"start": "Arrêt en cours",
"end": "Arrêté"
},
"restart": {
"start": "",
"end": ""
"start": "Redémarrage en cours",
"end": "Redémarré"
},
"remove": {
"start": "",
"end": ""
"start": "Suppression en cours",
"end": "Supprimé"
}
},
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Cherchez sur le web..."
}
},
"switched-to": "Basculé vers",
"searchEngines": {
"search": {
"name": "Web",
"description": "Recherche à l'aide de votre moteur de recherche (défini dans les paramètres)"
},
"youtube": {
"name": "YouTube",
"description": "Cherchez sur YouTube"
},
"torrents": {
"name": "Torrents",
"description": "Chercher des torrents"
},
"overseerr": {
"name": "Overseerr",
"description": "Chercher des films et séries TV avec Overseerr (le module doit-être activé)"
}
},
"tip": "Vous pouvez sélectionner la barre de recherche avec le raccourci ",
"switchedSearchEngine": "Basculer vers la recherche avec {{searchEngine}}"
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Vous permet de voir votre file d'attente et votre historique Usenet (Sabnzbd ou NZBGet), de mettre en pause et de reprendre les téléchargements"
"description": "Permet de voir la file d'attente et l'historique de vos téléchargements usenet (Sabnzbd ou NZBGet), de mettre en pause et de reprendre les téléchargements"
},
"card": {
"errors": {

View File

@@ -9,6 +9,6 @@
"placeholder": "URL de requête personnalisée"
},
"searchNewTab": {
"label": ""
"label": "Ouvrir les résultats de la recherche dans un nouvel onglet"
}
}

View File

@@ -19,8 +19,8 @@
},
"lineChart": {
"title": "מהירות הורדה נוכחית",
"download": "הורדה ממתינה",
"upload": "העלאה ממתינה",
"download": "הורדה",
"upload": "העלאה",
"timeSpan": "לפני שניות",
"totalDownload": "הורדה ממתינה",
"totalUpload": "העלאה ממתינה"

View File

@@ -9,7 +9,7 @@
"table": {
"header": {
"name": "שם",
"image": "תמונה",
"image": "קובץ תמונה",
"ports": "יציאות",
"state": "מצב"
},

View File

@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "זמן תגובה",
"name": "פינג",
"description": "מאפשר בדיקה אם השירות פעיל או מחזיר קוד ספציפי של HTTP"
},
"states": {
"online": "מקון",
"offline": "לא מקון",
"online": "מקוון",
"offline": "לא מקוון",
"loading": "טוען..."
}
}

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "חפש באינטרנט..."
}
},
"switched-to": "להחליף ל",
"searchEngines": {
"search": {
"name": "אינטרנט",
"description": "חיפוש באמצעות מנוע החיפוש שלך (מוגדר בהגדרות)"
},
"youtube": {
"name": "יוטיוב",
"description": "חפש ביוטיוב"
},
"torrents": {
"name": "טורנטים",
"description": "חיפוש אחר טורנטים"
},
"overseerr": {
"name": "Overseerr",
"description": "חיפוש סרטים ותוכניות טלוויזיה באמצעות Overseerr (המודול חייב להיות מאופשר)"
}
},
"tip": "באפשרותך לבחור את סרגל החיפוש באמצעות קיצור הדרך",
"switchedSearchEngine": "עבור לחיפוש עם"
}

View File

@@ -24,8 +24,8 @@
},
"lineChart": {
"title": "מהירות הורדה נוכחית",
"download": "הורדה ממתינה",
"upload": "העלאה ממתינה",
"download": "הורדה",
"upload": "העלאה",
"timeSpan": "לפני שניות",
"totalDownload": "הורדה ממתינה",
"totalUpload": "העלאה ממתינה"

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Sabnzbd",
"description": "מאמאפשר לך לראות את התור וההיסטוריה של usenet (Sabnzbd או NZBGet), להשהות ולחדש הורדות"
"name": "קבוצת דיון",
"description": "מאפשר צפייה בהיסטוריה, תור ההורדות, עצירה וחידוש הורדות בקבוצות הדיון (Sabnzbd או NZBGet)"
},
"card": {
"errors": {

View File

@@ -1,5 +1,5 @@
{
"title": "Bentornato!",
"title": "Bentornati!",
"text": "Inserisci la password",
"form": {
"fields": {
@@ -21,7 +21,7 @@
"title": "Password corretta, reindirizzamento..."
},
"wrong": {
"title": "Password errata. Per favore, prova di nuovo."
"title": "Password errata. Per favore riprova."
}
}
}

View File

@@ -1,13 +1,13 @@
{
"actionIcon": {
"tooltip": "Aggiungere un servizio"
"tooltip": "Aggiungi un servizio"
},
"modal": {
"title": "Aggiungi servizio",
"form": {
"validation": {
"invalidUrl": "Inserire un URL valido",
"noStatusCodeSelected": "Selezionare un codice di stato"
"noStatusCodeSelected": "Scegliere uno status code"
}
},
"tabs": {
@@ -15,28 +15,28 @@
"title": "Opzioni",
"form": {
"serviceName": {
"label": "Nome del servizio",
"label": "Nome servizio",
"placeholder": "Plex"
},
"iconUrl": {
"label": "Icona URL"
"label": "URL dell'icona"
},
"serviceUrl": {
"label": "URL del servizio"
"label": "URL servizio"
},
"onClickUrl": {
"label": "URL di destinatione"
"label": "URL di destinazione"
},
"serviceType": {
"label": "Tipo di servizio",
"label": "Tipologia servizio",
"defaultValue": "Altro",
"placeholder": "Scegli Uno"
"placeholder": "Sceglierne uno"
},
"category": {
"label": "Categoria",
"placeholder": "Selezionare una categoria o crearne una nuova",
"nothingFound": "Non è stato trovato nulla",
"createLabel": "+ Creare {{query}}"
"placeholder": "Seleziona una categoria o creane una nuova",
"nothingFound": "Nessun risultato",
"createLabel": "+ Crea {{query}}"
},
"integrations": {
"apiKey": {
@@ -46,7 +46,7 @@
"noKey": "Chiave non valida"
},
"tip": {
"text": "Ottenere la chiave API",
"text": "Ottieni la tua chiave API",
"link": "qui."
}
},
@@ -111,16 +111,16 @@
}
},
"advancedOptions": {
"title": "Opzioni avanzate",
"title": "Impostazioni avanzate",
"form": {
"httpStatusCodes": {
"label": "Codici di stato HTTP",
"placeholder": "Selezionare i codici di stato validi",
"clearButtonLabel": "Cancella la selezione",
"nothingFound": "Non è stato trovato nulla"
"label": "Status Codes HTTP",
"placeholder": "Selezionare status codes validi",
"clearButtonLabel": "Elimina selezione",
"nothingFound": "Nessun risultato"
},
"openServiceInNewTab": {
"label": "Aprire il servizio in una nuova scheda"
"label": "Apri servizio in una nuova scheda"
},
"buttons": {
"submit": {

View File

@@ -1,18 +1,18 @@
{
"modal": {
"title": "Modificare un servizio",
"title": "Modifica un servizio",
"buttons": {
"save": "Salvataggio del servizio"
"save": "Salva servizio"
}
},
"menu": {
"labels": {
"settings": "Impostazioni",
"dangerZone": "Zona di pericolo"
"dangerZone": "Danger zone"
},
"actions": {
"edit": "Modifica",
"delete": "Cancella"
"delete": "Elimina"
}
}
}

View File

@@ -2,8 +2,8 @@
"accordions": {
"downloads": {
"text": "I tuoi download",
"torrents": "Scaricamenti di Torrent",
"usenet": "I tuoi scaricamenti di Usenet"
"torrents": "I tuoi torrent",
"usenet": "I tuoi download di Usenet"
},
"others": {
"text": "Altri"

View File

@@ -1,10 +1,10 @@
{
"descriptor": {
"name": "Calendario",
"description": "Un calendario per la visualizzazione dei prossimi rilasci. Interagisce con le API di Sonarr e Radarr.",
"description": "Un calendario per la visualizzazione delle prossime uscite. Può comunicare con le API di Sonarr e Radarr.",
"settings": {
"sundayStart": {
"label": "Inizio settimana di domenica"
"label": "Inizia la settimana di domenica"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"buttons": {
"play": "Avvia",
"request": "Richiedi"
"play": "Riproduci",
"request": "Richiesta"
}
}

View File

@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Dash.",
"description": "Un modulo per visualizzare i grafici dell'istanza di Dash in esecuzione.",
"description": "Un modulo per visualizzare i grafici dell'istanza di Dash. in esecuzione.",
"settings": {
"cpuMultiView": {
"label": "Vista CPU Multi-Core"
},
"storageMultiView": {
"label": "Visualizzazione dell'unità multipla di archiviazione"
"label": "Visualizzazione archiviazione Multi-Drive"
},
"useCompactView": {
"label": "Visualizzazione compatta"
@@ -23,8 +23,8 @@
"card": {
"title": "Dash.",
"errors": {
"noService": "Nessun servizio Dash. trovato. Aggiungerne uno alla dashboard di Homarr o impostare un Dash. nelle opzioni del modulo",
"noInformation": "Impossibile acquisire informazioni da dash. - state eseguendo l'ultima versione?"
"noService": "Nessun servizio Dash. trovato. Aggiungine uno alla dashboard di Homarr o imposta un URL nelle impostazioni del modulo",
"noInformation": "Impossibile reperire informazioni da dash. - è in esecuzione l'ultima versione?"
},
"graphs": {
"storage": {
@@ -35,8 +35,8 @@
"title": "Rete",
"label": "Rete:",
"metrics": {
"download": "In basso",
"upload": "Su"
"download": "Down",
"upload": "Up"
}
},
"cpu": {

View File

@@ -4,7 +4,7 @@
"description": "Mostra l'ora e la data corrente in una scheda",
"settings": {
"display24HourFormat": {
"label": "Visualizzazione a tempo pieno (24 ore)"
"label": "Visualizza formato 24 ore"
}
}
}

View File

@@ -1,15 +1,15 @@
{
"descriptor": {
"name": "Velocità Di Download",
"description": "Mostra la velocità di download attuale dei servizi supportati"
"description": "Mostra la velocità di download corrente dei servizi supportati"
},
"card": {
"table": {
"header": {
"name": "Nome",
"size": "Dimensione",
"download": "In basso",
"upload": "Su",
"download": "Down",
"upload": "Up",
"estimatedTimeOfArrival": "ETA",
"progress": "Avanzamento"
},
@@ -18,7 +18,7 @@
}
},
"lineChart": {
"title": "Velocità di download attuale",
"title": "Velocità di download",
"download": "Download: {{download}}",
"upload": "Upload: {{upload}}",
"timeSpan": "{{seconds}} secondi fa",
@@ -28,7 +28,7 @@
"errors": {
"noDownloadClients": {
"title": "Nessun client di download supportato trovato!",
"text": "Aggiungere un servizio di download per visualizzare i download correnti"
"text": "Aggiungi un servizio di download per visualizzare i tuoi download attuali"
}
}
}

View File

@@ -1,10 +1,10 @@
{
"descriptor": {
"name": "Docker",
"description": "Ti permette di gestire facilmente i tuoi container docker"
"description": "Gestisci facilmente i tuoi container di docker"
},
"search": {
"placeholder": "Ricerca per contenitore o nome dell'immagine"
"placeholder": "Ricerca per container nome immagine"
},
"table": {
"header": {
@@ -14,7 +14,7 @@
"state": "Stato"
},
"body": {
"portCollapse": "{{ports}} di più"
"portCollapse": "{{ports}} altro"
},
"states": {
"running": "In esecuzione",
@@ -38,13 +38,13 @@
"title": "Avvia"
},
"refreshData": {
"title": "Aggiornare i dati"
"title": "Aggiorna dati"
},
"remove": {
"title": "Rimuovi"
},
"addToHomarr": {
"title": "Aggiungi a Homarr"
"title": "Aggiungi ad Homarr"
}
},
"actions": {
@@ -61,13 +61,13 @@
"end": "Riavviato"
},
"remove": {
"start": "Rimozione",
"start": "Rimozione in corso",
"end": "Rimosso"
}
},
"errors": {
"integrationFailed": {
"title": "Integrazione di Docker fallita",
"title": "Integrazione Docker fallita",
"message": "Hai dimenticato di montare il socket di docker?"
},
"unknownError": {

View File

@@ -8,17 +8,17 @@
"buttons": {
"askFor": "Richiedi {{title}}",
"cancel": "Annulla",
"request": "Richiedi"
"request": "Richiesta"
},
"alerts": {
"automaticApproval": {
"title": "Utilizzo della chiave API",
"title": "Chiave API in uso",
"text": "Questa richiesta verrà approvata automaticamente"
}
}
},
"seasonSelector": {
"caption": "Spuntare le stagioni che si desidera scaricare.",
"caption": "Seleziona le stagioni che desideri scaricare",
"table": {
"header": {
"season": "Stagione",

View File

@@ -1,11 +1,11 @@
{
"descriptor": {
"name": "Ping",
"description": "Consente di verificare se il servizio è attivo o se restituisce un codice di stato HTTP specifico."
"description": "Consente di controllare se il servizio è attivo o restituisce uno specifico status code HTTP."
},
"states": {
"online": "Online {{response}}",
"offline": "Offline {{response}}",
"loading": "Caricamento..."
"loading": "Caricamento in corso..."
}
}

View File

@@ -1,9 +1,30 @@
{
"descriptor": {
"name": "Barra di ricerca",
"description": "Barra di ricerca per cercare sul web, su Youtube, su Torrents o su Overseerr"
"description": "Barra di ricerca per cercare sul web, Youtube, Torrent o Overseerr"
},
"input": {
"placeholder": "Cerca sul web..."
}
},
"switched-to": "Impostato su",
"searchEngines": {
"search": {
"name": "Web",
"description": "Cerca usando il tuo motore di ricerca (definito nelle impostazioni)"
},
"youtube": {
"name": "Youtube",
"description": "Cerca su YouTube"
},
"torrents": {
"name": "Torrents",
"description": "Cerca Torrents"
},
"overseerr": {
"name": "Overseerr",
"description": "Cerca film e serie TV con Overseerr (il modulo deve essere abilitato)"
}
},
"tip": "Puoi selezionare la barra di ricerca con la scorciatoia ",
"switchedSearchEngine": "Ricerca cambiata con {{searchEngine}}"
}

View File

@@ -1,10 +1,10 @@
{
"descriptor": {
"name": "Torrent",
"description": "Mostra la velocità di download attuale dei servizi supportati",
"description": "Mostra la velocità di download corrente dei servizi supportati",
"settings": {
"hideComplete": {
"label": "Nascondere i torrent completati"
"label": "Nascondi torrent completati"
}
}
},
@@ -13,8 +13,8 @@
"header": {
"name": "Nome",
"size": "Dimensione",
"download": "In basso",
"upload": "Su",
"download": "Down",
"upload": "Up",
"estimatedTimeOfArrival": "ETA",
"progress": "Avanzamento"
},
@@ -23,7 +23,7 @@
}
},
"lineChart": {
"title": "Velocità di download attuale",
"title": "Velocità di download",
"download": "Download: {{download}}",
"upload": "Upload: {{upload}}",
"timeSpan": "{{seconds}} secondi fa",
@@ -33,7 +33,7 @@
"errors": {
"noDownloadClients": {
"title": "Nessun client di download supportato trovato!",
"text": "Aggiungere un servizio di download per visualizzare i download correnti"
"text": "Aggiungi un servizio di download per visualizzare i tuoi download attuali"
}
}
}

View File

@@ -1,13 +1,13 @@
{
"descriptor": {
"name": "Usenet",
"description": "Ti consente di vedere la coda e la cronologia di usenet (Sabnzbd o NZBGet), mettere in pausa e riprendere i download."
"description": "Permette di vedere la coda e la cronologia di usenet (Sabnzbd o NZBGet), di mettere in pausa e riprendere i download"
},
"card": {
"errors": {
"noDownloadClients": {
"title": "Nessun client di download supportato trovato!",
"text": "Aggiungere un servizio di download per visualizzare i download correnti"
"text": "Aggiungi un servizio di download per visualizzare i tuoi download attuali"
}
}
},

View File

@@ -4,26 +4,26 @@
"description": "Consulta il meteo attuale della propria località",
"settings": {
"displayInFahrenheit": {
"label": "Visualizzazione in Fahrenheit"
"label": "Mostra in Fahrenheit"
},
"location": {
"label": "Posizione meteo"
"label": "Località meteo"
}
}
},
"card": {
"weatherDescriptions": {
"clear": "Cancella",
"mainlyClear": "Sereno",
"clear": "Sereno",
"mainlyClear": "Per lo più sereno",
"fog": "Nebbia",
"drizzle": "Pioggerella",
"freezingDrizzle": "Pioggia gelata",
"drizzle": "Pioggia leggera",
"freezingDrizzle": "Pioggia leggera gelata",
"rain": "Pioggia",
"freezingRain": "Pioggia gelata",
"snowFall": "Caduta della neve",
"snowGrains": "Granelli di neve",
"rainShowers": "Piogge a catinelle",
"snowShowers": "Piogge di neve",
"snowFall": "Neve",
"snowGrains": "Neve tonda",
"rainShowers": "Rovesci",
"snowShowers": "Forti nevicate",
"thunderstorm": "Temporale",
"thunderstormWithHail": "Temporale con grandine",
"unknown": "Sconosciuto"

View File

@@ -2,11 +2,11 @@
"title": "Impostazioni",
"tooltip": "Impostazioni",
"tabs": {
"common": "Comune",
"customizations": "Personalizzazioni"
"common": "Generale",
"customizations": "Personalizza"
},
"tips": {
"configTip": "Caricate il vostro file di configurazione trascinandolo sulla pagina!"
"configTip": "Carica il file di configurazione trascinandolo e rilasciandolo sulla pagina!"
},
"credits": {
"madeWithLove": "Realizzato con ❤️ da @"

View File

@@ -1,3 +1,3 @@
{
"label": "Larghezza dell'applicazione"
"label": "Larghezza App"
}

View File

@@ -1,3 +1,3 @@
{
"label": "Opacità dell'applicazione"
"label": "Opacità App"
}

View File

@@ -1,6 +1,6 @@
{
"pageTitle": {
"label": "Titolo della pagina",
"label": "Titolo pagina",
"placeholder": "Homarr 🦞"
},
"logo": {

View File

@@ -1,3 +1,3 @@
{
"label": "Ombreggiatura"
"label": "Tonalità"
}

View File

@@ -1,13 +1,13 @@
{
"configSelect": {
"label": "Caricatore di configurazione"
"label": "Carica configurazione"
},
"modal": {
"title": "Scegliere il nome della nuova configurazione",
"title": "Scegli il nome della nuova configurazione",
"form": {
"configName": {
"label": "Nome della configurazione",
"placeholder": "Il nome della nuova configurazione"
"label": "Nome configurazione",
"placeholder": "Nuovo nome configurazione"
},
"submitButton": "Conferma"
},
@@ -19,21 +19,21 @@
}
},
"buttons": {
"download": "Scarica la configurazione",
"download": "Download configurazione",
"delete": {
"text": "Cancellare la configurazione",
"text": "Elimina configurazione",
"notifications": {
"deleted": {
"title": "Configurazione cancellata",
"message": "Configurazione cancellata"
"title": "Configurazione eliminata",
"message": "Configurazione eliminata"
},
"deleteFailed": {
"title": "Cancellazione configurazione fallita",
"message": "Cancellazione configurazione fallita"
"title": "Eliminazione configurazione fallita",
"message": "Eliminazione configurazione fallita"
}
}
},
"saveCopy": "Salvare una copia"
"saveCopy": "Salva una copia"
},
"dropzone": {
"notifications": {
@@ -46,10 +46,10 @@
}
},
"accept": {
"text": "Trascinare i file qui per caricare una configurazione. Supporto solo per JSON."
"text": "Trascina qui i file per caricare una configurazione. Solo file JSON supportati."
},
"reject": {
"text": "Questo formato di file non è supportato. Caricare solo JSON."
"text": "Formato file non supportato. Caricare solo file JSON."
}
}
}

View File

@@ -1,3 +1,3 @@
{
"title": "Modulo abilitatore"
"title": "Abilitatore moduli"
}

View File

@@ -1,7 +1,7 @@
{
"title": "Motore di ricerca",
"tips": {
"generalTip": "Utilizzate i prefissi !yt e !t davanti alla vostra ricerca per cercare rispettivamente su YouTube o su un Torrent.",
"generalTip": "Usa i prefissi !yt e !t di fronte alla tua ricerca per cercare rispettivamente su YouTube o un Torrent.",
"placeholderTip": "%s può essere usato come segnaposto per la ricerca."
},
"customEngine": {

View File

@@ -1,3 +1,3 @@
{
"label": "Posizionare i widget a sinistra"
"label": "Posiziona widget a sinistra"
}

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "ウェブで検索..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "オーバーホール",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "ユースネット",
"description": "Usenet (Sabnzbd または NZBGet) のキューと履歴を表示し、ダウンロードを一時停止および再開できます。"
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -7,7 +7,7 @@
"label": "ロゴマーク"
},
"favicon": {
"label": "Favicon"
"label": "ファビコン"
},
"background": {
"label": "背景"

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "웹에서 검색..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "ユースネット",
"description": "유즈넷(Sabnzbd 또는 NZBGet) 대기열 및 기록을 보고 다운로드를 일시 중지 및 재개할 수 있습니다."
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -7,7 +7,7 @@
"label": "로고"
},
"favicon": {
"label": "Favicon"
"label": "파비콘"
},
"background": {
"label": "배경"

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Search teh web..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Allowz u 2 c ur usenet (Sabnzbd & NZBGet) queue an histowee, paues an resuem downloadz"
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Doorzoek het web..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Hiermee kunt u uw usenet (Sabnzbd of NZBGet) wachtrij en geschiedenis bekijken, downloads pauzeren en hervatten."
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Szukaj w internecie..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Pozwala zobaczyć kolejkę i historię usenetu (Sabnzbd lub NZBGet), wstrzymywać i wznawiać pobieranie."
"description": ""
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Pesquisar na Internet..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Permite que você veja sua fila e histórico de usenet (Sabnzbd ou NZBGet), pausar e retomar downloads."
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Искать в интернете..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Юнет",
"description": "Позволяет просматривать очередь и историю Usenet (Sabnzbd или NZBGet), приостанавливать и возобновлять загрузки."
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Preišči splet..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Omogoča ogled vaše čakalne vrste in zgodovine usenet (Sabnzbd ali NZBGet), zaustavitev in nadaljevanje prenosov."
"name": "",
"description": ""
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Sök på webben..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Usenet",
"description": "Låter dig se din usenet (Sabnzbd eller NZBGet) kö och historik, pausa och återuppta nedladdningar."
"description": "Låter dig se din usenet (Sabnzbd eller NZBGet) kö och historik, pausa och återuppta nedladdningar"
},
"card": {
"errors": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Пошук в Інтернеті..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "Sabnzbd",
"description": "Дозволяє переглядати чергу та історію usenet (Sabnzbd або NZBGet), призупиняти та відновлювати завантаження."
"name": "Usenet",
"description": "Дозволяє вам бачити вашу чергу та історію в usenet (Sabnzbd або NZBGet), керувати завантаженнями"
},
"card": {
"errors": {

View File

@@ -8,6 +8,7 @@
},
"favicon": {
"label": "Іконка"
},
"background": {
"label": "Фон"
},

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "Tìm kiếm trên web..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -9,6 +9,6 @@
"configTip": "Tải tệp cấu hình của bạn lên chỉ bằng cách kéo và thả tệp vào trong trang!"
},
"credits": {
"madeWithLove": "Được tạo bằng ❤️ bởi @"
"madeWithLove": "From @ with ❤️"
}
}

View File

@@ -19,7 +19,7 @@
}
},
"buttons": {
"download": "Tải cấu hình xuống",
"download": "Tải cấu hình",
"delete": {
"text": "Xóa cấu hình",
"notifications": {

View File

@@ -5,5 +5,26 @@
},
"input": {
"placeholder": "在网上搜索..."
}
},
"switched-to": "",
"searchEngines": {
"search": {
"name": "",
"description": ""
},
"youtube": {
"name": "",
"description": ""
},
"torrents": {
"name": "",
"description": ""
},
"overseerr": {
"name": "Overseerr",
"description": ""
}
},
"tip": "",
"switchedSearchEngine": ""
}

View File

@@ -1,7 +1,7 @@
{
"descriptor": {
"name": "用户网",
"description": "允许您查看您的用户网Sabnzbd 或 NZBGet队列和历史记录暂停和恢复下载。"
"name": "",
"description": ""
},
"card": {
"errors": {
@@ -16,7 +16,7 @@
"history": "历史"
},
"info": {
"sizeLeft": "左侧大小",
"sizeLeft": "左侧尺寸调整",
"paused": "暂停使用"
},
"queue": {

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Accordion, Grid, Paper, Stack, Text, useMantineColorScheme } from '@mantine/core';
import { Accordion, Grid, Stack, Title, useMantineColorScheme } from '@mantine/core';
import {
closestCenter,
DndContext,
@@ -9,16 +9,16 @@ import {
useSensor,
useSensors,
} from '@dnd-kit/core';
import { arrayMove, SortableContext } from '@dnd-kit/sortable';
import { useLocalStorage } from '@mantine/hooks';
import { useTranslation } from 'next-i18next';
import * as Modules from '../../modules';
import { useConfig } from '../../tools/state';
import { SortableAppShelfItem, AppShelfItem } from './AppShelfItem';
import { ModuleMenu, ModuleWrapper } from '../../modules/moduleWrapper';
import { AppShelfItem, SortableItem } from './AppShelfItem';
import { ModuleWrapper } from '../../modules/moduleWrapper';
import { UsenetModule, TorrentsModule } from '../../modules';
import TorrentsComponent from '../../modules/torrents/TorrentsModule';
import { UsenetComponent } from '../../modules/usenet/UsenetModule';
const AppShelf = (props: any) => {
const { config, setConfig } = useConfig();
@@ -79,13 +79,14 @@ const AppShelf = (props: any) => {
const getItems = (filter?: string) => {
// If filter is not set, return all the services without a category or a null category
let filtered = config.services;
const modules = Object.values(Modules).map((module) => module);
if (!filter) {
filtered = config.services.filter((e) => !e.category || e.category === null);
}
if (filter) {
filtered = config.services.filter((e) => e.category === filter);
}
return (
<DndContext
sensors={sensors}
@@ -94,17 +95,12 @@ const AppShelf = (props: any) => {
onDragEnd={handleDragEnd}
>
<SortableContext items={config.services}>
<Grid gutter="xl" align="center">
<Grid gutter="lg" align="center">
{filtered.map((service) => (
<Grid.Col
key={service.id}
span={6}
xl={config.settings.appCardWidth || 2}
xs={4}
sm={3}
md={3}
>
<SortableAppShelfItem service={service} key={service.id} id={service.id} />
<Grid.Col key={service.id} span="content">
<SortableItem service={service} key={service.id} id={service.id}>
<AppShelfItem service={service} />
</SortableItem>
</Grid.Col>
))}
</Grid>
@@ -123,100 +119,36 @@ const AppShelf = (props: any) => {
);
};
if (categoryList.length > 0) {
const noCategory = config.services.filter(
(e) => e.category === undefined || e.category === null
);
const torrentEnabled = config.modules?.[TorrentsModule.id]?.enabled ?? false;
const usenetEnabled = config.modules?.[UsenetModule.id]?.enabled ?? false;
const downloadEnabled = usenetEnabled || torrentEnabled;
// Create an item with 0: true, 1: true, 2: true... For each category
return (
// TODO: Style accordion so that the bar is transparent to the user settings
<Stack>
<Accordion
variant="separated"
radius="lg"
order={2}
multiple
value={toggledCategories}
onChange={(state) => {
setToggledCategories([...state]);
}}
>
{categoryList.map((category, idx) => (
<Accordion.Item
style={{
background: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '255, 255, 255,'} \
${(config.settings.appOpacity || 100) / 100}`,
borderColor: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '233, 236, 239,'} \
${(config.settings.appOpacity || 100) / 100}`,
}}
key={category}
value={idx.toString()}
>
<Accordion.Control>{category}</Accordion.Control>
<Accordion.Panel>{getItems(category)}</Accordion.Panel>
</Accordion.Item>
))}
{/* Return the item for all services without category */}
{noCategory && noCategory.length > 0 ? (
<Accordion.Item
style={{
background: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '255, 255, 255,'} \
${(config.settings.appOpacity || 100) / 100}`,
borderColor: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '233, 236, 239,'} \
${(config.settings.appOpacity || 100) / 100}`,
}}
key="Other"
value="Other"
>
<Accordion.Control>{t('accordions.others.text')}</Accordion.Control>
<Accordion.Panel>{getItems()}</Accordion.Panel>
</Accordion.Item>
) : null}
{downloadEnabled ? (
<Accordion.Item
style={{
color: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '255, 255, 255,'} \
${(config.settings.appOpacity || 100) / 100}`,
background: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '255, 255, 255,'} \
${(config.settings.appOpacity || 100) / 100}`,
borderColor: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '233, 236, 239,'} \
${(config.settings.appOpacity || 100) / 100}`,
}}
key="Downloads"
value="Your downloads"
>
<Accordion.Control>{t('accordions.downloads.text')}</Accordion.Control>
<Accordion.Panel>
<Paper radius="lg" style={{ position: 'relative' }}>
{torrentEnabled && (
<>
<Text>{t('accordions.downloads.torrents')}</Text>
<ModuleMenu module={TorrentsModule} hovered />
<TorrentsComponent />
</>
)}
{usenetEnabled && (
<>
<Text mt="md">{t('accordions.downloads.usenet')}</Text>
<ModuleMenu module={UsenetModule} hovered />
<UsenetComponent />
</>
)}
</Paper>
</Accordion.Panel>
</Accordion.Item>
) : null}
</Accordion>
</Stack>
);
}
return (
<Stack>
<Accordion
variant="separated"
radius="lg"
order={2}
multiple
value={toggledCategories}
onChange={(state) => {
setToggledCategories([...state]);
}}
>
{categoryList.map((category, idx) => (
<Accordion.Item
style={{
background: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '255, 255, 255,'} \
${(config.settings.appOpacity || 100) / 100}`,
borderColor: `rgba(${colorScheme === 'dark' ? '32, 33, 35,' : '233, 236, 239,'} \
${(config.settings.appOpacity || 100) / 100}`,
}}
key={category}
value={idx.toString()}
>
<Accordion.Control>
<Title order={5}>{category}</Title>
</Accordion.Control>
<Accordion.Panel>{getItems(category)}</Accordion.Panel>
</Accordion.Item>
))}
</Accordion>
{getItems()}
<ModuleWrapper mt="xl" module={TorrentsModule} />
<ModuleWrapper mt="xl" module={UsenetModule} />

View File

@@ -31,7 +31,7 @@ const useStyles = createStyles((theme) => ({
},
}));
export function SortableAppShelfItem(props: any) {
export function SortableItem(props: any) {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id: props.id,
});
@@ -43,7 +43,7 @@ export function SortableAppShelfItem(props: any) {
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<AppShelfItem service={props.service} />
{props.children}
</div>
);
}
@@ -75,6 +75,8 @@ export function AppShelfItem(props: any) {
shadow="md"
className={classes.item}
style={{
// Use the grab cursor when hovering over the card
cursor: hovering ? 'grab' : 'auto',
background: `rgba(${colorScheme === 'dark' ? '37, 38, 43,' : '255, 255, 255,'} \
${(config.settings.appOpacity || 100) / 100}`,
borderColor: `rgba(${colorScheme === 'dark' ? '37, 38, 43,' : '233, 236, 239,'} \
@@ -105,14 +107,14 @@ export function AppShelfItem(props: any) {
<AppShelfMenu service={service} />
</motion.div>
</Card.Section>
<Center>
<Card.Section>
<Card.Section>
<Center>
<AspectRatio
ratio={3 / 5}
m="xl"
m="lg"
style={{
width: 150,
height: 90,
height: 75 * ((config.settings.appCardWidth ?? 1) * 1.2),
width: 75 * ((config.settings.appCardWidth ?? 1) * 2),
}}
>
<motion.i
@@ -126,8 +128,8 @@ export function AppShelfItem(props: any) {
>
<Image
styles={{ root: { cursor: 'pointer' } }}
width={80}
height={80}
width={75 * ((config.settings.appCardWidth ?? 1) * 1.2)}
height={75 * ((config.settings.appCardWidth ?? 1) * 1.2)}
src={service.icon}
fit="contain"
/>
@@ -135,8 +137,8 @@ export function AppShelfItem(props: any) {
</motion.i>
</AspectRatio>
{service.ping !== false && <PingComponent url={service.url} status={service.status} />}
</Card.Section>
</Center>
</Center>
</Card.Section>
</Card>
</motion.div>
);

View File

@@ -21,11 +21,11 @@ export function AppCardWidthSelector() {
<Stack spacing="xs">
<Text>{t('label')}</Text>
<Slider
label={null}
defaultValue={config.settings.appCardWidth}
step={0.2}
min={0.8}
max={2}
label={config.settings.appCardWidth?.toFixed(1)}
defaultValue={config.settings.appCardWidth ?? 0.7}
step={0.1}
min={0.3}
max={1.2}
styles={{ markLabel: { fontSize: 'xx-small' } }}
onChange={(value) => setappCardWidth(value)}
/>

View File

@@ -9,7 +9,7 @@ export function Logo({ style, withoutText }: any) {
const { primaryColor, secondaryColor } = useColorTheme();
return (
<Group spacing="xs">
<Group spacing="xs" noWrap>
<Image
width={50}
src={config.settings.logo || '/imgs/logo/logo.png'}
@@ -18,26 +18,18 @@ export function Logo({ style, withoutText }: any) {
}}
/>
{withoutText ? null : (
<NextLink
href="/"
style={{
textDecoration: 'none',
position: 'relative',
<Text
sx={style}
weight="bold"
variant="gradient"
gradient={{
from: primaryColor,
to: secondaryColor,
deg: 145,
}}
>
<Text
sx={style}
weight="bold"
variant="gradient"
gradient={{
from: primaryColor,
to: secondaryColor,
deg: 145,
}}
>
{config.settings.title || 'Homarr'}
</Text>
</NextLink>
{config.settings.title || 'Homarr'}
</Text>
)}
</Group>
);

View File

@@ -1,27 +1,16 @@
import { Box, createStyles, Group, Header as Head, useMantineColorScheme } from '@mantine/core';
import { Group, Header as Head, useMantineColorScheme, useMantineTheme } from '@mantine/core';
import { useViewportSize } from '@mantine/hooks';
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
import DockerMenuButton from '../../../modules/docker/DockerModule';
import SearchBar from '../../../modules/search/SearchModule';
import { SettingsMenuButton } from '../../Settings/SettingsMenu';
import { Logo } from '../Logo';
import { useConfig } from '../../../tools/state';
const useStyles = createStyles((theme) => ({
hide: {
[theme.fn.smallerThan('xs')]: {
display: 'none',
},
},
burger: {
[theme.fn.largerThan('sm')]: {
display: 'none',
},
},
}));
import { SearchModuleComponent } from '../../../modules/search/SearchModule';
export function Header(props: any) {
const { classes } = useStyles();
const { width } = useViewportSize();
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
const { config } = useConfig();
const { colorScheme } = useMantineColorScheme();
@@ -35,12 +24,10 @@ export function Header(props: any) {
${(config.settings.appOpacity || 100) / 100}`,
}}
>
<Group p="xs" position="apart">
<Box className={classes.hide}>
<Logo style={{ fontSize: 22 }} />
</Box>
<Group noWrap>
<SearchBar />
<Group p="xs" noWrap grow>
{width > MIN_WIDTH_MOBILE && <Logo style={{ fontSize: 22 }} />}
<Group position="right" noWrap>
<SearchModuleComponent />
<DockerMenuButton />
<SettingsMenuButton />
<AddItemShelfButton />

View File

@@ -7,6 +7,7 @@ import {
ScrollArea,
createStyles,
useMantineTheme,
Space,
} from '@mantine/core';
import React, { useEffect, useState } from 'react';
import { Calendar } from '@mantine/dates';
@@ -143,12 +144,6 @@ export default function CalendarComponent(props: any) {
margin: 1,
}
}
styles={{
calendarHeader: {
marginRight: 40,
marginLeft: 40,
},
}}
allowLevelChange={false}
dayClassName={(date, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
renderDay={(renderdate) => (
@@ -278,10 +273,11 @@ function DayComponent(props: any) {
scrollbarSize={5}
style={{
height:
totalFiltered.slice(0, 2).length > 1 ? totalFiltered.slice(0, 2).length * 150 : 200,
totalFiltered.slice(0, 2).length > 1 ? totalFiltered.slice(0, 2).length * 150 : 220,
width: 400,
}}
>
<Space mt={5} />
{sonarrFiltered.map((media: any, index: number) => (
<React.Fragment key={index}>
<SonarrMediaDisplay media={media} />

View File

@@ -163,7 +163,7 @@ export function MediaDisplay({ media }: { media: IMedia }) {
const { t } = useTranslation('modules/common-media-cards');
return (
<Group mr="xs" align="stretch" noWrap style={{ maxHeight: 200 }}>
<Group mr="xs" align="stretch" noWrap style={{ maxHeight: 200 }} spacing="xs">
<Image src={media.poster} height={200} width={150} radius="md" fit="cover" />
<Stack justify="space-around">
<Stack spacing="sm">

View File

@@ -38,7 +38,7 @@ export const DashdotModule = asModule({
id: 'dashdot',
});
const useStyles = createStyles((theme, _params) => ({
const useStyles = createStyles((theme, _params, getRef) => ({
heading: {
marginTop: 0,
marginBottom: 10,
@@ -70,6 +70,21 @@ const useStyles = createStyles((theme, _params) => ({
maxWidth: '100%',
height: '140px',
borderRadius: theme.radius.lg,
border: 'none',
colorScheme: 'none',
},
graphTitle: {
ref: getRef('graphTitle'),
position: 'absolute',
right: 0,
opacity: 0,
transition: 'opacity .1s ease-in-out',
pointerEvents: 'none',
},
graphStack: {
[`&:hover .${getRef('graphTitle')}`]: {
opacity: 0.5,
},
},
}));
@@ -221,6 +236,7 @@ export function DashdotComponent() {
{graphs.map((graph) => (
<Stack
className={classes.graphStack}
style={
isCompact
? {
@@ -230,7 +246,7 @@ export function DashdotComponent() {
: undefined
}
>
<Title style={{ position: 'absolute', right: 0 }} order={4} mt={10} mr={25}>
<Title className={classes.graphTitle} order={4} mt={10} mr={25}>
{graph.name}
</Title>
<iframe
@@ -248,7 +264,7 @@ export function DashdotComponent() {
.join('&')}`
: ''
}`}
frameBorder="0"
allowTransparency
/>
</Stack>
))}

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Button, Group, TextInput, Title } from '@mantine/core';
import { closeAllModals, closeModal, openModal } from '@mantine/modals';
import { Button, Group } from '@mantine/core';
import { closeModal, openModal } from '@mantine/modals';
import { showNotification, updateNotification } from '@mantine/notifications';
import {
IconCheck,
@@ -72,7 +72,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
const { config, setConfig } = useConfig();
return (
<Group>
<Group spacing="xs">
<Button
leftIcon={<IconRefresh />}
onClick={() => {

View File

@@ -1,4 +1,5 @@
import { Table, Checkbox, Group, Badge, createStyles, ScrollArea, TextInput } from '@mantine/core';
import { Table, Checkbox, Group, Badge, createStyles, ScrollArea, TextInput, useMantineTheme } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons';
import Dockerode from 'dockerode';
import { useTranslation } from 'next-i18next';
@@ -23,9 +24,11 @@ export default function DockerTable({
containers: Dockerode.ContainerInfo[];
selection: Dockerode.ContainerInfo[];
}) {
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
const [usedContainers, setContainers] = useState<Dockerode.ContainerInfo[]>(containers);
const { classes, cx } = useStyles();
const [search, setSearch] = useState('');
const { ref, width, height } = useElementSize();
const { t } = useTranslation('modules/docker');
@@ -67,28 +70,30 @@ export default function DockerTable({
/>
</td>
<td>{element.Names[0].replace('/', '')}</td>
<td>{element.Image}</td>
<td>
<Group>
{element.Ports.sort((a, b) => a.PrivatePort - b.PrivatePort)
// Remove duplicates with filter function
.filter(
(port, index, self) =>
index === self.findIndex((t) => t.PrivatePort === port.PrivatePort)
)
.slice(-3)
.map((port) => (
<Badge key={port.PrivatePort} variant="outline">
{port.PrivatePort}:{port.PublicPort}
{width > MIN_WIDTH_MOBILE && <td>{element.Image}</td>}
{width > MIN_WIDTH_MOBILE && (
<td>
<Group>
{element.Ports.sort((a, b) => a.PrivatePort - b.PrivatePort)
// Remove duplicates with filter function
.filter(
(port, index, self) =>
index === self.findIndex((t) => t.PrivatePort === port.PrivatePort)
)
.slice(-3)
.map((port) => (
<Badge key={port.PrivatePort} variant="outline">
{port.PrivatePort}:{port.PublicPort}
</Badge>
))}
{element.Ports.length > 3 && (
<Badge variant="filled">
{t('table.body.portCollapse', { ports: element.Ports.length - 3 })}
</Badge>
))}
{element.Ports.length > 3 && (
<Badge variant="filled">
{t('table.body.portCollapse', { ports: element.Ports.length - 3 })}
</Badge>
)}
</Group>
</td>
)}
</Group>
</td>
)}
<td>
<ContainerState state={element.State} />
</td>
@@ -106,7 +111,7 @@ export default function DockerTable({
onChange={handleSearchChange}
disabled={usedContainers.length === 0}
/>
<Table captionSide="bottom" highlightOnHover sx={{ minWidth: 800 }} verticalSpacing="sm">
<Table ref={ref} captionSide="bottom" highlightOnHover verticalSpacing="sm">
<thead>
<tr>
<th style={{ width: 40 }}>
@@ -119,8 +124,8 @@ export default function DockerTable({
/>
</th>
<th>{t('table.header.name')}</th>
<th>{t('table.header.image')}</th>
<th>{t('table.header.ports')}</th>
{width > MIN_WIDTH_MOBILE ? <th>{t('table.header.image')}</th> : null}
{width > MIN_WIDTH_MOBILE ? <th>{t('table.header.ports')}</th> : null}
<th>{t('table.header.state')}</th>
</tr>
</thead>

View File

@@ -188,7 +188,7 @@ export function TvRequestModal({
</thead>
<tbody>{rows}</tbody>
</Table>
<Group>
<Group position="center">
<Button variant="outline" color="gray" onClick={() => setOpened(false)}>
{t('popup.item.buttons.cancel')}
</Button>

View File

@@ -1,236 +1,159 @@
import { Kbd, createStyles, Autocomplete, Popover, ScrollArea, Divider } from '@mantine/core';
import { useClickOutside, useDebouncedValue, useHotkeys } from '@mantine/hooks';
import { useForm } from '@mantine/form';
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import {
IconSearch as Search,
IconBrandYoutube as BrandYoutube,
IconDownload as Download,
IconMovie,
} from '@tabler/icons';
ActionIcon,
Box,
createStyles,
Divider,
Kbd,
Menu,
Popover,
ScrollArea,
TextInput,
Tooltip,
} from '@mantine/core';
import { IconSearch, IconBrandYoutube, IconDownload, IconMovie } from '@tabler/icons';
import React, { useEffect, useRef, useState } from 'react';
import { useDebouncedValue, useHotkeys } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
import { useTranslation } from 'next-i18next';
import axios from 'axios';
import { showNotification } from '@mantine/notifications';
import { useConfig } from '../../tools/state';
import { IModule } from '../ModuleTypes';
import { useConfig } from '../../tools/state';
import { OverseerrModule } from '../overseerr';
import Tip from '../../components/layout/Tip';
import { OverseerrMediaDisplay } from '../common';
import SmallServiceItem from '../../components/AppShelf/SmallServiceItem';
const useStyles = createStyles((theme) => ({
hide: {
[theme.fn.smallerThan('sm')]: {
display: 'none',
},
display: 'flex',
alignItems: 'center',
},
}));
export const SearchModule: IModule = {
title: 'Search',
icon: Search,
component: SearchBar,
icon: IconSearch,
component: SearchModuleComponent,
id: 'search',
};
export default function SearchBar(props: any) {
const { classes, cx } = useStyles();
// Config
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
label: string;
disabled: boolean;
value: string;
description: string;
icon: React.ReactNode;
url: string;
shortcut: string;
}
const useStyles = createStyles((theme) => ({
item: {
'&[data-hovered]': {
backgroundColor: theme.colors[theme.primaryColor][theme.fn.primaryShade()],
color: theme.white,
},
},
}));
export function SearchModuleComponent() {
const { config } = useConfig();
const isModuleEnabled = config.modules?.[SearchModule.id]?.enabled ?? false;
const { t } = useTranslation('modules/search');
const [searchQuery, setSearchQuery] = useState('');
const [debounced, cancel] = useDebouncedValue(searchQuery, 250);
const queryUrl = config.settings.searchUrl;
const isOverseerrEnabled = config.modules?.[OverseerrModule.id]?.enabled ?? false;
const OverseerrService = config.services.find(
(service) => service.type === 'Overseerr' || service.type === 'Jellyseerr'
);
const queryUrl = config.settings.searchUrl ?? 'https://www.google.com/search?q=';
const [OverseerrResults, setOverseerrResults] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [icon, setIcon] = useState(<Search />);
const [results, setResults] = useState<any[]>([]);
const [opened, setOpened] = useState(false);
const ref = useClickOutside(() => setOpened(false));
const textInput = useRef<HTMLInputElement>();
useHotkeys([['ctrl+K', () => textInput.current && textInput.current.focus()]]);
const form = useForm({
initialValues: {
query: '',
const searchEnginesList: ItemProps[] = [
{
icon: <IconSearch />,
disabled: false,
label: t('searchEngines.search.name'),
value: 'search',
description: t('searchEngines.search.description'),
url: queryUrl,
shortcut: 's',
},
});
const [debounced, cancel] = useDebouncedValue(form.values.query, 250);
const { t } = useTranslation('modules/search');
{
icon: <IconDownload />,
disabled: false,
label: t('searchEngines.torrents.name'),
value: 'torrents',
description: t('searchEngines.torrents.description'),
url: 'https://www.torrentdownloads.me/search/?search=',
shortcut: 't',
},
{
icon: <IconBrandYoutube />,
disabled: false,
label: t('searchEngines.youtube.name'),
value: 'youtube',
description: t('searchEngines.youtube.description'),
url: 'https://www.youtube.com/results?search_query=',
shortcut: 'y',
},
{
icon: <IconMovie />,
disabled: !(isOverseerrEnabled === true && OverseerrService !== undefined),
label: t('searchEngines.overseerr.name'),
value: 'overseerr',
description: t('searchEngines.overseerr.description'),
url: `${OverseerrService?.url}search?query=`,
shortcut: 'm',
},
];
const [selectedSearchEngine, setSearchEngine] = useState<ItemProps>(searchEnginesList[0]);
const textInput = useRef<HTMLInputElement>(null);
useHotkeys([['mod+K', () => textInput.current && textInput.current.focus()]]);
const { classes } = useStyles();
const openInNewTab = config.settings.searchNewTab ? '_blank' : '_self';
const [OverseerrResults, setOverseerrResults] = useState<any[]>([]);
const [opened, setOpened] = useState(false);
useEffect(() => {
if (OverseerrService === undefined && isOverseerrEnabled) {
showNotification({
title: 'Overseerr integration',
message:
'Module enabled but no service is configured with the type "Overseerr" / "Jellyseerr"',
color: 'red',
if (debounced !== '' && selectedSearchEngine.value === 'overseerr' && searchQuery.length > 3) {
axios.get(`/api/modules/overseerr?query=${searchQuery}`).then((res) => {
setOverseerrResults(res.data.results ?? []);
});
}
}, [OverseerrService, isOverseerrEnabled]);
useEffect(() => {
if (
form.values.query !== debounced ||
form.values.query === '' ||
(form.values.query.startsWith('!') && !form.values.query.startsWith('!os'))
) {
return;
}
if (form.values.query.startsWith('!os')) {
axios
.get(`/api/modules/overseerr?query=${form.values.query.replace('!os', '').trim()}`)
.then((res) => {
setOverseerrResults(res.data.results ?? []);
setLoading(false);
});
setLoading(true);
} else {
setOverseerrResults([]);
axios
.get(`/api/modules/search?q=${form.values.query}`)
.then((res) => setResults(res.data ?? []));
}
}, [debounced]);
const isModuleEnabled = config.modules?.[SearchModule.id]?.enabled ?? false;
if (!isModuleEnabled) {
return null;
}
// Match all the services that contain the query in their name if the query is not empty
const matchingServices = config.services.filter((service) => {
if (form.values.query === '' || form.values.query === undefined) {
return false;
}
return service.name.toLowerCase().includes(form.values.query.toLowerCase());
});
const autocompleteData = matchingServices.map((service) => ({
label: service.name,
value: service.name,
icon: service.icon,
url: service.openedUrl ?? service.url,
}));
// Append the matching results to the autocomplete data
const autoCompleteResults = results.map((result) => ({
label: result.phrase,
value: result.phrase,
icon: result.icon,
url: result.url,
}));
autocompleteData.push(...autoCompleteResults);
const AutoCompleteItem = forwardRef<HTMLDivElement, any>(
({ label, value, icon, url, ...others }: any, ref) => (
<div ref={ref} {...others}>
<SmallServiceItem service={{ label, value, icon, url }} />
</div>
)
);
//TODO: Fix the bug where clicking anything inside the Modal to ask for a movie
// will close it (Because it closes the underlying Popover)
return (
<form
onChange={() => {
// If query contains !yt or !t add "Searching on YouTube" or "Searching torrent"
const query = form.values.query.trim();
switch (query.substring(0, 3)) {
case '!yt':
setIcon(<BrandYoutube />);
break;
case '!t ':
setIcon(<Download />);
break;
case '!os':
setIcon(<IconMovie />);
break;
default:
setIcon(<Search />);
break;
}
}}
onSubmit={form.onSubmit((values) => {
const query = values.query.trim();
const open_in = config.settings.searchNewTab ? '_blank' : '_self';
setTimeout(() => {
form.setValues({ query: '' });
switch (query.substring(0, 3)) {
case '!yt':
window.open(
`https://www.youtube.com/results?search_query=${query.substring(3)}`,
open_in
);
break;
case '!t ':
window.open(
`https://www.torrentdownloads.me/search/?search=${query.substring(3)}`,
open_in
);
break;
case '!os':
break;
default:
window.open(
`${
queryUrl.includes('%s') ? queryUrl.replace('%s', query) : `${queryUrl}${query}`
}`,
open_in
);
break;
}
}, 500);
})}
>
<Box style={{ width: '100%', maxWidth: 400, minWidth: 300 }}>
<Popover
opened={OverseerrResults.length > 0 && opened}
opened={OverseerrResults.length > 0 && opened && searchQuery.length > 3}
position="bottom"
withArrow
withinPortal
shadow="md"
radius="md"
zIndex={100}
trapFocus
transition="pop-top-right"
>
<Popover.Target>
<Autocomplete
<TextInput
ref={textInput}
onFocusCapture={() => setOpened(true)}
autoFocus
variant="filled"
itemComponent={AutoCompleteItem}
onItemSubmit={(item) => {
setOpened(false);
if (item.url) {
results.splice(0, autocompleteData.length);
form.reset();
window.open(item.url);
rightSection={<SearchModuleMenu />}
placeholder={t(`searchEngines.${selectedSearchEngine.value}.description`)}
value={searchQuery}
onChange={(event) => tryMatchSearchEngine(event.currentTarget.value, setSearchQuery)}
// Replace %s if it is in selectedSearchEngine.url with searchQuery, otherwise append searchQuery at the end of it
onKeyDown={(event) => {
if (event.key === 'Enter' && searchQuery.length > 0) {
if (selectedSearchEngine.url.includes('%s')) {
window.open(selectedSearchEngine.url.replace('%s', searchQuery), openInNewTab);
} else {
window.open(selectedSearchEngine.url + searchQuery, openInNewTab);
}
}
}}
data={autocompleteData}
icon={icon}
ref={textInput}
rightSectionWidth={90}
rightSection={
<div className={classes.hide}>
<Kbd>Ctrl</Kbd>
<span style={{ margin: '0 5px' }}>+</span>
<Kbd>K</Kbd>
</div>
}
radius="md"
size="md"
styles={{ rightSection: { pointerEvents: 'none' } }}
placeholder={t('input.placeholder')}
{...props}
{...form.getInputProps('query')}
/>
</Popover.Target>
<Popover.Dropdown>
<div ref={ref}>
<ScrollArea style={{ height: 400, width: 400 }} offsetScrollbars>
<div>
<ScrollArea style={{ height: 400, width: 420 }} offsetScrollbars>
{OverseerrResults.slice(0, 5).map((result, index) => (
<React.Fragment key={index}>
<OverseerrMediaDisplay key={result.id} media={result} />
@@ -241,6 +164,71 @@ export default function SearchBar(props: any) {
</div>
</Popover.Dropdown>
</Popover>
</form>
</Box>
);
function tryMatchSearchEngine(query: string, setSearchQuery: (value: string) => void) {
const foundSearchEngine = searchEnginesList.find(
(engine) => query.includes(`!${engine.shortcut}`) && !engine.disabled
);
if (foundSearchEngine) {
setSearchQuery(query.replace(`!${foundSearchEngine.shortcut}`, ''));
changeSearchEngine(foundSearchEngine);
} else {
setSearchQuery(query);
}
}
function SearchModuleMenu() {
return (
<Menu shadow="md" width={200} withinPortal classNames={classes}>
<Menu.Target>
<ActionIcon>{selectedSearchEngine.icon}</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
{searchEnginesList.map((item) => (
<Tooltip
multiline
label={item.description}
withinPortal
width={200}
position="left"
key={item.value}
>
<Menu.Item
key={item.value}
icon={item.icon}
rightSection={<Kbd>!{item.shortcut}</Kbd>}
disabled={item.disabled}
onClick={() => {
changeSearchEngine(item);
}}
>
{item.label}
</Menu.Item>
</Tooltip>
))}
<Menu.Divider />
<Menu.Label>
<Tip>
{t('tip')} <Kbd>mod+k</Kbd>{' '}
</Tip>
</Menu.Label>
</Menu.Dropdown>
</Menu>
);
}
function changeSearchEngine(item: ItemProps) {
setSearchEngine(item);
showNotification({
radius: 'lg',
disallowClose: true,
id: 'spotlight',
autoClose: 1000,
icon: <ActionIcon size="sm">{item.icon}</ActionIcon>,
message: t('switchedSearchEngine', { searchEngine: t(`searchEngines.${item.value}.name`) }),
});
}
}

View File

@@ -9,11 +9,12 @@ import {
ScrollArea,
Center,
Stack,
useMantineTheme,
} from '@mantine/core';
import { IconDownload as Download } from '@tabler/icons';
import { useEffect, useState } from 'react';
import axios from 'axios';
import { useViewportSize } from '@mantine/hooks';
import { useElementSize } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
import { NormalizedTorrent } from '@ctrl/shared-torrent';
import { useTranslation } from 'next-i18next';
@@ -34,15 +35,10 @@ export const TorrentsModule: IModule = {
value: false,
},
},
padding: {
right: 0,
top: -10,
},
};
export default function TorrentsComponent() {
const { config } = useConfig();
const { height, width } = useViewportSize();
const downloadServices =
config.services.filter(
(service) =>
@@ -56,6 +52,8 @@ export default function TorrentsComponent() {
const [torrents, setTorrents] = useState<NormalizedTorrent[]>([]);
const setSafeInterval = useSetSafeInterval();
const [isLoading, setIsLoading] = useState(true);
const { ref, width, height } = useElementSize();
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
const { t } = useTranslation(`modules/${TorrentsModule.id}`);
@@ -112,14 +110,13 @@ export default function TorrentsComponent() {
</>
);
}
const DEVICE_WIDTH = 576;
const ths = (
<tr>
<tr ref={ref}>
<th>{t('card.table.header.name')}</th>
<th>{t('card.table.header.size')}</th>
{width > 576 ? <th>{t('card.table.header.download')}</th> : ''}
{width > 576 ? <th>{t('card.table.header.upload')}</th> : ''}
<th>{t('card.table.header.estimatedTimeOfArrival')}</th>
{width > MIN_WIDTH_MOBILE && <th>{t('card.table.header.download')}</th>}
{width > MIN_WIDTH_MOBILE && <th>{t('card.table.header.upload')}</th>}
{width > MIN_WIDTH_MOBILE && <th>{t('card.table.header.estimatedTimeOfArrival')}</th>}
<th>{t('card.table.header.progress')}</th>
</tr>
);
@@ -164,23 +161,21 @@ export default function TorrentsComponent() {
<td>
<Text size="xs">{humanFileSize(size)}</Text>
</td>
{width > 576 ? (
{width > MIN_WIDTH_MOBILE && (
<td>
<Text size="xs">{downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
</td>
) : (
''
)}
{width > 576 ? (
{width > MIN_WIDTH_MOBILE && (
<td>
<Text size="xs">{uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
</td>
) : (
''
)}
<td>
<Text size="xs">{torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)}</Text>
</td>
{width > MIN_WIDTH_MOBILE && (
<td>
<Text size="xs">{torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)}</Text>
</td>
)}
<td>
<Text>{(torrent.progress * 100).toFixed(1)}%</Text>
<Progress

View File

@@ -1,10 +1,21 @@
import { Badge, Button, Group, Select, Stack, Tabs, Text, Title } from '@mantine/core';
import {
Badge,
Button,
Group,
Select,
Stack,
Tabs,
Text,
Title,
useMantineTheme,
} from '@mantine/core';
import { IconDownload, IconPlayerPause, IconPlayerPlay } from '@tabler/icons';
import { FunctionComponent, useEffect, useState } from 'react';
import { useTranslation } from 'next-i18next';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { useElementSize } from '@mantine/hooks';
import { IModule } from '../ModuleTypes';
import { UsenetQueueList } from './UsenetQueueList';
import { UsenetHistoryList } from './UsenetHistoryList';
@@ -48,40 +59,46 @@ export const UsenetComponent: FunctionComponent = () => {
return null;
}
const { ref, width, height } = useElementSize();
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
return (
<Tabs keepMounted={false} defaultValue="queue">
<Group mb="md">
<Tabs.List style={{ flex: 1 }}>
<Tabs.Tab value="queue">{t('tabs.queue')}</Tabs.Tab>
<Tabs.Tab value="history">{t('tabs.history')}</Tabs.Tab>
{data && (
<Group position="right" ml="auto" mb="lg">
<Badge>{humanFileSize(data?.speed)}/s</Badge>
<Badge>
{t('info.sizeLeft')}: {humanFileSize(data?.sizeLeft)}
</Badge>
{data.paused ? (
<Button uppercase onClick={() => resume()} radius="xl" size="xs">
<IconPlayerPlay size={16} style={{ marginRight: 5 }} /> {t('info.paused')}
</Button>
) : (
<Button uppercase onClick={() => pause()} radius="xl" size="xs">
<IconPlayerPause size={16} style={{ marginRight: 5 }} />{' '}
{dayjs.duration(data.eta, 's').format('HH:mm:ss')}
</Button>
)}
</Group>
)}
</Tabs.List>
{downloadServices.length > 1 && (
<Select
value={selectedServiceId}
onChange={setSelectedService}
ml="xs"
data={downloadServices.map((service) => ({ value: service.id, label: service.name }))}
/>
<Tabs.List ref={ref} mb="md" style={{ flex: 1 }}>
<Tabs.Tab value="queue">{t('tabs.queue')}</Tabs.Tab>
<Tabs.Tab value="history">{t('tabs.history')}</Tabs.Tab>
{data && (
<Group position="right" ml="auto">
{width > MIN_WIDTH_MOBILE && (
<>
<Badge>{humanFileSize(data?.speed)}/s</Badge>
<Badge>
{t('info.sizeLeft')}: {humanFileSize(data?.sizeLeft)}
</Badge>
</>
)}
{data.paused ? (
<Button uppercase onClick={() => resume()} radius="xl" size="xs">
<IconPlayerPlay size={12} style={{ marginRight: 5 }} /> {t('info.paused')}
</Button>
) : (
<Button uppercase onClick={() => pause()} radius="xl" size="xs">
<IconPlayerPause size={12} style={{ marginRight: 5 }} />{' '}
{dayjs.duration(data.eta, 's').format('HH:mm')}
</Button>
)}
</Group>
)}
</Group>
</Tabs.List>
{downloadServices.length > 1 && (
<Select
value={selectedServiceId}
onChange={setSelectedService}
ml="xs"
data={downloadServices.map((service) => ({ value: service.id, label: service.name }))}
/>
)}
<Tabs.Panel value="queue">
<UsenetQueueList serviceId={selectedServiceId} />
</Tabs.Panel>

View File

@@ -1,3 +1,4 @@
import Consola from 'consola';
import { NextApiRequest, NextApiResponse } from 'next';
function Post(req: NextApiRequest, res: NextApiResponse) {
@@ -8,6 +9,12 @@ function Post(req: NextApiRequest, res: NextApiResponse) {
success: true,
});
}
// Warn that there was a wrong password attempt (date : wrong password, person's IP)
Consola.warn(
`${new Date().toLocaleString()} : Wrong password attempt, from ${
req.headers['x-forwarded-for']
}`
);
return res.status(200).json({
success: false,
});

View File

@@ -1,11 +1,13 @@
import axios from 'axios';
import https from 'https';
import { NextApiRequest, NextApiResponse } from 'next';
async function Get(req: NextApiRequest, res: NextApiResponse) {
// Parse req.body as a ServiceItem
const { url } = req.query;
const agent = new https.Agent({ rejectUnauthorized: false });
await axios
.get(url as string)
.get(url as string, { httpsAgent: agent })
.then((response) => {
res.status(response.status).json(response.statusText);
})