diff --git a/.gitignore b/.gitignore index f52e0a830..2d8e3ba74 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ log rvd_front.conf pkg-debian-out public/img/screenshots +public/js/custom yarn.lock node_modules/ t/vm/b10* 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/etc/rvd_front.conf.example b/etc/rvd_front.conf.example index 7f0c2679f..369aa24dc 100644 --- a/etc/rvd_front.conf.example +++ b/etc/rvd_front.conf.example @@ -40,4 +40,7 @@ # Insert widget in /js/custom/insert_here_widget.js # this widget embed js in templates/bootstrap/scripts.html.ep ,widget => '' +# Content-Security-Policy HTTP response header helps you reduce XSS risks +# define custom directives. More info https://content-security-policy.com/ + ,security_policy => 'foo.bar.com' }; diff --git a/lib/Ravada.pm b/lib/Ravada.pm index 8d7f8aeaa..cc0f18d64 100644 --- a/lib/Ravada.pm +++ b/lib/Ravada.pm @@ -344,6 +344,8 @@ sub _update_isos { my $self = shift; my $table = 'iso_images'; my $field = 'name'; + my @now = localtime(time); + my $year = $now[5]+1900; my %data = ( androidx86 => { name => 'Android 8.1 x86' @@ -788,24 +790,24 @@ sub _update_isos { ,min_disk_size => '10' } ,kali_64 => { - name => 'Kali Linux 2022' - ,description => 'Kali Linux 2022 64 Bits' + name => "Kali Linux $year" + ,description => "Kali Linux $year 64 Bits" ,arch => 'x86_64' ,xml => 'jessie-amd64.xml' ,xml_volume => 'jessie-volume.xml' - ,url => 'https://cdimage.kali.org/kali-2022.\d+/' - ,file_re => 'kali-linux-202\d.\d+-installer-amd64.iso' + ,url => "https://cdimage.kali.org/kali-$year".'.\d+/' + ,file_re => "kali-linux-$year.".'\d+-installer-amd64.iso' ,sha256_url => '$url/SHA256SUMS' ,min_disk_size => '10' } ,kali_64_netinst => { - name => 'Kali Linux 2022 (NetInstaller)' - ,description => 'Kali Linux 2022 64 Bits (light NetInstall)' + name => "Kali Linux $year (NetInstaller)" + ,description => "Kali Linux $year 64 Bits (light NetInstall)" ,arch => 'x86_64' ,xml => 'jessie-amd64.xml' ,xml_volume => 'jessie-volume.xml' - ,url => 'https://cdimage.kali.org/kali-2022.\d+/' - ,file_re => 'kali-linux-202\d.\d+-installer-netinst-amd64.iso' + ,url => "https://cdimage.kali.org/kali-$year".'.\d+/' + ,file_re => "kali-linux-$year.".'\d+-installer-netinst-amd64.iso' ,sha256_url => '$url/SHA256SUMS' ,min_disk_size => '10' } @@ -2568,6 +2570,59 @@ sub _sql_insert_defaults($self){ ,name => 'auto_view' ,value => $conf->{auto_view} } + ,{ id_parent => $id_frontend + ,name => "widget" + } + ,{ + id_parent => $id_frontend + ,name => 'content_security_policy' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "all" + ,value => '' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "default-src" + ,value => '' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "style-src" + ,value => '' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "script-src" + ,value => '' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "object-src" + ,value => '' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "frame-src" + ,value => '' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "font-src" + ,value => '' + } + + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "connect-src" + ,value => '' + } + ,{ + id_parent => "/frontend/content_security_policy" + ,name => "media-src" + ,value => '' + } ,{ id_parent => $id_backend ,name => 'start_limit' @@ -3280,6 +3335,7 @@ sub _req_add_disk($uid, $id_domain, $type, $size, $request, $storage=undef) { ,name => 'disk' ,data => $data ,@after_req + ,at => time + 1 ); } sub _start_domain_after_create($domain, $request, $uid,$previous_request) { @@ -3834,7 +3890,7 @@ sub process_requests { "pid=".($req->pid or '')." ".$req->status() ."$txt_retry " .$req->command - ." ".Dumper($req->args) if $DEBUG || $debug; + ." ".Dumper($req->args) if ( $DEBUG || $debug ) && $req->command ne 'set_time'; my ($n_retry) = $req->status() =~ /retry (\d+)/; $n_retry = 0 if !$n_retry; @@ -3844,14 +3900,16 @@ sub process_requests { next if !$DEBUG && !$debug; warn ''.localtime." req ".$req->id." , cmd: ".$req->command." ".$req->status() - ." , err: '".($req->error or '')."'\n" if $DEBUG || $VERBOSE; + ." , err: '".($req->error or '')."'\n" if ($DEBUG || $VERBOSE ) + && $req->command ne 'set_time'; # sleep 1 if $DEBUG; } + my @reqs2 = grep { $_->command ne 'set_time' } @reqs; warn Dumper([map { $_->id." ".($_->pid or '')." ".$_->command." ".$_->status } - grep { $_->id } @reqs ]) - if ($DEBUG || $debug ) && @reqs; + grep { $_->id } @reqs2 ]) + if ($DEBUG || $debug ) && @reqs2; return scalar(@reqs); } @@ -4492,7 +4550,7 @@ sub _wait_pids($self) { $request->status('done') if $request->status =~ /working/i; }; warn("$$ request id=$id_req ".$request->command." ".$request->status() - .", error='".($request->error or '')."'\n") if $DEBUG && $request; + .", error='".($request->error or '')."'\n") if $DEBUG && $request && $request->command ne 'set_time'; } } @@ -4866,14 +4924,38 @@ sub _cmd_prepare_base { my $id_domain = $request->id_domain or confess "Missing request id_domain"; my $uid = $request->args('uid') or confess "Missing argument uid"; + my $domain = $self->search_domain_by_id($id_domain); + die "Unknown domain id '$id_domain'\n" if !$domain; + my $user = Ravada::Auth::SQL->search_by_id( $uid) or confess "Error: Unknown user id $uid in request ".Dumper($request); - my $with_cd = $request->defined_arg('with_cd'); + die "User ".$user->name." [".$user->id."] not allowed to prepare base " + .$domain->name."\n" + unless $user->is_admin || ( + $domain->id_owner == $user->id && $user->can_create_base()); - my $domain = $self->search_domain_by_id($id_domain); - die "Unknown domain id '$id_domain'\n" if !$domain; + my $with_cd = $request->defined_arg('with_cd'); + + if ($domain->is_active) { + my $req_shutdown = Ravada::Request->shutdown_domain( + uid => $user->id + ,id_domain => $domain->id + ,timeout => 0 + ); + $request->after_request($req_shutdown->id); + $request->at(time + 10); + if ( !defined $request->retry() ) { + $request->retry(5); + $request->status("retry"); + } elsif($request->retry>0) { + $request->retry($request->retry-1); + $request->status("retry"); + } + $request->error("Machine must be shut down ".$domain->name." [".$domain->id."]"); + return; + } $self->_remove_unnecessary_request($domain); $self->_remove_unnecessary_downs($domain); @@ -6178,6 +6260,7 @@ sub _req_method { ,list_cpu_models => \&_cmd_list_cpu_models ,enforce_limits => \&_cmd_enforce_limits ,force_shutdown => \&_cmd_force_shutdown +,force_shutdown_domain => \&_cmd_force_shutdown ,force_reboot => \&_cmd_force_reboot ,shutdown_start => \&_cmd_shutdown_start ,rebase => \&_cmd_rebase diff --git a/lib/Ravada/Auth.pm b/lib/Ravada/Auth.pm index 10109c4f0..6fcb8a071 100644 --- a/lib/Ravada/Auth.pm +++ b/lib/Ravada/Auth.pm @@ -6,6 +6,7 @@ use strict; our $LDAP_OK; our $SSO_OK; +use Data::Dumper; use Ravada::Auth::SQL; =head1 NAME diff --git a/lib/Ravada/Auth/SQL.pm b/lib/Ravada/Auth/SQL.pm index b5ef6664b..c9d80f6e2 100644 --- a/lib/Ravada/Auth/SQL.pm +++ b/lib/Ravada/Auth/SQL.pm @@ -141,7 +141,7 @@ sub add_user { ." VALUES(?,?,?,?,?,?)"); }; confess $@ if $@; - if ($password) { + if ($password && !$external_auth) { $password = sha1_hex($password); } else { $password = '*LK* no pss'; diff --git a/lib/Ravada/Auth/SSO.pm b/lib/Ravada/Auth/SSO.pm index 240cd1aa4..c01043ed6 100644 --- a/lib/Ravada/Auth/SSO.pm +++ b/lib/Ravada/Auth/SSO.pm @@ -72,6 +72,7 @@ sub _get_session_userid_by_ticket my ($cookie) = @_; my $result; die 'Can\'t read pubkey file (sso->cookie->pub_key value at ravada.conf file)' if (! -r $$CONFIG->{sso}->{cookie}->{pub_key}); + eval { $result = Authen::ModAuthPubTkt::pubtkt_verify(publickey => $$CONFIG->{sso}->{cookie}->{pub_key}, keytype => $$CONFIG->{sso}->{cookie}->{type}, ticket => $cookie); }; die $@ ? $@ : 'Cannot validate ticket' if ((! $result) || ($@)); my %data = Authen::ModAuthPubTkt::pubtkt_parse($cookie); @@ -123,6 +124,11 @@ sub init { return 0; } } + if (!$$CONFIG->{sso}->{cookie}->{type}) { + $ERR = "Error: missing sso / cookie / type in config file\n"; + warn $ERR unless $warn++; + return 0; + } for my $field (qw(priv_key pub_key)) { if ( !exists $$CONFIG->{sso}->{cookie}->{$field} || ! $$CONFIG->{sso}->{cookie}->{$field}) { diff --git a/lib/Ravada/Domain.pm b/lib/Ravada/Domain.pm index 3e5d5865c..8447824bf 100644 --- a/lib/Ravada/Domain.pm +++ b/lib/Ravada/Domain.pm @@ -704,7 +704,9 @@ sub _around_add_volume { ($name) = $file =~ m{.*/(.*)} if !$name && $file; $name = $self->name if !$name; - $name .= "-".$args{target}."-".Ravada::Utils::random_name(4); + $name .= "-".$args{target}."-".Ravada::Utils::random_name(4) + if $name !~ /\.iso$/; + $args{name} = $name; } @@ -717,10 +719,12 @@ sub _around_add_volume { $args{allocation} = Ravada::Utils::size_to_number($args{allocation}) if exists $args{allocation} && defined $args{allocation}; - my $free = $self->_vm->free_disk(); + my $storage = $args{storage}; + + my $free = $self->_vm->free_disk($storage); my $free_out = int($free / 1024 / 1024 / 1024 ) * 1024 *1024 *1024; - confess "Error creating volume, out of space $size . Disk free: " + die "Error creating volume, out of space $size . Disk free: " .Ravada::Utils::number_to_size($free_out) ."\n" if exists $args{size} && $args{size} && $args{size} >= $free; @@ -1945,9 +1949,6 @@ sub display($self, $user) { my ($display_info) = grep { $_->{driver} !~ /-tls$/ } @display_info; - confess "Error: I can't find builtin display info for ".$self->name." ".ref($self)."\n".Dumper($display_info) - if !exists $display_info->{port}; - return '' if !$display_info->{driver} || !$display_info->{ip} || !$display_info->{port}; diff --git a/lib/Ravada/Front.pm b/lib/Ravada/Front.pm index 2a5b01241..fd3e8639e 100644 --- a/lib/Ravada/Front.pm +++ b/lib/Ravada/Front.pm @@ -16,6 +16,7 @@ use Hash::Util qw(lock_hash); use IPC::Run3 qw(run3); use JSON::XS; use Moose; +use Storable qw(dclone); use Ravada; use Ravada::Auth::LDAP; use Ravada::Front::Domain; @@ -1481,6 +1482,19 @@ sub _settings_by_id($self) { return $orig_settings; } +sub _settings_by_parent($self,$parent) { + my $data = $self->_setting_data($parent); + my $sth = $self->_dbh->prepare("SELECT name,value FROM settings " + ." WHERE id_parent = ? "); + $sth->execute($data->{id}); + my $ret; + while (my ($name, $value) = $sth->fetchrow) { + $value = '' if !defined $value; + $ret->{$name} = $value; + } + return $ret; +} + =head2 feature Returns if a feature is available @@ -1521,9 +1535,10 @@ sub update_settings_global($self, $arg, $user, $reload, $orig_settings = $self-> confess Dumper([$field,$arg->{$field}]) if !ref($arg->{$field}); if ( scalar(keys %{$arg->{$field}})>2 ) { confess if !keys %{$arg->{$field}}; - $self->update_settings_global($arg->{$field}, $user, $reload, $orig_settings); + my $field2 = dclone($arg->{$field}); + $self->update_settings_global($field2, $user, $reload, $orig_settings); } - confess "Error: invalid field $field" if $field !~ /^\w+$/; + confess "Error: invalid field $field" if $field !~ /^\w[\w\-]+$/; my ( $value, $id ) = ($arg->{$field}->{value} , $arg->{$field}->{id} @@ -1751,6 +1766,64 @@ sub _filter_active($pools, $active) { } +=head2 upload_users + +Upload a list of users to the database + +=head3 Arguments + +=over + +=item * string with users and passwords in each line + +=item * type: it can be SQL, LDAP or SSO + +=item * create: optionally create the entries in LDAP + +=back + +=cut + +sub upload_users($self, $users, $type, $create=0) { + + my @external; + if ($type ne 'sql') { + @external = ( is_external => 1, external_auth => $type ); + } + + my ($found,$count) = (0,0); + my @error; + for my $line (split /\n/,$users) { + my ($name, $password) = split(/:/,$line); + $found++; + my $user = Ravada::Auth::SQL->new(name => $name); + if ($user && $user->id) { + push @error,("User $name already added"); + next; + } + if ($type ne 'sql' && $create) { + if ($type eq 'ldap') { + if (!$password) { + push @error,("Error: user $name , password empty"); + next; + } + eval { $user = Ravada::Auth::LDAP::add_user($name,$password) }; + push @error, ($@) if $@; + } else { + push @error,("$type users can't be created from Ravada"); + } + } + if ($type eq 'sql' && !$password) { + push @error,("Error: user $name requires password"); + next; + } + Ravada::Auth::SQL::add_user(name => $name, password => $password + ,@external); + $count++; + } + return ($found, $count, \@error); +} + =head2 version Returns the version of the main module diff --git a/lib/Ravada/I18N/de.po b/lib/Ravada/I18N/de.po index ab7efaae2..7dbe61095 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 "Ausführzeit" + +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 "Neustart erzwingen" + +msgid "Force ShutDown" +msgstr "Herunterfahren erzwingen" + +msgid "Force change password on first access" +msgstr "Erzwinge Passwortänderung beim ersten Zugriff" + +msgid "From" +msgstr "Von" + +msgid "Frontend" +msgstr "Frontend" + +msgid "Global Settings" +msgstr "Globale Einstellungen" + +msgid "Group" +msgstr "Gruppe" + +msgid "Group name" +msgstr "Gruppenname" + +msgid "Group name can not exceed 80 characters" +msgstr "Gruppenname darf nicht länger als 80 Zeichen sein" + +msgid "Group name is required" +msgstr "Gruppenname erforderlich" + +msgid "Groups" +msgstr "Gruppen" + +msgid "Groups require a LDAP server configured." +msgstr "Gruppen erfordern einen konfigurierten LDAP Server" + +msgid "Groups with permissions" +msgstr "Gruppen mit Rechten" + +msgid "Hardware" +msgstr "Hardware" + +msgid "Hardware address" +msgstr "Hardwareadresse" + +msgid "Hibernated" +msgstr "Im Ruhezustand" + +msgid "Hide active" +msgstr "Aktive ausblenden" + +msgid "Hide clones" +msgstr "Klone ausblenden" + +msgid "I can't find" +msgstr "" + +msgid "I don't know settings tabs for:" +msgstr "" + +msgid "ISO file" +msgstr "Datenträgeabbild" + +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 "Bei Problemen, kontaktieren Sie bitte unseren Support. Wir bitten Sie von unnötigen Anfragen ab." + +msgid "Import" +msgstr "Importieren" + +msgid "Internal IP" +msgstr "Interne IP" + +msgid "Invalid IP network address. Expecting a.b.c.d/e" +msgstr "Ungültige Netzwerkadresse. Erwartet: a.b.c.d/e" + +msgid "Invalid Template" +msgstr "Ungültige Vorlage" + +msgid "Keep the CD for the clones" +msgstr "CD für Klone behalten" + +msgid "LDAP" +msgstr "LDAP" + +msgid "LDAP groups are required to set up bookings. Some groups where found but no members belong to them. Add new entries here." +msgstr "LDAP Gruppen werden benötigt, um Reservierungen zu aktivieren. Es wurden Gruppen ohne Mitglieder gefunden. Neuer Eintrag." + +msgid "Legacy" +msgstr "Legacy" + +msgid "Loading ..." +msgstr "Lade ..." + +msgid "Loading machine" +msgstr "Lade Maschine" + +msgid "Loading nodes" +msgstr "Lade Knoten" + +msgid "Machine" +msgstr "Maschine" + +msgid "Machine Information" +msgstr "Maschineninformationen" + +msgid "Machines Notifications" +msgstr "Maschinenbenachrichtigungen" + +msgid "Maintenance" +msgstr "Wartung" + +msgid "Maintenance End" +msgstr "Ende der Wartung" + +msgid "Maintenance Start" +msgstr "Beginn der Wartung" + +msgid "Manage machine" +msgstr "Maschine verwalten" + +msgid "Max Memory" +msgstr "Max. Speicher" + +msgid "Max memory (MB)" +msgstr "Max. Speicher (MB)" + +msgid "Minimum port number to expose virtual machine services." +msgstr "Kleinste Port-Nummer zur Veröffentlichung von VM Diensten" + +msgid "Monitoring" +msgstr "Überwachung" + +msgid "NAT" +msgstr "NAT" + +msgid "Network" +msgstr "Netzwerk" + +msgid "Network address is required." +msgstr "Netzwerkadresse ist erforderlich" + +msgid "Network name is required" +msgstr "Netzwerkname ist erforderlich" + +msgid "Networks" +msgstr "Netzwerke" + +msgid "New Booking" +msgstr "Neue Reservierung" + +msgid "New Group" +msgstr "Neue Gruppe" + +msgid "New Network" +msgstr "Neues Netzwerk" + +msgid "New Password can only contain words and numbers" +msgstr "Neues Passwort darf nur aus Buchstaben und Zahlen bestehen" + +msgid "New Password is required" +msgstr "Neues Passwort ist erforderlich" + +msgid "New Password must be at least 6 characters" +msgstr "Neues Passwort muss mindestens 6 Zeichen lang sein" + +msgid "New feature" +msgstr "Neues Feature" + +msgid "New group member" +msgstr "Neues Gruppenmitglied" + +msgid "New name" +msgstr "Neuer Name" + +msgid "New network" +msgstr "Neues Netzwerk" + +msgid "New password" +msgstr "Neues Passwort" + +msgid "New user" +msgstr "Neuer Benutzer" + +msgid "No LDAP groups created." +msgstr "Keine LDAP Gruppen erstellt." + +msgid "No Results Found" +msgstr "Keine Ergebnisse gefunden" + +msgid "No bases found" +msgstr "" + +msgid "No bridges found" +msgstr "Keine Brücken gefunden" + +msgid "No groups found" +msgstr "Keine Gruppen gefunden" + +msgid "No machines" +msgstr "Keine Maschinen" + +msgid "No machines found" +msgstr "Keine Maschinen gefunden" + +msgid "No members found" +msgstr "Keine Mitglieder gefunden" + +msgid "No message to show!" +msgstr "Keine Nachricht zum Anzeigen!" + +msgid "No, cancel" +msgstr "Nein, Abbrechen" + +msgid "Node" +msgstr "Knoten" + +msgid "Node disabled" +msgstr "Knoten deaktiviert" + +msgid "Node down" +msgstr "Knote offline" + +msgid "Node with machines from the same user" +msgstr "Knoten mit Maschinen des selben Nutzers" + +msgid "Node with more free memory" +msgstr "Knoten mit freiem Speicher" + +msgid "Nodes" +msgstr "Knoten" + +msgid "Nothing to monitoring" +msgstr "Nichts zu überwachen" + +msgid "Number of virtual machines that normal users can have running at the same time" +msgstr "Max. Anzahl virtueller Maschinen, die ein Nutzer gleichzeitig starten kann" + +msgid "Old Password can only contain words and numbers" +msgstr "Altes Passwort darf nur aus Buchstaben und Zahlen bestehen" + +msgid "Old Password is required" +msgstr "Altes Passwort ist erforderlich" + +msgid "Old and New Passwords match!" +msgstr "Altes und neues Passwort stimmen überein!" + +msgid "Only remote client can access this port" +msgstr "Nur Remote Clients können diesen Port verwenden" + +msgid "Only remote client can access this port if restricted" +msgstr "Nur Remote Clients können diesen Port verwenden, wenn eingeschränkt" + +msgid "Only users from these groups will be allowed to execute this machine" +msgstr "Nur Mitglieder dieser Gruppe dürfen diese Maschine ausführen" + +msgid "Oops!" +msgstr "Ups!" + +msgid "Open ports" +msgstr "Offene Ports" + +msgid "Password and their confirmation do not match!" +msgstr "Passwort und Passwortwiederholung stimmen nicht überein!" + +msgid "Passwords do not match!" +msgstr "Passwörter stimmen nicht überein" + +msgid "Permission granted to user" +msgstr "Rechte wurden Benutzer hinzugefügt" + +msgid "Permission revoked from user" +msgstr "Rechte wurden Benutzer entzogen" + +msgid "Phone" +msgstr "Telefon" + +msgid "Phone Number" +msgstr "Telefonnummer" + +msgid "Please insert" +msgstr "Bitte eingeben" + +msgid "Please select an ISO file" +msgstr "Bitte ISO-Datei auswählen" + +msgid "Please, make sure you have the right path and release, according to your PC configuration." +msgstr "" + +msgid "Pool" +msgstr "Pool" + +msgid "Port" +msgstr "Port" + +msgid "Port expose" +msgstr "Öffentlicher Port" + +msgid "Ports" +msgstr "Ports" + +msgid "Poweroff" +msgstr "Ausschalten" + +msgid "Prepare Base" +msgstr "" + +msgid "Prepare this machine as a base to create clones from it." +msgstr "Diese Maschine als Vorlage vorbereiten, um Klone damit zu erstellen." + +msgid "PrepareBase" +msgstr "" + +msgid "Press SHIFT + F12 to exit the virtual machine" +msgstr "Umschalt + F12 drücken, um Maus und Tastatur freizugeben" + +msgid "Primary can not be unset, enable it in another video device" +msgstr "" + +msgid "Primary video devices will move to first" +msgstr "Primäres Videogerät wird an die erste Stelle verschoben" + +msgid "Public" +msgstr "Öffentlich" + +msgid "Public Port" +msgstr "Öffentlicher Port" + +msgid "Purge" +msgstr "Löschen" + +msgid "Purge disk volumes" +msgstr "Festplatte löschen" + +msgid "RAM (Gb)" +msgstr "RAM (Gb)" + +msgid "Ravada Apache documentation" +msgstr "Ravada Apache Dokumentation" + +msgid "Re-Enter New Password" +msgstr "Neues Passwort erneut eigeben" + +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 "Neustart" + +msgid "Recent requests" +msgstr "Letzte Anfragen" + +msgid "Refresh" +msgstr "Aktualisieren" + +msgid "Refresh ports" +msgstr "Ports aktualisieren" + +msgid "Reload" +msgstr "Neu laden" + +msgid "Remove Base" +msgstr "" + +msgid "Remove Clones" +msgstr "Klone löschen" + +msgid "Remove bases in node" +msgstr "" + +msgid "Remove group" +msgstr "Gruppe entfernen" + +msgid "Remove the base prepared from this machine" +msgstr "" + +msgid "Removing Virtual Machine" +msgstr "Entferne virtuelle Maschine" + +msgid "Repeat" +msgstr "Wiederholen" + +msgid "Repeated New Password is required" +msgstr "Wiederholtes, neues Passwort ist erforderlich" + +msgid "Request" +msgstr "Anfrage" + +msgid "Requests" +msgstr "Anfragen" + +msgid "Requires viewer password when implemented" +msgstr "Benötigt Anzeigepasswort, wenn implementiert" + +msgid "Restricted" +msgstr "Eingeschränkt" + +msgid "Run Timeout" +msgstr "" + +msgid "SSO/CAS Login" +msgstr "SSO/CAS Login" + +msgid "Save" +msgstr "Speichern" + +msgid "Save changes" +msgstr "Änderungen speichern" + +msgid "Schedule" +msgstr "Planen" + +msgid "Search group" +msgstr "Gruppe suchen" + +msgid "See documentation about:" +msgstr "Dokumentation prüfen für:" + +msgid "Select the .iso file the machine will utilize when installing the OS." +msgstr "Wählen Sie eine ISO-Datei aus, welches das Betriebssystem enthält." + +msgid "Server in maintenance until" +msgstr "Serverwartung bis" + +msgid "Set New Password" +msgstr "Neues Passwort setzen" + +msgid "Set defaults" +msgstr "Setze Standardwerte" + +msgid "Set either" +msgstr "" + +msgid "Setup a LDAP Server" +msgstr "LDAP Server einrichten" + +msgid "Show active" +msgstr "Aktive anzeigen" + +msgid "Show clones" +msgstr "Klone anzeigen" + +msgid "Show/Hide clones" +msgstr "Klone anzeigen/verstecken" + +msgid "ShutDown" +msgstr "Herunterfahren" + +msgid "Shutdown Timeout" +msgstr "Herunterfahren Zeitüberschreitung" + +msgid "Shutdown disconnected" +msgstr "Herunterfahren getrennt" + +msgid "Sorry" +msgstr "Entschuldige" + +msgid "Source Machine" +msgstr "Quellmaschine" + +msgid "Spinoff clone" +msgstr "" + +msgid "Spinoff this clone from its base." +msgstr "" + +msgid "Start Limit" +msgstr "Startlimit" + +msgid "Start after create the virtual machine" +msgstr "Nach erstellen starten" + +msgid "Start after migration" +msgstr "Nach migration starten" + +msgid "Support Contact" +msgstr "Supportkontakt" + +msgid "Swap" +msgstr "Swap" + +msgid "System Disk: (GB)" +msgstr "System Disk: (GB)" + +msgid "Template selection is required" +msgstr "Auswahl einer Vorlage ist erforderlich" + +msgid "Testing connection to" +msgstr "Teste verbindung zu" + +msgid "The Minimum Disk Size needed for this ISO is" +msgstr "Minimale Festplattengröße für diese ISO beträgt" + +msgid "The Minimum Swap Disk Size needed for this ISO is" +msgstr "Minimale Swap Größe für diese ISO beträgt" + +msgid "The VM is" +msgstr "Die VM ist" + +msgid "The machine will power off after this minutes after shutdown." +msgstr "Nach dem Herunterfahren wird die Maschine nach dieser Zeit ausgeschaltet" + +msgid "The machine will turn off after this time" +msgstr "Die Maschine wird nach dieser Zeit ausgeschaltet" + +msgid "The new user" +msgstr "Der neue Benutzer" + +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 "Es wurden keine LDAP Gruppen definiert." + +msgid "There are no active virtual machines" +msgstr "Es gibt keine aktiven virtuellen Maschinen" + +msgid "This ISO is being downloaded. The virtual machine will be created after." +msgstr "Diese ISO wird gerade heruntergeladen. Die virtuelle Maschine wird danach gestartet." + +msgid "This Virtual Machine has no display hardware attached" +msgstr "Diese virtuelle Maschine verfügt über kein angeschlossenes Display" + +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/I18N/en.po b/lib/Ravada/I18N/en.po index 8526e3eb8..36c0f7c5c 100644 --- a/lib/Ravada/I18N/en.po +++ b/lib/Ravada/I18N/en.po @@ -303,9 +303,6 @@ msgstr "Welcome" msgid "A viewer is required to run the virtual machines." msgstr "A viewer is required to run the virtual machines." -msgid "It is required a viewer to run the virtual machines." -msgstr "It is required a viewer to run the virtual machines." - msgid "Virtual Machines" msgstr "Virtual Machines" @@ -2170,5 +2167,5 @@ msgstr "Source Machine" msgid "Monitoring" msgstr "Monitoring" -msgid "For Spice client setup follow this " -msgstr "For Spice client setup follow this " +msgid "Follow these steps for Spice client setup" +msgstr "Follow these steps for Spice client setup" diff --git a/lib/Ravada/I18N/it.po b/lib/Ravada/I18N/it.po index 4d81b94a2..42785e52f 100644 --- a/lib/Ravada/I18N/it.po +++ b/lib/Ravada/I18N/it.po @@ -1076,3 +1076,521 @@ msgstr "Rimuovi gruppo" msgid "Are you sure you want to remove this group ?" msgstr "Sei sicuro di voler rimuovere questo gruppo ?" + +msgid "Schedule" +msgstr "Programma" + +msgid "Confirm" +msgstr "Confermare" + +msgid "Data" +msgstr "Dati" + +msgid "Node with more free memory" +msgstr "Nodo con più memoria libera" + +msgid "locked" +msgstr "bloccato" + +msgid "Can rename any virtual machine." +msgstr "Può rinominare qualsiasi macchina virtuale." + +msgid "Error: you want to have" +msgstr "Errore: si vuole avere" + +msgid "Remove Clones" +msgstr "Rimuovere i cloni" + +msgid "The new user" +msgstr "Il nuovo utente" + +msgid "has been added successfully!" +msgstr "è stato aggiunto con successo!" + +msgid "Set defaults" +msgstr "Imposta valori predefiniti" + +msgid "What do you want to do now?" +msgstr "Cosa vuoi fare ora?" + +msgid "Shutdown Timeout" +msgstr "Timeout spegnimento" + +msgid "" +"This server has reservations for today. Machines from users out of\n" +" the booking list will be shutdown." +msgstr "" +"Questo server ha prenotazioni per oggi. Macchine da utenti fuori\n" +" la lista delle prenotazioni sarà chiusa." + +msgid "filter" +msgstr "filtro" + +msgid "Invalid Template" +msgstr "Modello non valido" + +msgid "Start after create the virtual machine" +msgstr "Avvio dopo la creazione della macchina virtuale" + +msgid "Advanced options" +msgstr "Opzioni avanzate" + +msgid "The Minimum Disk Size needed for this ISO is" +msgstr "La dimensione minima del disco necessaria per questa ISO è" + +msgid "Requests" +msgstr "Richieste" + +msgid "There are no LDAP groups defined." +msgstr "Non sono definiti gruppi LDAP." + +msgid "This virtual machine has no group restrictions." +msgstr "Questa macchina virtuale non ha restrizioni di gruppo." + +msgid "Can reboot all virtual machines." +msgstr "Può riavviare tutte le macchine virtuali." + +msgid "The source machine is not a base. It must be prepared before it can be copied. This process may take some minutes." +msgstr "" +"La macchina sorgente non è una base. Deve essere preparata prima di poter " +"essere copiata. Questo processo può richiedere alcuni minuti." + +msgid "Can shutdown own virtual machines." +msgstr "Può spegnere le proprie macchine virtuali." + +msgid "Restricted" +msgstr "Limitato" + +msgid "remove" +msgstr "rimuovere" + +msgid "Machines Notifications" +msgstr "Notifiche delle macchine" + +msgid "Change" +msgstr "Cambiamento" + +msgid "Warning" +msgstr "Avvertenze" + +msgid "Old and New Passwords match!" +msgstr "Le vecchie e le nuove password coincidono!" + +msgid "bases" +msgstr "basi" + +msgid "Clones created from this machine will be removed on shutdown." +msgstr "I cloni creati da questa macchina verranno rimossi allo spegnimento." + +msgid "Event properties" +msgstr "Proprietà dell'evento" + +msgid "New name" +msgstr "Nuovo nome" + +msgid "Add another user" +msgstr "Aggiungere un altro utente" + +msgid "The machine will power off after this minutes after shutdown." +msgstr "La macchina si spegnerà dopo questi minuti dallo spegnimento." + +msgid "Re-Enter New Password" +msgstr "Reinserire la nuova password" + +msgid "allowed" +msgstr "consentito" + +msgid "Can have an unlimited amount of machines started." +msgstr "È possibile avviare un numero illimitato di macchine." + +msgid "Monitoring" +msgstr "Monitoraggio" + +msgid "Any remote client can access this port" +msgstr "Qualsiasi client remoto può accedere a questa porta" + +msgid "New Password can only contain words and numbers" +msgstr "La nuova password può contenere solo parole e numeri" + +msgid "To" +msgstr "A" + +msgid "Can view groups." +msgstr "Può visualizzare gruppi." + +msgid "LDAP groups are required to set up bookings. Some groups where found but no members belong to them. Add new entries here." +msgstr "" +"I gruppi LDAP sono necessari per impostare le prenotazioni. Alcuni gruppi " +"sono stati trovati ma nessun membro vi appartiene. Aggiungi nuove voci qui." + +msgid "Swap" +msgstr "Scambio" + +msgid "Nothing to monitoring" +msgstr "Niente da monitorare" + +msgid "Can reboot own virtual machines." +msgstr "Può riavviare le macchine virtuali." + +msgid "Phone" +msgstr "Telefono" + +msgid "Can expose virtual machine ports." +msgstr "Può esporre le porte delle macchine virtuali." + +msgid "Enter Old Password" +msgstr "Inserire la vecchia password" + +msgid "All events" +msgstr "Tutti gli eventi" + +msgid "added" +msgstr "aggiunto" + +msgid "This field is required" +msgstr "Questo campo è obbligatorio" + +msgid "Type the ISO pathname" +msgstr "Digitare il percorso ISO" + +msgid "can change the settings of any virtual machine cloned from one base owned by the user." +msgstr "" +"può modificare le impostazioni di qualsiasi macchina virtuale clonata da una " +"base di proprietà dell'utente." + +msgid "Public Port" +msgstr "Porto pubblico" + +msgid "Support Contact" +msgstr "Contatto di supporto" + +msgid "This machine is hibernated and can't be renamed." +msgstr "Questa macchina è ibernata e non può essere rinominata." + +msgid "Rebase" +msgstr "rebase" + +msgid "Port" +msgstr "Porto" + +msgid "Can rename any virtual machine owned by the user." +msgstr "Può rinominare qualsiasi macchina virtuale di proprietà dell'utente." + +msgid "Spinoff this clone from its base." +msgstr "Spinoff di questo clone dalla sua base." + +msgid "stopped" +msgstr "fermato" + +msgid "but there are only" +msgstr "ma ci sono solo" + +msgid "Can rename clones from virtual machines owned by the user." +msgstr "" +"Può rinominare i cloni delle macchine virtuali di proprietà dell'utente." + +msgid "Create a new group" +msgstr "Creare un nuovo gruppo" + +msgid "System Disk: (GB)" +msgstr "Disco di sistema: (GB)" + +msgid "This feature requires a LDAP server configured." +msgstr "Questa funzione richiede la configurazione di un server LDAP." + +msgid "User not found" +msgstr "Utente non trovato" + +msgid "Save changes" +msgstr "Salva le modifiche" + +msgid "Only remote client can access this port" +msgstr "Solo il client remoto può accedere a questa porta" + +msgid "Type the template name" +msgstr "Digitare il nome del modello" + +msgid "Password and their confirmation do not match!" +msgstr "La password e la sua conferma non corrispondono!" + +msgid "Reload" +msgstr "Ricaricare" + +msgid "Source Machine" +msgstr "Fonte Macchina" + +msgid "UEFI" +msgstr "UEFI" + +msgid "Select the .iso file the machine will utilize when installing the OS." +msgstr "" +"Selezionare il file .iso che la macchina utilizzerà per l'installazione del " +"sistema operativo." + +msgid "Poweroff" +msgstr "Spegnimento" + +msgid "Hide active" +msgstr "Nascondi attivo" + +msgid "Group name is required" +msgstr "Il nome del gruppo è richiesto" + +msgid "BIOS" +msgstr "BIOS" + +msgid "No, cancel" +msgstr "No, annullare" + +msgid "Group name" +msgstr "Nome del gruppo" + +msgid "Permission granted to user" +msgstr "Autorizzazione concessa all'utente" + +msgid "Remove the base prepared from this machine" +msgstr "Rimuovere la base preparata dalla macchina" + +msgid "The Minimum Swap Disk Size needed for this ISO is" +msgstr "La dimensione minima del disco di swap necessaria per questa ISO è" + +msgid "Max memory (MB)" +msgstr "Memoria massima (MB)" + +msgid "Title" +msgstr "Titolo" + +msgid "Only users from these groups will be allowed to execute this machine" +msgstr "Solo gli utenti di questi gruppi potranno eseguire questa macchina" + +msgid "Spinoff clone" +msgstr "Clone spinoff" + +msgid "Fill required camps with valid data" +msgstr "Riempire i campi richiesti con dati validi" + +msgid "This virtual machine has clones that are bases and won't be removed" +msgstr "" +"Questa macchina virtuale ha dei cloni che sono basi e non verranno rimossi" + +msgid "Until" +msgstr "Fino a" + +msgid "options" +msgstr "opzioni" + +msgid "Repeated New Password is required" +msgstr "È necessario ripetere la nuova password" + +msgid "Congrats" +msgstr "Congratulazioni" + +msgid "Only remote client can access this port if restricted" +msgstr "Solo il client remoto può accedere a questa porta, se limitata" + +msgid "Changing the base of a virtual machine is potentially dangerous and can't be undone." +msgstr "" +"La modifica della base di una macchina virtuale è potenzialmente pericolosa " +"e non può essere annullata." + +msgid "This event and the following" +msgstr "Questo evento e i seguenti" + +msgid "From" +msgstr "Da" + +msgid "For Spice client setup follow this " +msgstr "Per la configurazione del client Spice segui questo ' " + +msgid "Refresh ports" +msgstr "Aggiorna le porte" + +msgid "Edit the event" +msgstr "Modificare l'evento" + +msgid "Prepare Base" +msgstr "Preparare la base" + +msgid "Groups with permissions" +msgstr "Gruppi con autorizzazioni" + +msgid "Enter group name" +msgstr "Inserire il nome del gruppo" + +msgid "Current memory (MB)" +msgstr "Memoria attuale (MB)" + +msgid "Node with machines from the same user" +msgstr "Nodo con macchine dello stesso utente" + +msgid "Phone Number" +msgstr "Numero di telefono" + +msgid "hide" +msgstr "nascondersi" + +msgid "Can reboot clones own virtual machines." +msgstr "Può riavviare i cloni delle proprie macchine virtuali." + +msgid "Machine" +msgstr "Macchina" + +msgid "This booking overlaps already scheduled reservations" +msgstr "Questa prenotazione si sovrappone a prenotazioni già programmate" + +msgid "Are you sure you want to spinoff from the base?" +msgstr "Sei sicura di voler fare uno spin-off dalla base?" + +msgid "not allowed" +msgstr "non consentito" + +msgid "This ISO is being downloaded. The virtual machine will be created after." +msgstr "Questa ISO viene scaricata. La macchina virtuale verrà creata dopo." + +msgid "This event" +msgstr "Questo evento" + +msgid "Balance" +msgstr "Equilibrio" + +msgid "Run Timeout" +msgstr "Timeout esecuzione" + +msgid "Old Password can only contain words and numbers" +msgstr "La vecchia password può contenere solo parole e numeri" + +msgid "New Password is required" +msgstr "È richiesta una nuova password" + +msgid "Old Password is required" +msgstr "È richiesta la vecchia password" + +msgid "Contact Method" +msgstr "Metodo di contatto" + +msgid "Yes, rebase" +msgstr "Sì, ribasare" + +msgid "disabled" +msgstr "disabilitato" + +msgid "Email" +msgstr "Email" + +msgid "groups" +msgstr "gruppi" + +msgid "Virtual Machine will be shutdown when user disconnects." +msgstr "La macchina virtuale verrà spenta quando l'utente si disconnette." + +msgid "Please select an ISO file" +msgstr "Selezionare un file ISO" + +msgid "Delete the event" +msgstr "Cancellare l'evento" + +msgid "Passwords do not match!" +msgstr "Le password non corrispondono!" + +msgid "Can manage groups." +msgstr "Può gestire gruppi." + +msgid "volatile" +msgstr "volatili" + +msgid "Rebase this machine to another virtual machine" +msgstr "Reimpostare questa macchina su un'altra macchina virtuale" + +msgid "Clones are volatile, so all the pool will be started." +msgstr "I cloni sono volatili, quindi tutto il pool verrà avviato." + +msgid "Delete" +msgstr "Cancellare" + +msgid "settings" +msgstr "impostazioni" + +msgid "Color" +msgstr "Colore" + +msgid "Permission revoked from user" +msgstr "Autorizzazione revocata all'utente" + +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 "" +"In caso di problemi, è possibile contattare il gruppo di supporto e " +"cercheremo di aiutarvi. Contattare il Centro di assistenza solo se è " +"strettamente necessario." + +msgid "can manage users." +msgstr "può gestire gli utenti." + +msgid "Will be destroyed on shutdown" +msgstr "Verrà distrutto al momento dello spegnimento" + +msgid "Can get a screenshot of own virtual machines." +msgstr "È possibile ottenere uno screenshot delle proprie macchine virtuali." + +msgid "No Results Found" +msgstr "Nessun risultato trovato" + +msgid "Search group" +msgstr "Gruppo di ricerca" + +msgid "Group name can not exceed 80 characters" +msgstr "Il nome del gruppo non può superare gli 80 caratteri" + +msgid "Event saved successfully" +msgstr "Evento salvato con successo" + +msgid "clones defined." +msgstr "cloni definiti." + +msgid "Repeat" +msgstr "Ripetere" + +msgid "Virtual Machine will start on host start." +msgstr "La macchina virtuale si avvia all'avvio dell'host." + +msgid "Legacy" +msgstr "Eredità" + +msgid "can have their own limit on started machines." +msgstr "possono avere un proprio limite di macchine avviate." + +msgid "networks" +msgstr "reti" + +msgid "Rebase all the clones to a new base" +msgstr "Reimpostare tutti i cloni su una nuova base" + +msgid "Client headers" +msgstr "Intestazioni del client" + +msgid "nodes" +msgstr "nodi" + +msgid "Enter New Password" +msgstr "Inserire la nuova password" + +msgid "Attachment" +msgstr "Allegato" + +msgid "The VM is" +msgstr "La macchina virtuale è" + +msgid "Choose the virtualization type of the Virtual Machine." +msgstr "Scegliere il tipo di virtualizzazione della macchina virtuale." + +msgid "This event and the following with same day of week" +msgstr "Questo evento e il successivo con lo stesso giorno della settimana" + +msgid "Weekly" +msgstr "Settimanale" + +msgid "Today Schedule" +msgstr "Programma di oggi" + +msgid "Shutdown disconnected" +msgstr "Spegnimento scollegato" diff --git a/lib/Ravada/I18N/ko.po b/lib/Ravada/I18N/ko.po index 616d53bed..85ac2b94d 100644 --- a/lib/Ravada/I18N/ko.po +++ b/lib/Ravada/I18N/ko.po @@ -2,871 +2,878 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) msgid "" msgstr "" +"PO-Revision-Date: 2023-10-13 20:02+0000\n" +"Last-Translator: Claire Charles \n" +"Language-Team: Korean \n" "Language: ko\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 5.1-dev\n" msgid "Ravada broker" -msgstr "" +msgstr "라바다 브로커" msgid "Machine Name" -msgstr "" +msgstr "시스템 이름" msgid "Tools" -msgstr "" +msgstr "도구" msgid "Choose a Machine to Start" -msgstr "" +msgstr "시동할 기계 선택" msgid "Start" -msgstr "" +msgstr "시작" msgid "Stop" -msgstr "" +msgstr "중지" msgid "View" -msgstr "" +msgstr "보기" msgid "Prepare base" -msgstr "" +msgstr "베이스 준비" msgid "Clone" -msgstr "" +msgstr "클론" msgid "Clone/s" -msgstr "" +msgstr "클론" msgid "Users List" -msgstr "" +msgstr "사용자 목록" msgid "Name" -msgstr "" +msgstr "이름" msgid "Log Out" -msgstr "" +msgstr "로그아웃" msgid "Available Machines" -msgstr "" +msgstr "사용 가능한 기계" msgid "Help" -msgstr "" +msgstr "도움말" msgid "Requirements" -msgstr "" +msgstr "요구 사항" msgid "About" -msgstr "" +msgstr "대해" msgid "Mark all as read" -msgstr "" +msgstr "모두 읽음으로 표시" msgid "Settings" -msgstr "" +msgstr "설정" msgid "New Machine" -msgstr "" +msgstr "새로운 기계" msgid "I want to change my password" -msgstr "" +msgstr "비밀번호를 변경하고 싶어요" msgid "I want to change my language" -msgstr "" +msgstr "언어를 변경하고 싶어요" msgid "Language:" -msgstr "" +msgstr "언어:" msgid "English" -msgstr "" +msgstr "영어" msgid "Spanish" -msgstr "" +msgstr "스페인어" msgid "Catalan" -msgstr "" +msgstr "카탈로니아어" msgid "Galician" -msgstr "" +msgstr "갈리시아어" msgid "French" -msgstr "" +msgstr "프랑스어" msgid "Hindi" -msgstr "" +msgstr "힌디어" msgid "Indonesian" -msgstr "" +msgstr "인도네시아어" msgid "Persian" -msgstr "" +msgstr "페르시아어" msgid "Japanese" -msgstr "" +msgstr "일본어" msgid "German" -msgstr "" +msgstr "독일어" msgid "New Password:" -msgstr "" +msgstr "새 비밀번호:" msgid "Confirm Password:" -msgstr "" +msgstr "비밀번호 확인:" msgid "Submit" -msgstr "" +msgstr "제출" msgid "Your language has been changed successfully" -msgstr "" +msgstr "언어가 성공적으로 변경되었습니다" msgid "Your password has been changed successfully" -msgstr "" +msgstr "비밀번호가 성공적으로 변경되었습니다" msgid "Password is too short" -msgstr "" +msgstr "암호가 너무 짧습니다" msgid "Password fields do not match" -msgstr "" +msgstr "암호 필드가 일치하지 않습니다" msgid "Some of the password fields are empty" -msgstr "" +msgstr "일부 암호 필드가 비어 있습니다" msgid "Status" -msgstr "" +msgstr "상태" msgid "Actions" -msgstr "" +msgstr "작업" msgid "Down" -msgstr "" +msgstr "아래로" msgid "Running" -msgstr "" +msgstr "실행 중" msgid "Shutdown" -msgstr "" +msgstr "종료" msgid "Screenshot" -msgstr "" +msgstr "스크린샷" msgid "Paused" -msgstr "" +msgstr "일시 중지" msgid "Machine locked by request" -msgstr "" +msgstr "요청에 의해 기계가 잠김" msgid "Process" -msgstr "" +msgstr "프로세스" msgid "Copy" -msgstr "" +msgstr "복사" msgid "Error!" -msgstr "" +msgstr "오류!" msgid "Error" -msgstr "" +msgstr "오류" msgid "Backend not available!" -msgstr "" +msgstr "백엔드를 사용할 수 없습니다!" msgid "Subject" -msgstr "" +msgstr "제목" msgid "Date" -msgstr "" +msgstr "날짜" msgid "Mark as read" -msgstr "" +msgstr "읽은 상태로 표시" msgid "Mark as unread" -msgstr "" +msgstr "읽지 않은 상태로 표시" msgid "No messages to show" -msgstr "" +msgstr "표시할 메시지가 없습니다" msgid "Admin" -msgstr "" +msgstr "관리자" msgid "Machines" -msgstr "" +msgstr "기계" msgid "machines" -msgstr "" +msgstr "기계" msgid "Users" -msgstr "" +msgstr "사용자" msgid "users" -msgstr "" +msgstr "사용자" msgid "Messages" -msgstr "" +msgstr "메시지" msgid "messages" -msgstr "" +msgstr "메시지" msgid "Admin tools" -msgstr "" +msgstr "관리 도구" msgid "Remove base" -msgstr "" +msgstr "베이스 제거" msgid "Restore" -msgstr "" +msgstr "복원" msgid "Hibernate" -msgstr "" +msgstr "최대 절전 모드" msgid "Are you sure?" -msgstr "" +msgstr "확실한가요?" msgid "Yes" -msgstr "" +msgstr "네" msgid "No" -msgstr "" +msgstr "아니요" msgid "Make public" -msgstr "" +msgstr "공개하기" msgid "Machine settings" -msgstr "" +msgstr "기계 설정" msgid "Cannot remove base, machine has clones" -msgstr "" +msgstr "베이스를 제거할 수 없습니다. 시스템에 클론이 있습니다" msgid "show clones" -msgstr "" +msgstr "클론 표시" msgid "hide clones" -msgstr "" +msgstr "클론 숨기기" msgid "For Spice redirection you will need to install" -msgstr "" +msgstr "Spice 리디렉션을 위해서는 설치가 필요합니다" msgid "in your computer." -msgstr "" +msgstr "컴퓨터에서." msgid "Search in your distro, e.g. in Debian/Ubuntu with" -msgstr "" +msgstr "배포판에서 검색하십시오 (예 : Debian / Ubuntu에서" msgid "You will need to install" -msgstr "" +msgstr "다음을 설치해야 합니다" msgid "and USB drivers (" -msgstr "" +msgstr "및 USB 드라이버(" msgid "" "Be aware that in Windows, Spice redirection is not automatic. It is " "necessary to associate protocol with the app." -msgstr "" +msgstr "Windows에서 Spice 리디렉션은 자동이 아닙니다. 프로토콜을 연결해야합니다" msgid "To make this possible, download" -msgstr "" +msgstr "이를 가능하게 하려면 다운로드하십시오." msgid "" "or copy the following lines in an ASCII file and save with extension .reg, " "then execute the file." -msgstr "" +msgstr "또는 ASCII 파일에서 다음 줄을 복사하고 확장명 .reg로 저장합니다." +"그런 다음 파일을 실행하십시오." msgid "Login" -msgstr "" +msgstr "로그인" msgid "Welcome" -msgstr "" +msgstr "환영합니다" msgid "A viewer is required to run the virtual machines." -msgstr "" +msgstr "뷰어는 가상 머신을 실행하는 데 필요합니다." msgid "It is required a viewer to run the virtual machines." -msgstr "" +msgstr "가상 머신을 실행하려면 뷰어가 필요합니다." msgid "Virtual Machines" -msgstr "" +msgstr "가상 머신" msgid "User" -msgstr "" +msgstr "사용자" msgid "Password" -msgstr "" +msgstr "암호" msgid "Start session" -msgstr "" +msgstr "세션 시작" msgid "bits) in your computer." -msgstr "" +msgstr "bits)를 컴퓨터에 저장합니다." msgid "Permissions" -msgstr "" +msgstr "권한을" msgid "can change the settings of owned virtual machines." -msgstr "" +msgstr "소유한 가상 컴퓨터의 설정을 변경할 수 있습니다." msgid "can change the settings of any virtual machines." -msgstr "" +msgstr "모든 가상 컴퓨터의 설정을 변경할 수 있습니다." msgid "can change the settings of any virtual machines " -msgstr "" +msgstr "모든 가상 컴퓨터의 설정을 변경할 수 있음" msgid "cloned from one base owned by the user." -msgstr "" +msgstr "사용자가 소유한 하나의 베이스에서 복제됩니다." msgid "can clone public virtual machines." -msgstr "" +msgstr "공용 가상 머신을 복제할 수 있습니다." msgid "can clone any virtual machine." -msgstr "" +msgstr "모든 가상 머신을 복제할 수 있습니다." msgid "can create bases." -msgstr "" +msgstr "기초를 만들 수 있습니다." msgid "can create virtual machines." -msgstr "" +msgstr "가상 시스템을 생성할 수 있습니다." msgid "can grant permissions to other users" -msgstr "" +msgstr "다른 사용자에게 권한을 부여할 수 있습니다." msgid "can hibernate any virtual machine." -msgstr "" +msgstr "모든 가상 컴퓨터를 최대 절전 모드로 전환할 수 있습니다." msgid "can hibernate clones from virtual machines owned by the user." -msgstr "" +msgstr "사용자가 소유한 가상 시스템의 클론을 최대 절전 모드로 생성할 수 있습니다." msgid "can hibernate any clone." -msgstr "" +msgstr "모든 클론을 최대 절전 모드로 전환할 수 있습니다." msgid "can remove any virtual machines owned by the user." -msgstr "" +msgstr "사용자가 소유한 모든 가상 머신을 제거할 수 있습니다." msgid "can remove any virtual machine." -msgstr "" +msgstr "모든 가상 머신을 제거할 수 있습니다." msgid "can remove clones from virtual machines owned by the user." -msgstr "" +msgstr "사용자가 소유한 가상 시스템에서 클론을 제거할 수 있습니다." msgid "can remove any clone." -msgstr "" +msgstr "모든 클론을 제거할 수 있습니다." msgid "can take a screenshot of any virtual machine owned by the user." -msgstr "" +msgstr "사용자가 소유한 가상 머신의 스크린샷을 찍을 수 있습니다." msgid "can take a screenshot of any virtual machine." -msgstr "" +msgstr "모든 가상 머신의 스크린샷을 찍을 수 있습니다." msgid "can shutdown any virtual machine." -msgstr "" +msgstr "모든 가상 머신을 종료할 수 있습니다." msgid "can shutdown clones from virtual machines owned by the user." -msgstr "" +msgstr "사용자가 소유한 가상 시스템에서 클론을 종료할 수 있습니다." msgid "Submit Query" -msgstr "" +msgstr "쿼리 제출" msgid "From Template" -msgstr "" +msgstr "템플릿에서" msgid "From Machine" -msgstr "" +msgstr "기계에서" msgid "Backend" -msgstr "" +msgstr "백엔드" msgid "Select Template" -msgstr "" +msgstr "템플릿 선택" msgid "This ISO image has not been downloaded yet. It may take some minutes, even hours until the file is fetched from the Internet." -msgstr "" +msgstr "이 ISO 이미지는 아직 다운로드되지 않았습니다. 인터넷에서 파일을 가져올 때까지 몇 분, 심지어 몇 시간이 걸릴 수 있습니다." msgid "Download now" -msgstr "" +msgstr "지금 다운로드" msgid "Select ISO" -msgstr "" +msgstr "ISO 선택" msgid "Template" -msgstr "" +msgstr "템플릿" msgid "Disk Size: (GB)" -msgstr "" +msgstr "디스크 크기: (GB)" msgid "Swap Size: (GB)" -msgstr "" +msgstr "스왑 크기: (GB)" msgid "Ram: (GB)" -msgstr "" +msgstr "RAM(GB):" msgid "Machine name is required." -msgstr "" +msgstr "시스템 이름이 필요합니다." msgid "Machine name can't exceed 20 characters." -msgstr "" +msgstr "컴퓨터 이름은 20자를 초과할 수 없습니다." msgid "Backend selection is required." -msgstr "" +msgstr "백엔드를 선택해야 합니다." msgid "ISO image selection is required." -msgstr "" +msgstr "ISO 이미지를 선택해야 합니다." msgid "Template selection is required." -msgstr "" +msgstr "템플릿을 선택해야 합니다." msgid "A machine with that name already exists." -msgstr "" +msgstr "해당 이름의 컴퓨터가 이미 있습니다." msgid "The machine name is only allowed to consist of alphabetic characters, numbers, dashes and points." -msgstr "" +msgstr "컴퓨터 이름은 알파벳 문자, 숫자, 대시 및 점으로만 구성될 수 있습니다." msgid "Create" -msgstr "" +msgstr "생성" msgid "" "can change the settings of all virtual machines " "cloned from one base owned by the user." -msgstr "" +msgstr "모든 가상 컴퓨터의 설정을 변경할 수 있음 사용자가 소유한 하나의 베이스에서 복제됩니다." msgid "monitoring" -msgstr "" +msgstr "모니터링" msgid "Are you sure you want to remove the Base of" -msgstr "" +msgstr "베이스를 제거하시겠습니까?" msgid "Are you sure you want to prepare the Base of" -msgstr "" +msgstr "베이스를 준비하시겠습니까?" msgid "Are you sure you want to change the Public state of" -msgstr "" +msgstr "의 공개 상태를 변경하시겠습니까?" msgid "Search" -msgstr "" +msgstr "검색" msgid "There are no public bases available in this system." -msgstr "" +msgstr "이 시스템에 사용할 수 있는 공공 기지가 없습니다." msgid "There are no machines available in this system." -msgstr "" +msgstr "이 시스템에 사용할 수 있는 장비가 없습니다." msgid "Create one." -msgstr "" +msgstr "하나를 만듭니다." msgid "not public" -msgstr "" +msgstr "비공개" msgid "User Settings" -msgstr "" +msgstr "사용자 설정" msgid "Create a new account" -msgstr "" +msgstr "새 계정 만들기" msgid "Username" -msgstr "" +msgstr "사용자 이름" msgid "Hide Private" -msgstr "" +msgstr "비공개 숨기기" msgid "Show All" -msgstr "" +msgstr "모두 표시" msgid "Shutting down ..." -msgstr "" +msgstr "종료 중..." msgid "It may take a couple of minutes." -msgstr "" +msgstr "몇 분 정도 걸릴 수 있습니다." msgid "Hibernating ..." -msgstr "" +msgstr "최대 절전 모드..." msgid "Description" -msgstr "" +msgstr "설명" msgid "Rename" -msgstr "" +msgstr "이름 바꾸기" msgid "Options" -msgstr "" +msgstr "옵션" msgid "Drivers" -msgstr "" +msgstr "드라이버" msgid "Graphics" -msgstr "" +msgstr "그래픽" msgid "Remove Machine" -msgstr "" +msgstr "기계 제거" msgid "System overview" -msgstr "" +msgstr "시스템 개요" msgid "Danger Zone" -msgstr "" +msgstr "위험 구역" msgid "Once you delete the machine, there is no going back. Please be certain." -msgstr "" +msgstr "컴퓨터를 삭제하면 다시 돌아갈 수 없습니다. 확실하게 해 주십시오." msgid "Danger" -msgstr "" +msgstr "위험" msgid "This will remove all the contents of the machine" -msgstr "" +msgstr "이렇게 하면 기계의 모든 내용물이 제거됩니다" msgid "This action can't be undone" -msgstr "" +msgstr "이 작업은 실행 취소할 수 없습니다" msgid "Are you sure ?" -msgstr "" +msgstr "정말이야?" msgid "Yes, remove" -msgstr "" +msgstr "예, 삭제합니다." msgid "Virtual Machine" -msgstr "" +msgstr "가상 머신" msgid "removed" -msgstr "" +msgstr "제거됨" msgid "This will remove all the" -msgstr "" +msgstr "이렇게 하면 모든" msgid "clones of the machine" -msgstr "" +msgstr "시스템의 클론" msgid "Yes, remove all the clones" -msgstr "" +msgstr "예, 모든 클론을 제거합니다." msgid "This virtual machine can't be removed because it has" -msgstr "" +msgstr "이 가상 머신은 다음과 같은 이유로 제거할 수 없습니다." msgid "clones" -msgstr "" +msgstr "클론" msgid "Clones" -msgstr "" +msgstr "클론" msgid "This machine has not Graphics parameters!" -msgstr "" +msgstr "이 컴퓨터에 그래픽 매개변수가 없습니다!" msgid "recommended" -msgstr "" +msgstr "권장" msgid "to" -msgstr "" +msgstr "에" msgid "Changing Public State" -msgstr "" +msgstr "공용 상태 변경" msgid "Changing Base State" -msgstr "" +msgstr "기본 상태 변경" msgid "action" -msgstr "" +msgstr "조치" msgid "Grants" -msgstr "" +msgstr "보조금" msgid "public" -msgstr "" +msgstr "공공" msgid "New Base" -msgstr "" +msgstr "새로운 기지" msgid "Version" -msgstr "" +msgstr "버전" msgid "Authors" -msgstr "" +msgstr "저자" msgid "Development" -msgstr "" +msgstr "개발" msgid "Below are listed the technologies used in this project:" -msgstr "" +msgstr "이 프로젝트에 사용된 기술은 다음과 같습니다." msgid "The code is available on " -msgstr "" +msgstr "코드는 에서 사용할 수 있습니다." msgid "The code is " -msgstr "" +msgstr "코드는 다음과 같습니다 " msgid "License" -msgstr "" +msgstr "사용권" msgid "Machine locked by" -msgstr "" +msgstr "잠긴 기계" msgid "This Machine is a base" -msgstr "" +msgstr "이 기계는 기본입니다" msgid "Cloned" -msgstr "" +msgstr "복제됨" msgid "is admin" -msgstr "" +msgstr "관리자임" msgid "Add new user" -msgstr "" +msgstr "새 사용자 추가" msgid "Register" -msgstr "" +msgstr "등록" msgid "Current Password:" -msgstr "" +msgstr "현재 비밀번호:" msgid "For more information, check" -msgstr "" +msgstr "자세한 내용은 다음을 확인하십시오" msgid "the Windows Clients documentation" -msgstr "" +msgstr "Windows 클라이언트 설명서" msgid "It is programmed in" -msgstr "" +msgstr "에 프로그래밍되어 있습니다" msgid "with perl framework" -msgstr "" +msgstr "Perl 프레임워크 사용" msgid "and" -msgstr "" +msgstr "그리고" msgid "Memory" -msgstr "" +msgstr "메모리" msgid "MB available" -msgstr "" +msgstr "MB 사용 가능" msgid "The machine will shutdown after these minutes" -msgstr "" +msgstr "이 몇 분 후에 기계가 종료됩니다" msgid "Cancel" -msgstr "" +msgstr "취소" msgid "Please enter the following information to create the account" -msgstr "" +msgstr "계정을 만들려면 다음 정보를 입력하십시오." msgid "Enter Username" -msgstr "" +msgstr "사용자 이름 입력" msgid "Enter Password" -msgstr "" +msgstr "비밀번호 입력" msgid "Confirm Password" -msgstr "" +msgstr "비밀번호 확인" msgid "Username is required" -msgstr "" +msgstr "사용자 이름은 필수입니다." msgid "Username can not exceed 20 characters" -msgstr "" +msgstr "사용자 이름은 20자를 초과할 수 없습니다." msgid "Username must be at least 5 characters" -msgstr "" +msgstr "사용자 이름은 5자 이상이어야 합니다." msgid "Username can only contain words, numbers, dashes, dots and underscores" -msgstr "" +msgstr "사용자 이름에는 단어, 숫자, 대시, 점 및 밑줄만 포함될 수 있습니다." msgid "Password is required" -msgstr "" +msgstr "비밀번호가 필요합니다" msgid "Password can not exceed 20 characters" -msgstr "" +msgstr "비밀번호는 20자를 초과할 수 없습니다." msgid "Password must be at least 5 characters" -msgstr "" +msgstr "비밀번호는 5자 이상이어야 합니다." msgid "Password can only contain words and numbers" -msgstr "" +msgstr "비밀번호는 단어와 숫자만 포함할 수 있습니다." msgid "Confirm Password is required" -msgstr "" +msgstr "비밀번호 확인이 필요합니다" msgid "Confirm Password can not exceed 20 characters" -msgstr "" +msgstr "비밀번호 확인은 20자를 초과할 수 없습니다." msgid "Confirm Password must be at least 5 characters" -msgstr "" +msgstr "비밀번호 확인은 5자 이상이어야 합니다." msgid "Confirm Password can only contain words and numbers" -msgstr "" +msgstr "비밀번호 확인은 단어와 숫자만 포함할 수 있습니다." msgid "This information will be available to the users" -msgstr "" +msgstr "이 정보는 사용자가 사용할 수 있습니다" msgid "Volatile Clones" -msgstr "" +msgstr "휘발성 클론" msgid "Owner" -msgstr "" +msgstr "소유자" msgid "Change the owner of the machine" -msgstr "" +msgstr "컴퓨터 소유자 변경" msgid "Press SHIFT + F12 to exit" -msgstr "" +msgstr "종료하려면 SHIFT + F12를 누릅니다." msgid "If you can not see the machine screen in a few seconds check for a file called" -msgstr "" +msgstr "몇 초 안에 기계 화면을 볼 수 없다면 다음 파일을 확인하십시오" msgid "in your downloads folder." -msgstr "" +msgstr "다운로드 폴더에서." msgid "copied to clipboard" -msgstr "" +msgstr "클립보아에 복사됨" msgid "Portuguese" -msgstr "" +msgstr "포르투갈어" msgid "Read more." -msgstr "" +msgstr "자세히 보기." msgid "The password for this virtual machine connection is :" -msgstr "" +msgstr "이 가상 컴퓨터 연결의 암호는 다음과 같습니다." msgid "view" -msgstr "" +msgstr "보기" msgid "Start again" -msgstr "" +msgstr "다시 시작" msgid "The machine is down." -msgstr "" +msgstr "기계가 다운되었습니다." msgid "Remove" -msgstr "" +msgstr "Remove" msgid "The machine must be started to take the screenshot." -msgstr "" +msgstr "스크린샷을 찍으려면 컴퓨터를 시동해야 합니다." msgid "Take screenshot" -msgstr "" +msgstr "스크린샷 찍기" msgid "Set base picture" -msgstr "" +msgstr "기본 그림 설정" msgid "Saving the screenshot, please wait a moment..." -msgstr "" +msgstr "스크린샷을 저장하는 중입니다. 잠시 기다려 주십시오..." msgid "You need to run the machine in order to take a screenshot." -msgstr "" +msgstr "스크린샷을 찍으려면 기계를 실행해야합니다." msgid "Copying the screenshot, please wait a moment..." -msgstr "" +msgstr "스크린샷을 복사하는 중입니다. 잠시 기다려 주십시오..." msgid "You need a screenshot in order to copy it to the base machine." -msgstr "" +msgstr "기본 컴퓨터에 복사하려면 스크린샷이 필요합니다." msgid "Add" -msgstr "" +msgstr "추가" msgid "The changes will apply on next restart" -msgstr "" +msgstr "변경 사항은 다음에 다시 시작할 때 적용됩니다." msgid "This VM is running and can't be renamed." -msgstr "" +msgstr "이 VM이 실행 중이므로 이름을 바꿀 수 없습니다." msgid "Thanks to:" -msgstr "" +msgstr "감사:" msgid "licensed." -msgstr "" +msgstr "라이센스가 부여되었습니다." msgid "New Node" -msgstr "" +msgstr "새 노드" msgid "Address" -msgstr "" +msgstr "주소" msgid "A node with that name already exists." -msgstr "" +msgstr "해당 이름의 노드가 이미 있습니다." msgid "Setting your default language" -msgstr "" +msgstr "기본 언어 설정" msgid "Change password" -msgstr "" +msgstr "암호 변경" msgid "Update password" -msgstr "" +msgstr "비밀번호 업데이트" msgid "Start all clones" -msgstr "" +msgstr "모든 클론 시작" msgid "Access denied" -msgstr "" +msgstr "액세스 거부됨" msgid "Empty login name" -msgstr "" +msgstr "빈 로그인 이름" msgid "Empty password" -msgstr "" +msgstr "빈 암호" msgid "Session timeout" -msgstr "" +msgstr "세션 시간 초과" msgid "Vietnamese" -msgstr "" +msgstr "베트남어" msgid "Italian" -msgstr "" +msgstr "이탈리아어" msgid "Type a typical LDAP user name to fetch the attribute list" -msgstr "" +msgstr "일반적인 LDAP 사용자 이름을 입력하여 속성 목록을 가져옵니다" msgid "User name" -msgstr "" +msgstr "사용자 이름" msgid "not found in LDAP server" -msgstr "" +msgstr "LDAP 서버에서 찾을 수 없습니다." msgid "Attribute" -msgstr "" +msgstr "속성" msgid "Value" -msgstr "" +msgstr "값" msgid "Allowed" -msgstr "" +msgstr "허용" msgid "Last" -msgstr "" +msgstr "최근" msgid "Default" -msgstr "" +msgstr "Default" msgid "If none of the previous match, access is allowed." -msgstr "" +msgstr "이전에 일치하는 항목이 없으면 액세스가 허용됩니다." msgid "If none of the previous match, access is denied." -msgstr "" +msgstr "이전 일치 항목이 없으면 액세스가 거부됩니다." msgid "verify" -msgstr "" +msgstr "확인" msgid "save" -msgstr "" +msgstr "저장" msgid "Verifying" -msgstr "" +msgstr "검증" msgid "No entries found" -msgstr "" +msgstr "항목을 찾을 수 없습니다." msgid "has at least" -msgstr "" +msgstr "적어도" msgid "entries." -msgstr "" +msgstr "항목." msgid "Enable last for not allowed restrictions." -msgstr "" +msgstr "허용되지 않는 제한에 대해 last를 사용하도록 설정합니다." msgid "Add description" -msgstr "" +msgstr "설명 추가" msgid "These actions affect all the clones on the machine" -msgstr "" +msgstr "이러한 작업은 시스템의 모든 클론에 영향을 미칩니다." msgid "Shutdown all clones" -msgstr "" +msgstr "모든 클론을 종료합니다" msgid "Keep in mind that there may be users using a clone" -msgstr "" +msgstr "클론을 사용하는 사용자가 있을 수 있습니다." msgid "Yes, shutwdown all the clones" -msgstr "" +msgstr "예, 모든 클론을 종료합니다." diff --git a/lib/Ravada/Request.pm b/lib/Ravada/Request.pm index 398f36e92..b18735093 100644 --- a/lib/Ravada/Request.pm +++ b/lib/Ravada/Request.pm @@ -71,6 +71,7 @@ our %VALID_ARG = ( , check => 2 , id_vm => 2 } ,force_shutdown_domain => { id_domain => 1, uid => 1, at => 2, id_vm => 2 } + ,force_shutdown => { id_domain => 1, uid => 1, at => 2, id_vm => 2 } ,reboot_domain => { name => 2, id_domain => 2, uid => 1, timeout => 2, at => 2 , id_vm => 2 } ,force_reboot_domain => { id_domain => 1, uid => 1, at => 2, id_vm => 2 } diff --git a/lib/Ravada/VM.pm b/lib/Ravada/VM.pm index 2de18017e..f8b93cecf 100644 --- a/lib/Ravada/VM.pm +++ b/lib/Ravada/VM.pm @@ -2691,6 +2691,32 @@ sub dir_backup($self) { return $dir_backup; } + +sub _follow_link($self, $file) { + my ($dir, $name) = $file =~ m{(.*)/(.*)}; + my $file2 = $file; + if ($dir) { + my $dir2 = $self->_follow_link($dir); + $file2 = "$dir2/$name"; + } + + if (!defined $self->{_is_link}->{$file2} ) { + my ($out,$err) = $self->run_command("stat","-c",'"%N"', $file2); + chomp $out; + my ($link) = $out =~ m{ -> '(.+)'}; + if ($link) { + if ($link !~ m{^/}) { + my ($path) = $file2 =~ m{(.*/)}; + $path = "/" if !$path; + $link = "$path$link"; + } + $self->{_is_link}->{$file2} = $link; + } + } + $self->{_is_link}->{$file2} = $file2 if !exists $self->{_is_link}->{$file2}; + return $self->{_is_link}->{$file2}; +} + sub _is_link_remote($self, $vol) { my ($out,$err) = $self->run_command("stat",$vol); @@ -2733,12 +2759,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 f0cb5ab48..edfebba89 100644 --- a/lib/Ravada/VM/KVM.pm +++ b/lib/Ravada/VM/KVM.pm @@ -346,6 +346,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; @@ -393,15 +395,16 @@ sub remove_file($self,@files) { sub _list_volumes($self) { my @volumes; for my $pool (_list_storage_pools($self->vm)) { - next if !$pool->is_active; - 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; } @@ -459,7 +462,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); @@ -468,20 +471,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() }; @@ -490,11 +487,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) { @@ -605,6 +601,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 ) { @@ -627,20 +624,6 @@ sub _file_exists_remote($self, $file) { return scalar(@ls); } -sub _follow_link($self, $file) { - my ($dir, $name) = $file =~ m{(.*)/(.*)}; - if (!defined $self->{_is_link}->{$dir} ) { - my ($out,$err) = $self->run_command("stat", $dir ); - chomp $out; - $out =~ m{ -> (/.*)}; - $self->{_is_link}->{$dir} = $1; - } - my $path = $self->{_is_link}->{$dir}; - return $file if !$path; - return "$path/$name"; - -} - sub _wait_storage($self, $sub) { my @ret; for ( 1 .. 10 ) { @@ -730,6 +713,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 1b6aac16a..046133624 100644 --- a/lib/Ravada/VM/Void.pm +++ b/lib/Ravada/VM/Void.pm @@ -473,7 +473,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()); @@ -484,32 +484,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) { @@ -821,6 +822,19 @@ sub change_network($self,$data) { DumpFile($file_out,$net); } +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/public/js/admin.js b/public/js/admin.js index f8846fb81..72727a686 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -18,6 +18,7 @@ ravadaApp.directive("solShowMachine", swMach) .controller("settings_global", settings_global_ctrl) .controller("admin_groups", admin_groups_ctrl) .controller('admin_charts', admin_charts_ctrl) + .controller('upload_users', upload_users) ; ravadaApp.directive('ipaddress', function() { @@ -1518,7 +1519,27 @@ ravadaApp.directive("solShowMachine", swMach) function settings_global_ctrl($scope, $http) { $scope.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - $scope.init = function() { + $scope.csp_locked = false; + $scope.set_csp_locked=function() { + var keys = Object.keys($scope.settings.frontend.content_security_policy); + var found = 0; + for ( var n_key=0 ; n_key0; + if ($scope.csp_locked && !$scope.csp_advanced) { + $scope.csp_advanced = true; + } + }; + $scope.init = function(url, csp_advanced) { + $scope.csp_advanced=false; + if (csp_advanced) { + $scope.csp_advanced=true; + } $http.get('/settings_global.json').then(function(response) { $scope.settings = response.data; var now = new Date(); @@ -1537,10 +1558,12 @@ ravadaApp.directive("solShowMachine", swMach) $scope.settings.frontend.maintenance_end.value =new Date($scope.settings.frontend.maintenance_end.value); } + $scope.set_csp_locked(); }); }; $scope.load_settings = function() { $scope.init(); + $scope.set_csp_locked(); $scope.formSettings.$setPristine(); }; $scope.update_settings = function() { @@ -1548,6 +1571,7 @@ ravadaApp.directive("solShowMachine", swMach) $http.post('/settings_global' ,JSON.stringify($scope.settings) ).then(function(response) { + $scope.set_csp_locked(); if (response.data.reload) { window.location.reload(); } @@ -1690,4 +1714,8 @@ ravadaApp.directive("solShowMachine", swMach) }; + + function upload_users($scope, $http) { + $scope.type = 'sql'; + }; }()); diff --git a/public/js/ravada.js b/public/js/ravada.js index 5b57ca93f..55ea2a5a0 100644 --- a/public/js/ravada.js +++ b/public/js/ravada.js @@ -139,7 +139,6 @@ if (typeof(machine.clone) != 'undefined' && machine.clone) { machine.is_active = machine.clone.is_active; - machine.screenshot= machine.clone.screenshot; if (machine.clone.description && machine.clone.description.length) { machine.description2 = machine.clone.description; @@ -258,9 +257,6 @@ } else { machine.list_clones[i].is_active = data.list_clones[i].is_active; machine.list_clones[i].screenshot = data.list_clones[i].screenshot; - if (machine.clone.id == machine.list_clones[i].id) { - machine.screenshot = machine.list_clones[i].screenshot; - } } } diff --git a/script/rvd_back b/script/rvd_back index 13c5f390d..829db9bf3 100755 --- a/script/rvd_back +++ b/script/rvd_back @@ -16,6 +16,7 @@ use File::Path; use File::Basename; use Ravada; +use Ravada::Front; use Ravada::Auth::SQL; use Ravada::Auth::LDAP; use Ravada::Utils; @@ -35,6 +36,7 @@ my $FILE_CONFIG_DEFAULT = "/etc/ravada.conf"; my $FILE_CONFIG; my $ADD_USER_LDAP; +my $ADD_USER_FILE; my $ADD_GROUP_LDAP; my $RM_GROUP_LDAP; my $ADD_USER_GROUP; @@ -77,8 +79,12 @@ my $BACKUP; my $RESTORE; my $TIME_CONNECTION = 10; +my $TYPE; +my $CREATE; + my $USAGE = "$0 " ." [--debug] [--config=$FILE_CONFIG_DEFAULT] [--add-user=name] [--add-user-ldap=name]" + ." [--add-user-file=filename]" ." [--change-password] [--make-admin=username] [--import-vbox=image_file.vdi]" ." [--test-ldap] " ." [-X] [start|stop|status]" @@ -89,6 +95,7 @@ my $USAGE = "$0 " ."\n" ." --add-user : adds a new db user\n" ." --add-user-ldap : adds a new LDAP user\n" + ." --add-user-file: adds new users from a file\n" ." --remove-user : removes a db user\n" ." --add-group-ldap : creates a new LDAP group\n" ." --remove-group-ldap : removes a LDAP group\n" @@ -125,6 +132,9 @@ my $USAGE = "$0 " ." --log: saves STDOUT and STDERR app traces to the specified filename\n" ." --list-unused-volumes\n" ." --show-volume\n" + ."Create users modifiers\n" + ." --type : plain, ldap or sso. Defaults to plain\n" + ." --create : only valid for LDAP users, creates the entries.\n" ."\n" ; @@ -153,6 +163,7 @@ GetOptions ( help => \$help ,'remove-admin=s' => \$REMOVE_ADMIN_USER ,'change-password'=> \$CHANGE_PASSWORD ,'add-user-ldap=s'=> \$ADD_USER_LDAP + ,'add-user-file=s'=> \$ADD_USER_FILE ,'add-group-ldap=s'=> \$ADD_GROUP_LDAP ,'remove-group-ldap=s'=> \$RM_GROUP_LDAP ,'add-user-group=s'=> \$ADD_USER_GROUP @@ -177,6 +188,8 @@ GetOptions ( help => \$help ,"purge-nodes" => \$PURGE_NODES ,"time-connection=s" => \$TIME_CONNECTION + ,"type=s" => \$TYPE + ,"create" => \$CREATE ) or exit; $START = 1 if $DEBUG || $FILE_CONFIG || $NOFORK; @@ -344,6 +357,26 @@ sub add_user { , is_admin => $is_admin); } +sub add_user_file($ADD_USER_FILE) { + my $ravada = Ravada::Front->new(); + die "Error: File $ADD_USER_FILE does not exist\n" + if !-e $ADD_USER_FILE; + + open my $in,"<",$ADD_USER_FILE or die "$! $ADD_USER_FILE\n"; + my $users = join("",<$in>); + close $in; + + my ($found, $count, $error)=$ravada->upload_users($users, ($TYPE or 'sql'), $CREATE); + + print "$found found, $count added.\n"; + if (scalar (@$error)) { + print "Errors:\n"; + for (sort @$error) { + print " - $_\n"; + } + } +} + sub add_user_ldap { my $login = shift; @@ -1144,6 +1177,7 @@ if $HIBERNATE_DOMAIN || $SHUTDOWN_DOMAIN; add_user($ADD_USER) if $ADD_USER; add_user_ldap($ADD_USER_LDAP) if $ADD_USER_LDAP; +add_user_file($ADD_USER_FILE) if $ADD_USER_FILE; add_group_ldap($ADD_GROUP_LDAP) if $ADD_GROUP_LDAP; remove_group_ldap($RM_GROUP_LDAP) if $RM_GROUP_LDAP; add_user_group($rvd_back, $ADD_USER_GROUP) if $ADD_USER_GROUP; diff --git a/script/rvd_front b/script/rvd_front index edd987e99..cb8df268c 100644 --- a/script/rvd_front +++ b/script/rvd_front @@ -147,21 +147,50 @@ sub _time() { return strftime('%Y/%m/%d:%H:%M:%S %z',localtime); } +sub _security_policy() { + + my $config=$RAVADA->_settings_by_parent("/frontend/content_security_policy"); + my $all = ($config->{all} or ''); + my %src = ( + "default-src" => "'self' sha256- sha384 http: https: data:" + ,"style-src" => "'self' cdnjs.cloudflare.com stackpath.bootstrapcdn.com cdn.ckeditor.com cdn.jsdelivr.net use.fontawesome.com 'unsafe-inline'" + ,"script-src" => "'self' code.jquery.com cdn.ckeditor.com cdnjs.cloudflare.com stackpath.bootstrapcdn.com ajax.googleapis.com cdn.jsdelivr.net 'unsafe-inline' 'unsafe-eval'" + ,"object-src" => " 'self'" + ,"media-src" => "'self'" + ,"frame-src" => "'self'" + ,"font-src" => "'self' data: use.fontawesome.com" + ,"connect-src" => "'self'" + ); + my $sec = ''; + for my $field (sort keys %src) { + $sec .= " " if $sec; + $sec .= $field." ".$src{$field}; + $sec .= " $all" if $all; + if ( exists $config->{$field} ) { + $sec .= " ".${config}->{$field}; + } + $field =~ s/-/_/g; + if ( exists $config->{$field} ) { + $sec .= " ".$config->{$field}; + } + + $sec .= ";"; + } + return $sec; +} + hook before_routes => sub { my $c = shift; - $c ->res->headers->content_security_policy ( - "object-src 'auto';" - ."media-src 'self';" - ." frame-src 'self';" - ." connect-src 'self'; "); - + my $sec = _security_policy(); + $c ->res->headers->content_security_policy($sec); $USER = undef; $c->stash(version => $RAVADA->version); my $url = $c->req->url->to_abs->path; my $host = $c->req->url->to_abs->host; + my $widget=( $CONFIG_FRONT->{widget} or $RAVADA->setting('/frontend/widget')); $c->stash(css=>['/css/sb-admin.css'] ,js_mod=>[ ## angular modules '/js/booking/booking.module.js?v='.$RAVADA->version @@ -184,7 +213,7 @@ hook before_routes => sub { ,host => $host ,bookings => $RAVADA->setting('/backend/bookings') ,FEATURE => {} - ,widget => $CONFIG_FRONT->{widget} + ,widget => $widget ); $USER = _logged_in($c); @@ -1456,6 +1485,62 @@ any '/group/new' => sub { return new_group($c); }; +any '/admin/users/upload.#req' => sub($c) { + return access_denied_json($c) unless $USER->is_admin; + + _add_admin_libs($c); + + my $type = $c->req->param('type'); + + return $c->render(template => "/main/upload_users", done => 0, count => 0, found => 0, type => 'sql') if !$type; + + my $create = ( $c->req->param('create') or 0); + + return $c->render(json => { error => "Unknown type $type" }) + if $type !~ /^(sql|ldap|sso)/; + + my $csv = $c->req->upload('users'); + if($csv->headers->content_type !~ m{text/(csv|plain)}) { + return $c->render(status => 400 + ,text => "Wrong content type ".$csv->headers->content_type + ." , it should be text/csv or plain" + ); + } + + my ($found, $count, $error) = $RAVADA->upload_users( + $csv->slurp, $type, $create + ); + + return $c->render(json => + { output => "$count users added" + ,error => $error + }) if $c->stash('req') eq 'json'; + + return $c->render(template => "/main/upload_users" + ,count => $count + ,found => $found + ,error => $error + ,done => 1 + ); +}; + +get '/admin/user/remove/#id' => sub($c) { + return access_denied($c) unless $USER->is_admin; + my $id = $c->stash('id'); + my $user = Ravada::Auth::SQL->search_by_id($id); + return $c->render(message => "User not found. id=$id." + , link => ['/admin/users','Users'] + ,template => '/main/standard_message' + ,status => 404 + ) if !$user; + $user->remove(); + + return $c->render(message => "User removed : ".$user->name + , link => ['/admin/users','Users'] + ,template => '/main/standard_message' + ); +}; + any '/admin/user/(#id).(:type)' => sub { my $c = shift; @@ -1500,6 +1585,7 @@ any '/admin/user/(#id).(:type)' => sub { } $c->stash(user => $user); + return $c->render(json => {name => $user->name}) if $c->stash('type') eq 'json'; return $c->render(template => 'main/manage_user'); }; @@ -2664,7 +2750,6 @@ sub login($c, $status=200) { my $url = ($c->param('url') or $c->req->url->to_abs->path); $url = '/' if $url =~ m{^/login}; - my @error =(); if (((defined $login) || (defined $password) || ((defined $c->param('submit')) && ($c->param('submit') ne 'sso'))) && (! $ticket)) { @@ -2903,6 +2988,11 @@ sub admin { return access_denied($c) unless $USER->is_admin; my $url = $c->req->url->to_abs->path; my $host = $c->req->url->to_abs->host; + my $csp = $RAVADA->_settings_by_parent("/frontend/content_security_policy"); + my $csp_advanced = 0; + $csp_advanced = grep $csp->{$_}, grep /-/,keys %$csp; + + $c->stash( csp => $csp , csp_advanced => $csp_advanced); $c->stash(url_login => "/login"); } if ($page eq 'storage') { diff --git a/t/lib/Test/Ravada.pm b/t/lib/Test/Ravada.pm index 9d1d9b858..60b83ccb6 100644 --- a/t/lib/Test/Ravada.pm +++ b/t/lib/Test/Ravada.pm @@ -86,6 +86,8 @@ create_domain remove_old_users remove_old_users_ldap + remove_qemu_pools + mangle_volume test_volume_contents test_volume_format @@ -273,6 +275,7 @@ sub create_domain_v2(%args) { my $user = (delete $args{user} or $USER_ADMIN); my $iso_name = delete $args{iso_name}; my $id_iso = delete $args{id_iso}; + my $disk = delete $args{disk}; croak "Error: supply either iso_name or id_iso ".Dumper(\%args) if $iso_name && $id_iso; @@ -298,13 +301,12 @@ sub create_domain_v2(%args) { if keys %args; my $iso; - my $disk; if ($vm->type eq 'KVM' && (!$iso_name || $iso_name !~ /Alpine/i)) { $iso = $vm->_search_iso($id_iso); - $disk = ($iso->{min_disk_size} or 2 ); + $disk = ($iso->{min_disk_size} or 2 ) if !$disk; diag("Creating [$id_iso] $iso->{name}"); } else { - $disk = 2; + $disk = 2 if !$disk; } if ($vm->type eq 'KVM' && !$options ) { @@ -1021,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; } } @@ -1469,7 +1476,7 @@ sub remove_qemu_networks($vm=undef) { } 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') }; @@ -1484,18 +1491,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,/; @@ -1560,6 +1571,7 @@ sub remove_old_storage_pools_void() { } sub remove_old_storage_pools_kvm() { + remove_qemu_pools(); } sub remove_old_storage_pools() { diff --git a/t/mojo/10_login.t b/t/mojo/10_login.t index a13ccf7eb..fcb4713b6 100644 --- a/t/mojo/10_login.t +++ b/t/mojo/10_login.t @@ -228,7 +228,7 @@ sub test_login_non_admin($t, $base, $clone){ for ( 1 .. 10 ) { my $clone2 = rvd_front->search_domain($clone->name); last if $clone2->is_base || !$clone2->list_requests; - _wait_request(debug => 1, background => 1, check_error => 1); + _wait_request(debug => 0, background => 1, check_error => 1); mojo_check_login($t, $USERNAME, $PASSWORD); } is($clone->is_base,1) or next; @@ -246,10 +246,9 @@ sub test_login_non_admin($t, $base, $clone){ login($name, $pass); $t->get_ok('/')->status_is(200)->content_like(qr/choose a machine/i); - $t->get_ok("/machine/clone/".$clone->id.".html") ->status_is(200); - wait_request(debug => 1, check_error => 1, background => 1, timeout => 120); + wait_request(debug => 0, check_error => 1, background => 1, timeout => 120); mojo_check_login($t, $name, $pass); my $clone_new_name = $base->name."-".$name; @@ -269,7 +268,7 @@ sub test_login_non_admin($t, $base, $clone){ for ( 1 .. 60 ) { my ($req) = grep { $_->status ne 'done' } $user->list_requests(); last if !$req; - wait_request(debug => 1, check_error => 1, background => 1, timeout => 120); + wait_request(debug => 0, check_error => 1, background => 1, timeout => 120); delete_request('open_exposed_ports'); } my ($req) = reverse $user->list_requests(); @@ -300,7 +299,7 @@ sub test_list_ldap_attributes($t, $expected_code=200) { sub test_login_non_admin_req($t, $base, $clone){ mojo_check_login($t, $USERNAME, $PASSWORD); - my $clone2; + my $clone2 = $clone; for ( 1 .. 3 ) { if (!$clone->is_base) { @@ -313,7 +312,7 @@ sub test_login_non_admin_req($t, $base, $clone){ for ( 1 .. 60 ) { $clone2 = rvd_front->search_domain($clone->name); last if $clone2->is_base || !$clone2->list_requests; - _wait_request(debug => 1, background => 1, check_error => 1); + _wait_request(debug => 0, background => 1, check_error => 1); mojo_check_login($t, $USERNAME, $PASSWORD); } last if $clone2->is_base; @@ -340,7 +339,7 @@ sub test_login_non_admin_req($t, $base, $clone){ } ); - wait_request(debug => 1, check_error => 1, background => 1, timeout => 120); + wait_request(debug => 0, check_error => 1, background => 1, timeout => 120); mojo_check_login($t, $name, $pass); my $clone_new = rvd_front->search_domain($clone_new_name); @@ -358,7 +357,7 @@ sub test_login_non_admin_req($t, $base, $clone){ for ( 1 .. 10 ) { - wait_request(debug => 1, check_error => 1, background => 1, timeout => 120); + wait_request(debug => 0, check_error => 1, background => 1, timeout => 120); $clone_new = rvd_front->search_domain($clone_new_name); last if $clone_new; } @@ -426,11 +425,11 @@ sub test_copy_without_prepare($clone) { is(scalar @clones, $n_clones_clone+$n_clones,"Expecting clones from ".$clone->name) or exit; mojo_request($t, "spinoff", { id_domain => $clone->id }); - wait_request(debug => 1, check_error => 1, background => 1, timeout => 120); + wait_request(debug => 0, check_error => 1, background => 1, timeout => 120); # is($clone->id_base,0 ); mojo_check_login($t); mojo_request($t, "clone", { id_domain => $clone->id, number => $n_clones }); - wait_request(debug => 1, check_error => 1, background => 1, timeout => 120); + wait_request(debug => 0, check_error => 1, background => 1, timeout => 120); is($clone->is_base, 1 ); my @n_clones_clone_2= $clone->clones(); is(scalar @n_clones_clone_2, $n_clones_clone+$n_clones*2) or exit; @@ -569,21 +568,28 @@ sub _add_displays($t, $domain) { } -sub _clone_and_base($vm_name, $t, $base0) { +sub _clone_and_base($vm_name, $t) { mojo_check_login($t); - my $base1 = $base0; + my ($base,$base1); if ($vm_name eq 'KVM') { - my $base = rvd_front->search_domain($BASE_NAME); + $base = rvd_front->search_domain($BASE_NAME); die "Error: test base $BASE_NAME not found" if !$base; + } else { + $base = test_create_base($t, $vm_name, new_domain_name()); + } + confess if !defined $base; + + { my $name = new_domain_name()."-".$vm_name."-$$"; mojo_request_url_post($t,"/machine/copy",{id_base => $base->id, new_name => $name, copy_ram => 0.128, copy_number => 1}); - for ( 1 .. 60 ) { + for ( 1 .. 90 ) { $base1 = rvd_front->search_domain($name); last if $base1; wait_request(); } ok($base1, "Expecting domain $name created") or exit; + mojo_request($t,"spinoff",{id_domain => $base1->id}); } mojo_check_login($t); @@ -667,7 +673,7 @@ sub _download_iso($iso_name) { my $req = Ravada::Request->download(id_iso => $id_iso); for ( 1 .. 300 ) { last if $req->status eq 'done'; - _wait_request(debug => 1, background => 1, check_error => 1); + _wait_request(debug => 0, background => 1, check_error => 1); } is($req->status,'done'); is($req->error, '') or exit; @@ -746,9 +752,9 @@ sub test_new_machine_default($t, $vm_name, $empty_iso_file=undef) { for ( 1 .. 10 ) { ($data) = grep { $_->{file} =~ /DATA/ } @$disks; last if $data; - sleep 1; + wait_request(); } - ok($data,"Expecting a data disk volume") or exit; + ok($data,"Expecting a data disk volume in ".$domain->name) or exit; my ($iso) = grep { $_->{file} =~ /iso$/ } @$disks; ok($iso,"Expecting an ISO cdrom disk volume"); @@ -860,6 +866,7 @@ sub test_create_base($t, $vm_name, $name) { ,disk => 1 ,ram => 1 ,swap => 1 + ,start => 0 ,submit => 1 } )->status_is(302); @@ -869,7 +876,7 @@ sub test_create_base($t, $vm_name, $name) { my @requests = $user->list_requests(); my ($req_create) = grep { $_->command eq 'create' } @requests; - _wait_request(debug => 1, background => 1, check_error => 1); + _wait_request(debug => 0, background => 1, check_error => 1); my $base; for ( 1 .. 120 ) { $base = rvd_front->search_domain($name); @@ -952,7 +959,7 @@ sub test_clone_same_name($t, $base) { wait_request(); for ( 1 .. 10 ) { - wait_request(debug => 1); + wait_request(debug => 0); @clones2 = $base->clones(); last if scalar(@clones2)>scalar(@clones); sleep 1; @@ -966,6 +973,7 @@ sub test_clone_same_name($t, $base) { die Dumper(\@clones2) if !$clone; + mojo_request($t,"force_shutdown", {id_domain => $clone->{id} }); mojo_request($t,"prepare_base", {id_domain => $clone->{id} }); sleep 1; wait_request(); @@ -979,7 +987,7 @@ sub test_clone_same_name($t, $base) { $t->get_ok("/machine/clone/".$base->id.".html") ->status_is(200); - wait_request(); + wait_request( debug => 0); my @clones3; for ( 1 .. 20 ) { @@ -989,7 +997,8 @@ sub test_clone_same_name($t, $base) { wait_request( background=>1); } @clones3 = $base->clones(); - is(scalar(@clones3) , scalar(@clones2)+1) or exit; + is(scalar(@clones3) , scalar(@clones2)+1) or die "Expecting ".(scalar(@clones2)+1) + ." clones of ".$base->name; for my $c ($base->clones) { mojo_request($t,"remove_domain", {name => $c->{name} }); @@ -1064,8 +1073,10 @@ for my $vm_name (reverse @{rvd_front->list_vm_types} ) { } test_admin_can_do_anything($t, $base0); - my $base2 =test_create_base($t, $vm_name, new_domain_name()."-$vm_name-$$"); - push @bases,($base2->name); + my $base2a =test_create_base($t, $vm_name, new_domain_name()."-$vm_name-$$"); + push @bases,($base2a->name); + + my $base2 = _clone_and_base($vm_name, $t); mojo_request($t, "add_hardware", { id_domain => $base0->id, name => 'network' }); wait_request(debug => 0, check_error => 1, background => 1, timeout => 120); @@ -1073,7 +1084,7 @@ for my $vm_name (reverse @{rvd_front->list_vm_types} ) { test_validate_html("/machine/manage/".$base0->id.".html"); - my $base1 = _clone_and_base($vm_name, $t, $base0); + my $base1 = _clone_and_base($vm_name, $t); push @bases,($base1->name); is($base1->is_base,1) or next; diff --git a/t/mojo/20_ws.t b/t/mojo/20_ws.t index 51210cb56..3332b0def 100644 --- a/t/mojo/20_ws.t +++ b/t/mojo/20_ws.t @@ -89,6 +89,9 @@ sub test_bases($t, $bases) { my $n_bases = 0; my $n_machines = scalar(@$bases); for my $base ( @$bases ) { + + mojo_request($t, "force_shutdown", { id_domain => $base->id }); + my $url = "/machine/prepare/".$base->id.".json"; $t->get_ok($url)->status_is(200); wait_mojo_request($t, $url); @@ -159,6 +162,10 @@ sub test_list_machines_non_admin($t, $bases) { my @list_machines = list_machines($t); is(scalar(@list_machines),0) or die Dumper([map {[$_->{id_base},$_->{name}]} @list_machines]); + Ravada::Request->force_shutdown( + uid => user_admin->id + ,id_domain => $clone->{id} + ); my $req = Ravada::Request->prepare_base( uid => user_admin->id ,id_domain => $clone->{id} diff --git a/t/mojo/40_security_policy.t b/t/mojo/40_security_policy.t new file mode 100644 index 000000000..4e4af063d --- /dev/null +++ b/t/mojo/40_security_policy.t @@ -0,0 +1,85 @@ +use warnings; +use strict; + +use Carp qw(confess); +use Data::Dumper; +use HTML::Lint; +use Test::More; +use Test::Mojo; +use Mojo::File 'path'; +use Mojo::JSON qw(decode_json); +use Storable qw(dclone); + +use lib 't/lib'; +use Test::Ravada; + +no warnings "experimental::signatures"; +use feature qw(signatures); + +my $SECONDS_TIMEOUT = 15; + +my $t; + +my $URL_LOGOUT = '/logout'; +my ($USERNAME, $PASSWORD) = (user_admin->name, "$$ $$"); +my $SCRIPT = path(__FILE__)->dirname->sibling('../script/rvd_front'); + +$ENV{MOJO_MODE} = 'devel'; +init('/etc/ravada.conf',0); +my $connector = rvd_back->connector; +like($connector->{driver} , qr/mysql/i) or BAIL_OUT; + +$Test::Ravada::BACKGROUND=1; + +$t = Test::Mojo->new($SCRIPT); +$t->ua->inactivity_timeout(900); +$t->ua->connect_timeout(60); + +mojo_login($t, $USERNAME, $PASSWORD); + +my $sth = rvd_front->_dbh->prepare("UPDATE settings set value='' WHERE id_parent=?"); + +$t->get_ok("/settings_global.json")->status_is(200); +my $body = $t->tx->res->body(); +my $settings = decode_json($body); + +$sth->execute($settings->{frontend}->{content_security_policy}->{id}); + +my $new = dclone($settings); +my $exp_default = "foodefault.example.com"; +my $exp_all = "fooall.example.com"; +$new->{frontend}->{content_security_policy}->{'default-src'}->{value} = $exp_default; +$new->{frontend}->{content_security_policy}->{'all'}->{value} = $exp_all; +delete $new->{backend}; + +my $reload=0; +rvd_front->update_settings_global($new,user_admin,$reload); + +$t->post_ok("/settings_global", json => $new ); + +$t->get_ok("/settings_global.json")->status_is(200); +$body = $t->tx->res->body(); +my $settings2 = decode_json($body); +is($settings2->{frontend}->{content_security_policy}->{'all'}->{value} , $exp_all) or exit; +is($settings2->{frontend}->{content_security_policy}->{'default-src'}->{value} , $exp_default) or exit; + +my $config_csp = rvd_front->_settings_by_parent("/frontend/content_security_policy"); +is($config_csp->{all}, $exp_all); +is($config_csp->{'default-src'}, $exp_default); + +my $header = $t->tx->res->headers->content_security_policy(); +my %csp; +for my $entry (split /;/,$header) { + my ($key,$value) = $entry =~ /\s*(.*?)\s+(.*)/; + $csp{$key}=$value; +} + +like($csp{'default-src'},qr/$exp_all/); +like($csp{'default-src'},qr/$exp_default/); + +$sth->execute($settings->{frontend}->{content_security_policy}->{id}); + +$new->{frontend}->{content_security_policy}->{'default-src'}->{value} = ''; +$new->{frontend}->{content_security_policy}->{'all'}->{value} = ''; +$t->post_ok("/settings_global", json => $new ); +done_testing(); diff --git a/t/mojo/60_upload.t b/t/mojo/60_upload.t new file mode 100644 index 000000000..b91b1d4c8 --- /dev/null +++ b/t/mojo/60_upload.t @@ -0,0 +1,230 @@ +use warnings; +use strict; + +use Carp qw(confess); +use Data::Dumper; +use Test::More; +use Test::Mojo; +use Mojo::File 'path'; +use Mojo::JSON qw(decode_json); + +use lib 't/lib'; +use Test::Ravada; + +no warnings "experimental::signatures"; +use feature qw(signatures); + +my $SECONDS_TIMEOUT = 15; + +my $t; + +my $URL_LOGOUT = '/logout'; +my $SCRIPT = path(__FILE__)->dirname->sibling('../script/rvd_front'); + +################################################################################ + +sub _clean(@name) { + for my $name (@name) { + my $user = Ravada::Auth::SQL->new(name => $name); + $user->remove() if $user; + } +} + +sub _clean_ldap(@name) { + for my $name (@name) { + if ( Ravada::Auth::LDAP::search_user($name) ) { + Ravada::Auth::LDAP::remove_user($name) + } + } +} + +sub _create($type, %users) { + return if $type eq 'sql'; + while (my ($name, $pass) = each %users) { + if ( $type eq 'ldap') { + create_ldap_user($name, $pass); + } + } +} + +sub test_upload_users_nopassword( $type, $mojo=0 ) { + + my $user1 = new_domain_name(); + my $user2 = new_domain_name(); + + _clean_ldap($user1, $user2); + _clean($user1, $user2); + + my $users = $user1."\n" + .$user2."\n" + ; + + if ($mojo) { + $t->post_ok('/admin/users/upload.json' => form => { + type => $type + ,create => 0 + ,users => { content => $users, filename => 'users.txt', 'Content-Type' => 'text/csv' }, + })->status_is(200); + die $t->tx->res->body if $t->tx->res->code != 200; + + my $response = $t->tx->res->json(); + like($response->{output}, qr/2 users added/); + is_deeply($response->{error},[]); + } else { + rvd_front->upload_users($users, $type); + } + + test_users_added($type, $user1,$user2); +} + +sub test_upload_users( $type, $create=0, $mojo=0 ) { + + my ($user1, $pass1) = ( new_domain_name(), $$.1); + my ($user2, $pass2) = ( new_domain_name(), $$.2); + _clean_ldap($user1, $user2); + + _create($type, $user1, $pass1, $user2, $pass2) if !$create; + _clean($user1, $user2); + + my $users = join(":",($user1, $pass1)) ."\n" + .join(":",($user2, $pass2)) ."\n" + ; + + if ($mojo) { + $t->post_ok('/admin/users/upload.json' => form => { + type => $type + ,create => $create + ,users => { content => $users, filename => 'users.txt', 'Content-Type' => 'text/csv' }, + })->status_is(200); + die $t->tx->res->body if $t->tx->res->code != 200; + + my $response = $t->tx->res->json(); + like($response->{output}, qr/2 users added/); + is_deeply($response->{error},[]); + } else { + rvd_front->upload_users($users, $type, $create); + } + if ($type ne 'sso') { + $t->post_ok('/login' => form => {login => $user1, password => $pass1}) + ->status_is(302); + $t->get_ok('/logout'); + $t->post_ok('/login' => form => {login => $user2, password => $pass2}) + ->status_is(302); + $t->get_ok('/logout'); + + _login($t); + } + $t->post_ok('/admin/users/upload.json' => form => { + type => 'sql' + ,users => { content => $users, filename => 'users.txt', 'Content-Type' => 'text/csv' }, +})->status_is(200); + + exit if $t->tx->res->code == 401; + die $t->tx->res->body if $t->tx->res->code != 200; + + my $response = $t->tx->res->json(); + like($response->{output}, qr/0 users added/); + is(scalar(@{$response->{error}}),2); + + test_users_added($type, $user1, $user2); + + for my $name ($user1, $user2) { + my $user = Ravada::Auth::SQL->new(name => $name); + + $t->get_ok('/admin/user/'.$user->id.".html")->status_is(200); + die $t->tx->res->body if $t->tx->res->code != 200; + + $t->get_ok('/admin/user/'.$user->id.".json")->status_is(200); + + my $body = $t->tx->res->body; + my $json; + eval { $json = decode_json($body) }; + is($@, '') or die $body; + + is($json->{name}, $user->name); + } + +} + +sub test_users_added($type, @name) { + my $sth = connector->dbh->prepare( + "SELECT * FROM users WHERE name=?" + ); + for my $name (@name) { + $sth->execute($name); + my $row = $sth->fetchrow_hashref; + is($row->{name},$name); + if ($type eq 'sql') { + is($row->{external_auth}, undef); + } else { + is($row->{external_auth},$type,"Expecting $name in $type"); + } + } +} + +sub _login($t) { + my $user_name = new_domain_name(); + + my $user_db = Ravada::Auth::SQL->new( name => $user_name); + $user_db->remove(); + + my $user = create_user($user_name, $$); + user_admin->make_admin($user->id); + + mojo_login($t, $user_name, $$); +} + +sub test_upload_no_admin($t) { + my $user_name = new_domain_name(); + + my $user_db = Ravada::Auth::SQL->new( name => $user_name); + $user_db->remove(); + + my $user = create_user($user_name, $$); + die "Error, it shouldn't be admin" if $user->is_admin; + + mojo_login($t,$user_name, $$); + my $users = join(":",('a','b' )); + + for my $type ( ('json','html', 'foo')) { + $t->post_ok("/admin/users/upload.$type" => form => { + type => 'sql' + ,users => { content => $users, filename => 'users.txt', 'Content-Type' => 'text/csv' }, + })->status_is(403); + + die $t->tx->res->body if $t->tx->res->code != 403; + } + +} + +################################################################################ + +$ENV{MOJO_MODE} = 'development'; +init('/etc/ravada.conf',0); +my $connector = rvd_back->connector; +like($connector->{driver} , qr/mysql/i) or BAIL_OUT; + +$t = Test::Mojo->new($SCRIPT); +$t->ua->inactivity_timeout(900); +$t->ua->connect_timeout(60); + +test_upload_no_admin($t); + +_login($t); + +for my $type ('ldap','sso') { + test_upload_users_nopassword( $type ); + test_upload_users_nopassword( $type, 1 ); +} + +test_upload_users( 'sql',0,1 ); #test with mojo +test_upload_users( 'sql' ); # test without mojo + +test_upload_users( 'ldap', 1 ); # create users in Ravada +test_upload_users( 'ldap', 1, 1 ); # create users in Ravada +for my $type ( 'ldap', 'sso' ) { + test_upload_users( $type, 0 ,1 ); # do not create users in Ravada +} + +end(); +done_testing(); diff --git a/t/request/40_base.t b/t/request/40_base.t index 9c050c4b0..1b783b46f 100644 --- a/t/request/40_base.t +++ b/t/request/40_base.t @@ -172,6 +172,24 @@ sub test_req_create_domain { return $name; } +sub test_req_prepare_base_active($vm) { + my $domain; + if ($vm->type ne 'Void') { + my $base = import_domain($vm); + $domain = $base->clone(user => user_admin, name => new_domain_name); + } else { + $domain = create_domain($vm); + } + $domain->start(user_admin); + my $req = Ravada::Request->prepare_base(uid => user_admin->id + ,id_domain => $domain->id + ); + wait_request(debug => 0); + is($domain->is_active,0); + is($domain->is_base,1); +} + + sub test_req_prepare_base { my $vm_name = shift; my $name = shift; @@ -196,6 +214,10 @@ sub test_req_prepare_base { ok($req->status); ok($domain->is_locked,"Domain $name should be locked when preparing base"); + $req->status('working'); + ok($domain->is_locked,"Domain $name should be locked when preparing base"); + $req->status('requested'); + } wait_request(background => 0); @@ -665,6 +687,8 @@ for my $vm_name ( vm_names ) { my $iso = $vm_connected->_search_iso($ID_ISO); $vm_connected->_iso_name($iso, undef); } + test_req_prepare_base_active($vm_connected); + test_domain_name_iso($vm_connected); test_swap($vm_name); diff --git a/t/storage_list_unused.t b/t/storage_list_unused.t index a6ae69460..ead72aba0 100644 --- a/t/storage_list_unused.t +++ b/t/storage_list_unused.t @@ -14,7 +14,6 @@ use Test::Ravada; no warnings "experimental::signatures"; use feature qw(signatures); -my @FILES; ######################################################################## sub _new_file($vm) { @@ -27,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"; @@ -59,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) { @@ -67,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"; @@ -118,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); } @@ -131,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; @@ -197,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( @@ -235,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) { @@ -300,30 +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"; - } - -} - sub _create_clone($vm) { my $base0 = create_domain($vm); $base0->prepare_base(user_admin); @@ -361,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"); @@ -435,6 +413,247 @@ 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(); + + my $new_dir = "/var/tmp/".$new_name; + remove_dir($new_dir); + + symlink($dir,$new_dir) or die "$dir -> $new_dir"; + + $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; + $pool->refresh(); + } + + 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_filename/, @$list); + is( scalar(@found),1) or die Dumper(\@found); + + $vm->remove_storage_pool($new_name); + unlink $new_dir; + unlink "$dir/$new_filename" or die $!; +} + +sub remove_dir($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 $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(); + my $new_name=new_domain_name(); + + my $new_dir = "/var/tmp/".$new_name; + + remove_dir($new_dir); + mkdir $new_dir or die "$! $new_dir"; + + my $new_link = "/run/user"; + $new_link .= "/$<" if $<; + $new_link .= "/".new_domain_name(); + + 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"; + + 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, $new_link); + + 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_filename$/, @$list); + is( scalar(@found),1); + + $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) { + return if $<; + + my $dir = $vm->dir_img(); + my $new_name = new_domain_name(); + my $new_link = "/".$new_name; + unlink $new_link if -e $new_link; + + symlink($dir,$new_link) or die "$dir -> $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; + $pool->refresh(); + } + + 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_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 $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; + } + } +} + ######################################################################## init(); @@ -454,28 +673,48 @@ 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_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/20_base.t b/t/vm/20_base.t index df26050d6..fe9d12073 100644 --- a/t/vm/20_base.t +++ b/t/vm/20_base.t @@ -300,17 +300,15 @@ sub test_displays_added_on_refresh($domain, $n_expected, $delete=1) { _wait_ip($domain); + my $domain_f0 = Ravada::Front::Domain->open($domain->id); + my $display0_raw = $domain_f0->info(user_admin)->{hardware}->{display}; + + my @display0 = grep { $_->{is_secondary} == 0 } @$display0_raw; + my $n_expected2 = scalar(@display0); + if ($delete) { my $sth = connector->dbh->prepare("DELETE FROM domain_displays WHERE id_domain=?"); $sth->execute($domain->id); - }else { - my $sth = connector->dbh->prepare("SELECT count(*) FROM domain_displays WHERE id_domain=?"); - $sth->execute($domain->id); - my ($n_exp2) = $sth->fetchrow; - if ($n_exp2 && $n_expected != $n_exp2) { - cluck "n_expected was $n_expected, but it should be $n_exp2"; - $n_expected = $n_exp2; - } } my $req; for ( 1 .. 3 ) { @@ -333,7 +331,7 @@ sub test_displays_added_on_refresh($domain, $n_expected, $delete=1) { my $count; for ( 1 .. 10 ) { my $sth_count = connector->dbh->prepare( - "SELECT count(*) FROM domain_displays WHERE id_domain=?"); + "SELECT count(*) FROM domain_displays WHERE id_domain=? AND is_secondary=0"); $sth_count->execute($domain->id); ($count) = $sth_count->fetchrow; last if $count; @@ -344,11 +342,13 @@ sub test_displays_added_on_refresh($domain, $n_expected, $delete=1) { ); wait_request(); } - ok($count>=$n_expected,"Got $count, expecting >$n_expected displays on table domain_displays for ".$domain->name) or confess; + ok($count>=$n_expected || $count >=$n_expected2,"Got $count, expecting >$n_expected or $n_expected2 displays on table domain_displays for ".$domain->name) or confess; my $domain_f = Ravada::Front::Domain->open($domain->id); - my $display = $domain_f->info(user_admin)->{hardware}->{display}; - is(scalar(@$display), $n_expected,"Expecting $n_expected displays on info->{hardware}->{display} in ".$domain->name) or confess Dumper($display); + my $display_raw = $domain_f->info(user_admin)->{hardware}->{display}; + my @display = grep { $_->{is_secondary} == 0 } @$display_raw; + ok(scalar(@display) == $n_expected || scalar(@display) == $n_expected2 + ,"Expecting $n_expected or $n_expected2 displays on info->{hardware}->{display} in ".$domain->name) or confess Dumper(@display); } diff --git a/t/vm/70_clone.t b/t/vm/70_clone.t index d4c58f611..ea4ff0902 100644 --- a/t/vm/70_clone.t +++ b/t/vm/70_clone.t @@ -159,6 +159,7 @@ sub test_many_clones($vm) { ,id_domain => $base->id ); $base->is_public(1); + wait_request(); my $req = Ravada::Request->clone( uid => $user->id ,id_domain => $base->id diff --git a/t/vm/s30_storage.t b/t/vm/s30_storage.t index 2ea8e4b46..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,9 +82,51 @@ 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; } + +sub test_storage_full($vm) { + + my $dir = "/run"; + $dir.="/user/".$< if $<; + my $storage_name = new_domain_name(); + + $dir.= "/".$storage_name; + + mkdir $dir or die "$! $dir" if ! -e $dir; + + if (! grep { $_ eq $storage_name} $vm->list_storage_pools) { + $vm->create_storage_pool($storage_name,$dir); + } + + my($out,$err) = $vm->run_command("df"); + + my ($available) = $out =~ m{(\d+)\s+\d+\% /run}ms; + + $available = $available*10; + + $vm->default_storage_pool_name($storage_name); + + my $name = new_domain_name; + my $req = Ravada::Request->create_domain( + vm => $vm->type + ,id_owner => user_admin->id + ,name => $name + ,disk => $available + ,storage => 'default' + ,id_iso => search_id_iso('Alpine%64') + ); + wait_request(debug => 0); + + my $domain = $vm->search_domain($name); + for my $vol ($domain->list_volumes) { + next if $vol =~ /iso$/; + unlike($vol,qr{^/run}); + } + +} + ########################################################### _clean_local(); @@ -101,6 +143,12 @@ for my $vm_name (vm_names() ) { } skip($msg,10) if !$vm; + + if ($vm->type eq 'KVM') { + remove_qemu_pools($vm); + } + test_storage_full($vm); + test_storage_pools($vm); test_storage_pools_fail($vm); } diff --git a/templates/bootstrap/header.html.ep b/templates/bootstrap/header.html.ep index fc50da478..afcd3049f 100644 --- a/templates/bootstrap/header.html.ep +++ b/templates/bootstrap/header.html.ep @@ -12,7 +12,7 @@ Ravada VDI % if ( !$fallback ) { - + diff --git a/templates/bootstrap/new_user.html.ep b/templates/bootstrap/new_user.html.ep index f030724a0..dd70970d6 100644 --- a/templates/bootstrap/new_user.html.ep +++ b/templates/bootstrap/new_user.html.ep @@ -10,8 +10,12 @@
+

