1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-23 22:50:09 +03:00

Merge branch 'master' of git.opennebula.org:one

This commit is contained in:
Tino Vazquez 2012-06-22 11:58:30 +02:00
commit 22a8915b1a
28 changed files with 1072 additions and 346 deletions

View File

@ -148,16 +148,11 @@ protected:
map<string, Attribute *>::iterator& it);
/**
* Deletes a quota from the index. The quota pointer is freed.
* @param it The quota iterator, as returned by Quota::get_quota
* Checks if a quota has 0 limit and usage, and deletes it
*
* @param qid id of the quota
*/
virtual void del(map<string, Attribute *>::iterator& it)
{
Attribute * attr = it->second;
delete attr;
attributes.erase(it);
}
void cleanup_quota(const string& qid);
private:
/**

View File

@ -60,6 +60,7 @@ public:
*/
void del(Template* tmpl);
protected:
/**
* Gets a quota, overrides base to not to use ID.
* @param id of the quota, ignored
@ -83,17 +84,10 @@ public:
VectorAttribute **va,
map<string, Attribute *>::iterator& it)
{
it = attributes.end();
it = attributes.begin();
return get_quota(id, va);
}
/**
* Overrides base to not delete anything
* @param it The quota iterator, ignored
*/
void del(map<string, Attribute *>::iterator& it){}
protected:
static const char * VM_METRICS[];
static const int NUM_VM_METRICS;

View File

@ -57,6 +57,20 @@ protected:
int new_uid,
int new_gid,
RequestAttributes& att);
/**
* Checks if the new owner cannot has other object with the same name (if
* the pool does not allow it)
*
* @param oid Object id
* @param noid New owner user id
* @param error_str Error reason, if any
*
* @return 0 if the operation is allowed, -1 otherwise
*/
virtual int check_name_unique(int oid, int noid, string& error_str);
virtual PoolObjectSQL * get(const string& name, int uid, bool lock) = 0;
};
/* ------------------------------------------------------------------------- */
@ -75,6 +89,16 @@ public:
};
~VirtualMachineChown(){};
int check_name_unique(int oid, int noid, string& error_str)
{
return 0;
};
PoolObjectSQL * get(const string& name, int uid, bool lock)
{
return 0;
};
};
/* ------------------------------------------------------------------------- */
@ -86,13 +110,18 @@ public:
TemplateChown():
RequestManagerChown("TemplateChown",
"Changes ownership of a virtual machine template")
{
{
Nebula& nd = Nebula::instance();
pool = nd.get_tpool();
auth_object = PoolObjectSQL::TEMPLATE;
};
~TemplateChown(){};
PoolObjectSQL * get(const string& name, int uid, bool lock)
{
return static_cast<VMTemplatePool*>(pool)->get(name, uid, lock);
};
};
/* ------------------------------------------------------------------------- */
@ -113,6 +142,10 @@ public:
~VirtualNetworkChown(){};
PoolObjectSQL * get(const string& name, int uid, bool lock)
{
return static_cast<VirtualNetworkPool*>(pool)->get(name, uid, lock);
};
};
/* ------------------------------------------------------------------------- */
@ -132,6 +165,10 @@ public:
~ImageChown(){};
PoolObjectSQL * get(const string& name, int uid, bool lock)
{
return static_cast<ImagePool*>(pool)->get(name, uid, lock);
};
};
/* ------------------------------------------------------------------------- */
@ -156,6 +193,11 @@ public:
virtual void request_execute(xmlrpc_c::paramList const& _paramList,
RequestAttributes& att);
PoolObjectSQL * get(const string& name, int uid, bool lock)
{
return 0;
};
};
/* ------------------------------------------------------------------------- */
@ -175,6 +217,10 @@ public:
~DatastoreChown(){};
PoolObjectSQL * get(const string& name, int uid, bool lock)
{
return 0;
};
};
/* ------------------------------------------------------------------------- */
@ -193,6 +239,11 @@ public:
};
~DocumentChown(){};
PoolObjectSQL * get(const string& name, int uid, bool lock)
{
return 0;
};
};
/* -------------------------------------------------------------------------- */

View File

@ -875,7 +875,7 @@ TM_LVM_FILES="src/tm_mad/lvm/clone \
#-------------------------------------------------------------------------------
DATASTORE_DRIVER_COMMON_SCRIPTS="src/datastore_mad/remotes/xpath.rb \
src/datastore_mad/remotes/downloader.rb \
src/datastore_mad/remotes/downloader.sh \
src/datastore_mad/remotes/libfs.sh"
DATASTORE_DRIVER_DUMMY_SCRIPTS="src/datastore_mad/remotes/dummy/cp \

View File

@ -206,9 +206,8 @@ EOT
else
rc = OneHelper.name_to_id(name, pool, poolname)
if rc.first==-1
return -1, "OpenNebula #{poolname} #{name} " <<
"not found, use the ID instead"
if rc.first == -1
return rc[0], rc[1]
end
rc[1]

View File

@ -18,6 +18,8 @@ require 'cli_helper'
class OneQuotaHelper
EDITOR_PATH='/usr/bin/vi'
#---------------------------------------------------------------------------
# Tables to format user quotas
#---------------------------------------------------------------------------

View File

