diff --git a/etc/missing_strings.pl b/etc/missing_strings.pl new file mode 100755 index 000000000..aef0b5556 --- /dev/null +++ b/etc/missing_strings.pl @@ -0,0 +1,76 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Data::Dumper; + +my $DIR = "lib/Ravada/I18N"; + +my %LIST = map { $_ => 1 } @ARGV; + +sub selected { + my $file = shift; + my ($name) = $file =~ m{(.*)\.\w+}; + + return 0 if !exists $LIST{$name}; + return $LIST{$name}; +} + +sub load_strings { + my $file = shift; + + if ($file !~ m{/}) { + $file = "$DIR/$file"; + } + open my $in,"<",$file or die "$! $file"; + + my $msgid; + my %found; + my $string; + while (my $line = <$in>) { + my ($msgstr) = $line =~ /^msgstr/; + if ($msgstr && $string) { + $found{$string}++; + $string = undef; + next; + } + my ($string1) = $line =~ /^msgid "(.*)"/; + if (defined $string1) { + $string = $string1; + next; + } + if (!defined $string1 && defined $string) { + my ($string2) = $line =~ /^"(.*)"/; + if (defined $string2) { + $msgid=0; + $string = "$string$string2"; + } + } + next if !$string; + } + close $in; + return \%found; +} + +my $english = load_strings('en.po'); +my $found=0; + + +opendir my $in,$DIR or die "$! $DIR"; +while (my $file = readdir $in) { + next if $file !~ /\.po$/; + next if keys %LIST && !selected($file); + my $path = "$DIR/$file"; + next if !-f $path; + print "$path\n"; + + my $string = load_strings($file); + for my $key (sort keys %$english) { + next if $string->{$key}; + print "msgid \"$key\"\n" + ."msgstr \"\"\n\n"; + $found++; + } + last if $found; +} +closedir $in; diff --git a/lib/Ravada/I18N/de.po b/lib/Ravada/I18N/de.po index ab7efaae2..02ba13f6e 100644 --- a/lib/Ravada/I18N/de.po +++ b/lib/Ravada/I18N/de.po @@ -912,3 +912,1060 @@ msgstr "Denken Sie dran, dass Nutzer den Klone verwenden könnten" msgid "Yes, shutwdown all the clones" msgstr "Ja, alle Klone herunterfahren" + +msgid "-- choose hardware --" +msgstr "-- Hardware auswählen --" + +msgid "A node with that address already exists." +msgstr "Es existiert bereits ein Knoten mit dieser Adresse." + +msgid "Accept" +msgstr "Akzeptieren" + +msgid "Access" +msgstr "Zugreifen" + +msgid "Action" +msgstr "Aktion" + +msgid "Active" +msgstr "Aktiv" + +msgid "Add another user" +msgstr "Weiteren Nutzer anlegen" + +msgid "Add groups" +msgstr "Gruppe hinzufügen" + +msgid "Add new Display" +msgstr "Neuen Bildschirm hinzufügen" + +msgid "Add new disk" +msgstr "Neue Festplatte hinzufügen" + +msgid "Add to group" +msgstr "Zur Gruppe hinzufügen" + +msgid "Admin users can still log in from" +msgstr "Administratoren können sich weiterhin einloggen" + +msgid "Advanced options" +msgstr "Erweiterte Optionen" + +msgid "All events" +msgstr "Alle Ereignisse" + +msgid "All machines" +msgstr "Alle Maschinen" + +msgid "Anonymous" +msgstr "Anonym" + +msgid "Any remote client can access this port" +msgstr "Jeder verbundene Client kann auf diesen Port zugreifen" + +msgid "Apply" +msgstr "Anwenden" + +msgid "Are you sure you want to migrate" +msgstr "Möchten Sie wirklich migrieren" + +msgid "Are you sure you want to prepare the base of" +msgstr "" + +msgid "Are you sure you want to remove the" +msgstr "" + +msgid "Are you sure you want to remove the base of" +msgstr "" + +msgid "Are you sure you want to remove the node" +msgstr "Möchten Sie diesen Knoten wirlich entfernen" + +msgid "Are you sure you want to remove this group ?" +msgstr "Möchten Sie diese Gruppe wirklich entfernen?" + +msgid "Are you sure you want to spinoff from the base?" +msgstr "" + +msgid "Assign this virtual machine to the pre-started pool" +msgstr "Diese virtuelle Maschine einem bereits gestartetem pool zuweisen" + +msgid "Attachment" +msgstr "Anhang" + +msgid "Autostart" +msgstr "Autostart" + +msgid "BIOS" +msgstr "BIOS" + +msgid "Balance" +msgstr "Balance" + +msgid "Base" +msgstr "Basis" + +msgid "Bases" +msgstr "" + +msgid "Bookings" +msgstr "Buchungen" + +msgid "Bookings require LDAP authentication." +msgstr "Buchungen benötigen LDAP authentifizierung" + +msgid "Bridge" +msgstr "Brücke" + +msgid "CPU Features" +msgstr "CPU Features" + +msgid "CPUs" +msgstr "CPUs" + +msgid "Can expose virtual machine ports." +msgstr "Kann Ports der virtuellen Maschine freigeben" + +msgid "Can get a screenshot of own virtual machines." +msgstr "Kann Bildschirmaufnahmen von eigenen virtuellen Maschinen erstellen" + +msgid "Can have an unlimited amount of machines started." +msgstr "Kann eine unbegrenzte Anzahl von Maschinen starten" + +msgid "Can manage groups." +msgstr "Kann Gruppen verwalten" + +msgid "Can reboot all virtual machines." +msgstr "Kann alle virtuellen Maschinen neu starten" + +msgid "Can reboot clones own virtual machines." +msgstr "" + +msgid "Can reboot own virtual machines." +msgstr "Kann eigene virtuelle Maschinen neu starten" + +msgid "Can rename any virtual machine owned by the user." +msgstr "Kann jede viruelle Maschine dieses Nutzers umbenennen" + +msgid "Can rename any virtual machine." +msgstr "Kann jede virtuelle Maschine umbenennen" + +msgid "Can rename clones from virtual machines owned by the user." +msgstr "Kann Klone von virtuellen Maschinen dieses Nutzers umbenennen" + +msgid "Can shutdown own virtual machines." +msgstr "Kann eigene virtuelle Maschinen umbenennen" + +msgid "Can view groups." +msgstr "Kann Gruppen anzeigen" + +msgid "Change" +msgstr "Ändern" + +msgid "Changing the base of a virtual machine is potentially dangerous and can't be undone." +msgstr "Das ändern der Grundlage einer virtuellen maschine ist potentiell gefährlich und kann nicht rückgängig gemacht werden." + +msgid "Check" +msgstr "Kontrollieren" + +msgid "Check connection to" +msgstr "Kontrolliere die Verbindung zu" + +msgid "Choose the virtualization type of the Node." +msgstr "Virtualisierungstyp des Knoten wählen." + +msgid "Choose the virtualization type of the Virtual Machine." +msgstr "Virtualisierungstyp der virtuellen Maschine wählen." + +msgid "Client" +msgstr "" + +msgid "Client headers" +msgstr "" + +msgid "Clones are volatile, so all the pool will be started." +msgstr "" + +msgid "Clones created from this machine will be removed on shutdown." +msgstr "Klone die auf dieser Maschine erstellt wurden, werden beim herunterfahren gelöscht." + +msgid "Close" +msgstr "Schließen" + +msgid "Color" +msgstr "Farbe" + +msgid "Compact" +msgstr "Kompakt" + +msgid "Compact disk volumes" +msgstr "" + +msgid "Configure LDAP Authentication" +msgstr "LDAP Authentifizierung einrichten" + +msgid "Confirm" +msgstr "Bestätigen" + +msgid "Confirm disable node" +msgstr "Deaktivieren des Knoten bestätigen" + +msgid "Confirm password can not exceed 20 characters" +msgstr "Passwort bestätigen darf nicht länger sein als 20 Zeichen" + +msgid "Confirm password can only contain words and numbers" +msgstr "Passwort bestätigen darf nur aus zahlen und wörtern bestehen" + +msgid "Confirm password is required" +msgstr "Passwort bestätigen wird benötigt" + +msgid "Confirm password must be at least 5 characters" +msgstr "Passwort bestätigen muss mindestend 5 Zeichen lang sein" + +msgid "Congrats" +msgstr "Glückwunsch" + +msgid "Contact Method" +msgstr "Kontaktmethode" + +msgid "Content will be cleaned on restore" +msgstr "Inhalt wird beim wiederherstellen gelöscht" + +msgid "Content will be cleaned on restore and shutdown" +msgstr "Inhalt wird beim wiederherstellen und herunterfahren gelöscht" + +msgid "Content will be kept on restore" +msgstr "Inhalt wird beim wiederherstellen behalten" + +msgid "Create a new group" +msgstr "Neue Gruppe erstellen" + +msgid "Current memory (MB)" +msgstr "Aktueller Speicher (MB)" + +msgid "Danger: This will destroy all the disk data permantently." +msgstr "Achtung: Alle daten werden dieser Festplatte werden permanent gelöscht." + +msgid "Data" +msgstr "Daten" + +msgid "Debug" +msgstr "" + +msgid "Debug Ports" +msgstr "Debug Ports" + +msgid "Debug Ports Exposed" +msgstr "Debug Ports öffentlich" + +msgid "Delete" +msgstr "Löschen" + +msgid "Delete the event" +msgstr "Das Ereignis löschen" + +msgid "Disable" +msgstr "Deaktivieren" + +msgid "Disabled" +msgstr "Deaktiviert" + +msgid "Disabling this node will shut all the" +msgstr "" + +msgid "Display" +msgstr "Anzeige" + +msgid "Display IP :" +msgstr "" + +msgid "Display Password" +msgstr "" + +msgid "Display Port :" +msgstr "" + +msgid "Display Port secure :" +msgstr "" + +msgid "Display URL :" +msgstr "" + +msgid "Edit the event" +msgstr "" + +msgid "Email" +msgstr "E-Mail" + +msgid "Enable" +msgstr "Aktivieren" + +msgid "Enables display password when available" +msgstr "" + +msgid "End" +msgstr "Ende" + +msgid "Enter New Password" +msgstr "Neues Passwort eigeben" + +msgid "Enter Old Password" +msgstr "Alten Passwort eingeben" + +msgid "Enter group name" +msgstr "Gruppenname eingeben" + +msgid "Error:" +msgstr "Fehler:" + +msgid "Error: the LDAP entry for this user has been removed." +msgstr "Fehler: Der LDAP Eintrag für diesen Benutzer wurde gelöscht." + +msgid "Error: you want to have" +msgstr "" + +msgid "Event properties" +msgstr "Ereigniseigenschaften" + +msgid "Event saved successfully" +msgstr "Ereignis erfolgreich gespeichert" + +msgid "Exec clones sequentially" +msgstr "" + +msgid "Exec. time" +msgstr "" + +msgid "Fail" +msgstr "Fehlgeschlagen" + +msgid "Fallback" +msgstr "Fallback" + +msgid "Fill required camps with valid data" +msgstr "" + +msgid "For Spice client setup follow this " +msgstr "" + +msgid "Force Reboot" +msgstr "" + +msgid "Force ShutDown" +msgstr "" + +msgid "Force change password on first access" +msgstr "" + +msgid "From" +msgstr "" + +msgid "Frontend" +msgstr "" + +msgid "Global Settings" +msgstr "" + +msgid "Group" +msgstr "" + +msgid "Group name" +msgstr "" + +msgid "Group name can not exceed 80 characters" +msgstr "" + +msgid "Group name is required" +msgstr "" + +msgid "Groups" +msgstr "" + +msgid "Groups require a LDAP server configured." +msgstr "" + +msgid "Groups with permissions" +msgstr "" + +msgid "Hardware" +msgstr "" + +msgid "Hardware address" +msgstr "" + +msgid "Hibernated" +msgstr "" + +msgid "Hide active" +msgstr "" + +msgid "Hide clones" +msgstr "" + +msgid "I can't find" +msgstr "" + +msgid "I don't know settings tabs for:" +msgstr "" + +msgid "ISO file" +msgstr "" + +msgid "If you have any issue, you can contact the Suport crew and we will try to help you. Please, only contact Support Center if it is strictly necessary." +msgstr "" + +msgid "Import" +msgstr "" + +msgid "Internal IP" +msgstr "" + +msgid "Invalid IP network address. Expecting a.b.c.d/e" +msgstr "" + +msgid "Invalid Template" +msgstr "" + +msgid "Keep the CD for the clones" +msgstr "" + +msgid "LDAP" +msgstr "" + +msgid "LDAP groups are required to set up bookings. Some groups where found but no members belong to them. Add new entries here." +msgstr "" + +msgid "Legacy" +msgstr "" + +msgid "Loading ..." +msgstr "" + +msgid "Loading machine" +msgstr "" + +msgid "Loading nodes" +msgstr "" + +msgid "Machine" +msgstr "" + +msgid "Machine Information" +msgstr "" + +msgid "Machines Notifications" +msgstr "" + +msgid "Maintenance" +msgstr "" + +msgid "Maintenance End" +msgstr "" + +msgid "Maintenance Start" +msgstr "" + +msgid "Manage machine" +msgstr "" + +msgid "Max Memory" +msgstr "" + +msgid "Max memory (MB)" +msgstr "" + +msgid "Minimum port number to expose virtual machine services." +msgstr "" + +msgid "Monitoring" +msgstr "" + +msgid "NAT" +msgstr "" + +msgid "Network" +msgstr "" + +msgid "Network address is required." +msgstr "" + +msgid "Network name is required" +msgstr "" + +msgid "Networks" +msgstr "" + +msgid "New Booking" +msgstr "" + +msgid "New Group" +msgstr "" + +msgid "New Network" +msgstr "" + +msgid "New Password can only contain words and numbers" +msgstr "" + +msgid "New Password is required" +msgstr "" + +msgid "New Password must be at least 6 characters" +msgstr "" + +msgid "New feature" +msgstr "" + +msgid "New group member" +msgstr "" + +msgid "New name" +msgstr "" + +msgid "New network" +msgstr "" + +msgid "New password" +msgstr "" + +msgid "New user" +msgstr "" + +msgid "No LDAP groups created." +msgstr "" + +msgid "No Results Found" +msgstr "" + +msgid "No bases found" +msgstr "" + +msgid "No bridges found" +msgstr "" + +msgid "No groups found" +msgstr "" + +msgid "No machines" +msgstr "" + +msgid "No machines found" +msgstr "" + +msgid "No members found" +msgstr "" + +msgid "No message to show!" +msgstr "" + +msgid "No, cancel" +msgstr "" + +msgid "Node" +msgstr "" + +msgid "Node disabled" +msgstr "" + +msgid "Node down" +msgstr "" + +msgid "Node with machines from the same user" +msgstr "" + +msgid "Node with more free memory" +msgstr "" + +msgid "Nodes" +msgstr "" + +msgid "Nothing to monitoring" +msgstr "" + +msgid "Number of virtual machines that normal users can have running at the same time" +msgstr "" + +msgid "Old Password can only contain words and numbers" +msgstr "" + +msgid "Old Password is required" +msgstr "" + +msgid "Old and New Passwords match!" +msgstr "" + +msgid "Only remote client can access this port" +msgstr "" + +msgid "Only remote client can access this port if restricted" +msgstr "" + +msgid "Only users from these groups will be allowed to execute this machine" +msgstr "" + +msgid "Oops!" +msgstr "" + +msgid "Open ports" +msgstr "" + +msgid "Password and their confirmation do not match!" +msgstr "" + +msgid "Passwords do not match!" +msgstr "" + +msgid "Permission granted to user" +msgstr "" + +msgid "Permission revoked from user" +msgstr "" + +msgid "Phone" +msgstr "" + +msgid "Phone Number" +msgstr "" + +msgid "Please insert" +msgstr "" + +msgid "Please select an ISO file" +msgstr "" + +msgid "Please, make sure you have the right path and release, according to your PC configuration." +msgstr "" + +msgid "Pool" +msgstr "" + +msgid "Port" +msgstr "" + +msgid "Port expose" +msgstr "" + +msgid "Ports" +msgstr "" + +msgid "Poweroff" +msgstr "" + +msgid "Prepare Base" +msgstr "" + +msgid "Prepare this machine as a base to create clones from it." +msgstr "" + +msgid "PrepareBase" +msgstr "" + +msgid "Press SHIFT + F12 to exit the virtual machine" +msgstr "" + +msgid "Primary can not be unset, enable it in another video device" +msgstr "" + +msgid "Primary video devices will move to first" +msgstr "" + +msgid "Public" +msgstr "" + +msgid "Public Port" +msgstr "" + +msgid "Purge" +msgstr "" + +msgid "Purge disk volumes" +msgstr "" + +msgid "RAM (Gb)" +msgstr "" + +msgid "Ravada Apache documentation" +msgstr "" + +msgid "Re-Enter New Password" +msgstr "" + +msgid "Rebase" +msgstr "" + +msgid "Rebase all the clones to a new base" +msgstr "" + +msgid "Rebase this machine to another virtual machine" +msgstr "" + +msgid "Reboot" +msgstr "" + +msgid "Recent requests" +msgstr "" + +msgid "Refresh" +msgstr "" + +msgid "Refresh ports" +msgstr "" + +msgid "Reload" +msgstr "" + +msgid "Remove Base" +msgstr "" + +msgid "Remove Clones" +msgstr "" + +msgid "Remove bases in node" +msgstr "" + +msgid "Remove group" +msgstr "" + +msgid "Remove the base prepared from this machine" +msgstr "" + +msgid "Removing Virtual Machine" +msgstr "" + +msgid "Repeat" +msgstr "" + +msgid "Repeated New Password is required" +msgstr "" + +msgid "Request" +msgstr "" + +msgid "Requests" +msgstr "" + +msgid "Requires viewer password when implemented" +msgstr "" + +msgid "Restricted" +msgstr "" + +msgid "Run Timeout" +msgstr "" + +msgid "SSO/CAS Login" +msgstr "" + +msgid "Save" +msgstr "" + +msgid "Save changes" +msgstr "" + +msgid "Schedule" +msgstr "" + +msgid "Search group" +msgstr "" + +msgid "See documentation about:" +msgstr "" + +msgid "Select the .iso file the machine will utilize when installing the OS." +msgstr "" + +msgid "Server in maintenance until" +msgstr "" + +msgid "Set New Password" +msgstr "" + +msgid "Set defaults" +msgstr "" + +msgid "Set either" +msgstr "" + +msgid "Setup a LDAP Server" +msgstr "" + +msgid "Show active" +msgstr "" + +msgid "Show clones" +msgstr "" + +msgid "Show/Hide clones" +msgstr "" + +msgid "ShutDown" +msgstr "" + +msgid "Shutdown Timeout" +msgstr "" + +msgid "Shutdown disconnected" +msgstr "" + +msgid "Sorry" +msgstr "" + +msgid "Source Machine" +msgstr "" + +msgid "Spinoff clone" +msgstr "" + +msgid "Spinoff this clone from its base." +msgstr "" + +msgid "Start Limit" +msgstr "" + +msgid "Start after create the virtual machine" +msgstr "" + +msgid "Start after migration" +msgstr "" + +msgid "Support Contact" +msgstr "" + +msgid "Swap" +msgstr "" + +msgid "System Disk: (GB)" +msgstr "" + +msgid "Template selection is required" +msgstr "" + +msgid "Testing connection to" +msgstr "" + +msgid "The Minimum Disk Size needed for this ISO is" +msgstr "" + +msgid "The Minimum Swap Disk Size needed for this ISO is" +msgstr "" + +msgid "The VM is" +msgstr "" + +msgid "The machine will power off after this minutes after shutdown." +msgstr "" + +msgid "The machine will turn off after this time" +msgstr "" + +msgid "The new user" +msgstr "" + +msgid "The source machine is not a base. It must be prepared before it can be copied. This process may take some minutes." +msgstr "" + +msgid "There are no LDAP groups defined." +msgstr "" + +msgid "There are no active virtual machines" +msgstr "" + +msgid "This ISO is being downloaded. The virtual machine will be created after." +msgstr "" + +msgid "This Virtual Machine has no display hardware attached" +msgstr "" + +msgid "This address is duplicated" +msgstr "" + +msgid "This base can't be removed because the domain has clones." +msgstr "" + +msgid "This base has" +msgstr "" + +msgid "This base has clones" +msgstr "" + +msgid "This booking overlaps already scheduled reservations" +msgstr "" + +msgid "This can't be undone" +msgstr "" + +msgid "This event" +msgstr "" + +msgid "This event and the following" +msgstr "" + +msgid "This event and the following with same day of week" +msgstr "" + +msgid "This feature requires a LDAP server configured." +msgstr "" + +msgid "This field is required" +msgstr "" + +msgid "This group has been removed." +msgstr "" + +msgid "This is a main node and can't be removed." +msgstr "" + +msgid "This machine has a CD-ROM" +msgstr "" + +msgid "This machine is base and can't be modified." +msgstr "" + +msgid "This machine is base and it can't be modified." +msgstr "" + +msgid "This machine is hibernated and can't be renamed." +msgstr "" + +msgid "This machine is locked by process" +msgstr "" + +msgid "This machine is running and can't be modified." +msgstr "" + +msgid "This machine is running and can't be renamed." +msgstr "" + +msgid "This machine will shut down" +msgstr "" + +msgid "This name is duplicated" +msgstr "" + +msgid "This name is invalid. It can only contain alphabetic, numbers, undercores and dashes and must start by a letter." +msgstr "" + +msgid "This node has" +msgstr "" + +msgid "This node is local" +msgstr "" + +msgid "This server has reservations for today. Machines from users out of\n the booking list will be shutdown." +msgstr "" + +msgid "This user has never logged in the Ravada server." +msgstr "" + +msgid "This virtual machine has already been compacted" +msgstr "" + +msgid "This virtual machine has clones that are bases and won't be removed" +msgstr "" + +msgid "This virtual machine has no backups to purge" +msgstr "" + +msgid "This virtual machine has no group restrictions." +msgstr "" + +msgid "This virtual machine is running. It must be shut down before migrate." +msgstr "" + +msgid "Time" +msgstr "" + +msgid "Title" +msgstr "" + +msgid "To" +msgstr "" + +msgid "To make this possible, copy the content of" +msgstr "" + +msgid "Today Schedule" +msgstr "" + +msgid "Type" +msgstr "" + +msgid "Type the ISO pathname" +msgstr "" + +msgid "Type the name of the volume disk to confirm:" +msgstr "" + +msgid "Type the template name" +msgstr "" + +msgid "UEFI" +msgstr "" + +msgid "Until" +msgstr "" + +msgid "User is not member of any group." +msgstr "" + +msgid "User not found" +msgstr "" + +msgid "Users from this network can run all virtual machines" +msgstr "" + +msgid "Users from this network can run no virtual machines" +msgstr "" + +msgid "Virtual Machine will be shutdown when user disconnects." +msgstr "" + +msgid "Virtual Machine will start on host start." +msgstr "" + +msgid "Virtual machines in the pool." +msgstr "" + +msgid "Virtual machines pre-started" +msgstr "" + +msgid "Volatile" +msgstr "" + +msgid "Waiting for machine to start" +msgstr "" + +msgid "Waiting for network to come up" +msgstr "" + +msgid "Waiting for requests to complete" +msgstr "" + +msgid "Warning" +msgstr "" + +msgid "Warning: anonymous machines won't show up unless you enable allowed." +msgstr "" + +msgid "Warning: anonymous machines won't show up unless you set them public." +msgstr "" + +msgid "Warning: this virtual machine will be prepared as a base. This may take long." +msgstr "" + +msgid "Web Service connection failed." +msgstr "" + +msgid "Weekly" +msgstr "" + +msgid "What do you want to do now?" +msgstr "" + +msgid "Will be destroyed on shutdown" +msgstr "" + +msgid "Yes, rebase" +msgstr "" + diff --git a/lib/Ravada/VM.pm b/lib/Ravada/VM.pm index 09b9b87d0..606700b59 100644 --- a/lib/Ravada/VM.pm +++ b/lib/Ravada/VM.pm @@ -146,8 +146,6 @@ around 'ping' => \&_around_ping; around 'connect' => \&_around_connect; after 'disconnect' => \&_post_disconnect; -around '_list_used_volumes' => \&_around_list_used_volumes; - ############################################################# # # method modifiers @@ -2515,7 +2513,6 @@ sub dir_backup($self) { return $dir_backup; } - sub _follow_link($self, $file) { my ($dir, $name) = $file =~ m{(.*)/(.*)}; my $file2 = $file; @@ -2525,13 +2522,20 @@ sub _follow_link($self, $file) { } if (!defined $self->{_is_link}->{$file2} ) { - my ($out,$err) = $self->run_command("stat", $file2); + my ($out,$err) = $self->run_command("stat","-c",'"%N"', $file2); chomp $out; - $out =~ m{ -> (/.*)}; - $self->{_is_link}->{$file2} = $1; + my ($link) = $out =~ m{ -> '(.+)'}; + if ($link) { + if ($link !~ m{^/}) { + my ($path) = $file2 =~ m{(.*/)}; + $path = "/" if !$path; + $link = "$path$link"; + } + $self->{_is_link}->{$file2} = $link; + } } - my $path = $self->{_is_link}->{$file2}; - return ($path or $file2); + $self->{_is_link}->{$file2} = $file2 if !exists $self->{_is_link}->{$file2}; + return $self->{_is_link}->{$file2}; } sub _is_link_remote($self, $vol) { @@ -2576,12 +2580,33 @@ sub _is_link($self,$vol) { return $path_link if $path_link; } -sub _around_list_used_volumes($orig, $self) { - my @vols = $self->$orig(); - my @links; - for my $vol ( @vols ) { - my $link = $self->_is_link($vol); - push @links,($link) if $link; +=head2 list_unused_volumes + +Returns a list of unused volume files + +=cut + +sub list_unused_volumes($self) { + my @all_vols = $self->list_volumes(); + + my @used = $self->list_used_volumes(); + my %used; + for my $vol (@used) { + $used{$vol}++; + my $link = $self->_follow_link($vol); + $used{$link}++ if $link; + } + + my @vols; + my %duplicated; + for my $vol ( @all_vols ) { + next if $used{$vol}; + + my $link = $self->_follow_link($vol); + next if $used{$link}; + next if $duplicated{$link}++; + + push @vols,($link); } return @vols; } diff --git a/lib/Ravada/VM/KVM.pm b/lib/Ravada/VM/KVM.pm index d9705750f..ad5eb5f70 100644 --- a/lib/Ravada/VM/KVM.pm +++ b/lib/Ravada/VM/KVM.pm @@ -336,6 +336,8 @@ sub search_volume_re($self,$pattern,$refresh=0) { my @vols; for ( 1 .. 10) { eval { @vols = $pool->list_all_volumes() }; + last if $@ && ref($@) && $@->code == 55; + last if $@ && $@ =~ /code: (55|38),/; last if !$@ || $@ =~ / no storage pool with matching uuid/; warn "WARNING: on search volume_re: $@"; sleep 1; @@ -385,24 +387,16 @@ sub _list_volumes($self) { my @pools; my %duplicated_path; for my $pool (_list_storage_pools($self->vm)) { - next if !$pool->is_active; - - my $xml = XML::LibXML->load_xml(string => $pool->get_xml_description()); - - my $path = $xml->findnodes('/pool/target/path/text()'); - $path = $self->_follow_link($path); - push @pools,($pool) if !$duplicated_path{$path}++; - - } - for my $pool (@pools) { - my @vols; - for ( 1 .. 10) { + my @vols; + for ( 1 .. 10) { + next if !$pool->is_active; eval { @vols = $pool->list_all_volumes() }; + last if $@ && ref($@) && $@->code == 55; last if !$@ || $@ =~ / no storage pool with matching uuid/; warn "WARNING: on search volume_re: $@"; sleep 1; - } - push @volumes,@vols; + } + push @volumes,@vols; } return @volumes; } @@ -460,7 +454,7 @@ sub _find_all_volumes($self, $xml) { return @used; } -sub _list_used_volumes($self) { +sub list_used_volumes($self) { my @used =$self->_list_used_volumes_known(); for my $name ( $self->discover ) { my $dom = $self->vm->get_domain_by_name($name); @@ -469,20 +463,14 @@ sub _list_used_volumes($self) { return @used; } -sub list_unused_volumes($self) { - my %used = map { $_ => 1 } $self->_list_used_volumes(); - my @unused; +sub list_volumes($self) { my $file; + my @volumes; - my $n_found=0; for my $vol ( $self->_list_volumes ) { eval { ($file) = $vol->get_path }; confess $@ if $@ && $@ !~ /libvirt error code: 50,/; - next if $used{$file}; - - my $link = $self->_is_link($file); - next if $link && $used{$link}; my $info; eval { $info = $vol->get_info() }; @@ -491,11 +479,10 @@ sub list_unused_volumes($self) { next if !$info || $info->{type} eq 2; - # cluck Dumper([ $file, [sort grep /2023/,keys %used]]) if $file =~/2023/; - push @unused,($file); + push @volumes,($file); } - return @unused; + return @volumes; } sub refresh_storage_pools($self) { @@ -606,6 +593,7 @@ sub file_exists($self, $file) { sub _file_exists_remote($self, $file) { $file = $self->_follow_link($file) unless $file =~ /which$/; for my $pool ($self->vm->list_all_storage_pools ) { + next if !$pool->is_active; $self->_wait_storage( sub { $pool->refresh() } ); my @volumes = $self->_wait_storage( sub { $pool->list_all_volumes }); for my $vol ( @volumes ) { @@ -717,6 +705,14 @@ sub create_storage_pool($self, $name, $dir, $vm=$self->vm) { } +sub remove_storage_pool($self, $name) { + my $sp = $self->vm->get_storage_pool_by_name($name); + return if !$sp; + + $sp->destroy if $sp->is_active; + $sp->undefine(); +} + sub _create_default_pool($self, $vm=$self->vm) { my $dir = "/var/lib/libvirt/images"; mkdir $dir if ! -e $dir; diff --git a/lib/Ravada/VM/Void.pm b/lib/Ravada/VM/Void.pm index 08f6b6c01..1cee3f0c7 100644 --- a/lib/Ravada/VM/Void.pm +++ b/lib/Ravada/VM/Void.pm @@ -352,7 +352,7 @@ sub search_volume($self, $pattern) { return; } -sub _list_used_volumes($self) { +sub list_used_volumes($self) { my @disk; for my $domain ($self->list_domains) { push @disk,($domain->list_disks()); @@ -363,32 +363,33 @@ sub _list_used_volumes($self) { return @disk } -sub _list_volumes($self) { +sub _list_volumes_sp($self, $sp) { die "Error: TODO remote!" if !$self->is_local; + confess if !defined $sp; + my $dir = $sp->{path} or die "Error: unknown path ".Dumper($sp); + return if ! -e $dir; + my @vol; - opendir my $ls,$self->dir_img or die $!; - my $dir = $self->dir_img; + opendir my $ls,$dir or die "$! $dir"; while (my $file = readdir $ls) { - push @vol,("$dir/$file"); + push @vol,("$dir/$file") if -f "$dir/$file"; } closedir $ls; + return @vol; } -sub list_unused_volumes($self) { - die "Error: TODO remote!" if !$self->is_local; - my %used = map { $_ => 1 } $self->_list_used_volumes(); - my @unused; - for my $vol ( sort $self->_list_volumes ) { - next if ! -f $vol; - next if $vol =~ m{/\..*yml$}; - - push @unused,($vol) unless $used{$vol}; +sub list_volumes($self) { + my @volumes; + for my $sp ($self->list_storage_pools(1)) { + for my $vol ( $self->_list_volumes_sp($sp) ) { + push @volumes,($vol); + } } - return @unused; + return @volumes; } sub _search_volume_remote($self, $pattern) { @@ -648,6 +649,19 @@ sub active_storage_pool($self, $name, $value) { $self->write_file($file_sp, Dump( \@list)); } +sub remove_storage_pool($self, $name) { + die "TODO remote VM" unless $self->is_local; + + my $file_sp = $self->dir_img."/.storage_pools.yml"; + my $sp_list = LoadFile($file_sp); + my @sp2; + for my $sp (@$sp_list) { + push @sp2,($sp) if $sp->{name} ne $name; + } + + $self->write_file($file_sp, Dump( \@sp2)); +} + #########################################################################3 1; diff --git a/t/lib/Test/Ravada.pm b/t/lib/Test/Ravada.pm index d693d7232..0f62448f6 100644 --- a/t/lib/Test/Ravada.pm +++ b/t/lib/Test/Ravada.pm @@ -1023,7 +1023,12 @@ sub _activate_storage_pools($vm) { next if $sp->is_active; next unless $sp->get_name =~ /^tst_/; diag("Activating sp ".$sp->get_name." on ".$vm->name); - $sp->build() unless $sp->is_active; + my $xml = XML::LibXML->load_xml(string => $sp->get_xml_description()); + my ($path) = $xml->findnodes('/pool/target/path'); + my $dir = $path->textContent(); + mkdir $dir or die "$! $dir" if ! -e $dir; + + $sp->build(); $sp->create() unless $sp->is_active; } } @@ -1426,7 +1431,7 @@ sub _qemu_storage_pool { } sub remove_qemu_pools($vm=undef) { - return if !$VM_VALID{'KVM'} || $>; + return if !$vm && (!$VM_VALID{'KVM'} || $>); return if defined $vm && $vm->type eq 'Void'; if (!defined $vm) { eval { $vm = rvd_back->search_vm('KVM') }; @@ -1441,18 +1446,22 @@ sub remove_qemu_pools($vm=undef) { my $base = base_pool_name(); $vm->connect(); - for my $pool ( Ravada::VM::KVM::_list_storage_pools($vm->vm)) { + for my $pool ( $vm->vm->list_all_storage_pools) { my $name = $pool->get_name; + next if $name !~ qr/^$base/; + diag($name); + eval {$pool->build(Sys::Virt::StoragePool::BUILD_NEW); $pool->create() }; warn $@ if $@ && $@ !~ /already active/; - next if $name !~ qr/^$base/; - diag("Removing ".$vm->name." storage_pool ".$pool->get_name); - for my $vol ( $pool->list_volumes ) { - diag("Removing ".$pool->get_name." vol ".$vol->get_name); - $vol->delete(); + if ($pool->is_active) { + diag("Removing ".$vm->name." storage_pool ".$pool->get_name); + for my $vol ( $pool->list_volumes ) { + diag("Removing ".$pool->get_name." vol ".$vol->get_name); + $vol->delete(); + } } _delete_qemu_pool($pool); - $pool->destroy(); + $pool->destroy() if $pool->is_active; eval { $pool->undefine() }; warn $@ if $@; warn $@ if$@ && $@ !~ /libvirt error code: 49,/; diff --git a/t/storage_list_unused.t b/t/storage_list_unused.t index 92750ce56..d76d23943 100644 --- a/t/storage_list_unused.t +++ b/t/storage_list_unused.t @@ -14,8 +14,6 @@ use Test::Ravada; no warnings "experimental::signatures"; use feature qw(signatures); -my @FILES; -my @DIRS; ######################################################################## sub _new_file($vm) { @@ -28,14 +26,14 @@ sub _new_file($vm) { return $file; } -sub test_links($vm, $machine) { +sub test_links($vm) { + my $machine = _create_clone($vm); my $dir = $vm->dir_img(); my ($vol) = $machine->list_volumes(); my ($file) = $vol =~ m{.*/(.*)}; my $dst = "/var/tmp/$file"; unlink $dst or die "$! $dst" if -e $dst; - push @FILES,($dst); copy($vol,$dst) or die "$! $vol -> $dst"; unlink $vol or die "$! $vol"; @@ -60,6 +58,7 @@ sub test_links($vm, $machine) { ok(!$found,"Expecting $vol not found") or die Dumper([$machine->list_volumes]); + remove_domain($machine); } sub test_links_dir($vm, $machine) { @@ -68,19 +67,16 @@ sub test_links_dir($vm, $machine) { my ($vol) = $machine->list_volumes(); my ($file) = $vol =~ m{.*/(.*)}; my $dir_dst = "/var/tmp/".new_domain_name(); + remove_dir($dir_dst); mkdir $dir_dst if ! -e $dir_dst; - push @FILES,($dir_dst); - my $dir_link = "/var/tmp/".new_domain_name(); my $file_link = "$dir_link/$file"; - push @FILES,($file_link); unlink($dir_link) or die "$! $dir_link" if -e $dir_link; symlink($dir_dst, $dir_link) or die "$! $dir_dst -> $dir_link"; - push @FILES,($dir_link); my $dst = "$dir_dst/$file"; copy($vol,$dst) or die "$vol -> $dst"; @@ -119,6 +115,8 @@ sub test_links_dir($vm, $machine) { ok(!$found,"Expecting $exp not found") or die Dumper([$machine->list_volumes]); } + remove_dir($dir_dst); + remove_dir($dir_link); } @@ -132,11 +130,16 @@ sub test_list_unused_discover($vm, $machine) { ); $sth->execute($machine->id); + my %used = map { $_ => 1 } $vm->list_used_volumes(); + for my $vol (@volumes) { + ok($used{$vol}) or die Dumper([sort keys %used]); + } + my $req = Ravada::Request->list_unused_volumes( uid => user_admin->id ,id_vm => $vm->id ,start => 0 - ,limit => 1000 + ,limit => 0 ); wait_request(); my $out_json = $req->output; @@ -198,18 +201,12 @@ sub test_list_unused($vm, $machine, $hidden_vols) { my $dir = $vm->dir_img(); my $file = _new_file($vm); - push @FILES,($file); - my $new_dir = $dir."/".new_domain_name(); - push @FILES,($new_dir); if (! -e $new_dir) { mkdir $new_dir or die "$! $new_dir"; } - open my $out,">",$file or die "$! $file"; - print $out "hi\n"; - close $out; $vm->refresh_storage(); my $req = Ravada::Request->list_unused_volumes( @@ -236,6 +233,8 @@ sub test_list_unused($vm, $machine, $hidden_vols) { ok(!$found_dir,"Expecting not found $new_dir"); _test_vm($vm, $machine, $output); + unlink $file; + remove_dir($new_dir); } sub test_page($vm) { @@ -301,34 +300,6 @@ sub _used_volumes($machine) { return @used; } -sub _clean_files($files=\@FILES) { - my @dirs; - for my $file (@$files) { - if (-f $file || -l $file) { - unlink $file or warn "$! $file"; - } elsif (-d $file) { - push @dirs,($file); - } - - } - - for my $file (@dirs) { - my @files; - my $pattern = base_domain_name(); - opendir my $ls,$file or die "$! $file"; - while (my $in = readdir $ls) { - push @files,("$file/$in") if $in =~ /^$pattern/; - } - _clean_files(\@files); - rmdir($file) or warn "$! $file"; - } - - for my $dir (@DIRS) { - remove_dir($dir); - } - -} - sub _create_clone($vm) { my $base0 = create_domain($vm); $base0->prepare_base(user_admin); @@ -366,7 +337,9 @@ sub _create_clone_hide_bs($clone) { return $clone2; } -sub test_remove($vm, $clone) { +sub test_remove($vm) { + my $clone = _create_clone($vm); + my $file = _new_file($vm); ok(-e $file) or exit; my $user = create_user(new_domain_name(),"bar"); @@ -440,7 +413,51 @@ sub test_more($vm) { ok(!$more); } +sub test_linked_sp_here($vm) { + diag("test_linked_sp_heare"); + my $new_name1=new_domain_name(); + + my $new_dir = "/var/tmp/".$new_name1; + remove_dir($new_dir); + mkdir $new_dir or die "$! $new_dir"; + $vm->create_storage_pool($new_name1, $new_dir) + if !grep { $_->{name} eq $new_name1} $vm->list_storage_pools(1); + + my $new_name2 = new_domain_name(); + my $new_link2 = "/var/tmp/".$new_name2; + remove_dir($new_link2); + symlink($new_dir,$new_link2) or die "$new_dir -> $new_link2"; + + $vm->create_storage_pool($new_name2, $new_link2) + if !grep { $_ eq $new_name2} $vm->list_storage_pools; + + my $file = _touch_file($vm, $new_dir); + + $vm->refresh_storage_pools(); + + my $req = Ravada::Request->list_unused_volumes( + uid => user_admin->id + ,id_vm => $vm->id + ,limit => 0 + ); + wait_request(); + my $out_json = $req->output; + $out_json = '{}' if !defined $out_json; + my $output = decode_json($out_json); + + my $list = $output->{list}; + my @found = grep ($_->{file} =~ /^$new_dir/, @$list); + is( scalar(@found),1, "Found something ".Dumper(\@found)); + + remove_dir($new_dir); + remove_dir($new_link2); + $vm->remove_storage_pool($new_name1); + $vm->remove_storage_pool($new_name2); +} + sub test_linked_sp($vm) { + diag("test linked sp"); + my $dir = $vm->dir_img(); my $new_name=new_domain_name(); @@ -452,6 +469,8 @@ sub test_linked_sp($vm) { $vm->create_storage_pool($new_name, $new_dir) if !grep { $_ eq $new_name} $vm->list_storage_pools; + my $new_filename = _touch_file($vm, $dir); + if ($vm->type eq 'KVM') { my $pool = $vm->vm->get_storage_pool_by_name($new_name); $pool->create() if !$pool->is_active; @@ -469,32 +488,43 @@ sub test_linked_sp($vm) { my $output = decode_json($out_json); my $list = $output->{list}; - my @found = grep ($_->{file} =~ /^$new_dir/, @$list); - is( scalar(@found),0); + my @found = grep ($_->{file} =~ /$new_filename/, @$list); + is( scalar(@found),1) or die Dumper(\@found); - if ($vm->type eq 'KVM') { - my $pool = $vm->vm->get_storage_pool_by_name($new_name); - $pool->destroy(); - $pool->undefine; - } + $vm->remove_storage_pool($new_name); unlink $new_dir; + unlink "$dir/$new_filename" or die $!; } sub remove_dir($dir) { - return if !-e $dir; - unlink $dir if -l $dir; + if ( -l $dir || -f $dir ) { + unlink $dir or die "$! $dir"; + return; + } my $base = base_domain_name; if (-d $dir) { opendir my $in,$dir or die "$! $dir"; while (my $file = readdir $in) { next if $file =~ /^\.+$/; - die "I will not delete $file" if $file !~ /^$base/; + die "I will not delete $dir/$file" if $file !~ /^$base/; my $path = "$dir/$file"; remove_dir($path); } + closedir $in; rmdir $dir or die "$! $dir"; + } else { + die "I don't know what is $dir" if -e $dir; } } +sub _touch_file($vm, $dir) { + my $new_filename = new_domain_name(); + my $new_file ="$dir/$new_filename"; + open my $out,">",$new_file or die "$! $new_file"; + close $out; + $vm->refresh_storage_pools(); + + return $new_filename; +} sub test_linked_sp_level2($vm) { my $dir = $vm->dir_img(); @@ -502,8 +532,6 @@ sub test_linked_sp_level2($vm) { my $new_dir = "/var/tmp/".$new_name; - push @DIRS,($new_dir); - remove_dir($new_dir); mkdir $new_dir or die "$! $new_dir"; @@ -513,6 +541,7 @@ sub test_linked_sp_level2($vm) { remove_dir($new_link); symlink($new_dir,$new_link) or die "$new_dir -> $new_link"; + my $new_link0 = $new_link; $new_link .="/".new_domain_name(); symlink($dir, $new_link) or die "$! $dir -> $new_link"; @@ -523,11 +552,7 @@ sub test_linked_sp_level2($vm) { $vm->create_storage_pool($new_name, $new_link) if !grep { $_ eq $new_name} $vm->list_storage_pools; - if ($vm->type eq 'KVM') { - my $pool = $vm->vm->get_storage_pool_by_name($new_name); - $pool->create() if !$pool->is_active; - $pool->refresh(); - } + my $new_filename = _touch_file($vm, $new_link); my $req = Ravada::Request->list_unused_volumes( uid => user_admin->id @@ -540,19 +565,15 @@ sub test_linked_sp_level2($vm) { my $output = decode_json($out_json); my $list = $output->{list}; - my @found = grep ($_->{file} =~ /^$new_dir/, @$list); - is( scalar(@found),0); - - @found = grep ($_->{file} =~ /^$new_link/, @$list); - is( scalar(@found),0); + my @found = grep ($_->{file} =~ /$new_filename$/, @$list); + is( scalar(@found),1); - - if ($vm->type eq 'KVM') { - my $pool = $vm->vm->get_storage_pool_by_name($new_name); - $pool->destroy(); - $pool->undefine; - } - unlink $new_dir; + $vm->remove_storage_pool($new_name); + unlink "$dir/$new_filename" or die $!; + unlink "$new_link/$new_filename" or warn $!if -e "$new_link/$new_filename"; + remove_dir($new_dir); + remove_dir($new_link0); + remove_dir($new_link); } sub test_linked_sp_level0($vm) { @@ -565,14 +586,14 @@ sub test_linked_sp_level0($vm) { symlink($dir,$new_link) or die "$dir -> $new_link"; - push @DIRS,($new_link); - my $followed = $vm->_follow_link($new_link); is($followed,$dir) or die "Expecting followed link matches"; $vm->create_storage_pool($new_name, $new_link) if !grep { $_ eq $new_name} $vm->list_storage_pools; + my $new_filename = _touch_file($vm, $dir); + if ($vm->type eq 'KVM') { my $pool = $vm->vm->get_storage_pool_by_name($new_name); $pool->create() if !$pool->is_active; @@ -590,16 +611,49 @@ sub test_linked_sp_level0($vm) { my $output = decode_json($out_json); my $list = $output->{list}; - my @found = grep ($_->{file} =~ /^$new_link/, @$list); - is( scalar(@found),0); + my @found = grep ($_->{file} =~ /$new_filename$/, @$list); + is( scalar(@found),1) or die "Expecting 1 $new_filename on $dir or $new_link"; + + $vm->remove_storage_pool($new_name); + unlink($new_link) or die $!; + unlink("$dir/$new_filename") or die $!; +} +sub _check_leftovers($vm, $delete=0) { + my $dir_run = "/run/user"; + $dir_run .= "/$<" if $<; + + my $base = base_domain_name(); + for my $dir ($vm->dir_img, "/var/tmp",$dir_run) { + opendir my $ls,$dir or die "$! $dir"; + while (my $file=readdir $ls) { + next if $file =~ /.(yml|void|lock|pid|qcow2)$/; + if ( $file =~ /$base/ ) { + if ($delete) { + remove_dir("$dir/$file"); + } else { + confess "$dir/$file"; + } + } + } + } + for my $sp ( $vm->list_storage_pools(1)) { + confess "SP found ".$sp->{name} if $sp->{name} =~ /$base/; + } +} + +sub _clean_old_sps($vm) { + remove_qemu_pools($vm) if $vm->type eq 'KVM'; if ($vm->type eq 'KVM') { - my $pool = $vm->vm->get_storage_pool_by_name($new_name); - $pool->destroy(); - $pool->undefine; + my $base = base_domain_name(); + for my $pool ( $vm->vm->list_all_storage_pools()) { + next if $pool->get_name !~ /^$base/; + $pool->destroy if $pool->is_active; + $pool->undefine; + } } - unlink $new_link; } + ######################################################################## init(); @@ -619,8 +673,23 @@ for my $vm_name ( vm_names() ) { diag($msg) if !$vm; skip $msg,10 if !$vm; + _clean_old_sps($vm); + + _check_leftovers($vm,1); + my $clone = _create_clone($vm); my @hidden_bs = _create_clone_hide_bs($clone); + _check_leftovers($vm); + + test_linked_sp($vm); + _check_leftovers($vm); + test_linked_sp_here($vm); + _check_leftovers($vm); + test_linked_sp_level0($vm); + _check_leftovers($vm); + + test_linked_sp_level2($vm); + _check_leftovers($vm); test_linked_sp($vm); test_linked_sp_level0($vm); @@ -628,23 +697,28 @@ for my $vm_name ( vm_names() ) { test_list_unused_discover($vm, $clone); test_list_unused_discover2($vm); + _check_leftovers($vm); test_list_unused($vm, $clone, \@hidden_bs); - + _check_leftovers($vm); test_links_dir($vm, $clone); - test_links($vm, $clone); + _check_leftovers($vm); + test_links($vm); + _check_leftovers($vm); test_page($vm); - test_remove($vm, $clone); + test_remove($vm); test_remove_many($vm); test_more($vm); + remove_domain($clone); + + _check_leftovers($vm); } } -_clean_files(); end(); done_testing(); diff --git a/t/vm/s30_storage.t b/t/vm/s30_storage.t index a13f2de23..27409f1ba 100644 --- a/t/vm/s30_storage.t +++ b/t/vm/s30_storage.t @@ -49,7 +49,7 @@ sub _add_fstab($vm) { } sub remove_fstab($vm, $dir) { - my $file = "$dir/check_storage";# or die "$!"; + my $file = "$dir/".base_domain_name()."check_storage";# or die "$!"; unlink $file or die "$! $file" if -e $file; copy("/etc/fstab.tst_rvd_backup","/etc/fstab") } @@ -82,7 +82,7 @@ sub test_storage_pools_fail($vm) { sub _clean_local { my $dir = "$DIR/".base_domain_name(); - my $file = "$dir/check_storage";# or die "$!"; + my $file = "$dir/".base_domain_name()."_check_storage";# or die "$!"; unlink $file or die "$! $file" if -e $file; } diff --git a/templates/main/list_bases_ng.html.ep b/templates/main/list_bases_ng.html.ep index b4ab2656c..2571c49e2 100644 --- a/templates/main/list_bases_ng.html.ep +++ b/templates/main/list_bases_ng.html.ep @@ -35,8 +35,8 @@ ng-src="data:image/png;base64,{{machine.screenshot}}" alt="{{machine.alias}}" class="img-thumbnail" width="260" > {{machine.alias}} + ng-src="data:image/png;base64,{{machine.clone.screenshot}}" alt="{{machine.alias}}" class="img-thumbnail" width="260" + >