<%=l 'Create a new account' %>

+ <%=l 'Batch upload' %> +
%= include '/ng-templates/new_user'
diff --git a/templates/main/admin_settings.html.ep b/templates/main/admin_settings.html.ep index c737fa445..b44a125f5 100644 --- a/templates/main/admin_settings.html.ep +++ b/templates/main/admin_settings.html.ep @@ -6,7 +6,8 @@ %= include 'bootstrap/navigation'
-
-
+
@@ -71,7 +71,7 @@
-
+
@@ -79,6 +79,47 @@ type="datetime-local">
+ +
+
+
<%=l 'Widget' %> + +
+
+ +
+
+ +
+
Content Security Policy
+
+
+
+
+
+
+
+ + +
+
+
+ % for my $item (sort keys %$csp) { +
+
+
<%= $item %>
+
+ +
+
+ % } +
+ +%= include "/main/admin_settings_submit" +
@@ -211,21 +252,7 @@
-
- -
-
- - -
-
- +%= include "/main/admin_settings_submit"
diff --git a/templates/main/admin_settings_submit.html.ep b/templates/main/admin_settings_submit.html.ep new file mode 100644 index 000000000..faa325bc7 --- /dev/null +++ b/templates/main/admin_settings_submit.html.ep @@ -0,0 +1,13 @@ +
+
+
+ + +
+
diff --git a/templates/main/list_bases_ng.html.ep b/templates/main/list_bases_ng.html.ep index ed12475fe..2571c49e2 100644 --- a/templates/main/list_bases_ng.html.ep +++ b/templates/main/list_bases_ng.html.ep @@ -31,10 +31,13 @@
- {{machine.alias}} - {{machine.alias}} + {{machine.alias}} diff --git a/templates/main/manage_user.html.ep b/templates/main/manage_user.html.ep index 8d784a992..32f167b8a 100644 --- a/templates/main/manage_user.html.ep +++ b/templates/main/manage_user.html.ep @@ -13,7 +13,7 @@ (<%= ($user->external_auth or $origin) %>) % } -% if ($user->external_auth && $user->ldap_entry ) { +% if ($user->external_auth && $user->external_auth eq 'ldap' && $user->ldap_entry ) { <%= $user->ldap_entry->dn %> % }
@@ -53,6 +53,12 @@ +% } +% if ($_user->is_admin) { + + % } @@ -79,9 +85,15 @@
% } % if ( $_user->is_admin && $user->id && $user->is_external && $user->external_auth eq 'ldap' && $user->ldap_entry ) { -
+
%= include '/main/manage_user_groups'
+% } +% if ( $_user->is_admin) { +
+ %= include '/main/manage_user_remove' +
+ % }
diff --git a/templates/main/manage_user_remove.html.ep b/templates/main/manage_user_remove.html.ep new file mode 100644 index 000000000..3274a0eff --- /dev/null +++ b/templates/main/manage_user_remove.html.ep @@ -0,0 +1,20 @@ +
+
+ <%=l 'Are you sure you want to remove this user ?' %> + <%= $user->name %> +
+ +
+ +
+ + <%=l 'Cancel' %> + + <%=l 'Remove' %> + +
diff --git a/templates/main/requirements.html.ep b/templates/main/requirements.html.ep index 54ad4f434..4e7daa59b 100644 --- a/templates/main/requirements.html.ep +++ b/templates/main/requirements.html.ep @@ -24,7 +24,7 @@ <%=l 'To make this possible, copy the content of' %> spice.reg <%=l 'to an ASCII file and save it with a .reg extension, then execute the file.' %>
<%=l 'Please, make sure you have the right path and release, according to your PC configuration.' %>

<%=l 'For more information, check' %> <%=l 'the Windows Clients documentation' %>.


- ©

<%=l 'For Spice client setup follow this ' %> link .

+ ©

<%=l 'Follow these steps for Spice client setup' %> link .

diff --git a/templates/main/standard_message.html.ep b/templates/main/standard_message.html.ep new file mode 100644 index 000000000..23b5fdbdf --- /dev/null +++ b/templates/main/standard_message.html.ep @@ -0,0 +1,24 @@ + + +%= include 'bootstrap/header' + +
+%= include 'bootstrap/navigation' +
+ +
+
+

+ <%= $message %> +

+% if ($link) { + <%= $link->[1] %> +% } +
+
+
+
+%= include $footer +%= include 'bootstrap/scripts' + + diff --git a/templates/main/upload_users.html.ep b/templates/main/upload_users.html.ep new file mode 100644 index 000000000..22cf2d77c --- /dev/null +++ b/templates/main/upload_users.html.ep @@ -0,0 +1,87 @@ + + +%= include '/bootstrap/header' + +
+ %= include '/bootstrap/navigation' +
+ + +
+
+%= include $footer +%= include '/bootstrap/scripts' + + +