This commit is contained in:
BuGu
2024-09-27 18:39:02 +08:00
15 changed files with 499 additions and 74 deletions

View File

@@ -1,6 +1,9 @@
export const languages = [
{ key: 'de', name: 'Deutsch' },
{ key: 'en', name: 'English' },
{ key: 'fr', name: 'Français' },
{ key: 'it', name: 'Italian' },
{ key: 'pl', name: 'Polski' },
{ key: 'ru', name: 'Русский' },
{ key: 'ko', name: '한국어' },
{ key: 'zh', name: '中文' }

View File

@@ -1,4 +1,4 @@
const en = {
const de = {
translation: {
language: 'Sprache',
changePassword: 'Passwort ändern',
@@ -12,7 +12,8 @@ const en = {
powerLong: 'Einschalten (Langes Drücken)',
hddLed: 'Festplatten-LED',
checkLibFailed: 'Überprüfung der Laufzeitbibliothek fehlgeschlagen, bitte erneut versuchen.',
updateLibFailed: 'Aktualisierung der Laufzeitbibliothek fehlgeschlagen, bitte erneut versuchen.',
updateLibFailed:
'Aktualisierung der Laufzeitbibliothek fehlgeschlagen, bitte erneut versuchen.',
updatingLib: 'Aktualiserung der Laufzeitbibliothek. Bitte nach dem Update die Seite neu laden.',
checkForUpdate: 'Nach Update suchen',
head: {
@@ -25,11 +26,12 @@ const en = {
login: 'Einloggen',
placeholderUsername: 'Bitte den Benutzernamen eingeben',
placeholderPassword: 'Bitte das Passwort eingeben',
placeholderPassword2: 'Bitte das Passwort erneut eingeben',
placeholderPassword2: 'Passwort wiederholen',
noEmptyUsername: 'Der Benutzername darf nicht leer sein',
noEmptyPassword: 'Das Passwort darf nicht leer sein',
noAccount: 'Fehler beim Auslsen der Benutzerinformation, bitte die Seite neu laden oder das Passwort zurücksetzen',
invalidUser: 'Ungültiger Username oder ungültiges Passwort',
noAccount:
'Fehler beim Auslsen der Benutzerinformation, bitte die Seite neu laden oder das Passwort zurücksetzen',
invalidUser: 'Falscher Benutzername oder falsches Passwort',
error: 'Unerwarteter Fehler',
changePassword: 'Passwort ändern',
differentPassword: 'Passwörter stimmt nicht überein',
@@ -37,7 +39,8 @@ const en = {
illegalPassword: 'Passwort beinhaltet ungültige Zeichen',
forgetPassword: 'Passwort vergessen',
resetPassword: 'Password zurücksetzen',
reset1: 'Falls sie das Passwort vergessen haben, führen sie folgende Schritte durch um dieses zurückzusetzen:',
reset1:
'Falls sie das Passwort vergessen haben, führen sie folgende Schritte durch um dieses zurückzusetzen:',
reset2: '1. In das NanoKVM via SSH einloggen;',
reset3: '2. Die Datei auf dem Gerät löschen: ',
reset4: '3. Den Standardaccount zum Einloggen benutzen: ',
@@ -48,13 +51,13 @@ const en = {
resolution: 'Auflösung',
auto: 'Automatisch',
autoTips:
"Falls es bei bestimmten Auflösungen zu Screen-Tearing oder einem Offset des Mauszeigers kommen sollte, bitte die Auflösung des Remote Hosts anpassen oder den automatischen Modus ausschalten.",
'Falls es bei bestimmten Auflösungen zu Screen-Tearing oder einem Offset des Mauszeigers kommen sollte, bitte die Auflösung des Remote Hosts anpassen oder den automatischen Modus ausschalten.',
fps: 'FPS',
customizeFps: 'Anpassen',
quality: 'Qualität',
frameDetect: 'Frame Detect',
frameDetectTip:
"Berechnet den Unterschied zwischen den Einzelbildern. Beendet die Liveübertragung des Videostreams wenn keine Änderungen auf dem Bildschirm des Hosts festgestellt werden kann."
'Berechnet den Unterschied zwischen den Einzelbildern. Beendet die Liveübertragung des Videostreams wenn keine Änderungen auf dem Bildschirm des Hosts festgestellt werden kann.'
},
keyboard: {
paste: 'Einfügen',
@@ -73,18 +76,22 @@ const en = {
mode: 'Maus Modus',
absolute: 'Absoluter Modus',
relative: 'Relativer Modus',
requestPointer: 'Der Relative Modus wird benutzt. Bitte auf den Desktop klicken um einen Mauszeiger anzuzeigen.',
requestPointer:
'Der Relative Modus wird benutzt. Bitte auf den Desktop klicken um einen Mauszeiger anzuzeigen.',
resetHid: 'Eingeabegeräte zurücksetzen'
},
image: {
title: 'ISO oder Festplatten Abbilder',
loading: 'Laden...',
empty: 'Nichts gefunden',
mountFailed: 'Mounten fehlgeschlagen',
mountDesc:
'Bei einigen Systemen muss die virtuelle Festplatte auf dem Remote-Host ausgeworfen werden, bevor das Image gemountet werden kann.',
tips: {
title: 'Wie man Abbilder hochlädt',
usb1: 'Den NanoKVM via USB mit dem Computer verbinden.',
usb2: 'Stellen sie sicher, das das Virtuelle USB-Gerät eingebunden ist. (Einstellungen - Virtuelles USB-Gerät).',
usb3: 'Öffnen sie die virtuelle USB-Disk auf ihrem Computer und kopieren sie die Abbilddatei in das root-Verzeichnis des USB-Geräts.',
usb2: 'Stellen sie sicher, das das Virtuelle Festplatte eingebunden ist. (Einstellungen - Virtuelle Festplatte).',
usb3: 'Öffnen sie die Virtuelle Festplatte auf ihrem Computer und kopieren sie die Abbilddatei in das root-Verzeichnis des USB-Geräts.',
scp1: 'Stellen sie sicher, das sich der NanoKVM und ihr Computer im gleichen lokalen Netzwerk befinden.',
scp2: 'Öffnen sie ein Terminal auf ihrem Computer und benutzen sie den SCP-Befehl um die Abbilddatei in das /data Verzeichnis des NanoKVM zu kopieren.',
scp3: 'Beispiel: scp ihr-abbild-verzeichnis root@ihre-nanokvm-ip:/data',
@@ -114,7 +121,7 @@ const en = {
nanokvm: 'NanoKVM Terminal',
serial: 'Terminal über den Seriellen Port',
serialPort: 'Serieller Port',
serialPortPlaceholder: 'Bitte die Nummer des seriellen Ports angeben',
serialPortPlaceholder: 'Serielle Portnummer eingeben',
baudrate: 'Baudrate',
confirm: 'Ok'
},
@@ -140,14 +147,15 @@ const en = {
queryFailed: 'Versionsnummer konnte nicht erkannt werden',
updateFailed: 'Update gescheitert. Bitte versuchen sie es erneut.',
isLatest: 'Die aktuelle Version ist bereits installiert.',
available: 'Ein Update ist verfügbar. Sind sie sicher, dass sie diese Version aktualisieren möchten?',
available:
'Ein Update ist verfügbar. Sind sie sicher, dass sie diese Version aktualisieren möchten?',
updating: 'Update wird gestartet. Bitte warten...',
confirm: 'Bestätigen',
cancel: 'Abbrechen'
},
virtualDevice: {
network: 'Virtuelles Netzwerk',
usb: 'Virtuelles USB Gerät'
disk: 'Virtuelle Festplatte'
},
tailscale: {
loading: 'Lade...',
@@ -155,7 +163,8 @@ const en = {
install: 'Installieren',
installing: 'Installation wird durchgeführt',
failed: 'Installation gescheitert',
retry: 'Bitte neu laden und noch einmal versuchen. Oder versuchen sie Tailscale manuell zu installieren',
retry:
'Bitte neu laden und noch einmal versuchen. Oder versuchen sie Tailscale manuell zu installieren',
download: 'Laden sie das',
package: 'Installationspaket',
unzip: 'und entpacken sie es manuell',
@@ -168,7 +177,7 @@ const en = {
login: 'Einloggen',
loginSuccess: 'Das Einloggen war erfolgreich',
enable: 'Tailscale aktivieren',
deviceName: 'Gerätename',
deviceName: 'Name des Geräts',
deviceIP: 'Geräte-IP',
account: 'Benutzerkonto',
logout: 'Ausloggen',

View File

@@ -80,11 +80,14 @@ const en = {
title: 'Images',
loading: 'Loading...',
empty: 'Nothing Found',
mountFailed: 'Mount Failed',
mountDesc:
"In some systems, it's necessary to eject the virtual disk on the remote host before mounting the image.",
tips: {
title: 'How to upload',
usb1: 'Connect the NanoKVM to your computer via USB.',
usb2: 'Ensure that the Virtual USB is mounted (Settings - Virtual USB).',
usb3: 'Open the virtual usb disk on your computer and copy the image file to the root directory of the usb disk.',
usb2: 'Ensure that the virtual disk is mounted (Settings - Virtual Disk).',
usb3: 'Open the virtual disk on your computer and copy the image file to the root directory of the virtual disk.',
scp1: 'Make sure the NanoKVM and your computer are on the same local network.',
scp2: 'Open a terminal on your computer and use the SCP command to upload the image file to the /data directory on the NanoKVM.',
scp3: 'Example: scp your-image-path root@your-nanokvm-ip:/data',
@@ -147,7 +150,7 @@ const en = {
},
virtualDevice: {
network: 'Virtual Network',
usb: 'Virtual USB'
disk: 'Virtual Disk'
},
tailscale: {
loading: 'Loading...',

View File

@@ -84,11 +84,14 @@ const fr = {
title: 'Images',
loading: 'Chargement',
empty: 'Vide',
mountFailed: "Échec du montage de l'image.",
mountDesc:
"Dans certains systèmes, il est nécessaire de déséjecter le disque virtuel sur l'hôte distant avant de monter l'image.",
tips: {
title: 'Comment télécharger',
usb1: 'Connectez le NanoKVM à votre ordinateur via USB.',
usb2: 'Assurez-vous que le disque virtuel est monté (Paramètres - Disque virtuel).',
usb3: 'Ouvrez le disque virtuel sur votre ordinateur et copiez le fichier image dans le répertoire racine du disque.',
usb3: 'Ouvrez le disque virtuel sur votre ordinateur et copiez le fichier image dans le répertoire racine du disque virtuel.',
scp1: 'Assurez-vous que le NanoKVM et votre ordinateur sont sur le même réseau local.',
scp2: 'Ouvrez un terminal sur votre ordinateur et utilisez la commande SCP pour télécharger le fichier image dans le répertoire /data du NanoKVM.',
scp3: 'Exemple : scp chemin-de-votre-image root@ip-de-votre-nanokvm:/data',
@@ -150,8 +153,8 @@ const fr = {
cancel: 'Annuler'
},
virtualDevice: {
network: 'Réseau virtuel',
usb: 'USB virtuel'
network: 'carte réseau virtuelle',
disk: 'disque virtuel'
},
tailscale: {
loading: 'Chargement...',

185
web/src/i18n/locales/it.ts Normal file
View File

@@ -0,0 +1,185 @@
const it = {
translation: {
language: 'Lingua',
changePassword: 'Cambia Password',
logout: 'Disconnetti',
settings: 'Impostazioni',
showMouse: 'Mostra mouse',
hideMouse: 'Nascondi mouse',
power: 'Accensione',
reset: 'Reimposta',
powerShort: 'Accensione (clic breve)',
powerLong: 'Accensione (clic lungo)',
hddLed: 'LED HDD',
checkLibFailed: 'Impossibile verificare la libreria runtime, riprova',
updateLibFailed: 'Impossibile aggiornare la libreria runtime, riprova',
updatingLib: "Aggiornamento libreria runtime. Aggiorna la pagina dopo l'aggiornamento.",
checkForUpdate: 'Controlla Aggiornamenti',
head: {
desktop: 'Desktop Remoto',
login: 'Accesso',
changePassword: 'Cambia Password',
terminal: 'Terminale'
},
auth: {
login: 'Accesso',
placeholderUsername: 'Inserisci il nome utente',
placeholderPassword: 'Inserisci la password',
placeholderPassword2: 'Inserisci nuovamente la password',
noEmptyUsername: 'Il nome utente non può essere vuoto',
noEmptyPassword: 'La password non può essere vuota',
noAccount:
'Impossibile ottenere informazioni utente, aggiorna la pagina o reimposta la password',
invalidUser: 'Nome utente o password non validi',
error: 'Errore imprevisto',
changePassword: 'Cambia Password',
differentPassword: 'Le password non corrispondono',
illegalUsername: 'Il nome utente contiene caratteri non validi',
illegalPassword: 'La password contiene caratteri non validi',
forgetPassword: 'Hai dimenticato la password',
resetPassword: 'Reimposta Password',
reset1: 'Se hai dimenticato la password, segui questi passaggi per reimpostarla:',
reset2: '1. Accedi al dispositivo NanoKVM tramite SSH;',
reset3: '2. Elimina il file nel dispositivo: ',
reset4: "3. Usa l'account predefinito per accedere: ",
ok: 'Ok',
cancel: 'Annulla'
},
screen: {
resolution: 'Risoluzione',
auto: 'Automatico',
autoTips:
'Potrebbero verificarsi tearing dello schermo o spostamento del mouse a risoluzioni specifiche. Considera di regolare la risoluzione del dispositivo remoto o disabilitare la modalità automatica.',
fps: 'FPS',
customizeFps: 'Personalizza',
quality: 'Qualità',
frameDetect: 'Rilevamento Frame',
frameDetectTip:
'Calcola la differenza tra i frame. Interrompe la trasmissione del flusso video quando non vengono rilevate modifiche sullo schermo del dispositivo remoto.'
},
keyboard: {
paste: 'Incolla',
tips: 'Sono supportati solo lettere e simboli standard della tastiera',
placeholder: 'Inserisci testo',
submit: 'Invia',
virtual: 'Tastiera'
},
mouse: {
default: 'Cursore predefinito',
pointer: 'Cursore a puntatore',
cell: 'Cursore a cella',
text: 'Cursore testo',
grab: 'Cursore di presa',
hide: 'Nascondi cursore',
mode: 'Modalità mouse',
absolute: 'Modalità assoluta',
relative: 'Modalità relativa',
requestPointer:
'Usando la modalità relativa. Clicca sul desktop per ottenere il puntatore del mouse.',
resetHid: 'Reimposta HID'
},
image: {
title: 'Immagini',
loading: 'Caricamento...',
empty: 'Nessun risultato',
mountFailed: 'Montaggio immagine fallito',
mountDesc:
"In alcuni sistemi, è necessario espellere il disco virtuale sull'host remoto prima di montare l'immagine.",
tips: {
title: 'Come caricare',
usb1: 'Collega il NanoKVM al tuo computer tramite USB.',
usb2: 'Assicurati che la Virtual Disk sia montata (Impostazioni - Virtual Disk).',
usb3: 'Apri il disk virtuale sul tuo computer e copia il file immagine nella directory principale del disk.',
scp1: 'Assicurati che il NanoKVM e il tuo computer siano sulla stessa rete locale.',
scp2: 'Apri un terminale sul tuo computer e usa il comando SCP per caricare il file immagine nella directory /data del NanoKVM.',
scp3: 'Esempio: scp il-tuo-percorso-immagine root@il-tuo-ip-nanokvm:/data',
tfCard: 'Scheda TF',
tf1: 'Questo metodo è supportato su sistemi Linux',
tf2: 'Recupera la scheda TF dal NanoKVM (per la versione FULL, smonta prima il case).',
tf3: 'Inserisci la scheda TF in un lettore di schede e collegala al tuo computer.',
tf4: 'Copia il file immagine nella directory /data sulla scheda TF.',
tf5: 'Inserisci la scheda TF nel NanoKVM.'
}
},
script: {
title: 'Script',
upload: 'Carica',
run: 'Esegui',
runBackground: 'Esegui in Background',
runFailed: 'Esecuzione fallita',
attention: 'Attenzione',
delDesc: 'Sei sicuro di voler eliminare questo file?',
confirm: 'Sì',
cancel: 'No',
delete: 'Elimina',
close: 'Chiudi'
},
terminal: {
title: 'Terminale',
nanokvm: 'Terminale NanoKVM',
serial: 'Terminale Porta Seriale',
serialPort: 'Porta Seriale',
serialPortPlaceholder: 'Inserisci la porta seriale',
baudrate: 'Baud rate',
confirm: 'Ok'
},
wol: {
sending: 'Invio comando...',
sent: 'Comando inviato',
input: 'Inserisci il MAC',
ok: 'Ok'
},
about: {
title: 'Informazioni su NanoKVM',
information: 'Informazioni',
ip: 'IP',
mdns: 'mDNS',
firmware: 'Versione Applicazione',
image: 'Versione Immagine',
deviceKey: 'Chiave Dispositivo',
queryFailed: 'Query fallita',
community: 'Comunità'
},
update: {
title: 'Controlla Aggiornamenti',
queryFailed: 'Impossibile ottenere la versione',
updateFailed: 'Aggiornamento fallito. Riprova.',
isLatest: 'Hai già la versione più recente.',
available: 'Un aggiornamento è disponibile. Sei sicuro di voler aggiornare?',
updating: 'Aggiornamento avviato. Attendere prego...',
confirm: 'Conferma',
cancel: 'Annulla'
},
virtualDevice: {
network: 'Rete Virtuale',
usb: 'Disk Virtuale'
},
tailscale: {
loading: 'Caricamento...',
notInstall: 'Tailscale non trovato! Per favore, installa.',
install: 'Installa',
installing: 'Installazione in corso',
failed: 'Installazione fallita',
retry: 'Riprova aggiornando la pagina o installa manualmente',
download: 'Scarica il',
package: 'pacchetto di installazione',
unzip: 'e decomprimilo',
upTailscale: 'Carica tailscale nella directory /usr/bin/ del NanoKVM',
upTailscaled: 'Carica tailscaled nella directory /usr/sbin/ del NanoKVM',
refresh: 'Aggiorna la pagina corrente',
notLogin:
'Il dispositivo non è ancora stato associato. Effettua il login e associa questo dispositivo al tuo account.',
urlPeriod: 'Questo URL è valido per 10 minuti',
login: 'Accedi',
loginSuccess: 'Accesso riuscito',
enable: 'Abilita Tailscale',
deviceName: 'Nome Dispositivo',
deviceIP: 'IP Dispositivo',
account: 'Account',
logout: 'Disconnetti',
logout2: 'Sei sicuro di voler uscire?'
}
}
};
export default it;

View File

@@ -81,11 +81,14 @@ const ko = {
title: '이미지',
loading: 'Loading...',
empty: '아무것도 없습니다.',
mountFailed: '이미지 마운트 실패',
mountDesc:
'일부 시스템에서는 이미지를 마운트하기 전에 원격 호스트에서 가상 디스크를 제거해야 합니다.',
tips: {
title: '업로드 방법',
usb1: 'USB를 통해 NanoKVM을 컴퓨터에 연결하세요.',
usb2: '가상 USB가 마운트되어 있는지 확인하세요(설정 - 가상 USB).',
usb3: '컴퓨터에서 가상 USB 디스크를 열고 이미지 파일을 USB 디스크의 루트 디렉터리에 복사하세요.',
usb2: '가상 디스크가 마운트되었는지 확인하세요. (설정 - 가상 디스크).',
usb3: '컴퓨터에서 가상 디스크를 열고 이미지 파일을 가상 디스크의 루트 디렉토리로 복사하세요.',
scp1: 'NanoKVM과 컴퓨터가 동일한 로컬 네트워크에 있는지 확인하세요.',
scp2: '컴퓨터에서 터미널을 열고 SCP 명령을 사용하여 이미지 파일을 NanoKVM의 /data 디렉터리에 업로드하세요.',
scp3: '예: scp your-image-path root@your-nanokvm-ip:/data',
@@ -147,8 +150,8 @@ const ko = {
cancel: '취소'
},
virtualDevice: {
network: '가상 네트워크',
usb: '가상 USB'
network: '가상 네트워크 카드',
disk: '가상 디스크'
},
tailscale: {
loading: 'Loading...',

184
web/src/i18n/locales/pl.ts Normal file
View File

@@ -0,0 +1,184 @@
const pl = {
translation: {
language: 'Język',
changePassword: 'Zmień hasło',
logout: 'Wyloguj',
settings: 'Ustawienia',
showMouse: 'Pokaż myszkę',
hideMouse: 'Ukryj myszkę',
power: 'Zasilanie',
reset: 'Reset',
powerShort: 'Zasilanie (krótkie kliknięcie)',
powerLong: 'Zasilanie (długie kliknięcie)',
hddLed: 'HDD LED',
checkLibFailed: 'Nie udało się sprawdzić biblioteki uruchomieniowej, spróbuj ponownie',
updateLibFailed: 'Nie udało się zaktualizować biblioteki uruchomieniowej, spróbuj ponownie',
updatingLib: 'Aktualizowanie biblioteki uruchomieniowej. Odśwież stronę po aktualizacji.',
checkForUpdate: 'Aktualizacja systemu',
head: {
desktop: 'Zdalny pulpit',
login: 'Login',
changePassword: 'Zmień Hasło',
terminal: 'Terminal'
},
auth: {
login: 'Login',
placeholderUsername: 'Wprowadź nazwę użykownika',
placeholderPassword: 'wprowadź hasło',
placeholderPassword2: 'wprowadź hasło ponownie',
noEmptyUsername: 'nazwa użykownika nie może być pusta',
noEmptyPassword: 'hasło nie może być puste',
noAccount:
'Nie udało się uzyskać informacji o użytkowniku, odśwież stronę lub zresetuj hasło',
invalidUser: 'Błędne hasło lub nazwa użykownika',
error: 'niespodziewany błąd',
changePassword: 'Zmień Hasło',
differentPassword: 'hasła nie zgadzają się',
illegalUsername: 'nazwa użytkownika zawiera niedozwolone znaki',
illegalPassword: 'hasło zawiera niedozwolone znaki',
forgetPassword: 'Zapomiałeś hasła?',
resetPassword: 'Zmień hasło',
reset1: 'Jeśli zapomniałeś hasła, postępuj zgodnie z instrukcjami, aby je zresetować:',
reset2: '1. Zaloguj się do urządzenia NanoKVM przez SSH:',
reset3: '2. Usuń ten plik z urządzenia: ',
reset4: '3. Użyj domyślnego login aby zalogować: ',
ok: 'Ok',
cancel: 'Anuluj'
},
screen: {
resolution: 'Rozdzielczość',
auto: 'Automatyczny',
autoTips:
'W określonych rozdzielczościach może wystąpić rozrywanie ekranu lub przesunięcie myszy. Rozważ dostosowanie rozdzielczości zdalnego hosta lub wyłącz tryb automatyczny.',
fps: 'FPS',
customizeFps: 'Personalizuj',
quality: 'Jakość',
frameDetect: 'Wykrywanie klatek',
frameDetectTip:
'Obliczanie różnicy między klatkami. Zatrzymaj transmisję strumienia wideo, gdy na ekranie zdalnego hosta nie zostaną wykryte żadne zmiany.'
},
keyboard: {
paste: 'Wklej',
tips: 'Tylko standardowe klawiaturowe znaki i symbole są obsługiwane.',
placeholder: 'Proszę wprowadzić coś',
submit: 'Prześlij',
virtual: 'Klawiatura'
},
mouse: {
default: 'Domyślny kursor',
pointer: 'Wskazujący kursor',
cell: 'Kursor komórki',
text: 'Kursor tekstowy',
grab: 'Kursor chwytania',
hide: 'Ukruj kursor',
mode: 'Tryb myszki',
absolute: 'Tryb bezwzględny',
relative: 'Tryb względny',
requestPointer: 'Korzystanie z trybu względnego. Kliknij pulpit, aby uzyskać wskaźnik myszy.',
resetHid: 'Zresetuj HID'
},
image: {
title: 'Obrazy',
loading: 'Ładowanie...',
empty: 'Nic nie znaleziono',
mountFailed: 'Nie udało się zamontować obrazu',
mountDesc:
'W niektórych systemach wymagane jest wyjęcie dysku wirtualnego na zdalnym hoście przed zamontowaniem obrazu.',
tips: {
title: 'Jak przesłać obrazy',
usb1: 'Podłącz urządzenie NanoKVM do komputera przez USB.',
usb2: 'Upewnij się, że dysk wirtualny jest zamontowany (Ustawienia - Dysk wirtualny).',
usb3: 'Otwórz dysk wirtualny na swoim komputerze i skopiuj plik obrazu do katalogu głównego dysku wirtualnego.',
scp1: 'Upewnij się że NanoKVM i twój komputer są na tej samej sieci lokalnej.',
scp2: 'Otwórz terminal na komputerze i użyj komendę SCP aby przesłać obraz do katalogu /data na NanoKVM.',
scp3: 'Przykład: scp lokalizacja-zrodlowego-obrazu root@ip-twojego-nanokvm:/data',
tfCard: 'Karta SD',
tf1: 'Ta metoda jest obsługiwana w systemie Linux',
tf2: 'Usuń kartę SD od NanoKVM (dla wersji FULL, rozbierz obudowę najpierw).',
tf3: 'Włóż kartę SD do czytnika kart i podłącz do twojego komputera.',
tf4: 'Kopjuj obraz do katalogu /data na karcie SD.',
tf5: 'Włóż kartę SD do NanoKVM.'
}
},
script: {
title: 'Skrypty',
upload: 'Prześlij',
run: 'Uruchom',
runBackground: 'Uruchomiony w tle',
runFailed: 'Uruchomienie nie powiodło się',
attention: 'Uwaga',
delDesc: 'Czy na pewno chcesz usunąć ten plik?',
confirm: 'Tak',
cancel: 'Nie',
delete: 'Usuń',
close: 'Zamknij'
},
terminal: {
title: 'Terminal',
nanokvm: 'Terminal NanoKVM',
serial: 'Terminal portu szeregowego',
serialPort: 'Port szeregowy',
serialPortPlaceholder: 'Wprowadź port szeregowy',
baudrate: 'Szybkość transmisji',
confirm: 'Ok'
},
wol: {
sending: 'Wysyłanie komendy...',
sent: 'Komenda wysłana',
input: 'Wprowadź numer adresu MAC',
ok: 'Ok'
},
about: {
title: 'NanoKVM - informacje',
information: 'Informacje o systemie',
ip: 'IP',
mdns: 'mDNS',
firmware: 'Wersja oprogramowania',
image: 'Wersja obrazu',
deviceKey: 'Klucz urządzenia',
queryFailed: 'Zapytanie nie powiodło się',
community: 'Społeczność'
},
update: {
title: 'Sprawdź aktualizacje',
queryFailed: 'Uzyskanie wersji nie powiodło się',
updateFailed: 'Aktualizacja nie powiodła się. Spróbuj ponownie.',
isLatest: 'Oprogramowanie jest aktualne.',
available: 'Aktualizacja jest dostępna. Czy na pewno chcesz dokonać aktualizacji?',
updating: 'Aktualizacja rozpoczęta. Proszę czekać...',
confirm: 'Potwierdź',
cancel: 'Anuluj'
},
virtualDevice: {
network: 'Sieć wirtualna',
usb: 'Dysk wirtualny'
},
tailscale: {
loading: 'Ładowanie...',
notInstall: 'Nie znaleziono Tailscale! Proszę zainstalować.',
install: 'Instaluj',
installing: 'Instalowanie',
failed: 'Instalowanie nie powiodło się',
retry: 'Odśwież stronę i spróbuj ponownie, albo spróbuj zainstalować manualnie.',
download: 'Pobierz',
package: 'pakiet instalacyjny',
unzip: 'i wypakuj pliki',
upTailscale: 'Prześlij tailscale do NanoKVM w katalogu /usr/bin/',
upTailscaled: 'Prześlij tailscaled do NanoKVM w katalogu /usr/sbin/',
refresh: 'Odśwież obecną stronę',
notLogin:
'Urządzenie nie zostało jeszcze powiązane. Zaloguj się i powiąż to urządzenie ze swoim kontem.',
urlPeriod: 'Ten URL jest ważny przez 10 minut',
login: 'Zaloguj',
loginSuccess: 'Zalogowanie pomyślne',
enable: 'Włącz Tailscale',
deviceName: 'Nazwa urządzenia',
deviceIP: 'Adres IP urządzenia',
account: 'Konto',
logout: 'Wyloguj',
logout2: 'Chcesz się wylogować?'
}
}
};
export default pl;

View File

@@ -85,11 +85,14 @@ const ru = {
title: 'Образы',
loading: 'Загрузка...',
empty: 'Пусто',
mountFailed: 'Монтирование образа не удалось',
mountDesc:
'В некоторых системах необходимо отсоединить виртуальный диск на удаленном хосте перед монтированием образа.',
tips: {
title: 'Как загрузить',
usb1: 'Подключите NanoKVM к вашему компьютеру через USB.',
usb2: 'Убедитесь что Виртуальный USB-накопитель смонтирован (Настройки - Виртуальный USB-накопитель).',
usb3: 'Откройте виртуальный USB-накопитель на вашем компьютере и скопируйте файл образа в корневую директорию USB-накопителя.',
usb2: 'Убедитесь, что виртуальный диск подключен (Настройки - Виртуальный диск).',
usb3: 'Откройте виртуальный диск на вашем компьютере и скопируйте файл образа в корневой каталог диска.',
scp1: 'Убедитесь что NanoKVM и ваш компьютер находятся в одной и той же сети.',
scp2: 'Откройте терминал на вашем компьютере и используйте команду scp чтобы загрузить файл образа в директорию /data, находящуюся на NanoKVM.',
scp3: 'Например: scp путь-к-образу root@ip-вашего-nanokvm:/data',
@@ -151,8 +154,8 @@ const ru = {
cancel: 'Отмена'
},
virtualDevice: {
network: 'Виртуальная сеть',
usb: 'Виртуальный USB-накопитель'
network: 'виртуальная сетевая карта',
disk: 'виртуальный диск'
},
tailscale: {
loading: 'Загрузка...',

View File

@@ -79,11 +79,13 @@ const zh = {
title: '镜像',
loading: '加载中',
empty: '无镜像文件',
mountFailed: '挂载失败',
mountDesc: '在某些系统中,需要在远程主机中弹出虚拟硬盘后再挂载镜像。',
tips: {
title: '如何上传',
usb1: '将 NanoKVM 通过 USB 连接到你的电脑;',
usb2: '确保已经挂载了虚拟U盘(设置 - 虚拟U盘);',
usb3: '在电脑上打开虚拟U盘,将镜像文件拷贝到虚拟U盘的根目录下。',
usb2: '确保已经挂载了虚拟硬盘(设置 - 虚拟硬盘);',
usb3: '在电脑上打开虚拟硬盘,将镜像文件拷贝到虚拟硬盘的根目录下。',
scp1: '确保 NanoKVM 和你的电脑在同一个局域网内;',
scp2: '在电脑上打开终端软件,使用 SCP 命令将镜像文件上传到 NanoKVM 的 /data 目录。',
scp3: '示例:scp your-image-path root@your-nanokvm-ip:/data',
@@ -145,8 +147,8 @@ const zh = {
cancel: '取消'
},
virtualDevice: {
network: '虚拟网口',
usb: '虚拟U盘'
network: '虚拟网卡',
disk: '虚拟硬盘'
},
tailscale: {
loading: '加载中...',

View File

@@ -49,7 +49,7 @@ export const Desktop = () => {
} else {
setIsLoading(false);
const now = Date.now();
setStreamUrl(`${getBaseUrl('http')}/api/stream/mjpeg?${now}`);
setStreamUrl(`${getBaseUrl('http')}/api/stream/mjpeg?n=${now}`);
}
});

View File

@@ -5,7 +5,8 @@ import { client } from '@/lib/websocket.ts';
import { KeyboardCodes } from './mappings.ts';
export const Keyboard = () => {
const metaRef = useRef(false);
const isMetaPressedRef = useRef(false);
const isAltRightPressedRef = useRef(false);
// listen keyboard events
useEffect(() => {
@@ -16,11 +17,15 @@ export const Keyboard = () => {
function handleKeyDown(event: any) {
disableEvent(event);
if (event.code === 'MetaLeft' || event.code === 'MetaRight') {
metaRef.current = true;
if (['MetaLeft', 'MetaRight'].includes(event.code)) {
isMetaPressedRef.current = true;
return;
}
metaRef.current = false;
isMetaPressedRef.current = false;
if (event.code === 'AltRight') {
isAltRightPressedRef.current = true;
}
const code = KeyboardCodes.get(event.code);
if (!code) {
@@ -30,7 +35,7 @@ export const Keyboard = () => {
const ctrl = event.ctrlKey ? 1 : 0;
const shift = event.shiftKey ? 1 : 0;
const alt = event.altKey ? 1 : 0;
const alt = event.altKey ? (isAltRightPressedRef.current ? 2 : 1) : 0;
const meta = event.metaKey ? 1 : 0;
const data = [1, code, ctrl, shift, alt, meta];
@@ -41,13 +46,16 @@ export const Keyboard = () => {
function handleKeyUp(event: any) {
disableEvent(event);
if (metaRef.current) {
if (isMetaPressedRef.current) {
client.send([1, KeyboardCodes.get('MetaLeft')!, 0, 0, 0, 1]);
metaRef.current = false;
isMetaPressedRef.current = false;
}
const data = [1, 0, 0, 0, 0, 0];
client.send(data);
if (event.code === 'AltRight') {
isAltRightPressedRef.current = false;
}
client.send([1, 0, 0, 0, 0, 0]);
}
return () => {

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { notification } from 'antd';
import clsx from 'clsx';
import { FileBoxIcon, LoaderCircleIcon, PackageIcon, PackageSearchIcon, XIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
@@ -12,6 +13,7 @@ type ImagesProps = {
export const Images = ({ setIsMounted }: ImagesProps) => {
const { t } = useTranslation();
const [notify, contextHolder] = notification.useNotification();
const [isLoading, setIsLoading] = useState(false);
const [images, setImages] = useState<string[]>([]);
@@ -72,6 +74,7 @@ export const Images = ({ setIsMounted }: ImagesProps) => {
.mountImage(filename)
.then((rsp) => {
if (rsp.code !== 0) {
openNotification();
return;
}
@@ -84,8 +87,18 @@ export const Images = ({ setIsMounted }: ImagesProps) => {
});
}
function openNotification() {
notify.open({
message: t('image.mountFailed'),
description: t('image.mountDesc'),
duration: 6
});
}
return (
<>
{contextHolder}
{isLoading ? (
// loading
<div className="flex items-center space-x-2 py-2 pl-2 pr-4 text-neutral-400">

View File

@@ -7,22 +7,21 @@ import * as api from '@/api/virtual-device';
export const VirtualDevices = () => {
const { t } = useTranslation();
const [isNetworkOn, setIsNetworkOn] = useState(false);
const [isUSBOn, setIsUSBOn] = useState(false);
const [loading, setLoading] = useState(''); // '' | 'network' | 'usb'
const [isDiskOn, setIsDiskOn] = useState(false);
const [loading, setLoading] = useState(''); // '' | 'network' | 'disk'
useEffect(() => {
api.getVirtualDevice().then((rsp) => {
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
setIsNetworkOn(rsp.data.network);
setIsUSBOn(rsp.data.usb);
setIsDiskOn(rsp.data.usb);
});
}, []);
function update(device: string) {
function update(device: 'network' | 'disk') {
if (loading) return;
setLoading(device);
@@ -30,14 +29,13 @@ export const VirtualDevices = () => {
.updateVirtualDevice(device)
.then((rsp) => {
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
if (device === 'network') {
setIsNetworkOn(rsp.data.on);
} else {
setIsUSBOn(rsp.data.on);
setIsDiskOn(rsp.data.on);
}
})
.finally(() => {
@@ -47,6 +45,14 @@ export const VirtualDevices = () => {
return (
<>
<div
className="flex cursor-pointer select-none items-center justify-between space-x-3 rounded px-3 py-1.5 text-white hover:bg-neutral-600"
onClick={() => update('disk')}
>
<div>{t('virtualDevice.disk')}</div>
<Switch size="small" checked={isDiskOn} loading={loading === 'disk'} />
</div>
<div
className="flex cursor-pointer select-none items-center justify-between space-x-3 rounded px-3 py-1.5 text-white hover:bg-neutral-600"
onClick={() => update('network')}
@@ -54,14 +60,6 @@ export const VirtualDevices = () => {
<div>{t('virtualDevice.network')}</div>
<Switch size="small" checked={isNetworkOn} loading={loading === 'network'} />
</div>
<div
className="flex cursor-pointer select-none items-center justify-between space-x-3 rounded px-3 py-1.5 text-white hover:bg-neutral-600"
onClick={() => update('usb')}
>
<div>{t('virtualDevice.usb')}</div>
<Switch size="small" checked={isUSBOn} loading={loading === 'usb'} />
</div>
</>
);
};

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { notification } from 'antd';
import clsx from 'clsx';
import { FileBoxIcon, LoaderCircleIcon, PackageIcon, PackageSearchIcon, XIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
@@ -12,6 +13,7 @@ type ImagesProps = {
export const Images = ({ setIsMounted }: ImagesProps) => {
const { t } = useTranslation();
const [notify, contextHolder] = notification.useNotification();
const [isLoading, setIsLoading] = useState(false);
const [images, setImages] = useState<string[]>([]);
@@ -72,6 +74,7 @@ export const Images = ({ setIsMounted }: ImagesProps) => {
.mountImage(filename)
.then((rsp) => {
if (rsp.code !== 0) {
openNotification();
return;
}
@@ -84,8 +87,18 @@ export const Images = ({ setIsMounted }: ImagesProps) => {
});
}
function openNotification() {
notify.open({
message: t('image.mountFailed'),
description: t('image.mountDesc'),
duration: 6
});
}
return (
<>
{contextHolder}
{isLoading ? (
// loading
<div className="flex items-center space-x-2 py-2 pl-2 pr-4 text-neutral-400">

View File

@@ -7,22 +7,21 @@ import * as api from '@/api/virtual-device';
export const VirtualDevices = () => {
const { t } = useTranslation();
const [isNetworkOn, setIsNetworkOn] = useState(false);
const [isUSBOn, setIsUSBOn] = useState(false);
const [loading, setLoading] = useState(''); // '' | 'network' | 'usb'
const [isDiskOn, setIsDiskOn] = useState(false);
const [loading, setLoading] = useState(''); // '' | 'network' | 'disk'
useEffect(() => {
api.getVirtualDevice().then((rsp) => {
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
setIsNetworkOn(rsp.data.network);
setIsUSBOn(rsp.data.usb);
setIsDiskOn(rsp.data.disk);
});
}, []);
function update(device: string) {
function update(device: 'network' | 'disk') {
if (loading) return;
setLoading(device);
@@ -30,14 +29,13 @@ export const VirtualDevices = () => {
.updateVirtualDevice(device)
.then((rsp) => {
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
if (device === 'network') {
setIsNetworkOn(rsp.data.on);
} else {
setIsUSBOn(rsp.data.on);
setIsDiskOn(rsp.data.on);
}
})
.finally(() => {
@@ -47,6 +45,14 @@ export const VirtualDevices = () => {
return (
<>
<div
className="flex cursor-pointer select-none items-center justify-between space-x-3 rounded px-3 py-1.5 text-white hover:bg-neutral-600"
onClick={() => update('disk')}
>
<div>{t('virtualDevice.disk')}</div>
<Switch size="small" checked={isDiskOn} loading={loading === 'disk'} />
</div>
<div
className="flex cursor-pointer select-none items-center justify-between space-x-3 rounded px-3 py-1.5 text-white hover:bg-neutral-600"
onClick={() => update('network')}
@@ -54,14 +60,6 @@ export const VirtualDevices = () => {
<div>{t('virtualDevice.network')}</div>
<Switch size="small" checked={isNetworkOn} loading={loading === 'network'} />
</div>
<div
className="flex cursor-pointer select-none items-center justify-between space-x-3 rounded px-3 py-1.5 text-white hover:bg-neutral-600"
onClick={() => update('usb')}
>
<div>{t('virtualDevice.usb')}</div>
<Switch size="small" checked={isUSBOn} loading={loading === 'usb'} />
</div>
</>
);
};