@ -1,286 +0,0 @@
#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'open3'
require 'net/http'
require 'uri'
require 'optparse'
require 'digest'
require 'fileutils'
require 'pp'
class Stream
BLOCK_SIZE=1024*64
TYPES={
"application/x-gzip" => "gunzip -c -",
"application/x-bzip2" => "bunzip2 -c -",
}
POSTPROCESS_TYPE={
"application/x-tar" => "tar -xf #IN# -C #OUT#",
"application/zip" => "unzip -d #OUT# #IN#"
}
DIGEST={
"md5" => lambda { Digest::MD5.new },
"sha1" => lambda { Digest::SHA1.new },
"sha256" => lambda { Digest::SHA2.new(256) },
"sha384" => lambda { Digest::SHA2.new(384) },
"sha512" => lambda { Digest::SHA2.new(512) },
}
def initialize(from, to, options={})
@from=from
@to=to
@options=options
@digests={}
@uncompress_proc=nil
@compr_in=nil
@compr_out=nil
@writer_thread=nil
prepare_digests
end
def open_output_file
if @to=='-'
@output_file=STDOUT.dup
else
begin
@output_file=File.open(@to, "w")
rescue
STDERR.puts "Error opening output file '#{@to}"
exit(-1)
end
end
end
def prepare_digests
@options.each do |key, value|
if DIGEST.has_key?(key)
@digests[key]={
:hash => value,
:digest => DIGEST[key].call
}
end
end
end
def process(data)
begin
try=5
begin
@compr_in.write(data)
@compr_in.flush
rescue
if try>0
try-=1
sleep 0.1
retry
end
end
rescue
STDERR.puts "Error uncompressing image."
exit(-1)
end
@digests.each do |name, algo|
algo[:digest] << data
end
end
def type(header)
io=Open3.popen3("file -b --mime-type -")
io[0].write(header)
io[0].close
out=io[1].read.strip
io[1].close
out
end
def set_compress(header)
t=type(header)
compr=TYPES[t]||"cat"
compr="#{compr} >&#{@output_file.fileno}"
@uncompress_proc=Open3.popen3(compr)
@compr_in=@uncompress_proc[0]
@compr_out=@uncompress_proc[1]
end
def start_file_writer
@writer_thread=Thread.new do
while(!@compr_out.eof?)
data=@compr_out.read(BLOCK_SIZE)
if data
#STDERR.puts "Compr reader #{data.length}"
@output_file.write(data)
#STDERR.puts "File writer #{data.length}"
else
#STDERR.puts "Data is empty!"
end
end
end
end
def wget_downloader(url)
@popen=Open3.popen3("wget -O - '#{url}'")
@popen[0].close
@popen[1]
end
def check_hashes
fail=false
@digests.each do |name, d|
given=d[:hash].downcase
computed=d[:digest].hexdigest.downcase
if given!=computed
fail=true
STDERR.puts "Digest #{name} does not match. "<<
"#{given}!=#{computed}"
end
end
exit(-1) if fail
end
def download
io=nil
begin
case @from
when '-'
io=STDIN
when /^https?:\/\//
io=wget_downloader(@from)
when /^file:\/\/(.*)$/
name=$1
io=open(name, 'r')
else
io=open(@from, 'r')
end
rescue # Errno::ENOENT
STDERR.puts "File not found"
exit(-1)
end
header=io.read(BLOCK_SIZE)
if !header
if @popen
STDERR.puts @popen[2].read.strip.split("\n").last
exit(-1)
end
end
open_output_file
set_compress(header)
download_stderr=""
process(header)
while(!io.eof?)
if defined?(@popen)
@popen[2].read_nonblock(BLOCK_SIZE, download_stderr)
end
data=io.read(BLOCK_SIZE)
process(data)
end
@finished=true
@compr_in.close_write
check_hashes
postprocess if @to!='-'
end
def postprocess
f=open(@to)
header=f.read(BLOCK_SIZE)
f.close
t=type(header)
if POSTPROCESS_TYPE.has_key?(t)
if @to[0,1]=='/'
to=@to
else
to=ENV['PWD']+'/'+@to
end
tmp_file="#{to}.tmp"
FileUtils.mv(to, tmp_file)
FileUtils.mkdir(to)
cmd=POSTPROCESS_TYPE[t]
cmd.gsub!('#IN#', tmp_file)
cmd.gsub!('#OUT#', @to)
system(cmd)
status=$?
if !status.success?
STDERR.puts "Error uncompressing archive"
exit(-1)
end
FileUtils.rm(tmp_file)
end
end
end
options={}
OptionParser.new do |opts|
opts.banner="Usage: download_helper <source> <destination>"
Stream::DIGEST.each do |name, value|
opts.on("--#{name} HASH", "Check #{name} hash") do |v|
options[name]=v
end
end
end.parse!
if ARGV.length<2
STDERR.puts "You need to specify source and destination"
exit(-1)
end
input=ARGV[0]
output=ARGV[1]
s=Stream.new(input, output, options)
s.download

View File

@ -0,0 +1,191 @@
#!/bin/bash
# -------------------------------------------------------------------------- #
# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
# Execute a command (first parameter) and use the first kb of stdout
# to determine the file type
function get_type
{
command=$1
( $command | head -n 1024 | file -b --mime-type - ) 2>/dev/null
}
# Gets the command needed to decompress an stream.
function get_decompressor
{
type=$1
case "$type" in
"application/x-gzip")
echo "gunzip -c -"
;;
"application/x-bzip2")
echo "bunzip2 -c -"
;;
*)
echo "cat"
;;
esac
}
# Function called to decompress a stream. The first parameter is the command
# used to decompress the stream. Second parameter is the output file or
# - for stdout.
function decompress
{
command="$1"
to="$2"
if [ "$to" = "-" ]; then
$command
else
$command > "$to"
fi
}
# Function called to hash a stream. First parameter is the algorithm name.
function hasher
{
algo=$1
if [ -n "$algo" ]; then
openssl dgst -$algo | awk '{print $NF}' > $HASH_FILE
else
# Needs something consuming stdin or the pipe will break
cat >/dev/null
fi
}
# Unarchives a tar or a zip a file to a directpry with the same name.
function unarchive
{
TO="$1"
file_type=$(get_type "cat $TO")
tmp="$TO"
# Add full path if it is relative
if [ ${tmp:0:1} != "/" ]; then
tmp="$PWD/$tmp"
fi
IN="$tmp.tmp"
OUT="$tmp"
case "$file_type" in
"application/x-tar")
command="tar -xf $IN -C $OUT"
;;
"application/zip")
command="unzip -d $OUT $IN"
;;
*)
command=""
;;
esac
if [ -n "$command" ]; then
mv "$OUT" "$IN"
mkdir "$OUT"
$command
if [ "$?" != "0" ]; then
echo "Error uncompressing archive" >&2
exit -1
fi
rm "$IN"
fi
}
TEMP=`getopt -o m:s: -l md5:,sha1: -- "$@"`
if [ $? != 0 ] ; then
echo "Arguments error"
exit -1
fi
eval set -- "$TEMP"
while true; do
case "$1" in
-m|--md5)
HASH_TYPE=md5
HASH=$2
shift 2
;;
-s|--sha1)
HASH_TYPE=sha1
HASH=$2
shift 2
;;
--)
shift
break
;;
*)
shift
;;
esac
done
FROM="$1"
TO="$2"
# File used by the hasher function to store the resulting hash
export HASH_FILE="/tmp/downloader.hash.$$"
case "$FROM" in
http://*|https://*)
# -k so it does not check the certificate
# -L to follow redirects
# -sS to hide output except on failure
command="curl -sS -k -L $FROM"
;;
*)
command="cat $FROM"
;;
esac
file_type=$(get_type "$command")
decompressor=$(get_decompressor "$file_type")
$command | tee >( decompress "$decompressor" "$TO" ) \
>( hasher $HASH_TYPE ) >/dev/null
if [ "$?" != "0" ]; then
echo "Error copying" >&2
exit -1
fi
if [ -n "$HASH_TYPE" ]; then
HASH_RESULT=$( cat $HASH_FILE)
rm $HASH_FILE
if [ "$HASH_RESULT" != "$HASH" ]; then
echo "Hash does not match" >&2
exit -1
fi
fi
# Unarchive only if the destination is filesystem
if [ "$TO" != "-" ]; then
unarchive "$TO"
fi

