mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-11-09 17:44:43 -06:00
feat(storage): mount images read-only and hand /data to one writer (#937)
Only one side may write the mounted image or the /data partition at a time, and uploaded images are validated before they are stored. Image mounts: * Serve every mounted image read-only, in CD-ROM and mass storage mode alike: detach the LUN first and set the read-only flag before the image is attached, so the target machine can neither write to nor format it. The flags were previously only written for CD-ROM mounts, so a mass storage mount inherited ro=0 and the image stayed writable from the host. * Report the virtual disk as on only while the LUN really carries the /data partition. With an image mounted the switch used to stay active but unchangeable, and enabling the disk is now a confirmed action instead of a rejected one: it cancels the mounted image, and the confirm also warns that the image download is interrupted because /data becomes read-only here. * Ask before mounting: the image list warns that the virtual disk is released for the image, or offers to enable it first when it is off, instead of dropping the /data share silently. The new strings are translated for every locale. * Name the mounted medium and the USB device after the image file, cut to the 16 bytes of the SCSI inquiry product field and the 126 bytes of the USB product string, and restore the previous names on unmount. * Reset the LUN flags on every gadget restart. They survived a restart, so the shared partition came back to the host read-only, or as an optical drive, after an image had been mounted. The /data handover: * Mount /data read-only at boot whenever the virtual disk serves the raw partition as a writable disk, so the handover holds from the first boot on. * Add SetDataWritable(), which remounts /data and verifies the new state. Enabling the virtual disk makes /data read-only before the host can write it, and refuses to expose the disk when the remount fails instead of taking the risk. Unmounting an image, which hands the raw partition back to the host, does the same, while mounting an image switches /data back to writable because a served image is read-only for the host. * Cancel a running image transfer before the handover and wait for it to stop, and report the state in the image list, where mounting still works while deleting and uploading do not. The delete action is disabled with a hint, and the downloader offers to turn the virtual disk off. Image uploads: * Validate the volume descriptors while streaming, so an unusable image is rejected in seconds instead of after the whole transfer: accept ISO 9660 and UDF volume descriptors (CD001, BEA01) as well as raw disk images (GPT, APFS, HFS+, MBR), and reject anything else. Require the image to fit in /data with a 128 MiB reserve before any of it is received, log the underlying errors on the rejection paths, and keep the temporary file, the progress ticker and the close handling in defers. Add unit tests for the upload header check, the free space preflight and the image name truncation.
This commit is contained in:
@@ -220,6 +220,12 @@ const ca = {
|
||||
mountMode: 'Mode de muntatge',
|
||||
mountFailed: 'Error en muntar',
|
||||
mountDesc: 'En alguns sistemes cal expulsar el disc virtual abans de muntar la imatge.',
|
||||
diskOnWarn:
|
||||
"Muntar la imatge desactiva el disc virtual, de manera que l'ordinador ja no pot accedir a /data. Voleu continuar?",
|
||||
diskOffWarn:
|
||||
"El disc virtual està desactivat i cal activar-lo per muntar la imatge. Els dispositius USB de l'ordinador es tornaran a enumerar. Voleu continuar?",
|
||||
diskShared:
|
||||
'El disc virtual està activat, així que /data és només de lectura aquí. Desactiveu-lo per suprimir o pujar imatges.',
|
||||
unmountFailed: "No s'ha pogut desmuntar",
|
||||
unmountDesc:
|
||||
"En alguns sistemes, cal expulsar manualment de l'amfitrió remot abans de desmuntar la imatge.",
|
||||
@@ -288,6 +294,8 @@ const ca = {
|
||||
input: 'Introdueix la URL de la imatge',
|
||||
ok: "D'acord",
|
||||
disabled: 'La partició /data és només lectura. No es pot descarregar la imatge.',
|
||||
diskShared: "Apagueu primer el disc virtual i després useu la descàrrega d'imatges.",
|
||||
diskOff: 'Apaga el disc virtual',
|
||||
uploadbox: 'Deixeu anar el fitxer aquí o feu clic per seleccionar-lo',
|
||||
inputfile: "Introduïu el fitxer d'imatge",
|
||||
NoISO: 'Cap ISO',
|
||||
@@ -446,6 +454,11 @@ const ca = {
|
||||
hidOnlyDesc: "Deixeu d'emular dispositius virtuals, conservant només el control bàsic HID",
|
||||
disk: 'Disc virtual',
|
||||
diskDesc: 'Munta un disc U virtual al dispositiu remot',
|
||||
diskLocked: 'Desactivat mentre hi ha una imatge muntada (activar-lo cancel·la el muntatge)',
|
||||
diskCancelMount:
|
||||
"Activar el disc virtual cancel·la la imatge muntada i interromp una descàrrega o pujada d'imatge en curs. Voleu continuar?",
|
||||
diskBreakDownload:
|
||||
"Activar el disc virtual interromp la descàrrega d'imatges. Voleu continuar?",
|
||||
network: 'Xarxa virtual',
|
||||
networkDesc: 'Munta una targeta de xarxa virtual al dispositiu remot',
|
||||
reboot: 'Reinicia',
|
||||
|
||||
@@ -222,6 +222,12 @@ const cz = {
|
||||
mountFailed: 'Připojení se nezdařilo',
|
||||
mountDesc:
|
||||
'V některých systémech je nutné před připojením obrazu vysunout virtuální disk na vzdáleném hostiteli.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Odpojení se nezdařilo',
|
||||
unmountDesc:
|
||||
'Na některých systémech se musíte před odpojením obrazu ručně vysunout ze vzdáleného hostitele.',
|
||||
@@ -290,6 +296,8 @@ const cz = {
|
||||
input: 'Zadejte prosím vzdálený obrázek URL',
|
||||
ok: 'OK',
|
||||
disabled: 'Oddíl /data je RO, takže obrázek nelze stáhnout',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Přetáhněte soubor sem nebo kliknutím vyberte',
|
||||
inputfile: 'Zadejte soubor obrázku',
|
||||
NoISO: 'Žádné ISO',
|
||||
@@ -447,6 +455,10 @@ const cz = {
|
||||
hidOnlyDesc: 'Zastavit emulaci virtuálních zařízení a zachovat pouze základní ovládání HID',
|
||||
disk: 'Virtuální disk',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Virtuální síť',
|
||||
networkDesc: 'Připojit virtuální síťovou kartu na vzdáleném hostiteli',
|
||||
reboot: 'Restartujte',
|
||||
|
||||
@@ -221,6 +221,12 @@ const da = {
|
||||
mountFailed: 'Montering af diskbillede mislykkedes',
|
||||
mountDesc:
|
||||
'På nogle systemer kan det være nødvendigt at skubbe den virtuelle disk ud på fjerncomputeren før du kan montere diskbilledet.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Afmontering mislykkedes',
|
||||
unmountDesc:
|
||||
'På nogle systemer skal du manuelt skubbe ud fra fjernværten, før du afmonterer billedet.',
|
||||
@@ -289,6 +295,8 @@ const da = {
|
||||
input: 'Indtast venligst et fjernbillede URL',
|
||||
ok: 'OK',
|
||||
disabled: '/data partitionen er RO, så vi kan ikke downloade billedet',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Slip filen her, eller klik for at vælge',
|
||||
inputfile: 'Indtast venligst billedfilen',
|
||||
NoISO: 'Ingen ISO',
|
||||
@@ -446,6 +454,10 @@ const da = {
|
||||
'Stop med at emulere virtuelle enheder, og behold kun grundlæggende HID kontrol',
|
||||
disk: 'Virtuel disk',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Virtuelt netværk',
|
||||
networkDesc: 'Monter det virtuelle netværkskort på den eksterne vært',
|
||||
reboot: 'Genstart',
|
||||
|
||||
@@ -226,6 +226,12 @@ const de = {
|
||||
mountFailed: 'Einbinden fehlgeschlagen',
|
||||
mountDesc:
|
||||
'In einigen Systemen ist es notwendig, die virtuelle Festplatte auf dem entfernten Host auszuwerfen, bevor das Image eingebunden werden kann.',
|
||||
diskOnWarn:
|
||||
'Beim Einbinden des Images wird das virtuelle Laufwerk ausgeschaltet, sodass der Computer nicht mehr auf /data zugreifen kann. Fortfahren?',
|
||||
diskOffWarn:
|
||||
'Das virtuelle Laufwerk ist ausgeschaltet und muss zum Einbinden des Images aktiviert werden. Die USB-Geräte am Computer werden neu erkannt. Fortfahren?',
|
||||
diskShared:
|
||||
'Das virtuelle Laufwerk ist aktiv, daher ist /data hier schreibgeschützt. Deaktivieren Sie es, um Images zu löschen oder hochzuladen.',
|
||||
unmountFailed: 'Das Aufheben der Bereitstellung ist fehlgeschlagen',
|
||||
unmountDesc:
|
||||
'Auf einigen Systemen müssen Sie das Image manuell vom Remote-Host auswerfen, bevor Sie die Bereitstellung aufheben.',
|
||||
@@ -295,6 +301,9 @@ const de = {
|
||||
ok: 'Ok',
|
||||
disabled:
|
||||
'/data Partition ist nur-lesbar, daher kann das Systemabbild nicht heruntergeladen werden',
|
||||
diskShared:
|
||||
'Schalten Sie zuerst das virtuelle Laufwerk aus und verwenden Sie dann den Image-Download.',
|
||||
diskOff: 'Virtuelles Laufwerk ausschalten',
|
||||
uploadbox: 'Datei hier ablegen oder klicken zum Auswählen',
|
||||
inputfile: 'Bitte geben Sie die Datei für das Systemabbild an',
|
||||
NoISO: 'Keine ISO',
|
||||
@@ -454,6 +463,11 @@ const de = {
|
||||
'Hören Sie auf, virtuelle Geräte zu emulieren, und behalten Sie nur die grundlegende HID-Steuerung bei',
|
||||
disk: 'Virtuelle Festplatte',
|
||||
diskDesc: 'Binde das virtuelle U-Laufwerk an den entfernten Host',
|
||||
diskLocked: 'Aus, solange ein Image eingebunden ist (Einschalten bricht die Einbindung ab)',
|
||||
diskCancelMount:
|
||||
'Das Aktivieren des virtuellen Laufwerks bricht das eingebundene Image ab und unterbricht einen laufenden Image-Download oder -Upload. Fortfahren?',
|
||||
diskBreakDownload:
|
||||
'Das Aktivieren des virtuellen Laufwerks unterbricht den Image-Download. Fortfahren?',
|
||||
network: 'Virtuelles Netzwerk',
|
||||
networkDesc: 'Binde die virtuelle Netzwerkkarte an den entfernten Host',
|
||||
reboot: 'Neustarten',
|
||||
|
||||
@@ -222,6 +222,12 @@ const en = {
|
||||
mountFailed: 'Mount failed',
|
||||
mountDesc:
|
||||
'On some systems, you need to eject the virtual disk from the remote host before mounting the image.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Unmount failed',
|
||||
unmountDesc:
|
||||
'On some systems, you need to manually eject from the remote host before unmounting the image.',
|
||||
@@ -290,6 +296,8 @@ const en = {
|
||||
input: 'Please enter a remote image URL',
|
||||
ok: 'Ok',
|
||||
disabled: '/data partition is RO, so we cannot download the image',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Drop file here or click to select',
|
||||
inputfile: 'Please enter the image File',
|
||||
NoISO: 'No ISO',
|
||||
@@ -445,6 +453,10 @@ const en = {
|
||||
hidOnlyDesc: 'Stop emulating virtual devices, retaining only basic HID control',
|
||||
disk: 'Virtual Disk',
|
||||
diskDesc: 'Mount SD card on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Virtual Network',
|
||||
networkDesc: 'Mount virtual network card on the remote host',
|
||||
reboot: 'Reboot',
|
||||
|
||||
@@ -223,6 +223,12 @@ const es = {
|
||||
mountFailed: 'Fallo al montar',
|
||||
mountDesc:
|
||||
'En algunos sistemas, es necesario expulsar el disco virtual del host remoto antes de montar una imagen.',
|
||||
diskOnWarn:
|
||||
'Montar la imagen desactiva el disco virtual, por lo que el equipo ya no puede acceder a /data. ¿Continuar?',
|
||||
diskOffWarn:
|
||||
'El disco virtual está desactivado y hay que activarlo para montar la imagen. Los dispositivos USB del equipo se enumeran de nuevo. ¿Continuar?',
|
||||
diskShared:
|
||||
'El disco virtual está activado, así que /data es de solo lectura aquí. Desactívalo para eliminar o subir imágenes.',
|
||||
unmountFailed: 'Fallo al desmontar',
|
||||
unmountDesc:
|
||||
'En algunos sistemas, es necesario expulsar manualmente el disco virtual desde el host remoto antes de desmontar la imagen.',
|
||||
@@ -291,6 +297,8 @@ const es = {
|
||||
input: 'Por favor, introduce la URL de una imagen remota',
|
||||
ok: 'Aceptar',
|
||||
disabled: 'La partición /data es de sólo lectura, no se puede descargar la imagen',
|
||||
diskShared: 'Desactiva primero el disco virtual y luego usa la descarga de imágenes.',
|
||||
diskOff: 'Desactivar el disco virtual',
|
||||
uploadbox: 'Suelte el archivo aquí o haga clic para seleccionar',
|
||||
inputfile: 'Por favor ingrese el archivo de imagen',
|
||||
NoISO: 'Sin ISO',
|
||||
@@ -450,6 +458,11 @@ const es = {
|
||||
'Dejar de emular dispositivos virtuales y conservar solo el control básico HID',
|
||||
disk: 'Disco Virtual',
|
||||
diskDesc: 'Montar disco virtual en el host remoto',
|
||||
diskLocked: 'Desactivado mientras hay una imagen montada (activarlo cancela el montaje)',
|
||||
diskCancelMount:
|
||||
'Activar el disco virtual cancela la imagen montada e interrumpe una descarga o subida de imagen en curso. ¿Continuar?',
|
||||
diskBreakDownload:
|
||||
'Activar el disco virtual interrumpe la descarga de imágenes. ¿Continuar?',
|
||||
network: 'Red Virtual',
|
||||
networkDesc: 'Montar tarjeta de red virtual en el host remoto',
|
||||
reboot: 'Reiniciar',
|
||||
|
||||
@@ -225,6 +225,12 @@ const fr = {
|
||||
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.",
|
||||
diskOnWarn:
|
||||
"Monter l'image désactive le disque virtuel : l'ordinateur ne peut plus accéder à /data. Continuer ?",
|
||||
diskOffWarn:
|
||||
"Le disque virtuel est désactivé et doit être activé pour monter l'image. Les périphériques USB de l'ordinateur sont réénumérés. Continuer ?",
|
||||
diskShared:
|
||||
'Le disque virtuel est activé, donc /data est en lecture seule ici. Désactivez-le pour supprimer ou envoyer des images.',
|
||||
unmountFailed: 'Échec du démontage',
|
||||
unmountDesc:
|
||||
"Sur certains systèmes, vous devez l'éjecter manuellement de l'hôte distant avant de démonter l'image.",
|
||||
@@ -293,6 +299,8 @@ const fr = {
|
||||
input: 'Veuillez entrer l’URL d’une image distante',
|
||||
ok: 'Ok',
|
||||
disabled: 'La partition /data est en lecture seule, impossible de télécharger l’image',
|
||||
diskShared: "Désactivez d'abord le disque virtuel, puis utilisez le téléchargement d'images.",
|
||||
diskOff: 'Désactiver le disque virtuel',
|
||||
uploadbox: 'Déposez le fichier ici ou cliquez pour sélectionner',
|
||||
inputfile: 'Veuillez saisir le fichier image',
|
||||
NoISO: 'Aucun ISO',
|
||||
@@ -452,6 +460,11 @@ const fr = {
|
||||
"Arrêtez d'émuler des périphériques virtuels, en ne conservant que le contrôle de base HID",
|
||||
disk: 'Disque virtuel',
|
||||
diskDesc: "Monter le disque virtuel U sur l'hôte distant",
|
||||
diskLocked: "Désactivé tant qu'une image est montée (l'activer annule le montage)",
|
||||
diskCancelMount:
|
||||
"Activer le disque virtuel annule l'image montée et interrompt un téléchargement ou un envoi d'image en cours. Continuer ?",
|
||||
diskBreakDownload:
|
||||
"Activer le disque virtuel interrompt le téléchargement d'images. Continuer ?",
|
||||
network: 'Réseau virtuel',
|
||||
networkDesc: "Monter la carte réseau virtuelle sur l'hôte distant",
|
||||
reboot: 'Redémarrer',
|
||||
|
||||
@@ -224,6 +224,12 @@ const hu = {
|
||||
mountFailed: 'Csatlakoztatás sikertelen',
|
||||
mountDesc:
|
||||
'Egyes rendszerekben szükséges lehet a virtuális lemez eltávolítása a távoli gépen, mielőtt a képet csatlakoztatja.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'A leválasztás nem sikerült',
|
||||
unmountDesc:
|
||||
'Egyes rendszereken manuálisan kell kiadnia a távoli gazdagépről a kép leválasztása előtt.',
|
||||
@@ -292,6 +298,8 @@ const hu = {
|
||||
input: 'Adjon meg egy távoli képet URL',
|
||||
ok: 'Ok',
|
||||
disabled: '/data partíció RO, ezért nem tudjuk letölteni a képet',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Dobja ide a fájlt, vagy kattintson a kiválasztáshoz',
|
||||
inputfile: 'Kérjük, írja be a képfájlt',
|
||||
NoISO: 'Nincs ISO',
|
||||
@@ -450,6 +458,10 @@ const hu = {
|
||||
'A virtuális eszközök emulálásának leállítása, csak az alapvető HID vezérlés megtartásával',
|
||||
disk: 'Virtuális lemez',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Virtuális hálózat',
|
||||
networkDesc: 'Virtuális hálózati kártya csatlakoztatása a távoli gazdagépen',
|
||||
reboot: 'Újraindítás',
|
||||
|
||||
@@ -222,6 +222,12 @@ const id = {
|
||||
mountFailed: 'Pemasangan Gagal',
|
||||
mountDesc:
|
||||
'Di beberapa sistem, perlu mengeluarkan disk virtual pada host jarak jauh sebelum memasang gambar.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Pelepasan gagal',
|
||||
unmountDesc:
|
||||
'Pada beberapa sistem, Anda perlu mengeluarkan secara manual dari host jarak jauh sebelum melepas gambar.',
|
||||
@@ -290,6 +296,8 @@ const id = {
|
||||
input: 'Silakan masukkan gambar jarak jauh URL',
|
||||
ok: 'Ok',
|
||||
disabled: 'Partisi /data adalah RO, jadi kami tidak dapat mengunduh gambarnya',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Letakkan file di sini atau klik untuk memilih',
|
||||
inputfile: 'Silakan masukkan File gambar',
|
||||
NoISO: 'Tidak ada ISO',
|
||||
@@ -447,6 +455,10 @@ const id = {
|
||||
hidOnlyDesc: 'Berhenti meniru perangkat virtual, hanya mempertahankan kontrol dasar HID',
|
||||
disk: 'Disk virtual',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Jaringan virtual',
|
||||
networkDesc: 'Pasang kartu jaringan virtual pada host jarak jauh',
|
||||
reboot: 'Mulai ulang',
|
||||
|
||||
@@ -224,6 +224,12 @@ const it = {
|
||||
mountFailed: 'Montaggio immagine fallito',
|
||||
mountDesc:
|
||||
"In alcuni sistemi, è necessario espellere il disco virtuale sull'host remoto prima di montare l'immagine.",
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Smontaggio non riuscito',
|
||||
unmountDesc:
|
||||
"Su alcuni sistemi, è necessario espellere manualmente l'host remoto prima di smontare l'immagine.",
|
||||
@@ -292,6 +298,8 @@ const it = {
|
||||
input: "Inserisci un'immagine remota URL",
|
||||
ok: 'Ok',
|
||||
disabled: "La partizione /data è RO, quindi non possiamo scaricare l'immagine",
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Rilascia il file qui o fai clic per selezionarlo',
|
||||
inputfile: 'Inserisci il file immagine',
|
||||
NoISO: 'Nessuna ISO',
|
||||
@@ -452,6 +460,10 @@ const it = {
|
||||
'Smette di emulare i dispositivi virtuali, mantenendo solo il controllo di base HID',
|
||||
disk: 'Disco virtuale',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Rete virtuale',
|
||||
networkDesc: 'Monta la scheda di rete virtuale sull’host remoto',
|
||||
reboot: 'Riavvia',
|
||||
|
||||
@@ -222,6 +222,12 @@ const ja = {
|
||||
mountFailed: 'マウントに失敗しました',
|
||||
mountDesc:
|
||||
'一部のシステムでは、イメージをマウントする前にリモートホストで仮想ディスクをアンマウントする必要があります。',
|
||||
diskOnWarn:
|
||||
'イメージをマウントすると仮想ディスクがオフになり、コンピュータから /data にアクセスできなくなります。続行しますか?',
|
||||
diskOffWarn:
|
||||
'仮想ディスクがオフになっているため、イメージのマウントには有効化が必要です。コンピュータの USB デバイスが再接続されます。続行しますか?',
|
||||
diskShared:
|
||||
'仮想ディスクが有効なため、このデバイスの /data は読み取り専用です。イメージを削除またはアップロードするには仮想ディスクを無効にしてください。',
|
||||
unmountFailed: 'アンマウントに失敗しました',
|
||||
unmountDesc:
|
||||
'一部のシステムでは、イメージをアンマウントする前にリモートホストから手動で取り出す必要があります。',
|
||||
@@ -291,6 +297,8 @@ const ja = {
|
||||
ok: 'OK',
|
||||
disabled:
|
||||
'/data パーティションは読み取り専用であり、イメージのダウンロードには使用できません',
|
||||
diskShared: '先に仮想ディスクをオフにしてから、イメージのダウンロードを使用してください。',
|
||||
diskOff: '仮想ディスクをオフにする',
|
||||
uploadbox: 'ここにファイルをドロップするか、クリックして選択してください',
|
||||
inputfile: '画像ファイルを入力してください',
|
||||
NoISO: 'ISO なし',
|
||||
@@ -450,6 +458,11 @@ const ja = {
|
||||
'このモードでは仮想デバイスはマウントされなくなり、基本的な HID 制御機能のみが保持されます。',
|
||||
disk: '仮想ディスク',
|
||||
diskDesc: 'リモートホストに仮想 USB ドライブをマウントする',
|
||||
diskLocked: 'イメージのマウント中はオフです(オンにするとマウントが解除されます)',
|
||||
diskCancelMount:
|
||||
'仮想ディスクを有効にすると、マウント中のイメージは解除され、進行中のイメージのダウンロード/アップロードも中断されます。続行しますか?',
|
||||
diskBreakDownload:
|
||||
'仮想ディスクを有効にすると、イメージのダウンロードが中断されます。続行しますか?',
|
||||
network: '仮想ネットワークカード',
|
||||
networkDesc: 'リモートホストに仮想ネットワークカードをマウントする',
|
||||
reboot: '再起動',
|
||||
|
||||
@@ -217,6 +217,12 @@ const ko = {
|
||||
mountFailed: '마운트 실패',
|
||||
mountDesc:
|
||||
'일부 시스템에서는 이미지를 마운트하기 전에 원격 호스트에서 가상 디스크를 제거해야 합니다.',
|
||||
diskOnWarn:
|
||||
'이미지를 마운트하면 가상 디스크가 꺼지고 컴퓨터에서 /data에 접근할 수 없게 됩니다. 계속하시겠습니까?',
|
||||
diskOffWarn:
|
||||
'가상 디스크가 꺼져 있어 이미지를 마운트하려면 먼저 켜야 합니다. 컴퓨터의 USB 장치가 다시 연결됩니다. 계속하시겠습니까?',
|
||||
diskShared:
|
||||
'가상 디스크가 켜져 있어 이 기기에서 /data는 읽기 전용입니다. 이미지를 삭제하거나 업로드하려면 가상 디스크를 끄세요.',
|
||||
unmountFailed: '언마운트 실패',
|
||||
unmountDesc:
|
||||
'일부 시스템에서는 이미지를 언마운트하기 전에 원격 호스트에서 수동으로 제거하여야 합니다.',
|
||||
@@ -285,6 +291,8 @@ const ko = {
|
||||
input: '원격 이미지 URL을 입력하세요.',
|
||||
ok: '확인',
|
||||
disabled: '/data 파티션이 읽기 전용(RO) 상태이므로 이미지를 다운로드할 수 없습니다.',
|
||||
diskShared: '먼저 가상 디스크를 끈 다음 이미지 다운로드를 사용하세요.',
|
||||
diskOff: '가상 디스크 끄기',
|
||||
uploadbox: '여기에 파일을 놓거나 클릭하여 선택하세요.',
|
||||
inputfile: '이미지 파일을 입력해주세요',
|
||||
NoISO: 'ISO 없음',
|
||||
@@ -442,6 +450,10 @@ const ko = {
|
||||
hidOnlyDesc: '가상 장치 에뮬레이션을 중지하고 기본 HID 제어만 유지합니다.',
|
||||
disk: '가상 디스크',
|
||||
diskDesc: '원격 호스트에서 가상 USB를 마운트합니다.',
|
||||
diskLocked: '이미지가 마운트된 동안 꺼짐 (켜면 마운트가 해제됩니다)',
|
||||
diskCancelMount:
|
||||
'가상 디스크를 켜면 마운트된 이미지가 해제되고 진행 중인 이미지 다운로드/업로드도 중단됩니다. 계속하시겠습니까?',
|
||||
diskBreakDownload: '가상 디스크를 켜면 이미지 다운로드가 중단됩니다. 계속하시겠습니까?',
|
||||
network: '가상 네트워크',
|
||||
networkDesc: '원격 호스트에서 가상 네트워크 카드를 마운트합니다.',
|
||||
reboot: '재부팅',
|
||||
|
||||
@@ -222,6 +222,12 @@ const nb = {
|
||||
mountFailed: 'Montering feilet',
|
||||
mountDesc:
|
||||
'På noen systemer er det nødvendig å koble fra den virtuelle disken på den eksterne verten før man kan montere arkivfilen.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Avmontering mislyktes',
|
||||
unmountDesc:
|
||||
'På noen systemer må du manuelt løse ut fra den eksterne verten før du demonterer bildet.',
|
||||
@@ -290,6 +296,8 @@ const nb = {
|
||||
input: 'Vennligst skriv inn et eksternt bilde URL',
|
||||
ok: 'Ok',
|
||||
disabled: '/data partisjonen er RO, så vi kan ikke laste ned bildet',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Slipp filen her eller klikk for å velge',
|
||||
inputfile: 'Vennligst skriv inn bildefilen',
|
||||
NoISO: 'Ingen ISO',
|
||||
@@ -446,6 +454,10 @@ const nb = {
|
||||
hidOnlyDesc: 'Slutt å emulere virtuelle enheter, behold bare grunnleggende HID-kontroll',
|
||||
disk: 'Virtuell disk',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Virtuelt nettverk',
|
||||
networkDesc: 'Monter virtuelt nettverkskort på den eksterne verten',
|
||||
reboot: 'Start på nytt',
|
||||
|
||||
@@ -225,6 +225,12 @@ const nl = {
|
||||
mountFailed: 'Koppelen mislukt',
|
||||
mountDesc:
|
||||
'In sommige systemen is het noodzakelijk om de virtuele schijf op de externe host uit te werpen voordat het image wordt gekoppeld.',
|
||||
diskOnWarn:
|
||||
'Het koppelen van de image schakelt de virtuele schijf uit, zodat de computer geen toegang meer heeft tot /data. Doorgaan?',
|
||||
diskOffWarn:
|
||||
'De virtuele schijf staat uit en moet aan staan om de image te koppelen. De USB-apparaten op de computer worden opnieuw herkend. Doorgaan?',
|
||||
diskShared:
|
||||
'De virtuele schijf staat aan, dus /data is hier alleen-lezen. Schakel deze uit om images te verwijderen of te uploaden.',
|
||||
unmountFailed: 'Ontkoppelen mislukt',
|
||||
unmountDesc:
|
||||
'Op sommige systemen moet u de image handmatig uitwerpen van de externe host voordat u de image ontkoppelt.',
|
||||
@@ -293,6 +299,8 @@ const nl = {
|
||||
input: 'Voer een externe afbeelding in URL',
|
||||
ok: 'Ok',
|
||||
disabled: '/data partitie is RO, dus we kunnen de afbeelding niet downloaden',
|
||||
diskShared: 'Schakel eerst de virtuele schijf uit en gebruik daarna de image-download.',
|
||||
diskOff: 'Virtuele schijf uitschakelen',
|
||||
uploadbox: 'Zet het bestand hier neer of klik om te selecteren',
|
||||
inputfile: 'Voer het afbeeldingsbestand in',
|
||||
NoISO: 'Geen ISO',
|
||||
@@ -453,6 +461,11 @@ const nl = {
|
||||
'Stop met het emuleren van virtuele apparaten en behoud alleen de basisbesturing van HID',
|
||||
disk: 'Virtuele schijf',
|
||||
diskDesc: 'Koppel virtuele U-schijf aan de externe host',
|
||||
diskLocked: 'Uit zolang een image is gekoppeld (aanzetten breekt het koppelen af)',
|
||||
diskCancelMount:
|
||||
'Het inschakelen van de virtuele schijf breekt de gekoppelde image af en onderbreekt een lopende image-download of -upload. Doorgaan?',
|
||||
diskBreakDownload:
|
||||
'Het inschakelen van de virtuele schijf onderbreekt de image-download. Doorgaan?',
|
||||
network: 'Virtueel Netwerk',
|
||||
networkDesc: 'Koppel virtueel netwerk kaart aan de externe host',
|
||||
reboot: 'Opnieuw opstarten',
|
||||
|
||||
@@ -223,6 +223,12 @@ const pl = {
|
||||
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.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Odmontowanie nie powiodło się',
|
||||
unmountDesc:
|
||||
'W niektórych systemach należy ręcznie wysunąć obraz ze zdalnego hosta przed odmontowaniem obrazu.',
|
||||
@@ -291,6 +297,8 @@ const pl = {
|
||||
input: 'Proszę wprowadzić zdalny obraz URL',
|
||||
ok: 'Ok',
|
||||
disabled: '/data partycja to RO, więc nie możemy pobrać obrazu',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Upuść plik tutaj lub kliknij, aby wybrać',
|
||||
inputfile: 'Proszę wprowadzić plik obrazu',
|
||||
NoISO: 'Brak ISO',
|
||||
@@ -450,6 +458,10 @@ const pl = {
|
||||
'Przestań emulować urządzenia wirtualne, zachowując jedynie podstawową kontrolę HID',
|
||||
disk: 'Dysk wirtualny',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Sieć wirtualna',
|
||||
networkDesc: 'Zamontuj wirtualną kartę sieciową na zdalnym hoście',
|
||||
reboot: 'Uruchom ponownie',
|
||||
|
||||
@@ -222,6 +222,12 @@ const pt_br = {
|
||||
mountFailed: 'Falha na Montagem',
|
||||
mountDesc:
|
||||
'Em alguns sistemas, é necessário ejetar o disco virtual no host remoto antes de montar a imagem.',
|
||||
diskOnWarn:
|
||||
'Montar a imagem desliga o disco virtual, então o computador não pode mais acessar /data. Continuar?',
|
||||
diskOffWarn:
|
||||
'O disco virtual está desligado e precisa ser ativado para montar a imagem. Os dispositivos USB do computador são reconectados. Continuar?',
|
||||
diskShared:
|
||||
'O disco virtual está ativado, então /data é somente leitura aqui. Desative-o para excluir ou enviar imagens.',
|
||||
unmountFailed: 'Falha na desmontagem',
|
||||
unmountDesc:
|
||||
'Em alguns sistemas, é necessário ejetar manualmente do host remoto antes de desmontar a imagem.',
|
||||
@@ -290,6 +296,8 @@ const pt_br = {
|
||||
input: 'Por favor, digite uma URL de imagem remota',
|
||||
ok: 'Ok',
|
||||
disabled: 'A partição /data é RO, então não podemos baixar a imagem',
|
||||
diskShared: 'Desligue primeiro o disco virtual e depois use o download de imagens.',
|
||||
diskOff: 'Desligar o disco virtual',
|
||||
uploadbox: 'Solte o arquivo aqui ou clique para selecionar',
|
||||
inputfile: 'Por favor insira o arquivo de imagem',
|
||||
NoISO: 'Sem ISO',
|
||||
@@ -448,6 +456,10 @@ const pt_br = {
|
||||
hidOnlyDesc: 'Pare de emular dispositivos virtuais, mantendo apenas o controle básico HID',
|
||||
disk: 'Disco Virtual',
|
||||
diskDesc: 'Montar U-disk virtual no host remoto',
|
||||
diskLocked: 'Desligado enquanto uma imagem estiver montada (ligá-lo cancela a montagem)',
|
||||
diskCancelMount:
|
||||
'Ativar o disco virtual cancela a imagem montada e interrompe um download ou upload de imagem em andamento. Continuar?',
|
||||
diskBreakDownload: 'Ativar o disco virtual interrompe o download de imagens. Continuar?',
|
||||
network: 'Rede Virtual',
|
||||
networkDesc: 'Montar placa de rede virtual no host remoto',
|
||||
reboot: 'Reiniciar',
|
||||
|
||||
@@ -222,6 +222,12 @@ const ru = {
|
||||
mountFailed: 'Монтирование образа не удалось',
|
||||
mountDesc:
|
||||
'В некоторых системах необходимо отсоединить виртуальный диск на удаленном хосте перед монтированием образа.',
|
||||
diskOnWarn:
|
||||
'Монтирование образа отключает виртуальный диск, и компьютер теряет доступ к /data. Продолжить?',
|
||||
diskOffWarn:
|
||||
'Виртуальный диск отключён, его нужно включить для монтирования образа. USB-устройства компьютера будут переподключены. Продолжить?',
|
||||
diskShared:
|
||||
'Виртуальный диск включён, поэтому /data здесь только для чтения. Отключите его, чтобы удалять или загружать образы.',
|
||||
unmountFailed: 'Не удалось размонтировать',
|
||||
unmountDesc:
|
||||
'В некоторых системах перед размонтированием образа необходимо вручную извлечь образ с удаленного хоста.',
|
||||
@@ -290,6 +296,8 @@ const ru = {
|
||||
input: 'Введите URL удаленного образа',
|
||||
ok: 'OK',
|
||||
disabled: 'Невозможно скачать образ, раздел /data находится в режиме только для чтения',
|
||||
diskShared: 'Сначала отключите виртуальный диск, затем используйте загрузку образов.',
|
||||
diskOff: 'Отключить виртуальный диск',
|
||||
uploadbox: 'Перетащите сюда файл или нажмите, чтобы выбрать',
|
||||
inputfile: 'Пожалуйста, введите файл изображения',
|
||||
NoISO: 'Нет ISO',
|
||||
@@ -450,6 +458,10 @@ const ru = {
|
||||
'Прекратить эмуляцию виртуальных устройств, сохранив только базовый контроль HID.',
|
||||
disk: 'Виртуальный диск',
|
||||
diskDesc: 'Смонтировать виртуальный USB диск на удаленном хосте',
|
||||
diskLocked: 'Отключён, пока смонтирован образ (включение отменит монтирование)',
|
||||
diskCancelMount:
|
||||
'Включение виртуального диска отменит смонтированный образ и прервёт текущую загрузку или выгрузку образа. Продолжить?',
|
||||
diskBreakDownload: 'Включение виртуального диска прервёт загрузку образов. Продолжить?',
|
||||
network: 'Виртуальная сеть',
|
||||
networkDesc: 'Смонтировать виртуальное сетевое устройство на удаленном хосте',
|
||||
reboot: 'Перезагрузка',
|
||||
|
||||
@@ -218,6 +218,12 @@ const se = {
|
||||
mountFailed: 'Montering misslyckades',
|
||||
mountDesc:
|
||||
'I vissa system måste den virtuella disken avmonteras på fjärrvärden innan avbildningen monteras.',
|
||||
diskOnWarn:
|
||||
'Montering av avbildningen stänger av den virtuella disken, så datorn kommer inte åt /data. Fortsätta?',
|
||||
diskOffWarn:
|
||||
'Den virtuella disken är avstängd och måste aktiveras för att montera avbildningen. Datorns USB-enheter ansluts på nytt. Fortsätta?',
|
||||
diskShared:
|
||||
'Den virtuella disken är aktiverad, så /data är skrivskyddad här. Stäng av den för att ta bort eller ladda upp avbildningar.',
|
||||
unmountFailed: 'Avmontering misslyckades',
|
||||
unmountDesc:
|
||||
'I vissa system måste du manuellt mata ut från fjärrvärden innan du avmonterar avbildningen.',
|
||||
@@ -286,6 +292,8 @@ const se = {
|
||||
input: 'Ange en fjärravbildnings-URL',
|
||||
ok: 'Ok',
|
||||
disabled: '/data partitionen är skrivskyddad, kan inte hämta avbildning',
|
||||
diskShared: 'Stäng av den virtuella disken först och använd sedan nedladdningen.',
|
||||
diskOff: 'Stäng av den virtuella disken',
|
||||
uploadbox: 'Släpp filen här eller klicka för att välja',
|
||||
inputfile: 'Vänligen ange bildfilen',
|
||||
NoISO: 'Ingen ISO',
|
||||
@@ -444,6 +452,11 @@ const se = {
|
||||
hidOnlyDesc: 'Sluta emulera virtuella enheter, behåll bara grundläggande HID kontroll',
|
||||
disk: 'Virtuell disk',
|
||||
diskDesc: 'Montera virtuell U-disk på fjärrvärden',
|
||||
diskLocked: 'Avstängd medan en avbildning är monterad (aktivering avbryter monteringen)',
|
||||
diskCancelMount:
|
||||
'Att aktivera den virtuella disken avbryter den monterade avbildningen och avbryter en pågående nedladdning eller uppladdning. Fortsätta?',
|
||||
diskBreakDownload:
|
||||
'Att aktivera den virtuella disken avbryter nedladdningen av avbildningar. Fortsätta?',
|
||||
network: 'Virtuellt nätverk',
|
||||
networkDesc: 'Montera virtuell nätverkskort på fjärrvärden',
|
||||
reboot: 'Starta om',
|
||||
|
||||
@@ -215,6 +215,12 @@ const th = {
|
||||
mountMode: 'โหมดเมาท์',
|
||||
mountFailed: 'การติดตั้งล้มเหลว',
|
||||
mountDesc: 'ในบางระบบ, จะต้อง eject ดิสก์จำลองก่อนที่จะใส่ดิสก์ใหม่.',
|
||||
diskOnWarn:
|
||||
'การเมานต์อิมเมจจะปิดดิสก์เสมือน ทำให้คอมพิวเตอร์เข้าถึง /data ไม่ได้อีกต่อไป ดำเนินการต่อหรือไม่?',
|
||||
diskOffWarn:
|
||||
'ดิสก์เสมือนปิดอยู่ ต้องเปิดก่อนจึงจะเมานต์อิมเมจได้ อุปกรณ์ USB ของคอมพิวเตอร์จะเชื่อมต่อใหม่ ดำเนินการต่อหรือไม่?',
|
||||
diskShared:
|
||||
'ดิสก์เสมือนเปิดอยู่ ดังนั้น /data จึงเป็นแบบอ่านอย่างเดียว ปิดดิสก์เสมือนเพื่อลบหรืออัปโหลดอิมเมจ',
|
||||
unmountFailed: 'การถอนติดตั้งล้มเหลว',
|
||||
unmountDesc:
|
||||
'ในบางระบบ คุณต้องดีดออกจากโฮสต์ระยะไกลด้วยตนเองก่อนที่จะยกเลิกการต่อเชื่อมอิมเมจ',
|
||||
@@ -283,6 +289,8 @@ const th = {
|
||||
input: 'กรุณาใส่ URL ของอิมเมจระยะไกล',
|
||||
ok: 'ยืนยัน',
|
||||
disabled: '/data มีการตั้งค่าเป็นอ่านอย่างเดียว ดังนั้นเราไม่สามารถดาวโหลด Disk Image ได้',
|
||||
diskShared: 'ปิดดิสก์เสมือนก่อน แล้วจึงใช้การดาวน์โหลดอิมเมจ',
|
||||
diskOff: 'ปิดดิสก์เสมือน',
|
||||
uploadbox: 'วางไฟล์ที่นี่หรือคลิกเพื่อเลือก',
|
||||
inputfile: 'กรุณากรอกไฟล์ภาพ',
|
||||
NoISO: 'ไม่มี ISO',
|
||||
@@ -439,6 +447,10 @@ const th = {
|
||||
hidOnlyDesc: 'หยุดการจำลองอุปกรณ์เสมือน โดยคงไว้เพียงการควบคุม HID พื้นฐานเท่านั้น',
|
||||
disk: 'ดิสก์จำลอง',
|
||||
diskDesc: 'เปิดใช้งาน U-disk จำลอง',
|
||||
diskLocked: 'ปิดอยู่ขณะเมานต์อิมเมจ (เปิดจะยกเลิกการเมานต์)',
|
||||
diskCancelMount:
|
||||
'การเปิดดิสก์เสมือนจะยกเลิกการเมานต์อิมเมจ และขัดขวางการดาวน์โหลดหรืออัปโหลดอิมเมจที่กำลังดำเนินอยู่ ดำเนินการต่อหรือไม่?',
|
||||
diskBreakDownload: 'การเปิดดิสก์เสมือนจะขัดขวางการดาวน์โหลดอิมเมจ ดำเนินการต่อหรือไม่?',
|
||||
network: 'เครือข่ายจำลอง',
|
||||
networkDesc: 'เปิดใช้งานอุปกรณ์เครือข่ายจำลอง',
|
||||
reboot: 'รีบูต',
|
||||
|
||||
@@ -221,6 +221,12 @@ const tr = {
|
||||
mountFailed: 'Bağlantı başarısız oldu',
|
||||
mountDesc:
|
||||
'Bazı sistemlerde, disk imajını bağlamadan önce uzak ana bilgisayardaki sanal diski çıkarmak gerekir.',
|
||||
diskOnWarn:
|
||||
'İmajı bağlamak sanal diski kapatır, böylece bilgisayar /data dizinine erişemez. Devam edilsin mi?',
|
||||
diskOffWarn:
|
||||
'Sanal disk kapalı ve imajı bağlamak için açılması gerekiyor. Bilgisayardaki USB aygıtları yeniden tanımlanır. Devam edilsin mi?',
|
||||
diskShared:
|
||||
'Sanal disk açık, bu nedenle /data burada salt okunur. İmaj silmek veya yüklemek için sanal diski kapatın.',
|
||||
unmountFailed: 'Bağlantıyı kesme işlemi başarısız oldu',
|
||||
unmountDesc:
|
||||
'Bazı sistemlerde, görüntünün bağlantısını kesmeden önce uzak ana bilgisayardan manuel olarak çıkarmanız gerekir.',
|
||||
@@ -289,6 +295,8 @@ const tr = {
|
||||
input: 'Uzak imaj URL’sini girin',
|
||||
ok: 'Tamam',
|
||||
disabled: '/data bölüntüsü salt okunur modda, disk imajı indirilemiyor.',
|
||||
diskShared: 'Önce sanal diski kapatın, sonra imaj indirmeyi kullanın.',
|
||||
diskOff: 'Sanal diski kapat',
|
||||
uploadbox: 'Dosyayı buraya bırakın veya seçmek için tıklayın',
|
||||
inputfile: 'Lütfen resim dosyasını giriniz',
|
||||
NoISO: 'ISO yok',
|
||||
@@ -449,6 +457,10 @@ const tr = {
|
||||
'Yalnızca temel HID kontrolünü koruyarak sanal aygıtları taklit etmeyi bırakın',
|
||||
disk: 'Sanal Disk',
|
||||
diskDesc: "Sanal U-disk'i uzak ana bilgisayara bağla",
|
||||
diskLocked: 'Bir imaj bağlıyken kapalı (açmak bağlamayı iptal eder)',
|
||||
diskCancelMount:
|
||||
'Sanal diski açmak, bağlı imajı iptal eder ve sürmekte olan imaj indirme veya yüklemeyi keser. Devam edilsin mi?',
|
||||
diskBreakDownload: 'Sanal diski açmak imaj indirmeyi keser. Devam edilsin mi?',
|
||||
network: 'Sanal Ağ',
|
||||
networkDesc: 'Sanal ağ kartını uzak ana bilgisayara bağla',
|
||||
reboot: 'Yeniden Başlat',
|
||||
|
||||
@@ -223,6 +223,12 @@ const uk = {
|
||||
mountFailed: 'Не вдалося змонтувати образ у режимі CD-ROM',
|
||||
mountDesc:
|
||||
'У деяких системах необхідно витягнути віртуальний диск на віддаленому хості перед монтуванням файлу образа.',
|
||||
diskOnWarn:
|
||||
"Монтування образу вимикає віртуальний диск, тому комп'ютер більше не має доступу до /data. Продовжити?",
|
||||
diskOffWarn:
|
||||
"Віртуальний диск вимкнено, його треба увімкнути для монтування образу. USB-пристрої комп'ютера буде перепідключено. Продовжити?",
|
||||
diskShared:
|
||||
'Віртуальний диск увімкнено, тому /data тут лише для читання. Вимкніть його, щоб видаляти або завантажувати образи.',
|
||||
unmountFailed: 'Не вдалося розмонтувати',
|
||||
unmountDesc:
|
||||
'У деяких системах вам потрібно вручну вийти з віддаленого хоста перед демонтуванням образу.',
|
||||
@@ -291,6 +297,9 @@ const uk = {
|
||||
input: 'Введіть URL-адресу віддаленого образу',
|
||||
ok: 'Так',
|
||||
disabled: 'Розділ даних /data у режимі лише для читання, тому ми не можемо завантажити образ',
|
||||
diskShared:
|
||||
'Спочатку вимкніть віртуальний диск, а потім скористайтеся завантаженням образів.',
|
||||
diskOff: 'Вимкнути віртуальний диск',
|
||||
uploadbox: 'Перетягніть файл сюди або натисніть, щоб вибрати',
|
||||
inputfile: 'Будь ласка, введіть файл зображення',
|
||||
NoISO: 'Немає ISO',
|
||||
@@ -447,6 +456,11 @@ const uk = {
|
||||
hidOnlyDesc: 'Зупиніть емуляцію віртуальних пристроїв, зберігши лише базовий контроль HID',
|
||||
disk: 'Віртуальний диск',
|
||||
diskDesc: 'Монтувати віртуальний U-диск на віддаленому хості',
|
||||
diskLocked: 'Вимкнено, поки змонтовано образ (увімкнення скасує монтування)',
|
||||
diskCancelMount:
|
||||
'Увімкнення віртуального диска скасує змонтований образ і перерве поточне завантаження чи вивантаження образу. Продовжити?',
|
||||
diskBreakDownload:
|
||||
'Увімкнення віртуального диска перерве завантаження образів. Продовжити?',
|
||||
network: 'Віртуальна мережа',
|
||||
networkDesc: 'Встановити віртуальну мережеву карту на віддаленому хості',
|
||||
reboot: 'Перезавантажити',
|
||||
|
||||
@@ -220,6 +220,12 @@ const vi = {
|
||||
mountMode: 'Chế độ gắn kết',
|
||||
mountFailed: 'Mount thất bại',
|
||||
mountDesc: 'Trong một số hệ thống, cần phải eject đĩa ảo trên máy remote trước khi mount.',
|
||||
diskOnWarn:
|
||||
'Mounting the image turns the virtual disk off, so the computer can no longer access /data. Continue?',
|
||||
diskOffWarn:
|
||||
'The virtual disk is off and has to be enabled to mount the image. The USB devices on the computer are enumerated again. Continue?',
|
||||
diskShared:
|
||||
'Virtual disk is enabled, so /data is read-only here. Disable it to delete or upload images.',
|
||||
unmountFailed: 'Tháo lắp không thành công',
|
||||
unmountDesc:
|
||||
'Trên một số hệ thống, bạn cần đẩy hình ảnh ra khỏi máy chủ từ xa theo cách thủ công trước khi ngắt kết nối hình ảnh.',
|
||||
@@ -288,6 +294,8 @@ const vi = {
|
||||
input: 'Vui lòng nhập hình ảnh từ xa URL',
|
||||
ok: 'OK',
|
||||
disabled: '/data phân vùng là RO nên không tải được image',
|
||||
diskShared: 'Turn the virtual disk off first, then use the image download.',
|
||||
diskOff: 'Turn off the virtual disk',
|
||||
uploadbox: 'Thả file vào đây hoặc bấm vào để chọn',
|
||||
inputfile: 'Vui lòng nhập File hình ảnh',
|
||||
NoISO: 'Không có ISO',
|
||||
@@ -445,6 +453,10 @@ const vi = {
|
||||
hidOnlyDesc: 'Dừng mô phỏng các thiết bị ảo, chỉ giữ lại điều khiển HID cơ bản',
|
||||
disk: 'Đĩa ảo',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
diskLocked: 'Off while an image is mounted (turning it on cancels the mount)',
|
||||
diskCancelMount:
|
||||
'Enabling the virtual disk cancels the mounted image and interrupts a running image download or upload. Continue?',
|
||||
diskBreakDownload: 'Enabling the virtual disk interrupts the image download. Continue?',
|
||||
network: 'Mạng ảo',
|
||||
networkDesc: 'Gắn card mạng ảo trên máy chủ từ xa',
|
||||
reboot: 'Khởi động lại',
|
||||
|
||||
@@ -215,6 +215,10 @@ const zh = {
|
||||
mountMode: '挂载模式',
|
||||
mountFailed: '挂载失败',
|
||||
mountDesc: '在某些系统中,需要在远程主机中弹出虚拟硬盘后再挂载镜像。',
|
||||
diskOnWarn: '挂载镜像会关闭虚拟U盘,电脑将无法再访问 /data 目录。是否继续?',
|
||||
diskOffWarn:
|
||||
'虚拟U盘当前已关闭,需要先开启才能挂载镜像,电脑上的 USB 设备会短暂断开重连。是否继续?',
|
||||
diskShared: '虚拟U盘已开启,本机 /data 为只读;关闭虚拟U盘后才能删除或上传镜像。',
|
||||
unmountFailed: '卸载失败',
|
||||
unmountDesc: '在某些系统中,需要在远程主机中手动弹出后再卸载镜像。',
|
||||
refresh: '刷新镜像列表',
|
||||
@@ -282,6 +286,8 @@ const zh = {
|
||||
input: '请输入远程镜像 URL',
|
||||
ok: '确定',
|
||||
disabled: '/data 是只读分区,无法下载镜像',
|
||||
diskShared: '请先关闭虚拟U盘功能,再使用镜像下载功能',
|
||||
diskOff: '关闭虚拟U盘功能',
|
||||
uploadbox: '将文件拖放到此处或单击选择',
|
||||
inputfile: '请输入镜像文件',
|
||||
NoISO: '无 ISO',
|
||||
@@ -436,6 +442,10 @@ const zh = {
|
||||
hidOnlyDesc: '该模式下不再挂载虚拟设备,仅保留基础的 HID 控制功能。',
|
||||
disk: '虚拟U盘',
|
||||
diskDesc: '在远程主机中挂载虚拟U盘',
|
||||
diskLocked: '挂载镜像期间已关闭(开启会取消镜像挂载)',
|
||||
diskCancelMount:
|
||||
'开启虚拟U盘会取消当前挂载的镜像,并中断正在进行的镜像下载或上传,是否继续?',
|
||||
diskBreakDownload: '开启虚拟U盘会中断镜像下载功能,是否继续?',
|
||||
network: '虚拟网卡',
|
||||
networkDesc: '在远程主机中挂载虚拟网卡',
|
||||
reboot: '重新启动',
|
||||
|
||||
@@ -212,6 +212,10 @@ const zh_tw = {
|
||||
mountMode: '掛載模式',
|
||||
mountFailed: '掛載失敗',
|
||||
mountDesc: '在某些系統中,需要在遠端主機中彈出虛擬硬碟後再掛載映像檔。',
|
||||
diskOnWarn: '掛載映像檔會關閉虛擬隨身碟,電腦將無法再存取 /data 目錄。是否繼續?',
|
||||
diskOffWarn:
|
||||
'虛擬隨身碟目前已關閉,需要先開啟才能掛載映像檔,電腦上的 USB 裝置會短暫斷開重連。是否繼續?',
|
||||
diskShared: '虛擬隨身碟已開啟,本機 /data 為唯讀;關閉虛擬隨身碟後才能刪除或上傳映像檔。',
|
||||
unmountFailed: '解除安裝失敗',
|
||||
unmountDesc: '在某些系統中,需要在遠端主機中手動彈出後再解除安裝映像。',
|
||||
refresh: '重新整理映像檔列表',
|
||||
@@ -279,6 +283,8 @@ const zh_tw = {
|
||||
input: '請輸入映像檔的下載 URL',
|
||||
ok: '確定',
|
||||
disabled: '/data 為唯讀目錄,無法下載映像檔',
|
||||
diskShared: '請先關閉虛擬隨身碟功能,再使用映像檔下載功能',
|
||||
diskOff: '關閉虛擬隨身碟功能',
|
||||
uploadbox: '將檔案拖曳到此處或按一下選擇',
|
||||
inputfile: '請輸入映像檔案',
|
||||
NoISO: '無 ISO',
|
||||
@@ -433,6 +439,10 @@ const zh_tw = {
|
||||
hidOnlyDesc: '停止模擬虛擬設備,僅保留基礎 HID 控制',
|
||||
disk: '虛擬隨身碟',
|
||||
diskDesc: '在遠端主機上連接虛擬隨身碟',
|
||||
diskLocked: '掛載映像檔期間已關閉(開啟會取消映像檔掛載)',
|
||||
diskCancelMount:
|
||||
'開啟虛擬隨身碟會取消目前掛載的映像檔,並中斷正在進行的映像檔下載或上傳,是否繼續?',
|
||||
diskBreakDownload: '開啟虛擬隨身碟會中斷映像檔下載功能,是否繼續?',
|
||||
network: '虛擬網卡',
|
||||
networkDesc: '在遠端主機上新增虛擬網卡',
|
||||
reboot: '重新啟動',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DownloadIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { cancelDownloadImage, downloadImage, imageEnabled, statusImage } from '@/api/download.ts';
|
||||
import { getVirtualDevice, updateVirtualDevice } from '@/api/virtual-device.ts';
|
||||
import { keyboardLockAtom } from '@/jotai/keyboard.ts';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
@@ -23,6 +24,8 @@ export const DownloadImage = () => {
|
||||
const [isCancelling, setIsCancelling] = useState(false);
|
||||
const [isRemoteDownloading, setIsRemoteDownloading] = useState(false);
|
||||
const [diskEnabled, setDiskEnabled] = useState(false);
|
||||
const [isDiskShared, setIsDiskShared] = useState(false);
|
||||
const [isDisablingDisk, setIsDisablingDisk] = useState(false);
|
||||
const [popoverKey, setPopoverKey] = useState(0);
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
@@ -47,6 +50,34 @@ export const DownloadImage = () => {
|
||||
.catch(() => {
|
||||
setDiskEnabled(false);
|
||||
});
|
||||
|
||||
// /data is read-only while the virtual disk serves it to the host
|
||||
getVirtualDevice()
|
||||
.then((res) => {
|
||||
setIsDiskShared(!!res.data?.disk);
|
||||
})
|
||||
.catch(() => {
|
||||
setIsDiskShared(false);
|
||||
});
|
||||
}
|
||||
|
||||
// hand /data back to the KVM, which makes the download possible again
|
||||
function disableDisk() {
|
||||
if (isDisablingDisk) return;
|
||||
setIsDisablingDisk(true);
|
||||
|
||||
updateVirtualDevice('disk')
|
||||
.then((rsp) => {
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
checkDiskEnabled();
|
||||
})
|
||||
.finally(() => {
|
||||
setIsDisablingDisk(false);
|
||||
});
|
||||
}
|
||||
|
||||
function handleOpenChange(open: boolean) {
|
||||
@@ -309,7 +340,16 @@ export const DownloadImage = () => {
|
||||
<Divider style={{ margin: '10px 0 10px 0' }} />
|
||||
|
||||
{!diskEnabled ? (
|
||||
<div className="text-red-500">{t('download.disabled')}</div>
|
||||
isDiskShared ? (
|
||||
<div className="space-y-3">
|
||||
<div className="text-neutral-300">{t('download.diskShared')}</div>
|
||||
<Button type="primary" block loading={isDisablingDisk} onClick={disableDisk}>
|
||||
{t('download.diskOff')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-red-500">{t('download.disabled')}</div>
|
||||
)
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import clsx from 'clsx';
|
||||
import {
|
||||
ArrowBigDownDashIcon,
|
||||
ArrowBigUpDashIcon,
|
||||
CircleAlertIcon,
|
||||
LoaderCircleIcon,
|
||||
PackageIcon,
|
||||
PackageSearchIcon,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/storage.ts';
|
||||
import { getVirtualDevice, updateVirtualDevice } from '@/api/virtual-device.ts';
|
||||
import { client } from '@/lib/websocket.ts';
|
||||
|
||||
const imageUpdatedEvent = 'nanokvm:image-updated';
|
||||
@@ -33,11 +35,15 @@ export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState('');
|
||||
const [deletingImage, setDeletingImage] = useState('');
|
||||
const [diskPrompt, setDiskPrompt] = useState('');
|
||||
const [pendingImage, setPendingImage] = useState('');
|
||||
const [isDiskShared, setIsDiskShared] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
getImages();
|
||||
getDiskState();
|
||||
|
||||
const handleImageUpdated = () => {
|
||||
getImages();
|
||||
@@ -86,9 +92,53 @@ export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
});
|
||||
}
|
||||
|
||||
// mount/unmount image
|
||||
// mounting an image takes the mass storage device away from the virtual disk,
|
||||
// so ask the user before the virtual disk changes on the computer
|
||||
function mountImage(image: string) {
|
||||
if (mountingImage) return;
|
||||
|
||||
// unmounting leaves the virtual disk alone
|
||||
if (mountedImage === image) {
|
||||
doMountImage(image);
|
||||
return;
|
||||
}
|
||||
|
||||
getVirtualDevice().then((rsp) => {
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
setDiskPrompt(rsp.data?.disk ? 'on' : 'off');
|
||||
setPendingImage(image);
|
||||
});
|
||||
}
|
||||
|
||||
// turn the virtual disk on, then mount
|
||||
async function confirmMountImage() {
|
||||
const image = pendingImage;
|
||||
const prompt = diskPrompt;
|
||||
|
||||
setPendingImage('');
|
||||
setDiskPrompt('');
|
||||
|
||||
if (!image) return;
|
||||
|
||||
// the media device only exists while the virtual disk is enabled
|
||||
if (prompt === 'off') {
|
||||
const rsp = await updateVirtualDevice('disk');
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
doMountImage(image);
|
||||
}
|
||||
|
||||
// mount/unmount image
|
||||
function doMountImage(image: string) {
|
||||
if (mountingImage) return;
|
||||
setMountingImage(image);
|
||||
|
||||
client.close();
|
||||
@@ -111,9 +161,23 @@ export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
.finally(() => {
|
||||
setMountingImage('');
|
||||
client.connect();
|
||||
getDiskState();
|
||||
});
|
||||
}
|
||||
|
||||
// the host writes /data while the virtual disk serves it, so images are
|
||||
// read-only here and can only be mounted, not deleted or uploaded
|
||||
function getDiskState() {
|
||||
getVirtualDevice().then((rsp) => {
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDiskShared(!!rsp.data?.disk);
|
||||
});
|
||||
}
|
||||
|
||||
// show delete image modal
|
||||
function showDeleteModal(e: any, image: string) {
|
||||
e.stopPropagation();
|
||||
@@ -121,7 +185,7 @@ export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
const isMounted = mountedImage === image;
|
||||
const isDeleting = deletingImage !== '';
|
||||
|
||||
if (isMounted || isDeleting) {
|
||||
if (isMounted || isDeleting || isDiskShared) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,6 +251,13 @@ export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{isDiskShared && (
|
||||
<div className="flex items-start space-x-1 pb-2 text-xs text-neutral-400">
|
||||
<CircleAlertIcon size={14} className="mt-[2px] shrink-0" />
|
||||
<span>{t('image.diskShared')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex max-h-[400px] flex-col overflow-y-auto pb-2">
|
||||
{images.map((image) => (
|
||||
<div
|
||||
@@ -215,21 +286,16 @@ export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
'flex h-[24px] w-[24px] items-center justify-center rounded hover:bg-neutral-500/50',
|
||||
mountedImage === image
|
||||
? 'cursor-not-allowed text-neutral-500'
|
||||
: 'text-neutral-300 hover:text-red-500'
|
||||
)}
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
className="h-[24px] w-[24px] p-0"
|
||||
icon={<Trash2Icon size={16} />}
|
||||
disabled={mountedImage === image || isDiskShared}
|
||||
loading={deletingImage === image}
|
||||
onClick={(e) => showDeleteModal(e, image)}
|
||||
>
|
||||
{deletingImage === image ? (
|
||||
<LoaderCircleIcon className="animate-spin text-red-500" size={16} />
|
||||
) : (
|
||||
<Trash2Icon size={16} />
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -254,6 +320,26 @@ export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={t('image.attention')}
|
||||
open={diskPrompt !== ''}
|
||||
width={520}
|
||||
footer={null}
|
||||
onCancel={() => setDiskPrompt('')}
|
||||
>
|
||||
<div className="flex flex-col items-center space-y-1 pb-10">
|
||||
<p>{t(diskPrompt === 'on' ? 'image.diskOnWarn' : 'image.diskOffWarn')}</p>
|
||||
<Typography.Text code>{pendingImage.replace(/^.*[\\/]/, '')}</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center space-x-3 pb-3">
|
||||
<Button type="primary" onClick={confirmMountImage}>
|
||||
{t('image.okBtn')}
|
||||
</Button>
|
||||
<Button onClick={() => setDiskPrompt('')}>{t('image.cancelBtn')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{contextHolder}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -19,6 +19,8 @@ export const Image = () => {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const [mode, setMode] = useState('mass-storage');
|
||||
|
||||
const imageUpdatedEvent = 'nanokvm:image-updated';
|
||||
|
||||
const modes = [
|
||||
{
|
||||
value: 'mass-storage',
|
||||
@@ -41,6 +43,21 @@ export const Image = () => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
getImageState();
|
||||
|
||||
const handleImageUpdated = () => {
|
||||
getImageState();
|
||||
};
|
||||
window.addEventListener(imageUpdatedEvent, handleImageUpdated);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(imageUpdatedEvent, handleImageUpdated);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// readers of the mounted image and of the CD-ROM flag: the icon state and the
|
||||
// mode selector here, and the same state in the mount panel
|
||||
function getImageState() {
|
||||
api.getMountedImage().then((rsp) => {
|
||||
if (rsp.code === 0) {
|
||||
setIsMounted(!!rsp.data?.file);
|
||||
@@ -52,7 +69,7 @@ export const Image = () => {
|
||||
setMode(rsp.data?.cdrom === 1 ? 'cd-rom' : 'mass-storage');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
}
|
||||
|
||||
function toggleModal(open: boolean) {
|
||||
setIsModalOpen(open);
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Switch } from 'antd';
|
||||
import { Button, Modal, Switch } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { cancelDownloadImage, statusImage } from '@/api/download.ts';
|
||||
import { getHidMode } from '@/api/hid.ts';
|
||||
import { getMountedImage } from '@/api/storage.ts';
|
||||
import * as api from '@/api/virtual-device.ts';
|
||||
|
||||
const imageUpdatedEvent = 'nanokvm:image-updated';
|
||||
|
||||
export const VirtualDevices = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isHidOnlyMode, setIsHidOnlyMode] = useState(false);
|
||||
const [isDiskEnabled, setIsDiskEnabled] = useState(false);
|
||||
const [isNetworkEnabled, setIsNetworkEnabled] = useState(false);
|
||||
const [isImageMounted, setIsImageMounted] = useState(false);
|
||||
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
|
||||
const [loading, setLoading] = useState<'' | 'disk' | 'network'>('');
|
||||
|
||||
useEffect(() => {
|
||||
getHidOnlyMode();
|
||||
getVirtualDevice();
|
||||
getImageState();
|
||||
}, []);
|
||||
|
||||
async function getHidOnlyMode() {
|
||||
@@ -46,6 +53,20 @@ export const VirtualDevices = () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function getImageState() {
|
||||
try {
|
||||
const rsp = await getMountedImage();
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsImageMounted(!!rsp.data?.file);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function update(device: 'disk' | 'network') {
|
||||
if (loading) return;
|
||||
setLoading(device);
|
||||
@@ -58,6 +79,10 @@ export const VirtualDevices = () => {
|
||||
}
|
||||
|
||||
await getVirtualDevice();
|
||||
await getImageState();
|
||||
|
||||
// the desktop shows the mounted image and the CD-ROM flag too
|
||||
window.dispatchEvent(new Event(imageUpdatedEvent));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} finally {
|
||||
@@ -65,6 +90,28 @@ export const VirtualDevices = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// enabling the disk takes /data away from the mounted image and from the
|
||||
// image downloader, which writes there
|
||||
async function toggleDisk() {
|
||||
if (isDiskEnabled) {
|
||||
await update('disk');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsConfirmOpen(true);
|
||||
}
|
||||
|
||||
// a transfer writes to /data, which the host takes over
|
||||
async function stopTransfer() {
|
||||
const rsp = await statusImage();
|
||||
if (rsp.code !== 0 || rsp.data?.status !== 'in_progress') return;
|
||||
|
||||
const cancel = await cancelDownloadImage();
|
||||
if (cancel.code !== 0) {
|
||||
console.log(cancel.msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (isHidOnlyMode) {
|
||||
return (
|
||||
<div className="flex items-center justify-between space-x-10">
|
||||
@@ -84,16 +131,47 @@ export const VirtualDevices = () => {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.device.disk')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.diskDesc')}</span>
|
||||
<span className="text-xs text-neutral-500">
|
||||
{isImageMounted ? t('settings.device.diskLocked') : t('settings.device.diskDesc')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={isDiskEnabled}
|
||||
loading={loading === 'disk'}
|
||||
onChange={() => update('disk')}
|
||||
/>
|
||||
<Switch checked={isDiskEnabled} loading={loading === 'disk'} onChange={toggleDisk} />
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title={t('image.attention')}
|
||||
open={isConfirmOpen}
|
||||
width={520}
|
||||
footer={null}
|
||||
onCancel={() => setIsConfirmOpen(false)}
|
||||
>
|
||||
<div className="flex flex-col items-center space-y-1 pb-10">
|
||||
<p>
|
||||
{t(
|
||||
isImageMounted
|
||||
? 'settings.device.diskCancelMount'
|
||||
: 'settings.device.diskBreakDownload'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center space-x-3 pb-3">
|
||||
<Button
|
||||
type="primary"
|
||||
loading={loading === 'disk'}
|
||||
onClick={async () => {
|
||||
setIsConfirmOpen(false);
|
||||
await stopTransfer();
|
||||
await update('disk');
|
||||
}}
|
||||
>
|
||||
{t('settings.device.okBtn')}
|
||||
</Button>
|
||||
<Button onClick={() => setIsConfirmOpen(false)}>{t('settings.device.cancelBtn')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* Virtual Network */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col space-y-1">
|
||||
|
||||
Reference in New Issue
Block a user