Unverified Commit 39911807 authored by Francesc Guasch's avatar Francesc Guasch Committed by GitHub
Browse files

Feature node and network settings (#1321)

feature(frontend): node and network settings

issue #1305
parent e3ac7ce4
......@@ -3768,6 +3768,12 @@ sub _cmd_list_network_interfaces($self, $request) {
$request->output(encode_json(\@ifs));
}
sub _cmd_list_storage_pools($self, $request) {
my $id_vm = $request->args('id_vm');
my $vm = Ravada::VM->open( $id_vm );
$request->output(encode_json([ $vm->list_storage_pools ]));
}
sub _cmd_list_isos($self, $request){
my $vm_type = $request->args('vm_type');
......@@ -4217,6 +4223,8 @@ sub _req_method {
,compact => \&_cmd_compact
,purge => \&_cmd_purge
,list_storage_pools => \&_cmd_list_storage_pools
# Domain ports
,expose => \&_cmd_expose
,remove_expose => \&_cmd_remove_expose
......
......@@ -570,6 +570,48 @@ sub _list_bases_vm($self, $id_node) {
return \@bases;
}
sub _list_bases_vm_all($self, $id_node) {
my $sth = $CONNECTOR->dbh->prepare(
"SELECT d.id, d.name FROM domains d, vms v "
." WHERE is_base=? AND vm=v.vm_type"
." AND d.vm =v.vm_type"
." AND v.id=?"
." ORDER BY d.name "
);
$sth->execute(1, $id_node);
my $sth_bv = $CONNECTOR->dbh->prepare(
"SELECT bv.enabled FROM bases_vm bv, domains d "
." WHERE bv.id_domain=? AND bv.id_vm=?"
." AND d.id = bv.id_domain "
);
my ($id_domain, $name_domain);
$sth->bind_columns(\($id_domain, $name_domain));
my $sth_clones = $CONNECTOR->dbh->prepare(
"SELECT count(*) FROM domain_instances "
." WHERE id_vm=? AND id_domain IN (SELECT id FROM domains WHERE id_base=?) "
);
my @bases;
while ( $sth->fetch ) {
$sth_bv->execute($id_domain, $id_node);
my ($enabled) = $sth_bv->fetchrow;
$sth_clones->execute($id_node,$id_domain);
my ($n_clones) = $sth_clones->fetchrow();
push @bases,{
id => $id_domain
,name => $name_domain
,clones => $n_clones
,enabled => ( $enabled or 0)
};
}
return \@bases;
}
sub _list_machines_vm($self, $id_node) {
my $sth = $CONNECTOR->dbh->prepare(
"SELECT d.id, name FROM domains d"
......@@ -585,6 +627,7 @@ sub _list_machines_vm($self, $id_node) {
return \@bases;
}
=head2 list_iso_images
Returns a reference to a list of the ISO images known by the system
......@@ -627,7 +670,8 @@ sub iso_file ($self, $vm_type) {
$self->wait_request($req);
return [] if $req->status ne 'done';
my $isos = decode_json($req->output());
my $isos = [];
$isos = decode_json($req->output()) if $req->output;
$self->_cache_store("list_isos",$isos);
......@@ -677,6 +721,41 @@ sub list_users($self,$name=undef) {
return \@users;
}
sub list_bases_network($self, $id_network) {
my $sth = $CONNECTOR->dbh->prepare("SELECT * FROM networks where name = 'default'");
$sth->execute;
my $default = $sth->fetchrow_hashref();
$sth->finish;
my $sth_nd = $CONNECTOR->dbh->prepare("SELECT id,allowed,anonymous FROM domains_network"
." WHERE id_domain=? AND id_network=? "
);
$sth = $CONNECTOR->dbh->prepare("SELECT * FROM domains where is_base=1 "
." ORDER BY name");
$sth->execute();
my @bases;
while (my $row = $sth->fetchrow_hashref) {
$row->{anonymous} = ( $default->{anonymous} or 0);
$sth_nd->execute($row->{id}, $id_network);
my ($id,$allowed, $anonymous) = $sth_nd->fetchrow;
$row->{anonymous} = $anonymous if defined $anonymous;
if (defined $allowed) {
$row->{allowed} = $allowed;
} else {
$row->{allowed} = 1;
}
lock_hash(%$row);
push @bases,($row);
}
return \@bases;
}
=head2 create_domain
Request the creation of a new domain or virtual machine
......
......@@ -78,6 +78,7 @@ our %VALID_ARG = (
,hybernate=> {uid => 1, id_domain => 1}
,download => {uid => 2, id_iso => 1, id_vm => 2, verbose => 2, delay => 2, test => 2}
,refresh_storage => { id_vm => 2 }
,list_storage_pools => { id_vm => 1 , uid => 1 }
,check_storage => { uid => 1 }
,set_base_vm=> {uid => 1, id_vm=> 1, id_domain => 1, value => 2 }
,cleanup => { }
......
......@@ -1130,6 +1130,29 @@ sub list_nodes($self) {
return @nodes;
}
=head2 list_bases
Returns a list of domains that are base in this node
=cut
sub list_bases($self) {
my $sth = $$CONNECTOR->dbh->prepare(
"SELECT d.id FROM domains d,bases_vm bv"
." WHERE d.is_base=1"
." AND d.id = bv.id_domain "
." AND bv.id_vm=?"
." AND bv.enabled=1"
);
my @bases;
$sth->execute($self->id);
while ( my ($id_domain) = $sth->fetchrow ) {
push @bases,($id_domain);
}
$sth->finish;
return @bases;
}
=head2 ping
Returns if the virtual manager connection is available
......
......@@ -35,6 +35,7 @@ my %SUB = (
,list_bases_anonymous => \&_list_bases_anonymous
,list_requests => \&_list_requests
,machine_info => \&_get_machine_info
,node_info => \&_get_node_info
,ping_backend => \&_ping_backend
,request => \&_request
);
......@@ -110,6 +111,7 @@ sub _request($rvd, $args) {
my $command_text = $req->command;
$command_text =~ s/_/ /g;
return {command => $req->command, command_text => $command_text
,output => $req->output
,status => $req->status, error => $req->error};
}
......@@ -203,6 +205,21 @@ sub _get_machine_info($rvd, $args) {
return $info;
}
sub _get_node_info($rvd, $args) {
my ($id_node) = $args->{channel} =~ m{/(\d+)};
my $login = $args->{login} or die "Error: no login arg ".Dumper($args);
my $user = Ravada::Auth::SQL->new(name => $login) or die "Error: uknown user $login";
return {} if!$user->is_admin;
my $node = Ravada::VM->open(id => $id_node, readonly => 1);
$node->_data('hostname');
$node->{_data}->{is_local} = $node->is_local;
$node->{_data}->{has_bases} = scalar($node->list_bases);
return $node->{_data};
}
sub _list_recent_requests($rvd, $seconds) {
my @now = localtime(time-$seconds);
$now[4]++;
......
......@@ -357,3 +357,7 @@ div.tab button.active {
border: 1px solid #ccc;
border-top: none;
}
input.ng-invalid {
background-color: pink;
}
......@@ -7,10 +7,36 @@ ravadaApp.directive("solShowMachine", swMach)
.controller("usersPage", usersPageC)
.controller("messagesPage", messagesPageC)
.controller("manage_nodes",manage_nodes)
.controller("manage_networks",manage_networks)
.controller("settings_node",settings_node)
.controller("settings_network",settings_network)
.controller("new_node", newNodeCtrl)
.controller("settings_global", settings_global_ctrl)
;
ravadaApp.directive('ipaddress', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(inputText) {
var ipformat = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[0-1][0-9]|2[0-4])$/;
if(ipformat.test(inputText))
{
ctrl.$setValidity('ipformat', true);
return inputText;
}
else
{
//alert("You have entered an invalid IP address!");
//document.form1.text1.focus();
ctrl.$setValidity('ipformat', false);
return undefined;
}
});
}
};
});
ravadaApp.filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
......@@ -423,7 +449,7 @@ ravadaApp.directive("solShowMachine", swMach)
});
};
$scope.node_remove=function(id) {
$http.get('/node/remove/'+id+'.json');
$http.get('/v1/node/remove/'+id);
$scope.list_nodes();
};
$scope.confirm_disable_node = function(id , n_machines) {
......@@ -475,6 +501,30 @@ ravadaApp.directive("solShowMachine", swMach)
$interval($scope.list_nodes,30 * 1000);
};
function manage_networks($scope, $http, $interval, $timeout) {
list_networks= function() {
$http.get('/list_networks.json').then(function(response) {
for (var i=0; i<response.data.length; i++) {
var item = response.data[i];
$scope.networks[item.id] = item;
}
});
}
$scope.update_network= function(id, field) {
var value = $scope.networks[id][field];
var args = { 'id': id };
args[field] = value;
$http.post('/v1/network/set'
, JSON.stringify( args ))
.then(function(response) {
});
};
$scope.networks={};
list_networks();
}
function newNodeCtrl($scope, $http, $timeout) {
$http.get('/list_vm_types.json').then(function(response) {
$scope.backends = response.data;
......@@ -495,6 +545,25 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.name_duplicated=false;
}
};
$scope.check_duplicated_hostname = function() {
if (typeof($scope.hostname) == 'undefined'
|| typeof($scope.vm_type) == 'undefined'
|| $scope.hostname.length == 0
|| $scope.vm_type.length == 0
) {
$scope.hostname_duplicated = false;
return;
}
$scope.hostname_duplicated = false;
var args = { hostname: $scope.hostname , vm_type: $scope.vm_type };
$http.post("/v1/exists/vms",JSON.stringify(args))
.then(function(response) {
console.log(response.data);
$scope.hostname_duplicated = response.data.id;
});
};
$scope.connect_node = function(backend, address) {
$scope.id_req = undefined;
$scope.request = undefined;
......@@ -517,39 +586,194 @@ ravadaApp.directive("solShowMachine", swMach)
};
};
function settings_global_ctrl($scope, $http) {
$scope.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
$scope.init = function() {
$http.get('/settings_global.json').then(function(response) {
$scope.settings = response.data;
var now = new Date();
if ($scope.settings.frontend.maintenance.value == 0 ) {
$scope.settings.frontend.maintenance_start.value
= new Date(now.getFullYear(), now.getMonth(), now.getDate()
, now.getHours(), now.getMinutes());
$scope.settings.frontend.maintenance_end.value
= new Date(now.getFullYear(), now.getMonth(), now.getDate()
, now.getHours(), now.getMinutes() + 15);
} else {
$scope.settings.frontend.maintenance_start.value
=new Date($scope.settings.frontend.maintenance_start.value);
function settings_network($scope, $http, $timeout) {
var url_ws;
$scope.init = function(id_network) {
if (typeof id_network == 'undefined') {
$scope.network = {
'name': ''
,'all_domains': 1
};
} else {
$scope.load_network(id_network);
$scope.list_domains_network(id_network);
}
}
$scope.check_no_domains = function() {
if ( $scope.network.no_domains == 1 ){
$scope.network.all_domains = 0;
}
};
$scope.check_all_domains = function() {
if ( $scope.network.all_domains == 1 ){
$scope.network.no_domains = 0;
}
};
$scope.update_network= function(field) {
var data = $scope.network;
if (typeof field != 'undefined') {
var data = {};
data[field] = $scope.network[field];
}
$scope.saved = false;
$scope.error = '';
$http.post('/v1/network/set/'
, JSON.stringify(data))
// , JSON.stringify({ value: $scope.network[field]}))
.then(function(response) {
if (response.data.ok == 1){
$scope.saved = true;
if (!$scope.network.id) {
$scope.new_saved = true;
}
}
$scope.error = response.data.error;
});
$scope.formNetwork.$setPristine();
};
$scope.settings.frontend.maintenance_end.value
=new Date($scope.settings.frontend.maintenance_end.value);
}
$scope.load_network = function(id_network) {
$scope.error = '';
$scope.saved = false;
$http.get('/network/info/'+id_network+'.json').then(function(response) {
$scope.network = response.data;
$scope.formNetwork.$setPristine();
$scope.network._old_name = $scope.network.name;
});
};
$scope.list_domains_network = function(id_network) {
$http.get('/network/list_domains/'+id_network).then(function(response) {
$scope.machines = response.data;
});
};
$scope.set_network_domain= function(id_domain, field, allowed) {
$http.get("/network/set/"+$scope.network.id+ "/" + field+ "/" +id_domain+"/"
+allowed)
.then(function(response) {
});
};
$scope.set_domain_public = function( id_domain, is_public) {
$http.get('/machine/set/'+id_domain+'/is_public/'+is_public)
.then(function(response) {
});
};
$scope.remove_network = function(id_network) {
if ($scope.network.name == 'default') {
$scope.error = $scope.network.name + " network can't be removed";
return;
}
$http.get('/v1/network/remove/'+id_network).then(function(response) {
$scope.message = "Network "+$scope.network.name+" removed";
$scope.network ={};
});
};
$scope.check_duplicate = function(field) {
var args = {};
if (typeof ($scope.network['id']) != 'undefined') {
args['id'] = $scope.network['id'];
}
args[field] = $scope.network[field];
$http.post("/v1/exists/networks",JSON.stringify(args))
.then(function(response) {
$scope.network["_duplicated_"+field]=response.data.id;
});
};
$scope.load_settings = function() {
$scope.init();
$scope.formSettings.$setPristine();
$scope.new_saved = false;
};
function settings_node($scope, $http, $timeout) {
var url_ws;
$scope.init = function(id_node, url) {
url_ws = url;
list_storage_pools(id_node);
list_bases(id_node);
subscribe_node_info(id_node, url);
};
subscribe_node_info = function(id_node, url) {
var ws = new WebSocket(url);
ws.onopen = function(event) { ws.send('node_info/'+id_node) };
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
$scope.$apply(function () {
$scope.node = data;
$scope.node._old_name = data.name;
$scope.old_node =$.extend({}, data);
});
}
};
$scope.update_settings = function() {
$scope.formSettings.$setPristine();
console.log($scope.settings);
$http.post('/settings_global'
,JSON.stringify($scope.settings)
$scope.load_node = function() {
$scope.node = $.extend({},$scope.old_node);
$scope.error = '';
};
$scope.update_node = function() {
var data = $scope.node;
$scope.saved = false;
$scope.error = '';
$http.post('/v1/node/set/'
, JSON.stringify(data))
// , JSON.stringify({ value: $scope.network[field]}))
.then(function(response) {
if (response.data.ok == 1){
$scope.saved = true;
}
$scope.error = response.data.error;
console.log($scope.error);
});
$scope.formNode.$setPristine();
};
subscribe_request = function(id_request, action) {
var ws = new WebSocket(url_ws);
ws.onopen = function(event) { ws.send('request/'+id_request) };
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
action(data);
}
};
list_storage_pools = function(id_vm) {
$http.post('/request/list_storage_pools/'
,JSON.stringify({ 'id_vm': id_vm })
).then(function(response) {
if (response.data.ok == 1 ) {
subscribe_request(response.data.request, function(data) {
$scope.$apply(function () {
if (data['output'] && data.output.length) {
$scope.storage_pools=JSON.parse(data.output);
}
});
});
} else {
$scope.storage_pools = response.data.error;
}
});
};
list_bases = function(id_vm) {
$http.get('/node/list_bases/'+id_vm).then(function(response) {
$scope.bases = response.data;
});
};
$scope.set_base_vm = function(id_base, value) {
var url = 'set_base_vm';
if (value == 0 || !value) {
url = 'remove_base_vm';
}
$http.get("/machine/"+url+"/" +$scope.node.id+ "/" +id_base+".json")
.then(function(response) {
});
};
$scope.remove_node = function(id_node) {
$http.get('/v1/node/remove/'+id_node).then(function(response) {
$scope.message = "Node "+$scope.node.name+" removed";
$scope.node={};
});
};
};
......
......@@ -28,6 +28,7 @@ use feature qw(signatures);
use Ravada::Front;
use Ravada::Front::Domain;
use Ravada::Auth;
use Ravada::Network;
use Ravada::WebSocket;
use POSIX qw(locale_h strftime);
......@@ -324,6 +325,117 @@ get '/list_nodes.json' => sub {
$c->render(json => [$RAVADA->list_vms]);
};
get '/list_networks.json' => sub {
my $c = shift;
$c->render(json => [ Ravada::Network->list_networks ]);
};
get '/network/info/#id' => sub($c) {
my $sth = $RAVADA->_dbh->prepare("SELECT * from networks WHERE id=?");
$sth->execute($c->stash('id'));
my $row = $sth->fetchrow_hashref;
return $c->render(json => $row);
};
get '/v1/network/remove/#id' => sub($c) {
my $sth = $RAVADA->_dbh->prepare("DELETE from networks WHERE id=?");
$sth->execute($c->stash('id'));
return $c->render(json => { ok => 1 });
};
get '/network/list_domains/#id' => sub($c) {
return $c->render( json => $RAVADA->list_bases_network($c->stash('id')));
};
any '/network/new' => sub($c) {
push @{$c->stash->{css}}, '/css/admin.css';
push @{$c->stash->{js}}, '/js/admin.js';
return $c->render(template => "/main/network_new");
};
post '/v1/network/set' => sub($c) {
return access_denied($c) if !$USER->is_admin;
my $arg = decode_json($c->req->body);
return _update_fields($c, "networks", $arg);
};
get '/network/set/#id/#field/#id_domain/#value' => sub ($c) {
my $id_network = $c->stash('id');
my $field = $c->stash('field');
my $id_domain = $c->stash('id_domain');
my $value = $c->stash('value');
my $sth = $RAVADA->_dbh->prepare(
"SELECT * "
." FROM domains_network WHERE id_network = ? "
." AND id_domain= ? "
);
$sth->execute($id_network, $id_domain);
my $domain_network = $sth->fetchrow_hashref();
my $id = $domain_network->{id};
return $c->render( json => { ok => 1 } )