View File

@ -78,7 +78,7 @@ if [ -n "$SHA1" ]; then
HASHES="$HASHES --sha1 $SHA1"
fi
COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC $DST"
COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC $DST"
# ------------ Copy the image to the repository -------------

View File

@ -101,7 +101,7 @@ if [ -n "$SHA1" ]; then
HASHES="$HASHES --sha1 $SHA1"
fi
COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC -"
COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC -"
case $SRC in
http://*)

View File

@ -90,7 +90,7 @@ if [ -n "$SHA1" ]; then
HASHES="$HASHES --sha1 $SHA1"
fi
COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC -"
COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC -"
case $SRC in
http://*)

View File

@ -78,7 +78,7 @@ if [ -n "$SHA1" ]; then
HASHES="$HASHES --sha1 $SHA1"
fi
COPY_COMMAND="$UTILS_PATH/downloader.rb $HASHES $SRC $DST"
COPY_COMMAND="$UTILS_PATH/downloader.sh $HASHES $SRC $DST"
# ------------ Copy the image to the repository -------------

View File

@ -92,7 +92,16 @@ int ImageManager::acquire_image(Image *img, string& error)
{
case Image::READY:
img->inc_running();
img->set_state(Image::USED);
if ( img->isPersistent() )
{
img->set_state(Image::USED_PERS);
}
else
{
img->set_state(Image::USED);
}
ipool->update(img);
break;
@ -641,15 +650,24 @@ int ImageManager::stat_image(Template* img_tmpl,
break;
case Image::DATABLOCK:
img_tmpl->get("SIZE", res);
img_tmpl->get("PATH", res);
if (res.empty())
if (res.empty())//no PATH
{
res = "SIZE attribute is mandatory for DATABLOCK.";
return -1;
img_tmpl->get("SIZE", res);
if (res.empty())
{
res = "Either SIZE or PATH are mandatory for DATABLOCK.";
return -1;
}
return 0;
}
else
{
img_data << "<IMAGE><PATH>" << res << "</PATH></IMAGE>";
}
return 0;
}
add_request(&sr);

View File

@ -89,16 +89,14 @@ module OpenNebula
if NOKOGIRI
element=@xml.xpath(key.to_s)
if element.size == 0
return nil
end
return nil if element.size == 0
else
element=@xml.elements[key.to_s]
return "" if element && !element.has_text?
end
if element
element.text
end
element.text if element
end
# Delete an element from the xml

View File

@ -28,6 +28,87 @@ module Migrator
def up
oneadmin_row = nil
@db.fetch("SELECT * FROM user_pool WHERE oid = 0") do |row|
oneadmin_row = row
end
if oneadmin_row[:gid] != 0
puts " > Oneadmin user will be moved to the oneadmin group"
# Change user group
doc = Document.new(oneadmin_row[:body])
doc.root.each_element("GID") { |e|
e.text = "0"
}
doc.root.each_element("GNAME") { |e|
e.text = "oneadmin"
}
@db[:user_pool].filter(:oid=>0).delete
@db[:user_pool].insert(
:oid => oneadmin_row[:oid],
:name => oneadmin_row[:name],
:body => doc.root.to_s,
:uid => oneadmin_row[:oid],
:gid => 0,
:owner_u => oneadmin_row[:owner_u],
:group_u => oneadmin_row[:group_u],
:other_u => oneadmin_row[:other_u])
# Remove oneadmin's id from previous group
group_row = nil
@db.fetch("SELECT * FROM group_pool WHERE oid = #{oneadmin_row[:gid]}") do |row|
group_row = row
end
doc = Document.new(group_row[:body])
doc.root.delete_element("USERS/ID[.=0]")
@db[:group_pool].filter(:oid=>group_row[:oid]).delete
@db[:group_pool].insert(
:oid => group_row[:oid],
:name => group_row[:name],
:body => doc.root.to_s,
:uid => group_row[:oid],
:gid => group_row[:gid],
:owner_u => group_row[:owner_u],
:group_u => group_row[:group_u],
:other_u => group_row[:other_u])
# Add oneadmin's id to oneadmin group
@db.fetch("SELECT * FROM group_pool WHERE oid = 0") do |row|
group_row = row
end
doc = Document.new(group_row[:body])
doc.root.get_elements("USERS")[0].add_element("ID").text = "0"
@db[:group_pool].filter(:oid=>group_row[:oid]).delete
@db[:group_pool].insert(
:oid => group_row[:oid],
:name => group_row[:name],
:body => doc.root.to_s,
:uid => group_row[:oid],
:gid => group_row[:gid],
:owner_u => group_row[:owner_u],
:group_u => group_row[:group_u],
:other_u => group_row[:other_u])
end
@db.run "ALTER TABLE datastore_pool RENAME TO old_datastore_pool;"
@db.run "CREATE TABLE datastore_pool (oid INTEGER PRIMARY KEY, name VARCHAR(128), body TEXT, uid INTEGER, gid INTEGER, owner_u INTEGER, group_u INTEGER, other_u INTEGER, UNIQUE(name));"

View File

@ -115,6 +115,41 @@ PoolObjectSQL * RequestManagerChown::get_and_quota(
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int RequestManagerChown::check_name_unique(int oid, int noid, string& error_str)
{
PoolObjectSQL * object;
string name;
int obj_oid;
ostringstream oss;
object = pool->get(oid, true);
name = object->get_name();
object->unlock();
object = get(name, noid, true);
if ( object != 0 )
{
obj_oid = object->get_oid();
object->unlock();
oss << PoolObjectSQL::type_to_str(PoolObjectSQL::USER)
<< " [" << noid << "] already owns "
<< PoolObjectSQL::type_to_str(auth_object) << " ["
<< obj_oid << "] with NAME " << name;
error_str = oss.str();
return -1;
}
return 0;
};
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
void RequestManagerChown::request_execute(xmlrpc_c::paramList const& paramList,
RequestAttributes& att)
{
@ -123,6 +158,7 @@ void RequestManagerChown::request_execute(xmlrpc_c::paramList const& paramList,
int ngid = xmlrpc_c::value_int(paramList.getInt(3));
int rc;
string error_str;
string oname;
string nuname;
@ -194,6 +230,17 @@ void RequestManagerChown::request_execute(xmlrpc_c::paramList const& paramList,
}
}
// --------------- Check name uniqueness -----------------------------------
if ( noid != -1 )
{
if ( check_name_unique(oid, noid, error_str) != 0 )
{
failure_response(INTERNAL, request_error(error_str, ""), att);
return;
}
}
// --------------- Update the object and check quotas ----------------------
if ( auth_object == PoolObjectSQL::VM ||
@ -286,6 +333,21 @@ void UserChown::request_execute(xmlrpc_c::paramList const& paramList,
return;
}
if ( oid == UserPool::ONEADMIN_ID )
{
ostringstream oss;
oss << PoolObjectSQL::type_to_str(PoolObjectSQL::USER)
<< " [" << UserPool::ONEADMIN_ID << "] " << UserPool::oneadmin_name
<< " cannot be moved outside of the "
<< PoolObjectSQL::type_to_str(PoolObjectSQL::GROUP)
<< " [" << GroupPool::ONEADMIN_ID << "] "
<< GroupPool::ONEADMIN_NAME;
failure_response(INTERNAL, request_error(oss.str(), ""), att);
return;
}
if ( att.uid != 0 )
{
AuthRequest ar(att.uid, att.gid);

View File

@ -45,7 +45,8 @@ module OpenNebulaJSON
end
rc = case action_hash['perform']
when "chown" then self.chown(action_hash['params'])
when "chown" then self.chown(action_hash['params'])
when "set_quota" then self.set_quota(action_hash['params'])
else
error_msg = "#{action_hash['perform']} action not " <<
" available for this resource"
@ -56,5 +57,11 @@ module OpenNebulaJSON
def chown(params=Hash.new)
super(params['owner_id'].to_i)
end
def set_quota(params=Hash.new)
quota_json = params['quotas']
quota_template = template_to_str(quota_json)
super(quota_template)
end
end
end

View File

@ -58,6 +58,7 @@ module OpenNebulaJSON
when "chown" then self.chown(action_hash['params'])
when "chmod" then self.chmod_octet(action_hash['params'])
when "chtype" then self.chtype(action_hash['params'])
when "clone" then self.clone(action_hash['params'])
else
error_msg = "#{action_hash['perform']} action not " <<
" available for this resource"
@ -84,5 +85,9 @@ module OpenNebulaJSON
def chtype(params=Hash.new)
super(params['type'])
end
def clone(params=Hash.new)
super(params['name'])
end
end
end

View File

@ -42,6 +42,7 @@ module OpenNebulaJSON
when "chgrp" then self.chgrp(action_hash['params'])
when "chauth" then self.chauth(action_hash['params'])
when "update" then self.update(action_hash['params'])
when "set_quota" then self.set_quota(action_hash['params'])
when "addgroup" then self.addgroup(action_hash['params'])
when "delgroup" then self.delgroup(action_hash['params'])
else
@ -67,6 +68,12 @@ module OpenNebulaJSON
super(params['template_raw'])
end
def set_quota(params=Hash.new)
quota_json = params['quotas']
quota_template = template_to_str(quota_json)
super(quota_template)
end
def addgroup(params=Hash.new)
super(params['group_id'].to_i)
end

View File

@ -663,4 +663,12 @@ ul.action_list li a:hover{
font-family: serif;
text-align:center;
vertical-align:middle;
}
.quota_edit_icon:hover, .quota_remove_icon:hover {
cursor: pointer;
}
div.current_quotas ul{
list-style: none;
}

View File

@ -693,6 +693,13 @@ var OpenNebula = {
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.Group.resource);
},
"set_quota" : function(params){
var action_obj = { quotas : params.data.extra_param };
OpenNebula.Action.simple_action(params,OpenNebula.Group.resource,"set_quota",action_obj);
},
"show" : function(params){
OpenNebula.Action.show(params,OpenNebula.Group.resource);
}
},
@ -736,6 +743,11 @@ var OpenNebula = {
"fetch_template" : function(params){
OpenNebula.Action.show(params,OpenNebula.User.resource,"template");
},
"set_quota" : function(params){
var action_obj = { quotas : params.data.extra_param };
OpenNebula.Action.simple_action(params,OpenNebula.User.resource,"set_quota",action_obj);
},
// "addgroup" : function(params){
// var action_obj = {"group_id": params.data.extra_param };
// OpenNebula.Action.simple_action(params,OpenNebula.User.resource,
@ -804,6 +816,11 @@ var OpenNebula = {
OpenNebula.Image.resource,
"chtype",
action_obj);
},
"clone" : function(params) {
var name = params.data.extra_param ? params.data.extra_param : "";
var action_obj = { "name" : name };
OpenNebula.Action.simple_action(params,OpenNebula.Image.resource, "clone", action_obj);
}
},

View File

@ -17,6 +17,7 @@
var groups_select="";
var dataTable_groups;
var $create_group_dialog;
var $group_quotas_dialog;
var groups_tab_content = '\
<h2>'+tr("Groups")+'</h2>\
@ -59,6 +60,69 @@ var create_group_tmpl =
</fieldset>\
</form>';
var group_quotas_tmpl = '<form id="group_quotas_form" action="">\
<fieldset>\
<div>'+tr("Please add/edit/remove quotas and click on the apply changes button. Note that if several items are selected, changes will be applied to each of them")+'.</div>\
<div>'+tr("Add quota")+':</div>\
<div id="quota_types">\
<label>'+tr("Quota type")+':</label>\
<input type="radio" name="quota_type" value="vm">'+tr("Virtual Machine")+'</input>\
<input type="radio" name="quota_type" value="datastore">'+tr("Datastore")+'</input>\
<input type="radio" name="quota_type" value="image">'+tr("Image")+'</input>\
<input type="radio" name="quota_type" value="network">'+tr("Network")+'</input>\
</div>\
<div id="vm_quota">\
<label>'+tr("Max VMs")+':</label>\
<input type="text" name="VMS"></input><br />\
<label>'+tr("Max Memory (MB)")+':</label>\
<input type="text" name="MEMORY"></input><br />\
<label>'+tr("Max CPU")+':</label>\
<input type="text" name="CPU"></input>\
</div>\
<div id="datastore_quota">\
<label>'+tr("Datastore")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max size (MB)")+':</label>\
<input type="text" name="SIZE"></input><br />\
<label>'+tr("Max images")+':</label>\
<input type="text" name="IMAGES"></input>\
</div>\
<div id="image_quota">\
<label>'+tr("Image")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max RVMs")+'</label>\
<input type="text" name="RVMS"></input>\
</div>\
<div id="network_quota">\
<label>'+tr("Network")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max leases")+'</label>\
<input type="text" name="LEASES"></input>\
</div>\
<button style="width:100px!important;" class="add_remove_button add_button" id="add_quota_button" value="add_quota">'+tr("Add/edit quota")+'</button>\
<div class="clear"></div>\
<div class="clear"></div>\
<div>'+tr("Current quotas")+':</div>\
<div class="current_quotas">\
<label>'+tr("VM quota")+':</label><br />\
<ul id="quotas_ul_vm">\
</ul>\
<label>'+tr("Datastore quotas")+':</label><br />\
<ul id="quotas_ul_datastore">\
</ul>\
<label>'+tr("Image quotas")+':</label><br />\
<ul id="quotas_ul_image">\
</ul>\
<label>'+tr("Network quotas")+':</label><br />\
<ul id="quotas_ul_network">\
</ul>\
</div>\
<div class="form_buttons">\
<button class="button" type="submit" value="Group.set_quota">'+tr("Apply changes")+'</button>\
</div>\
</fieldset>\
</form>';
var group_actions = {
"Group.create" : {
@ -119,6 +183,34 @@ var group_actions = {
// error : onError,
// notify:true
// },
"Group.fetch_quotas" : {
type: "single",
call: OpenNebula.Group.show,
callback: function (request,response) {
var parsed = parseQuotas(response.GROUP);
$('ul#quotas_ul_vm',$group_quotas_dialog).html(parsed.VM)
$('ul#quotas_ul_datastore',$group_quotas_dialog).html(parsed.DATASTORE)
$('ul#quotas_ul_image',$group_quotas_dialog).html(parsed.IMAGE)
$('ul#quotas_ul_network',$group_quotas_dialog).html(parsed.NETWORK)
},
error: onError
},
"Group.quotas_dialog" : {
type: "custom",
call: popUpGroupQuotasDialog
},
"Group.set_quota" : {
type: "multiple",
call: OpenNebula.Group.set_quota,
elements: groupElements,
callback: function() {
notifyMessage(tr("Quotas updated correctly"));
},
error: onError
},
"Group.help" : {
type: "custom",
call: function() {
@ -145,7 +237,10 @@ var group_buttons = {
// tip: "Select the new group owner:",
// condition : True
// },
"Group.quotas_dialog" : {
type : "action",
text : tr("Update quotas")
},
"Group.delete" : {
type: "confirm",
text: tr("Delete")
@ -276,6 +371,19 @@ function popUpCreateGroupDialog(){
return false;
}
function setupGroupQuotasDialog(){
dialogs_context.append('<div title="'+tr("Group quotas")+'" id="group_quotas_dialog"></div>');
$group_quotas_dialog = $('#group_quotas_dialog',dialogs_context);
var dialog = $group_quotas_dialog;
dialog.html(group_quotas_tmpl);
setupQuotasDialog(dialog);
}
function popUpGroupQuotasDialog(){
popUpQuotasDialog($group_quotas_dialog, 'Group', groupElements())
}
//Prepares the autorefresh
function setGroupAutorefresh(){
setInterval(function(){
@ -312,6 +420,7 @@ $(document).ready(function(){
Sunstone.runAction("Group.list");
setupCreateGroupDialog();
setupGroupQuotasDialog();
setGroupAutorefresh();
initCheckAllBoxes(dataTable_groups);

View File

@ -436,6 +436,16 @@ var image_actions = {
error: onError,
notify: true
},
"Image.clone_dialog" : {
type: "custom",
call: popUpImageCloneDialog
},
"Image.clone" : {
type: "single",
call: OpenNebula.Image.clone,
error: onError,
notify: true
},
"Image.help" : {
type: "custom",
call: function() {
@ -496,6 +506,10 @@ var image_buttons = {
}
}
},
"Image.clone_dialog" : {
type: "action",
text: tr("Clone"),
},
"Image.delete" : {
type: "confirm",
text: tr("Delete")
@ -1136,6 +1150,78 @@ function setupImageActionCheckboxes(){
}
function setupImageCloneDialog(){
//Append to DOM
dialogs_context.append('<div id="image_clone_dialog" title="'+tr("Clone an image")+'"></div>');
var dialog = $('#image_clone_dialog',dialogs_context);
//Put HTML in place
var html = '<form><fieldset>\
<div class="clone_one">'+tr("Choose a new name for the image")+':</div>\
<div class="clone_several">'+tr("Several image are selected, please choose prefix to name the new copies")+':</div>\
<br />\
<label class="clone_one">'+tr("Name")+':</label>\
<label class="clone_several">'+tr("Prefix")+':</label>\
<input type="text" name="name"></input>\
<div class="form_buttons">\
<button class="button" id="image_clone_button" value="Image.clone">\
'+tr("Clone")+'\
</button>\
</div></fieldset></form>\
';
dialog.html(html);
//Convert into jQuery
dialog.dialog({
autoOpen:false,
width:375,
modal:true,
resizable:false,
});
$('button',dialog).button();
$('form',dialog).submit(function(){
var name = $('input', this).val();
var sel_elems = imageElements();
if (!name || !sel_elems.length)
notifyError('A name or prefix is needed!');
if (sel_elems.length > 1){
for (var i=0; i< sel_elems.length; i++)
Sunstone.runAction('Image.clone',
sel_elems[i],
name+getImageName(sel_elems[i]));
} else {
Sunstone.runAction('Image.clone',sel_elems[0],name)
};
dialog.dialog('close');
setTimeout(function(){
Sunstone.runAction('Image.refresh');
}, 1500);
return false;
});
}
function popUpImageCloneDialog(){
var dialog = $('#image_clone_dialog');
var sel_elems = imageElements();
//show different text depending on how many elements are selected
if (sel_elems.length > 1){
$('.clone_one',dialog).hide();
$('.clone_several',dialog).show();
$('input',dialog).val('Copy of ');
}
else {
$('.clone_one',dialog).show();
$('.clone_several',dialog).hide();
$('input',dialog).val('Copy of '+getImageName(sel_elems[0]));
};
$(dialog).dialog('open');
}
//The DOM is ready at this point
$(document).ready(function(){
@ -1172,6 +1258,7 @@ $(document).ready(function(){
setupImageTemplateUpdateDialog();
setupTips($create_image_dialog);
setupImageActionCheckboxes();
setupImageCloneDialog();
setImageAutorefresh();
initCheckAllBoxes(dataTable_images);

View File

@ -91,16 +91,18 @@ var market_actions = {
document.getElementById("img_name").value = response['name'];
document.getElementById("img_path").value = response['links']['download']['href'];
$("#custom_var_image_box",$create_image_dialog).empty();
var md5 = response['files'][0]['checksum']['md5']
if ( md5 ) {
option = '<option value=\''+md5+'\' name="MD5">MD5='+md5+'</option>';
$("#custom_var_image_box").append(option);
$("#custom_var_image_box",$create_image_dialog).append(option);
}
var sha1 = response['files'][0]['checksum']['sha1']
if ( sha1 ) {
option = '<option value=\''+sha1+'\' name="SHA1">SHA1='+sha1+'</option>';
$("#custom_var_image_box").append(option);
$("#custom_var_image_box",$create_image_dialog).append(option);
}
popUpCreateImageDialog();
@ -237,6 +239,13 @@ function infoListenerMarket(dataTable){
var id = aData["_id"]["$oid"];
if (!id) return true;
var count = $('tbody .check_item:checked', dataTable).length;
if (e.ctrlKey || count >= 1){
$('.check_item',this).trigger('click');
return false;
}
popDialogLoading();
$.ajax({
@ -263,11 +272,9 @@ function infoListenerMarket(dataTable){
function onlyOneCheckboxListener(dataTable) {
$('tbody input.check_item', dataTable).live("change", function(){
var checked = $('input.check_item:checked', $('tr', dataTable));
var self = this;
checked.each(function(){
if(this!=self) this.checked = ''
})
var checked = $(this).is(':checked');
$('input.check_item:checked', dataTable).removeAttr('checked');
$(this).attr('checked', checked);
});
}

View File

@ -18,6 +18,7 @@
var dataTable_users;
var users_select="";
var $create_user_dialog;
var $user_quotas_dialog;
var $update_pw_dialog;
var users_tab_content = '\
@ -100,6 +101,69 @@ var update_pw_tmpl = '<form id="update_user_pw_form" action="">\
</fieldset>\
</form>';
var user_quotas_tmpl = '<form id="user_quotas_form" action="">\
<fieldset>\
<div>'+tr("Please add/edit/remove quotas and click on the apply changes button. Note that if several items are selected, changes will be applied to each of them")+'.</div>\
<div>'+tr("Add quota")+':</div>\
<div id="quota_types">\
<label>'+tr("Quota type")+':</label>\
<input type="radio" name="quota_type" value="vm">'+tr("Virtual Machine")+'</input>\
<input type="radio" name="quota_type" value="datastore">'+tr("Datastore")+'</input>\
<input type="radio" name="quota_type" value="image">'+tr("Image")+'</input>\
<input type="radio" name="quota_type" value="network">'+tr("Network")+'</input>\
</div>\
<div id="vm_quota">\
<label>'+tr("Max VMs")+':</label>\
<input type="text" name="VMS"></input><br />\
<label>'+tr("Max Memory (MB)")+':</label>\
<input type="text" name="MEMORY"></input><br />\
<label>'+tr("Max CPU")+':</label>\
<input type="text" name="CPU"></input>\
</div>\
<div id="datastore_quota">\
<label>'+tr("Datastore")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max size (MB)")+':</label>\
<input type="text" name="SIZE"></input><br />\
<label>'+tr("Max images")+':</label>\
<input type="text" name="IMAGES"></input>\
</div>\
<div id="image_quota">\
<label>'+tr("Image")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max RVMs")+'</label>\
<input type="text" name="RVMS"></input>\
</div>\
<div id="network_quota">\
<label>'+tr("Network")+'</label>\
<select name="ID"></select><br />\
<label>'+tr("Max leases")+'</label>\
<input type="text" name="LEASES"></input>\
</div>\
<button style="width:100px!important;" class="add_remove_button add_button" id="add_quota_button" value="add_quota">'+tr("Add/edit quota")+'</button>\
<div class="clear"></div>\
<div class="clear"></div>\
<div>'+tr("Current quotas")+':</div>\
<div class="current_quotas">\
<label>'+tr("VM quota")+':</label><br />\
<ul id="quotas_ul_vm">\
</ul>\
<label>'+tr("Datastore quotas")+':</label><br />\
<ul id="quotas_ul_datastore">\
</ul>\
<label>'+tr("Image quotas")+':</label><br />\
<ul id="quotas_ul_image">\
</ul>\
<label>'+tr("Network quotas")+':</label><br />\
<ul id="quotas_ul_network">\
</ul>\
</div>\
<div class="form_buttons">\
<button class="button" type="submit" value="User.set_quota">'+tr("Apply changes")+'</button>\
</div>\
</fieldset>\
</form>';
var user_actions = {
"User.create" : {
@ -254,6 +318,34 @@ var user_actions = {
error: onError
},
"User.fetch_quotas" : {
type: "single",
call: OpenNebula.User.show,
callback: function (request,response) {
var parsed = parseQuotas(response.USER);
$('ul#quotas_ul_vm',$user_quotas_dialog).html(parsed.VM)
$('ul#quotas_ul_datastore',$user_quotas_dialog).html(parsed.DATASTORE)
$('ul#quotas_ul_image',$user_quotas_dialog).html(parsed.IMAGE)
$('ul#quotas_ul_network',$user_quotas_dialog).html(parsed.NETWORK)
},
error: onError
},
"User.quotas_dialog" : {
type: "custom",
call: popUpUserQuotasDialog
},
"User.set_quota" : {
type: "multiple",
call: OpenNebula.User.set_quota,
elements: userElements,
callback: function() {
notifyMessage(tr("Quotas updated correctly"));
},
error: onError
},
"User.help" : {
type: "custom",
call: function() {
@ -283,6 +375,10 @@ var user_buttons = {
type : "action",
text : tr("Change password"),
},
"User.quotas_dialog" : {
type : "action",
text : tr("Update quotas")
},
"User.chgrp" : {
type: "confirm_with_select",
text: tr("Change group"),
@ -331,6 +427,10 @@ var user_info_panel = {
title: tr("User information"),
content:""
},
"user_quotas_tab" : {
title: tr("User quotas"),
content:""
},
};
var users_tab = {
@ -473,7 +573,25 @@ function updateUserInfo(request,user){
'</table>'
};
var quotas_tab = {
title : tr("User quotas"),
content : '\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.DATASTORE_QUOTA)+'</tbody>\
</table>\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.VM_QUOTA)+'</tbody>\
</table>\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.IMAGE_QUOTA)+'</tbody>\
</table>\
<table class="info_table">\
<tbody>'+prettyPrintJSON(user_info.NETWORK_QUOTA)+'</tbody>\
</table>'
};
Sunstone.updateInfoPanelTab("user_info_panel","user_info_tab",info_tab);
Sunstone.updateInfoPanelTab("user_info_panel","user_quotas_tab",quotas_tab);
Sunstone.popUpInfoPanel("user_info_panel");
};
@ -554,8 +672,22 @@ function setupUpdatePasswordDialog(){
});
};
function setupUserQuotasDialog(){
dialogs_context.append('<div title="'+tr("User quotas")+'" id="user_quotas_dialog"></div>');
$user_quotas_dialog = $('#user_quotas_dialog',dialogs_context);
var dialog = $user_quotas_dialog;
dialog.html(user_quotas_tmpl);
setupQuotasDialog(dialog);
}
function popUpUserQuotasDialog(){
popUpQuotasDialog($user_quotas_dialog, 'User', userElements())
}
function popUpCreateUserDialog(){
$create_user_dialog.dialog('open');
}
@ -564,8 +696,6 @@ function popUpUpdatePasswordDialog(){
$update_pw_dialog.dialog('open');
}
// Prepare the autorefresh of the list
function setUserAutorefresh(){
setInterval(function(){
@ -610,6 +740,7 @@ $(document).ready(function(){
setupCreateUserDialog();
setupUpdatePasswordDialog();
setupUserQuotasDialog();
setUserAutorefresh();
initCheckAllBoxes(dataTable_users);

View File

@ -953,4 +953,208 @@ function buildOctet(permTable){
other+=1;
return ""+owner+group+other;
};
};
function setupQuotasDialog(dialog){
var height = Math.floor($(window).height()*0.8); //set height to a percentage of the window
//Prepare jquery dialog
dialog.dialog({
autoOpen: false,
modal:true,
width: 740,
height: height
});
$('button',dialog).button();
$('#vm_quota,#datastore_quota,#image_quota,#network_quota',dialog).hide();
$('#quota_types input',dialog).click(function(){
$('#vm_quota,#datastore_quota,#image_quota,#network_quota',dialog).hide();
$('#'+$(this).val()+'_quota',dialog).show();
$('#add_quota_button',dialog).show();
})
$('#add_quota_button',dialog).hide();
$('#add_quota_button',dialog).click(function(){
var sel = $('#quota_types input:checked',dialog).val();
var fields = $('div#'+sel+'_quota input,div#'+sel+'_quota select',dialog);
var json = {};
for (var i = 0; i < fields.length; i++){
var field = $(fields[i]);
var name = field.attr('name');
var value = field.val();
if (name == 'ID' && !value.length){
notifyError(tr("Please select an element"));
return false;
};
if (!value) value = 0;
json[name] = value;
};
json['TYPE'] = sel.toUpperCase();
var li = quotaListItem(json)
$('ul#quotas_ul_'+sel,dialog).append($(li).hide().fadeIn());
return false;
});
$('form', dialog).submit(function(){
var obj = {};
$('ul li',this).each(function(){
var json = JSON.parse($(this).attr('quota'));
var type = json['TYPE'];
delete json['TYPE'];
obj[type.toUpperCase()] = json;
});
var action = $('div.form_buttons button',this).val();
var sel_elems = SunstoneCfg["actions"][action].elements();
Sunstone.runAction(action,sel_elems,obj);
dialog.dialog('close');
return false;
});
}
function popUpQuotasDialog(dialog, resource, sel_elems){
var im_sel = makeSelectOptions(dataTable_images,1,4,[],[]);
var vn_sel = makeSelectOptions(dataTable_vNetworks,1,4,[],[]);
$('#datastore_quota select',dialog).html(datastores_sel());
$('#image_quota select',dialog).html(im_sel);
$('#network_quota select',dialog).html(vn_sel);
//If only one user is selected we fecth the user's quotas, otherwise we do nothing.
if (sel_elems.length == 1){
var id = sel_elems[0];
Sunstone.runAction(resource + '.fetch_quotas',id);
} else {
$('ul',dialog).empty();
};
dialog.dialog('open');
}
function setupQuotaIcons(){
$('.quota_edit_icon').live('click',function(){
var dialog = $(this).parents('form');
var li = $(this).parents('li');
var quota = JSON.parse(li.attr('quota'));
switch (quota.TYPE){
case "VM":
$('div#vm_quota input[name="VMS"]',dialog).val(quota.VMS);
$('div#vm_quota input[name="MEMORY"]',dialog).val(quota.MEMORY);
$('div#vm_quota input[name="CPU"]',dialog).val(quota.CPU);
break;
case "DATASTORE":
$('div#datastore_quota select[name="ID"]',dialog).val(quota.ID);
$('div#datastore_quota input[name="SIZE"]',dialog).val(quota.SIZE);
$('div#datastore_quota input[name="IMAGES"]').val(quota.IMAGES);
break;
case "IMAGE":
$('div#image_quota select[name="ID"]',dialog).val(quota.ID);
$('div#image_quota input[name="RVMS"]',dialog).val(quota.RVMS);
break;
case "NETWORK":
$('div#network_quota select[name="ID"]',dialog).val(quota.ID);
$('div#network_quota input[name="LEASES"]',dialog).val(quota.LEASES);
break;
}
$('div#quota_types input[value="'+quota.TYPE.toLowerCase()+'"]',dialog).trigger('click');
$(this).parents('li').fadeOut(function(){$(this).remove()});
return false;
});
$('.quota_remove_icon').live('click',function(){
$(this).parents('li').fadeOut(function(){$(this).remove()});
return false;
});
}
function parseQuotas(elem){
var quotas = [];
var results = {
VM : "",
DATASTORE : "",
IMAGE : "",
NETWORK : ""
}
//max 1 vm quota
if (!$.isEmptyObject(elem.VM_QUOTA)){
elem.VM_QUOTA.VM.TYPE = 'VM'
quotas.push(elem.VM_QUOTA.VM)
}
var ds_arr = []
if ($.isArray(elem.DATASTORE_QUOTA.DATASTORE)){
ds_arr = elem.DATASTORE_QUOTA.DATASTORE
} else if (!$.isEmptyObject(elem.DATASTORE_QUOTA)){
ds_arr = [elem.DATASTORE_QUOTA.DATASTORE]
}
for (var i = 0; i < ds_arr.length; i++){
ds_arr[i].TYPE = 'DATASTORE';
quotas.push(ds_arr[i]);
}
var im_arr = []
if ($.isArray(elem.IMAGE_QUOTA.IMAGE)){
im_arr = elem.IMAGE_QUOTA.IMAGE
} else if (!$.isEmptyObject(elem.IMAGE_QUOTA)){
im_arr = [elem.IMAGE_QUOTA.IMAGE]
}
for (var i = 0; i < im_arr.length; i++){
im_arr[i].TYPE = 'IMAGE';
quotas.push(im_arr[i]);
}
var vn_arr = []
if ($.isArray(elem.NETWORK_QUOTA)){
vn_arr = elem.NETWORK_QUOTA.NETWORK
} else if (!$.isEmptyObject(elem.NETWORK_QUOTA)){
vn_arr = [elem.NETWORK_QUOTA.NETWORK]
}
for (var i = 0; i < vn_arr.length; i++){
vn_arr[i].TYPE = 'NETWORK';
quotas.push(vn_arr[i]);
}
for (var i = 0; i < quotas.length; i++){
var li = quotaListItem(quotas[i]);
results[quotas[i].TYPE] += li;
}
return results;
}
//Receives a quota json object. Returns a nice string out of it.
function quotaListItem(quota_json){
var value = JSON.stringify(quota_json)
var str = '<li quota=\''+value+'\'><pre style="margin:0;">';
switch(quota_json.TYPE){
case "VM":
str += 'VMs: ' + quota_json.VMS + (quota_json.VMS_USED ? ' (' + quota_json.VMS_USED + '). ' : ". ") +
'Memory: ' + quota_json.MEMORY + (quota_json.MEMORY_USED ? ' (' + quota_json.MEMORY_USED + '). ' : ". ") +
'CPU: ' + quota_json.CPU + (quota_json.CPU_USED ? ' (' + quota_json.CPU_USED + '). ' : ". ");
break;
case "DATASTORE":
str += 'ID: ' + getDatastoreName(quota_json.ID) + '. ' +
'Size: ' + quota_json.SIZE + (quota_json.SIZE_USED ? ' (' + quota_json.SIZE_USED + '). ' : ". ") +
'Images: ' + quota_json.IMAGES + (quota_json.IMAGES_USED ? ' (' + quota_json.IMAGES_USED + '). ' : ".");
break;
case "IMAGE":
str += 'ID: ' + getImageName(quota_json.ID) + '. ' +
'RVMs: ' + quota_json.RVMS + (quota_json.RVMS_USED ? ' (' + quota_json.RVMS_USED + '). ' : ". ");
break;
case "NETWORK":
str += 'ID: ' + getVNetName(quota_json.ID) + '. ' +
'Leases: ' + quota_json.LEASES + (quota_json.LEASES_USED ? ' (' + quota_json.LEASES_USED + '). ': ". ");
break;
}
str += '<i class="quota_edit_icon icon-pencil"></i> <i class="quota_remove_icon icon-remove"></i></pre></li>';
return str;
}

View File

@ -309,6 +309,11 @@ $(document).ready(function(){
//This dialog is shared to update templates
setupTemplateUpdateDialog();
//Setup quota icons
//Live listeners not working when being added in specific
//context of users/groups dialog. Adding them globally then.
setupQuotaIcons();
//Listen for .action_buttons
//An action buttons runs a predefined action. If it has type
//"multiple" it runs that action on the elements of a datatable.
@ -373,6 +378,8 @@ $(document).ready(function(){
return false;
});
//Start with the dashboard (supposing we have one).
showTab('dashboard_tab');

View File

@ -122,6 +122,8 @@ int Quota::set(vector<Attribute*> * new_quotas, string& error)
goto error_limits;
}
}
cleanup_quota(id);
}
return 0;
@ -271,12 +273,8 @@ void Quota::del_quota(const string& qid, map<string, int>& usage_req)
{
VectorAttribute * q;
map<string, int>::iterator it;
map<string, Attribute *>::iterator q_it;
int limit, limit_tmp;
int usage, usage_tmp;
if ( get_quota(qid, &q, q_it) == -1)
if ( get_quota(qid, &q) == -1)
{
return;
}
@ -286,9 +284,6 @@ void Quota::del_quota(const string& qid, map<string, int>& usage_req)
return;
}
limit = 0;
usage = 0;
for (int i=0; i < num_metrics; i++)
{
string metrics_used = metrics[i];
@ -303,6 +298,41 @@ void Quota::del_quota(const string& qid, map<string, int>& usage_req)
}
add_to_quota(q, metrics_used, -it->second);
}
cleanup_quota(qid);
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
void Quota::cleanup_quota(const string& qid)
{
VectorAttribute * q;
map<string, int>::iterator it;
map<string, Attribute *>::iterator q_it;
int limit, limit_tmp;
int usage, usage_tmp;
if ( get_quota(qid, &q, q_it) == -1)
{
return;
}
if ( q == 0 )
{
return;
}
limit = 0;
usage = 0;
for (int i=0; i < num_metrics; i++)
{
string metrics_used = metrics[i];
metrics_used += "_USED";
q->vector_value(metrics[i], limit_tmp);
q->vector_value(metrics_used.c_str(), usage_tmp);
@ -313,7 +343,9 @@ void Quota::del_quota(const string& qid, map<string, int>& usage_req)
if ( limit == 0 && usage == 0 )
{
del(q_it);
delete static_cast<Attribute *>(q_it->second);
attributes.erase(q_it);
}
}