Commit 7c876141 authored by Kostas Papadimitriou's avatar Kostas Papadimitriou

ui: New projects api integration

- Project model/collection objects
- Display project select widget in vm/network/ip create overlays
- Project reassign view for vm/network/ip resources
- Display project name in vm/network/ip views
parent a8110fa2
......@@ -2537,7 +2537,7 @@ div.list table thead .vmname {
}
.icon .spinner {
margin: 20px 4px 0 15px !important;
margin: 33px 4px 0 15px !important;
}
.single .state .spinner {
......@@ -4110,7 +4110,7 @@ div.console-footer {
.icon .wave {
margin-right: 4px !important;
margin-top: 15px !important;
margin-top: 33px !important;
}
.icon .status {
......@@ -4127,7 +4127,7 @@ div.console-footer {
}
.icon div.action-indicator {
margin-top: 14px;
margin-top: 30px;
margin-right: 4px;
}
......@@ -5233,7 +5233,7 @@ table.list-machines .wave {
}
#network-vms-select-content .empty-list {
font-size: 1.2em;
font-size: 1.2em !important;
}
#network-vms-select-content li.options-object .value {
......@@ -5498,6 +5498,13 @@ table.list-machines .wave {
border-top: 0px;
}
.no-project-notice {
float: right;
font-size: 0.9em;
color: #800;
padding: 10px;
}
.form-action {
float: right;
min-width: 140px;
......@@ -5618,6 +5625,21 @@ table.list-machines .wave {
margin: 15px;
}
.create-vm .step-cont .project-select {
height: 44px;
}
.create-vm .step-cont .project-select select {
width: 100%;
margin-bottom: 10px;
padding: 3px;
background-color: #eee;
color: #444;
border: 1px solid #8FBCD1;
}
.create-vm .step-cont .project-select option {}
.create-vm .create-step-cont {
min-height: 250px;
float: left;
......@@ -6371,7 +6393,7 @@ table.list-machines .wave {
.create-vm .personalize-cont,
.create-vm .confirm-cont {
height: 250px;
height: 290px;
}
.create-vm .image-warning p {
......@@ -6584,7 +6606,7 @@ table.list-machines .wave {
}
.create-vm .network-select {
height: 278px;
height: 310px;
overflow-y: scroll;
padding-right: 10px;
}
......@@ -6793,7 +6815,7 @@ input.has-errors {
.pane-view .collection-list-view .model-view .main-content .state-indicator {
width: 50px;
position: absolute;
top: 30px;
top: 46px;
right: 5px;
}
......@@ -6847,6 +6869,10 @@ input.has-errors {
width: 182px;
}
.ips .model-item .actions-content.inline {
right: -180px;
}
.pane-view .collection-list-view .model-view .main-content .actions-content {
width: 80px;
float: left;
......@@ -6879,6 +6905,8 @@ input.has-errors {
.collection .empty-list {
padding: 10px;
padding-top:6px;
padding-left: 0;
font-size: 0.8em;
color: #333;
}
......@@ -7091,6 +7119,10 @@ input.has-errors {
/* end vm sprites */
/* ips */
#ips-list-view .inline.ports.nested-model-list {
width: 280px;
}
.ip-port-view .title {
width: 100%;
}
......@@ -7559,6 +7591,11 @@ input.has-errors {
cursor: pointer;
}
.select-item.disabled {
background-color: #aaa !important;
color: #eeeeee;
}
.select-item.selected {
background-color: #FF7F2A;
}
......@@ -7644,6 +7681,11 @@ input.has-errors {
margin-top: 3px;
}
#project-select-content .select-item .name {
width: 100% !important;
float: none;
}
.select-item .name {
float: left;
width: 90%;
......@@ -7658,3 +7700,106 @@ input.has-errors {
float: left;
width: 5%;
}
#ips-create-content .form-actions {
margin-top: -23px;
}
.project-name-cont:hover {
color: #444;
}
.project-name-cont {
cursor: pointer;
position: absolute;
top: 40px;
font-weight: normal;
right: 5px;
padding: 3px 6px;
font-size: 11px;
color: #222;
}
.icon .machine-data {
position: relative;
}
.icon .machine-data .project-name-cont {
top: 48px;
}
.main-content-inner {
min-height: 70px;
}
.project.select-item .checkbox {
margin-top: 10px;
}
select .resource-key,
select .resource-value,
.project.select-item.disabled .quota .resource-key,
.project.select-item.disabled .quota .resource-value {
color: #ddd;
}
option.project.select-item.selected {
background-color: transparent;
}
.project.select-item.selected {
background-color: #74AEC9;
}
.project.select-item.selected .quota .resource-key {
color: #fff;
}
.project.select-item .quota .resource-key {
color: #444;
margin-right: 5px;
}
.project.select-item .quota .resource-value {
margin-right: 20px;
display: inline-block;
}
.project.select-item .quota .resource-value {
}
.project.select-item {
padding: 5px 0;
position: relative;
}
.project.select-item .quota {
margin-top: 5px;
font-size: 0.9em;
}
.project.select-item .current {
position: absolute;
right: 5px;
top: 2px;
font-size: 0.9em;
padding: 4px;
display: none;
color: #444;
}
.project.select-item.selected .current {
background-color: #74AEC9;
color: #FFF;
}
.model-item.current .project.select-item .current {
display: block;
}
.project.select-item.current {
background-color: #C7DEE9;
}
......@@ -115,11 +115,16 @@ _.extend(rivets.binders, {
var specs = this.options.formatters[0].split(",");
var cls_name = specs[0];
var params = specs[1];
if (params && this.view.models &&
this.view.models.view &&
this.view.models.view[params]) {
params = this.view.models.view[params](this, specs);
} else {
if (params) { params = JSON.parse(params) }
}
var view_cls = synnefo.views[cls_name];
var view_params = {collection: value};
if (params) {
_.extend(view_params, JSON.parse(params));
}
if (params) { _.extend(view_params, params); }
var view = this.view.models.view.create_view(view_cls, view_params);
this.view.models.view.add_subview(view);
view.show(true);
......
// Copyright 2014 GRNET S.A. All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
// conditions are met:
//
// 1. Redistributions of source code must retain the above
// copyright notice, this list of conditions and the following
// disclaimer.
//
// 2. Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and
// documentation are those of the authors and should not be
// interpreted as representing official policies, either expressed
// or implied, of GRNET S.A.
//
;(function(root){
// Neutron api models, collections, helpers
......@@ -60,6 +94,14 @@
models.Network = models.NetworkModel.extend({
path: 'networks',
url: function(options, method) {
var url = models.Network.__super__.url.call(this, method, options);
if (options.data && options.data.reassign) {
return url + '/action';
}
return url;
},
parse: function(obj) {
return obj.network;
},
......@@ -83,6 +125,7 @@
this.actions.reset_pending();
this.destroy({
success: _.bind(function() {
synnefo.api.trigger("quotas:call", 10);
this.set({status: 'REMOVING'});
this.set({ext_status: 'REMOVING'});
// force status display update
......@@ -140,11 +183,13 @@
'subnets': ['subnets', 'subnet', function(model, attr) {
var subnets = model.get(attr);
if (subnets && subnets.length) { return subnets[0] }
}]
}],
'tenant_id': ['projects', 'project']
},
// call rename api
rename: function(new_name, cb) {
var self = this;
this.sync("update", this, {
critical: true,
data: {
......@@ -156,6 +201,7 @@
success: _.bind(function(){
//this.set({name: new_name});
snf.api.trigger("call");
self.set({name: new_name});
}, this),
complete: cb || function() {}
});
......@@ -244,7 +290,25 @@
this.pending_connections++;
this.update_connecting_status();
synnefo.storage.ports.create(data, {complete: cb});
}
},
reassign_to_project: function(project, success, cb) {
var project_id = project.id ? project.id : project;
var self = this;
var _success = function() {
success();
self.set({'tenant_id': project_id});
}
synnefo.api.sync('create', this, {
success: _success,
complete: cb,
data: {
reassign: {
project: project_id
}
}
});
},
});
models.CombinedPublicNetwork = models.Network.extend({
......@@ -304,7 +368,7 @@
},
get_floating_ips_network: function() {
return this.filter(function(n) { return n.get('is_public')})[1]
return this.filter(function(n) { return n.get('is_public') })[1]
},
create_subnet: function(subnet_params, complete, error) {
......@@ -314,18 +378,20 @@
});
},
create: function (name, type, cidr, dhcp, callback) {
create: function (project, name, type, cidr, dhcp, callback) {
var quota = synnefo.storage.quotas;
var params = {network:{name:name}};
var subnet_params = {subnet:{network_id:undefined}};
if (!type) { throw "Network type cannot be empty"; }
params.network.type = type;
params.network.project = project.id;
if (cidr) { subnet_params.subnet.cidr = cidr; }
if (dhcp) { subnet_params.subnet.dhcp_enabled = dhcp; }
if (dhcp === false) { subnet_params.subnet.dhcp_enabled = false; }
var cb = function() {
synnefo.api.trigger("quotas:call");
callback && callback();
}
......@@ -350,7 +416,7 @@
created_network.destroy({no_skip: true});
});
}
quota.get('cyclades.network.private').increase();
project.quotas.get('cyclades.network.private').increase();
}
return this.api_call(this.path, "create", params, complete, error, success);
}
......@@ -502,12 +568,21 @@
models.FloatingIP = models.NetworkModel.extend({
path: 'floatingips',
url: function(options, method) {
var url = models.FloatingIP.__super__.url.call(this, method, options);
if (options.data && options.data.reassign) {
return url + '/action';
}
return url;
},
parse: function(obj) {
return obj.floatingip;
},
storage_attrs: {
'tenant_id': ['projects', 'project'],
'port_id': ['ports', 'port'],
'floating_network_id': ['networks', 'network'],
},
......@@ -531,11 +606,30 @@
}]
},
reassign_to_project: function(project, success, cb) {
var project_id = project.id ? project.id : project;
var self = this;
var _success = function() {
success();
self.set({'tenant_id': project_id});
}
synnefo.api.sync('create', this, {
success: _success,
complete: cb,
data: {
reassign: {
project: project_id
}
}
});
},
do_remove: function(succ, err) { return this.do_destroy(succ, err) },
do_destroy: function(succ, err) {
this.actions.reset_pending();
this.destroy({
success: _.bind(function() {
synnefo.api.trigger("quotas:call", 10);
this.set({status: 'REMOVING'});
succ && succ();
}, this),
......
// Copyright 2011 GRNET S.A. All rights reserved.
// Copyright 2014 GRNET S.A. All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
......@@ -536,6 +536,16 @@
api.STATES = { NORMAL:1, WARN:0, ERROR:-1 };
api.error_state = api.STATES.NORMAL;
api.bind("quota:update", function(delay) {
if (delay == undefined) {
delay = 0
}
window.setTimeout(function() {
synnefo.storage.quotas.fetch({refresh: true});
}, delay);
});
// on api error update the api error_state
api.bind("error", function() {
if (snf.api.error_state == snf.api.STATES.ERROR) { return };
......
......@@ -62,12 +62,16 @@
this.error = this.vm_view.find(".action-error");
this.close = this.vm_view.find(".close-action-error");
this.show_btn = this.vm_view.find(".show-action-error");
this.project_view = this.vm_view.find(".project-name");
this.init_handlers();
this.update_layout();
},
init_handlers: function() {
this.project_view.bind('click', _.bind(function() {
synnefo.ui.main.vm_reassign_view.show(this.vm);
}, this));
// action call failed notify the user
this.vm.bind("action:fail", _.bind(function(args){
if (this.vm.action_error) {
......@@ -102,6 +106,10 @@
}, this));
},
show_reassign_view: function(vm) {
synnefo.ui.main.reassign_view.show(vm);
},
show_error_overlay: function(args) {
var args = util.parse_api_error.apply(util, args);
......@@ -825,6 +833,10 @@
// update vm details
update_details: function(vm) {
var el = this.vm(vm);
var project = vm.get('project')
if (project) {
el.find(".project-name").text(_.truncate(project.get('name'), 20));
}
// truncate name
el.find("span.name").text(util.truncate(vm.get("name"), 40));
......
// Copyright 2011 GRNET S.A. All rights reserved.
// Copyright 2014 GRNET S.A. All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
......@@ -41,6 +41,7 @@
var snf = root.synnefo = root.synnefo || {};
var views = snf.views = snf.views || {}
var storage = snf.storage = snf.storage || {};
var util = snf.util = snf.util || {};
views.IpPortView = views.ext.ModelView.extend({
tpl: '#ip-port-view-tpl',
......@@ -119,6 +120,10 @@
tpl: '#ip-view-tpl',
auto_bind: ['connect_vm'],
show_reassign_view: function() {
synnefo.ui.main.ip_reassign_view.show(this.model);
},
status_cls: function() {
return this.status_cls_map[this.model.get('status')];
},
......@@ -127,6 +132,10 @@
return this.status_map[this.model.get('status')];
},
show_reassign_view: function() {
synnefo.ui.main.ip_reassign_view.show(this.model);
},
model_icon: function() {
var img = 'ip-icon-detached.png';
var src = synnefo.config.images_url + '/{0}';
......@@ -177,47 +186,118 @@
}
});
views.FloatingIPCreateView = views.Overlay.extend({
view_id: "ip_create_view",
content_selector: "#ips-create-content",
css_class: 'overlay-ip-create overlay-info',
overlay_id: "ip-create-overlay",
title: "Create new IP address",
subtitle: "IP addresses",
initialize: function(options) {
views.FloatingIPCreateView.__super__.initialize.apply(this);
this.create_button = this.$("form .form-action.create");
this.form = this.$("form");
this.project_select = this.$(".project-select");
this.init_handlers();
},
init_handlers: function() {
this.create_button.click(_.bind(function(e){
this.submit();
}, this));
this.form.submit(_.bind(function(e){
e.preventDefault();
this.submit();
return false;
}, this))
},
submit: function() {
if (this.validate()) {
this.create();
};
},
validate: function() {
var project = this.get_project();
if (!project || !project.quotas.can_fit({'cyclades.floating_ip': 1})) {
this.project_select.closest(".form-field").addClass("error");
this.project_select.focus();
return false;
}