1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-04-01 06:50:25 +03:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Hector Sanjuan 2012-03-29 11:11:13 +02:00
commit cb40cc21ee
36 changed files with 683 additions and 104 deletions

View File

@ -1011,6 +1011,7 @@ CLOUD_AUTH_LIB_FILES="src/cloud/common/CloudAuth/OCCICloudAuth.rb \
ECO_LIB_FILES="src/cloud/ec2/lib/EC2QueryClient.rb \
src/cloud/ec2/lib/EC2QueryServer.rb \
src/cloud/ec2/lib/ImageEC2.rb \
src/cloud/ec2/lib/elastic_ip.rb \
src/cloud/ec2/lib/econe-server.rb"
ECO_LIB_CLIENT_FILES="src/cloud/ec2/lib/EC2QueryClient.rb"
@ -1019,6 +1020,11 @@ ECO_LIB_VIEW_FILES="src/cloud/ec2/lib/views/describe_images.erb \
src/cloud/ec2/lib/views/describe_instances.erb \
src/cloud/ec2/lib/views/register_image.erb \
src/cloud/ec2/lib/views/run_instances.erb \
src/cloud/ec2/lib/views/allocate_address.erb \
src/cloud/ec2/lib/views/associate_address.erb \
src/cloud/ec2/lib/views/disassociate_address.erb \
src/cloud/ec2/lib/views/describe_addresses.erb \
src/cloud/ec2/lib/views/release_address.erb \
src/cloud/ec2/lib/views/terminate_instances.erb"
ECO_BIN_FILES="src/cloud/ec2/bin/econe-server \

View File

@ -17,6 +17,8 @@
require 'sequel'
require 'base64'
require 'yaml'
require 'uri'
require 'net/http'
class Quota
###########################################################################
@ -82,7 +84,15 @@ class Quota
if template['TYPE'] == 'DATABLOCK'
template['SIZE'].to_i
elsif template['PATH']
(File.size(template['PATH']).to_f / 2**20).round
uri = URI.parse(template['PATH'])
size = if uri.scheme.nil?
File.size(template['PATH'])
else
Net::HTTP.start(uri.host,uri.port) { |http|
http.head(uri.path)
}.content_length
end
(size.to_f / 2**20).round
elsif template['SAVED_VM_ID']
vm_id = template['SAVED_VM_ID'].to_i
disk_id = template['SAVED_DISK_ID'].to_i

View File

@ -37,9 +37,20 @@
# 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG
:debug_level: 3
#
#
#
:cluster_id:
:datastore_id:
#
#
#
:elasticips_vnet_id: 0
:associate_script: /usr/bin/false
:disassociate_script: /usr/bin/false
# VM types allowed and its template file (inside templates directory)
:instance_types:
:m1.small:

View File

@ -61,10 +61,18 @@ class EC2QueryServer < CloudServer
###########################################################################
def initialize(client, config, logger)
def initialize(client, oneadmin_client, config, logger)
super(config, logger)
@client = client
@oneadmin_client = oneadmin_client
if @config[:elasticips_vnet_id].nil?
logger.error { 'ElasticIP module not loaded' }
else
require 'elastic_ip'
extend ElasticIP
end
end
###########################################################################
@ -207,9 +215,34 @@ class EC2QueryServer < CloudServer
return response.result(binding), 200
end
###########################################################################
# Elastic IP
###########################################################################
def allocate_address(params)
return OpenNebula::Error.new('Unsupported'),400
end
def release_address(params)
return OpenNebula::Error.new('Unsupported'),400
end
def describe_addresses(params)
return OpenNebula::Error.new('Unsupported'),400
end
def associate_address(params)
return OpenNebula::Error.new('Unsupported'),400
end
def disassociate_address(params)
return OpenNebula::Error.new('Unsupported'),400
end
###########################################################################
# Helper functions
###########################################################################
private
def render_state(vm)
one_state = ONE_STATES[vm.status]
ec2_state = EC2_STATES[one_state||:pending]

View File

@ -135,8 +135,9 @@ before do
if username.nil?
error 401, error_xml("AuthFailure", 0)
else
client = settings.cloud_auth.client(username)
@econe_server = EC2QueryServer.new(client, settings.config, settings.logger)
client = settings.cloud_auth.client(username)
oneadmin_client = settings.cloud_auth.client
@econe_server = EC2QueryServer.new(client, oneadmin_client, settings.config, settings.logger)
end
end
@ -189,6 +190,16 @@ def do_http_request(params)
result,rc = @econe_server.describe_instances(params)
when 'TerminateInstances'
result,rc = @econe_server.terminate_instances(params)
when 'AllocateAddress'
result,rc = @econe_server.allocate_address(params)
when 'AssociateAddress'
result,rc = @econe_server.associate_address(params)
when 'DisassociateAddress'
result,rc = @econe_server.disassociate_address(params)
when 'ReleaseAddress'
result,rc = @econe_server.release_address(params)
when 'DescribeAddresses'
result,rc = @econe_server.describe_addresses(params)
end
if OpenNebula::is_error?(result)

View File

@ -0,0 +1,198 @@
module ElasticIP
def allocate_address(params)
# Get public IP
vnet = retrieve_eip_vnet
return vnet, 400 if OpenNebula::is_error?(vnet)
ips = vnet.retrieve_elements('LEASES/LEASE[USED=0]/IP')
if ips.nil?
logger.error { "There is no lease available to be allocated" }
return OpenNebula::Error.new('AddressLimitExceeded'), 400
end
eip = ips.first
# Hold IP
rc = vnet.hold(eip)
if OpenNebula::is_error?(rc)
logger.error rc.message
return OpenNebula::Error.new('Unsupported'),400
end
# Update EC2_ADDRESSES list
xml_hash = {'EC2_ADDRESSES' => {'IP' => eip, "UID" => retrieve_uid}}
vnet.add_element('TEMPLATE', xml_hash)
rc = vnet.update
if OpenNebula::is_error?(rc)
logger.error rc.message
return OpenNebula::Error.new('Unsupported'),400
end
response = ERB.new(File.read(@config[:views]+"/allocate_address.erb"))
return response.result(binding), 200
end
def release_address(params)
# Check public IP
vnet = retrieve_eip_vnet
return vnet, 400 if OpenNebula::is_error?(vnet)
eip = params["PublicIp"]
unless vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\" and UID=\"#{retrieve_uid}\"]/IP"]
logger.error { "address:#{eip} does not exist" }
return OpenNebula::Error.new('Unsupported'),400
end
# Disassociate address if needed
if vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID"]
cmd_output = `#{@config[:disassociate_script]} #{eip}`
if $?.to_i != 0
logger.error { cmd_output }
return OpenNebula::Error.new('Unsupported'),400
end
vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID")
end
# Release IP
rc = vnet.release(eip)
if OpenNebula::is_error?(rc)
logger.error {rc.message}
return OpenNebula::Error.new('Unsupported'),400
end
# Update EC2_ADDRESSES list
vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]")
rc = vnet.update
if OpenNebula::is_error?(rc)
logger.error {rc.message}
return OpenNebula::Error.new('Unsupported'),400
end
response = ERB.new(File.read(@config[:views]+"/release_address.erb"))
return response.result(binding), 200
end
def describe_addresses(params)
vnet = retrieve_eip_vnet
return vnet, 400 if OpenNebula::is_error?(vnet)
erb_version = params['Version']
user_id = retrieve_uid
response = ERB.new(File.read(@config[:views]+"/describe_addresses.erb"))
return response.result(binding), 200
end
def associate_address(params)
# Check public IP
vnet = retrieve_eip_vnet
return vnet, 400 if OpenNebula::is_error?(vnet)
user_id = retrieve_uid
eip = params["PublicIp"]
vmid = params['InstanceId']
vmid = vmid.split('-')[1] if vmid[0]==?i
unless vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\" and UID=\"#{retrieve_uid}\"]/IP"]
logger.error { "address:#{eip} does not exist" }
return OpenNebula::Error.new('Unsupported'),400
end
# Get private IP of the Instance
vm = VirtualMachine.new(VirtualMachine.build_xml(vmid), @client)
rc = vm.info
if OpenNebula::is_error?(rc)
logger.error {rc.message}
return OpenNebula::Error.new('Unsupported'),400
end
ips = vm.retrieve_elements('TEMPLATE/NIC/IP')
if ips.nil?
logger.error { "The instance does not have any NIC" }
return OpenNebula::Error.new('Unsupported'),400
end
private_ip = ips.first
# Disassociate address if needed
if vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID"]
cmd_output = `#{@config[:disassociate_script]} #{eip}`
if $?.to_i != 0
logger.error { cmd_output }
return OpenNebula::Error.new('Unsupported'),400
end
vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID")
end
# Run external script
vnet_base64 = Base64.encode64(vnet.to_xml).delete("\n")
cmd_output = `#{@config[:associate_script]} #{eip} #{private_ip} \"#{vnet_base64}\"`
if $?.to_i != 0
logger.error { "associate_script" << cmd_output }
return OpenNebula::Error.new('Unsupported'),400
end
# Update EC2_ADDRESSES list
vnet.add_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]", "VMID" => vmid)
rc = vnet.update
if OpenNebula::is_error?(rc)
logger.error {rc.message}
return OpenNebula::Error.new('Unsupported'),400
end
response = ERB.new(File.read(@config[:views]+"/associate_address.erb"))
return response.result(binding), 200
end
def disassociate_address(params)
# Check public IP
vnet = retrieve_eip_vnet
return vnet, 400 if OpenNebula::is_error?(vnet)
eip = params["PublicIp"]
unless vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\" and UID=\"#{retrieve_uid}\"]/VMID"]
logger.error { "address:#{eip} does not exist or is not associated with any instance" }
return OpenNebula::Error.new('Unsupported'),400
end
# Run external script
cmd_output = `#{@config[:disassociate_script]} #{eip}`
if $?.to_i != 0
logger.error { cmd_output }
return OpenNebula::Error.new('Unsupported'),400
end
# Update EC2_ADDRESSES list
vnet.delete_element("TEMPLATE/EC2_ADDRESSES[IP=\"#{eip}\"]/VMID")
rc = vnet.update
if OpenNebula::is_error?(rc)
logger.error {rc.message}
return OpenNebula::Error.new('Unsupported'),400
end
response = ERB.new(File.read(@config[:views]+"/disassociate_address.erb"))
return response.result(binding), 200
end
private
def retrieve_eip_vnet
vnet = VirtualNetwork.new(VirtualNetwork.build_xml(@config[:elasticips_vnet_id]),@oneadmin_client)
rc = vnet.info
if OpenNebula::is_error?(rc)
logger.error {rc.message}
return OpenNebula::Error.new('Unsupported')
end
vnet
end
def retrieve_uid
user = User.new_with_id(OpenNebula::User::SELF, @client)
user.info
user.id
end
end

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/<%= params['Version'] %>/">
<requestId>4ac62eaf-e266-4058-a970-2c01568cd417</requestId>
<publicIp><%= eip %></publicIp>
<domain>standard</domain>
<allocationId>9090909090</allocationId>
</AllocateAddressResponse>

View File

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<AssociateAddressResponse xmlns="http://ec2.amazonaws.com/doc/<%= params['Version'] %>/">
<requestId>4ac62eaf-e266-4058-a970-2c01568cd417</requestId>
<return>true</return>
<associationId/>
</AssociateAddressResponse>

View File

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<DescribeAddressesResponse xmlns="http://ec2.amazonaws.com/doc/<%= erb_version %>/">
<requestId>4ac62eaf-e266-4058-a970-2c01568cd417</requestId>
<addressesSet>
<% vnet.each("LEASES/LEASE[USED=1 and VID=-1]") do |eip| %>
<% if vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip["IP"]}\"]/UID"] == user_id.to_s %>
<item>
<publicIp><%= eip["IP"] %></publicIp>
<domain>standard</domain>
<% if vm_id = vnet["TEMPLATE/EC2_ADDRESSES[IP=\"#{eip["IP"]}\"]/VMID"] %>
<instanceId><%= vm_id %></instanceId>
<% else %>
<instanceId/>
<% end %>
<associationId/>
<allocationId/>
</item>
<% end %>
<% end %>
</addressesSet>
</DescribeAddressesResponse>

View File

@ -2,7 +2,6 @@
<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/<%=erb_version%>/">
<imagesSet>
<% impool.each do |im| %>
<% im.info %>
<item>
<imageId>ami-<%= sprintf('%08i', im.id) %></imageId>
<imageLocation><%= im['SOURCE'].split('/').last %></imageLocation>

View File

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<ReleaseAddressResponse xmlns="http://ec2.amazonaws.com/doc/<%= params['Version'] %>/">
<requestId>4ac62eaf-e266-4058-a970-2c01568cd417</requestId>
<return>true</return>
</ReleaseAddressResponse>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<ReleaseAddressResponse xmlns="http://ec2.amazonaws.com/doc/<%= params['Version'] %>/">
<requestId>4ac62eaf-e266-4058-a970-2c01568cd417</requestId>
<return>true</return>
</ReleaseAddressResponse>

View File

@ -161,7 +161,7 @@ var OCCI = {
});
},
"delete": function(params,resource){
"del": function(params,resource){
var callback = params.success;
var callback_error = params.error;
var id = params.data.id;
@ -373,8 +373,8 @@ var OCCI = {
"create": function(params){
OCCI.Action.create(params,OCCI.Network.resource);
},
"delete": function(params){
OCCI.Action.delete(params,OCCI.Network.resource);
"del": function(params){
OCCI.Action.del(params,OCCI.Network.resource);
},
"list": function(params){
OCCI.Action.list(params,OCCI.Network.resource);
@ -398,8 +398,8 @@ var OCCI = {
"create": function(params){
OCCI.Action.create(params,OCCI.VM.resource);
},
"delete": function(params){
OCCI.Action.delete(params,OCCI.VM.resource);
"del": function(params){
OCCI.Action.del(params,OCCI.VM.resource);
},
"list": function(params){
OCCI.Action.list(params,OCCI.VM.resource);
@ -502,8 +502,8 @@ var OCCI = {
}
});
},
"delete": function(params){
OCCI.Action.delete(params,OCCI.Image.resource);
"del": function(params){
OCCI.Action.del(params,OCCI.Image.resource);
},
"list": function(params){
OCCI.Action.list(params,OCCI.Image.resource);
@ -535,8 +535,8 @@ var OCCI = {
"create" : function(params){
OCCI.Action.create(params,OCCI.Template.resource);
},
"delete" : function(params){
OCCI.Action.delete(params,OCCI.Template.resource);
"del" : function(params){
OCCI.Action.del(params,OCCI.Template.resource);
},
"list" : function(params){
OCCI.Action.list(params,OCCI.Template.resource);

View File

@ -143,7 +143,7 @@ var vnet_actions = {
"Network.delete" : {
type: "multiple",
call: OCCI.Network.delete,
call: OCCI.Network.del,
callback: deleteVNetworkElement,
elements: vnElements,
error: onError,

View File

@ -199,7 +199,7 @@ var image_actions = {
"Image.delete" : {
type: "multiple",
call: OCCI.Image.delete,
call: OCCI.Image.del,
callback: deleteImageElement,
elements: imageElements,
error: onError,

View File

@ -178,6 +178,8 @@ module OpenNebula
def update(xml_method, new_template)
return Error.new('ID not defined') if !@pe_id
new_template ||= template_xml.delete("\n").gsub(/\s+/m,'')
rc = @client.call(xml_method,@pe_id, new_template)
rc = nil if !OpenNebula.is_error?(rc)

View File

@ -90,7 +90,7 @@ module OpenNebula
# Replaces the template contents
#
# +new_template+ New template contents
def update(new_template)
def update(new_template=nil)
super(VN_METHODS[:update], new_template)
end

View File

@ -101,6 +101,44 @@ module OpenNebula
end
end
def delete_element(xpath)
if NOKOGIRI
@xml.xpath(xpath.to_s).remove
else
@xml.delete_element(xpath.to_s)
end
end
def add_element(xpath, elems)
elems.each { |key, value|
if value.instance_of?(Hash)
if NOKOGIRI
elem = Nokogiri::XML::Node.new key, @xml.document
value.each { |k2, v2|
child = Nokogiri::XML::Node.new k2, elem
child.content = v2
elem.add_child(child)
}
@xml.xpath(xpath.to_s).first.add_child(elem)
else
elem = REXML::Element.new(key)
value.each { |k2, v2|
elem.add_element(k2).text = v2
}
@xml.elements[xpath].add_element(elem)
end
else
if NOKOGIRI
elem = Nokogiri::XML::Node.new key, @xml.document
elem.content = value
@xml.xpath(xpath.to_s).first.add_child(elem)
else
@xml.elements[xpath].add_element(key).text = value
end
end
}
end
# Gets an array of text from elemenets extracted
# using the XPATH expression passed as filter
def retrieve_elements(filter)
@ -201,6 +239,14 @@ module OpenNebula
template_like_str('TEMPLATE', indent)
end
def template_xml
if NOKOGIRI
@xml.xpath('TEMPLATE').to_s
else
@xml.elements['TEMPLATE'].to_s
end
end
def template_like_str(root_element, indent=true, xpath_exp=nil)
if NOKOGIRI
xml_template=@xml.xpath(root_element).to_s

View File

@ -223,7 +223,7 @@ var oZones = {
});
},
"delete": function(params,resource){
"del": function(params,resource){
var callback = params.success;
var callback_error = params.error;
var id = params.data.id;
@ -381,8 +381,8 @@ var oZones = {
"create": function(params){
oZones.Action.create(params,oZones.Zone.resource);
},
"delete" : function(params){
oZones.Action.delete(params,oZones.Zone.resource);
"del" : function(params){
oZones.Action.del(params,oZones.Zone.resource);
},
"list": function(params){
oZones.Action.list(params,oZones.Zone.resource);
@ -446,8 +446,8 @@ var oZones = {
"update": function(params){
oZones.Action.update(params,oZones.VDC.resource);
},
"delete": function(params){
oZones.Action.delete(params,oZones.VDC.resource);
"del": function(params){
oZones.Action.del(params,oZones.VDC.resource);
},
"list": function(params){
oZones.Action.list(params,oZones.VDC.resource);

View File

@ -155,7 +155,7 @@ var vdc_actions = {
"VDC.delete" : {
type: "multiple",
call: oZones.VDC.delete,
call: oZones.VDC.del,
callback: deleteVDCElement,
elements: vdcSelectedNodes,
error: onError,

View File

@ -118,7 +118,7 @@ var zone_actions = {
"Zone.delete" : {
type: "multiple",
call: oZones.Zone.delete,
call: oZones.Zone.del,
callback: deleteZoneElement,
elements: zoneSelectedNodes,
error: onError,

View File

@ -53,11 +53,15 @@
:ALL: true
:user:
:group:
- plugins/hosts-tab.js:
- plugins/clusters-tab.js:
:ALL: false
:user:
:group:
oneadmin: true
- plugins/hosts-tab.js:
:ALL: true
:user:
:group:
- plugins/datastores-tab.js:
:ALL: true
:user:
@ -66,8 +70,3 @@
:ALL: true
:user:
:group:
- plugins/clusters-tab.js:
:ALL: false
:user:
:group:
oneadmin: true

View File

@ -46,21 +46,24 @@ var OpenNebula = {
{
switch(type)
{
case "HOST","host":
case "HOST":
case "host":
return ["INIT",
"MONITORING",
"MONITORED",
"ERROR",
"DISABLED"][value];
break;
case "HOST_SIMPLE","host_simple":
case "HOST_SIMPLE":
case "host_simple":
return ["ON",
"ON",
"ON",
"ERROR",
"OFF"][value];
break;
case "VM","vm":
case "VM":
case "vm":
return ["INIT",
"PENDING",
"HOLD",
@ -70,7 +73,8 @@ var OpenNebula = {
"DONE",
"FAILED"][value];
break;
case "VM_LCM","vm_lcm":
case "VM_LCM":
case "vm_lcm":
return ["LCM_INIT",
"PROLOG",
"BOOT",
@ -89,7 +93,8 @@ var OpenNebula = {
"CLEANUP",
"UNKNOWN"][value];
break;
case "IMAGE","image":
case "IMAGE":
case "image":
return ["INIT",
"READY",
"USED",
@ -97,6 +102,14 @@ var OpenNebula = {
"LOCKED",
"ERROR"][value];
break;
case "VM_MIGRATE_REASON":
case "vm_migrate_reason":
return ["NONE",
"ERROR",
"STOP_RESUME",
"USER",
"CANCEL"][value];
break;
default:
return;
}
@ -203,7 +216,7 @@ var OpenNebula = {
});
},
"delete": function(params,resource){
"del": function(params,resource){
var callback = params.success;
var callback_error = params.error;
var id = params.data.id;
@ -437,8 +450,8 @@ var OpenNebula = {
"create": function(params){
OpenNebula.Action.create(params,OpenNebula.Host.resource);
},
"delete": function(params){
OpenNebula.Action.delete(params,OpenNebula.Host.resource);
"del": function(params){
OpenNebula.Action.del(params,OpenNebula.Host.resource);
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.Host.resource);
@ -476,8 +489,8 @@ var OpenNebula = {
"create": function(params){
OpenNebula.Action.create(params,OpenNebula.Network.resource);
},
"delete": function(params){
OpenNebula.Action.delete(params,OpenNebula.Network.resource);
"del": function(params){
OpenNebula.Action.del(params,OpenNebula.Network.resource);
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.Network.resource);
@ -550,8 +563,8 @@ var OpenNebula = {
"create": function(params){
OpenNebula.Action.create(params,OpenNebula.VM.resource);
},
"delete": function(params){
OpenNebula.Action.delete(params,OpenNebula.VM.resource);
"del": function(params){
OpenNebula.Action.del(params,OpenNebula.VM.resource);
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.VM.resource);
@ -668,8 +681,8 @@ var OpenNebula = {
"create": function(params){
OpenNebula.Action.create(params,OpenNebula.Group.resource);
},
"delete": function(params){
OpenNebula.Action.delete(params,OpenNebula.Group.resource);
"del": function(params){
OpenNebula.Action.del(params,OpenNebula.Group.resource);
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.Group.resource);
@ -682,8 +695,8 @@ var OpenNebula = {
"create": function(params){
OpenNebula.Action.create(params,OpenNebula.User.resource);
},
"delete": function(params){
OpenNebula.Action.delete(params,OpenNebula.User.resource);
"del": function(params){
OpenNebula.Action.del(params,OpenNebula.User.resource);
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.User.resource);
@ -734,8 +747,8 @@ var OpenNebula = {
"create": function(params){
OpenNebula.Action.create(params,OpenNebula.Image.resource);
},
"delete": function(params){
OpenNebula.Action.delete(params,OpenNebula.Image.resource);
"del": function(params){
OpenNebula.Action.del(params,OpenNebula.Image.resource);
},
"list": function(params){
OpenNebula.Action.list(params,OpenNebula.Image.resource);
@ -793,8 +806,8 @@ var OpenNebula = {
"create" : function(params){
OpenNebula.Action.create(params,OpenNebula.Template.resource);
},
"delete" : function(params){
OpenNebula.Action.delete(params,OpenNebula.Template.resource);
"del" : function(params){
OpenNebula.Action.del(params,OpenNebula.Template.resource);
},
"list" : function(params){
OpenNebula.Action.list(params,OpenNebula.Template.resource);
@ -846,8 +859,8 @@ var OpenNebula = {
"create" : function(params){
OpenNebula.Action.create(params,OpenNebula.Acl.resource);
},
"delete" : function(params){
OpenNebula.Action.delete(params,OpenNebula.Acl.resource);
"del" : function(params){
OpenNebula.Action.del(params,OpenNebula.Acl.resource);
},
"list" : function(params){
OpenNebula.Action.list(params,OpenNebula.Acl.resource);
@ -860,8 +873,8 @@ var OpenNebula = {
"create" : function(params){
OpenNebula.Action.create(params,OpenNebula.Cluster.resource);
},
"delete" : function(params){
OpenNebula.Action.delete(params,OpenNebula.Cluster.resource);
"del" : function(params){
OpenNebula.Action.del(params,OpenNebula.Cluster.resource);
},
"list" : function(params){
OpenNebula.Action.list(params,OpenNebula.Cluster.resource);
@ -906,8 +919,8 @@ var OpenNebula = {
"create" : function(params){
OpenNebula.Action.create(params,OpenNebula.Datastore.resource);
},
"delete" : function(params){
OpenNebula.Action.delete(params,OpenNebula.Datastore.resource);
"del" : function(params){
OpenNebula.Action.del(params,OpenNebula.Datastore.resource);
},
"list" : function(params){
OpenNebula.Action.list(params,OpenNebula.Datastore.resource);

View File

@ -129,7 +129,7 @@ var acl_actions = {
"Acl.delete" : {
type: "multiple",
call: OpenNebula.Acl.delete,
call: OpenNebula.Acl.del,
callback: deleteAclElement,
elements: aclElements,
error: onError,

View File

@ -166,7 +166,7 @@ var cluster_actions = {
"Cluster.delete" : {
type: "multiple",
call : OpenNebula.Cluster.delete,
call : OpenNebula.Cluster.del,
callback : deleteClusterElement,
elements: clusterElements,
error : onError,
@ -366,7 +366,82 @@ function clusterTabContent(cluster_json) {
vnets_list = '<li class="clusterElemLi">'+cluster.VNETS.ID+' - '+getVNetName(cluster.VNETS.ID)+'</li>';
*/
//special case for cluster none, simplified dashboard
if (cluster.ID == "-"){
var html_code = '\
<table class="dashboard_table">\
<tr>\
<td style="width:50%">\
<table style="width:100%">\
<tr>\
<td>\
<div class="panel">\
<h3>' + tr("Cluster information") + '</h3>\
<div class="panel_info">\
\
<table class="info_table">\
<tr>\
<td class="key_td">' + tr("ID") + '</td>\
<td class="value_td">'+cluster.ID+'</td>\
</tr>\
<tr>\
<td class="key_td">' + tr("Name") + '</td>\
<td class="value_td">'+cluster.NAME+'</td>\
</tr>\
</table>\
\
</div>\
</div>\
</td>\
</tr>\
<tr>\
<td>\
<div class="panel">\
<h3>' + tr("Hosts") + '</h3>\
<div class="panel_info">\
<br />\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="action_button" href="#hosts_tab" value="Host.create_dialog">'+tr("Create new host")+'</a><br />\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="show_tab_button" filter_id="'+cluster.ID+'" href="#hosts_tab">'+tr("Manage unclustered hosts")+'</a><br /></p>\
\
</div>\
</td>\
</tr>\
</table>\
</td>\
<td style="width:50%">\
<table style="width:100%">\
<tr>\
<td>\
<div class="panel">\
<h3>' + tr("Datastores") + '</h3>\
<div class="panel_info">\
<br />\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="action_button" href="#datastores_tab" value="Datastore.create_dialog">'+tr("Create new datastore")+'</a><br />\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="show_tab_button" filter_id="'+cluster.ID+'" href="#datastores_tab">'+tr("Manage unclustered datastores")+'</a><br /></p>\
</div>\
</div>\
</td>\
</tr>\
<tr>\
<td>\
<div class="panel">\
<h3>' + tr("Virtual Networks") + '</h3>\
<div class="panel_info">\
<br />\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="action_button" href="#vnets_tab" value="Network.create_dialog">'+tr("Create new virtual network")+'</a><br />\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="show_tab_button" filter_id="'+cluster.ID+'" href="#vnets_tab">'+tr("Manage unclustered virtual networks")+'</a><br /></p>\
</div>\
</div>\
</td>\
</tr>\
</table>\
</td>\
</tr></table>\
';
return html_code;
};
//end cluster none special html
var html_code = '\
<table class="dashboard_table">\
@ -453,7 +528,7 @@ function removeClusterMenus(){
// Sunstone.removeMainTab('cluster_vnets_tab_n',true);
// Sunstone.removeMainTab('cluster_datastores_tab_n',true);
// Sunstone.removeMainTab('cluster_hosts_tab_n',true);
Sunstone.removeMainTab('cluster_tab_n',true);
Sunstone.removeMainTab('cluster_tab_-',true);
for (var i=0; i < data.length; i++){
var id = data[i][1];

View File

@ -207,7 +207,7 @@ var datastore_actions = {
"Datastore.delete" : {
type: "multiple",
call : OpenNebula.Datastore.delete,
call : OpenNebula.Datastore.del,
callback : deleteDatastoreElement,
elements: datastoreElements,
error : onError,
@ -271,7 +271,8 @@ var datastore_buttons = {
"Datastore.update_dialog" : {
type: "action",
text: tr("Update properties"),
alwaysActive: true
alwaysActive: true,
condition: mustBeAdmin,
},
"Datastore.addtocluster" : {
type: "confirm_with_select",
@ -296,7 +297,8 @@ var datastore_buttons = {
},
"Datastore.delete" : {
type: "confirm",
text: tr("Delete")
text: tr("Delete"),
condition: mustBeAdmin
}
}
@ -443,7 +445,7 @@ function updateDatastoreInfo(request,ds){
</tr>\
<tr>\
<td class="key_td">'+tr("Cluster")+'</td>\
<td class="value_td">'+(element.CLUSTER.length ? element.CLUSTER : "-")+'</td>\
<td class="value_td">'+(info.CLUSTER.length ? info.CLUSTER : "-")+'</td>\
</tr>\
<tr>\
<td class="key_td">'+tr("DS Mad")+'</td>\

View File

@ -97,7 +97,7 @@ var group_actions = {
"Group.delete" : {
type: "multiple",
call : OpenNebula.Group.delete,
call : OpenNebula.Group.del,
callback : deleteGroupElement,
error : onError,
elements: groupElements,

View File

@ -189,7 +189,7 @@ var host_actions = {
"Host.delete" : {
type: "multiple",
call : OpenNebula.Host.delete,
call : OpenNebula.Host.del,
callback : deleteHostElement,
elements: hostElements,
error : onError,
@ -271,30 +271,36 @@ var host_buttons = {
},
"Host.create_dialog" : {
type: "create_dialog",
text: tr("+ New")
text: tr("+ New"),
condition: mustBeAdmin
},
"Host.update_dialog" : {
type: "action",
text: tr("Update a template"),
alwaysActive: true
alwaysActive: true,
condition: mustBeAdmin
},
"Host.addtocluster" : {
type: "confirm_with_select",
text: tr("Select cluster"),
select: clusters_sel,
tip: tr("Select the destination cluster:"),
condition: mustBeAdmin
},
"Host.enable" : {
type: "action",
text: tr("Enable")
text: tr("Enable"),
condition: mustBeAdmin
},
"Host.disable" : {
type: "action",
text: tr("Disable")
text: tr("Disable"),
condition: mustBeAdmin
},
"Host.delete" : {
type: "confirm",
text: tr("Delete host")
text: tr("Delete host"),
condition: mustBeAdmin
}
};
@ -475,7 +481,7 @@ function updateHostInfo(request,host){
</tr>\
<tr>\
<td class="key_td">' + tr("Cluster") + '</td>\
<td class="value_td">'+(host.CLUSTER.length ? host.CLUSTER : "-")+'</td>\
<td class="value_td">'+(host_info.CLUSTER.length ? host_info.CLUSTER : "-")+'</td>\
</tr>\
<tr>\
<td class="key_td">' + tr("State") + '</td>\
@ -493,10 +499,6 @@ function updateHostInfo(request,host){
<td class="key_td">'+ tr("VN MAD") +'</td>\
<td class="value_td">'+host_info.VN_MAD+'</td>\
</tr>\
<tr>\
<td class="key_td">'+ tr("TM MAD") +'</td>\
<td class="value_td">'+host_info.TM_MAD+'</td>\
</tr>\
</tbody>\
</table>\
<table id="host_shares_table" class="info_table">\

View File

@ -374,7 +374,7 @@ var image_actions = {
"Image.delete" : {
type: "multiple",
call: OpenNebula.Image.delete,
call: OpenNebula.Image.del,
callback: deleteImageElement,
elements: imageElements,
error: onError,

View File

@ -15,7 +15,7 @@
/* -------------------------------------------------------------------------- */
var infra_tab_content =
'<table class="dashboard_table" style=>\
'<table class="dashboard_table" id="infra_dashboard" style=>\
<tr>\
<td style="width:50%">\
<table style="width:100%">\
@ -26,7 +26,7 @@ var infra_tab_content =
<div class="panel_info">\
\
<table class="info_table">\
<tr>\
<tr class="cluster_related">\
<td class="key_td">' + tr("Clusters") + '</td>\
<td class="value_td"><span id="infra_total_clusters"></span></td>\
</tr>\
@ -54,7 +54,7 @@ var infra_tab_content =
<h3>' + tr("Quickstart") + '</h3>\
<div class="panel_info dashboard_p">\
<p></br>\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="action_button" href="#clusters_tab" value="Cluster.create_dialog">'+tr("Create new Cluster")+'</a></br>\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon cluster_related" /><a class="action_button cluster_related" href="#clusters_tab" value="Cluster.create_dialog">'+tr("Create new Cluster")+'</a></br>\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="action_button" href="#hosts_tab" value="Host.create_dialog">'+tr("Create new Host")+'</a></br>\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="action_button" href="#datastores_tab" value="Datastore.create_dialog">'+tr("Create new Datastore")+'</a></br>\
<span class="ui-icon ui-icon-arrowreturnthick-1-e inline-icon" /><a class="action_button" href="#vnets_tab" value="Network.create_dialog">'+tr("Create new Virtual Network")+'</a></br>\
@ -72,7 +72,7 @@ var infra_tab_content =
<div class="panel">\
<h3>' + tr("Infrastructure resources") + '</h3>\
<div class="panel_info">\
<p>'+tr("The Infrastructure menu allows management of Hosts, Datastores, Virtual Networks and the Clusters they are placed in. The Clusters node can be expanded, and resources can be managed for each cluster.")+'</p>\
<p>'+tr("The Infrastructure menu allows management of Hosts, Datastores, Virtual Networks. Users in the oneadmin group can manage clusters as well.")+'</p>\
<p>'+tr("You can find further information on the following links:")+'</p>\
<ul>\
<li><a href="http://opennebula.org/documentation:rel3.4:hostsubsystem" target="_blank">Host subsystem</a></li>\
@ -117,5 +117,6 @@ function updateInfraDashboard(what,json_info){
};
$(document).ready(function(){
if (!mustBeAdmin())
$('table#infra_dashboard .cluster_related', main_tabs_context).hide();
});

View File

@ -186,7 +186,7 @@ var create_template_tmpl = '<div id="template_create_tabs">\
<h3>'+tr("Add disks/images")+' <a id="add_disks" class="icon_left" href="#"><span class="ui-icon ui-icon-plus" /></a></h3>\
</div>\
<fieldset><legend>'+tr("Disks")+'</legend>\
<div class="" id="image_vs_disk">\
<div class="" id="image_vs_disk" style="display:none;">\
<label>'+tr("Add disk/image")+'</label>\
<input type="radio" id="add_disk" name="image_vs_disk" value="disk">'+tr("Disk")+'</input>\
<!--<label for="add_disk">Add a disk</label>-->\
@ -228,6 +228,11 @@ var create_template_tmpl = '<div id="template_create_tabs">\
<input type="text" id="SOURCE" name="source" />\
<div class="tip">'+tr("Disk file location path or URL")+'</div>\
</div>\
<div class="vm_param kvm xen vmware add_disk">\
<label for="TM_MAD">'+tr("Transfer Manager")+':</label>\
<input type="text" id="TM_MAD" name="tm_mad" />\
<div class="tip">'+tr("shared,ssh,iscsi,dummy")+'</div>\
</div>\
<div class="vm_param kvm_opt xen_opt add_disk ">\
<!--Mandatory for swap, fs and block images-->\
<label for="SIZE">'+tr("Size")+':</label>\
@ -283,7 +288,7 @@ var create_template_tmpl = '<div id="template_create_tabs">\
<h3>'+tr("Setup Networks")+' <a id="add_networks" class="icon_left" href="#"><span class="ui-icon ui-icon-plus" /></a></h3>\
</div>\
<fieldset><legend>'+tr("Network")+'</legend>\
<div class="" id="network_vs_niccfg">\
<div class="" id="network_vs_niccfg" style="display:none;">\
<label>'+tr("Add network")+'</label>\
<input type="radio" id="add_network" name="network_vs_niccfg" value="network">'+tr("Predefined")+'</input>\
<!--<label style="width:200px;" for="add_network">Pre-defined network</label>-->\
@ -324,7 +329,7 @@ var create_template_tmpl = '<div id="template_create_tabs">\
<input type="text" id="SCRIPT" name="script" />\
<div class="tip">'+tr("Name of a shell script to be executed after creating the tun device for the VM")+'</div>\
</div>\
<div class="vm_param kvm_opt xen_opt vmware_opt niccfg">\
<div class="vm_param kvm_opt xen_opt vmware_opt niccfg network">\
<label for="MODEL">'+tr("Model")+':</label>\
<input type="text" id="MODEL" name="model" />\
<div class="tip">'+tr("Hardware that will emulate this network interface. With Xen this is the type attribute of the vif.")+'</div>\
@ -720,7 +725,7 @@ var template_actions = {
"Template.delete" : {
type: "multiple",
call: OpenNebula.Template.delete,
call: OpenNebula.Template.del,
callback: deleteTemplateElement,
elements: templateElements,
error: onError,
@ -1406,7 +1411,7 @@ function setupCreateTemplateDialog(){
$('#image_vs_disk input',section_disks).click(function(){
//$('fieldset',section_disks).show();
$('.vm_param', section_disks).show();
var select = $('#image_vs_disk :checked',section_disks).val();
var select = $(this).val();
switch (select)
{
case "disk":
@ -1540,7 +1545,11 @@ function setupCreateTemplateDialog(){
box_remove_element(section_disks,'#disks_box');
return false;
});
};
//preselect now hidden option
$('#image_vs_disk input#add_image',section_disks).trigger('click');
};
// Sets up the network section
var networks_setup = function(){
@ -1570,7 +1579,7 @@ function setupCreateTemplateDialog(){
$('.firewall_select',section_networks).show();
$('.firewall_select select option',section_networks).removeAttr('selected');
select = $('#network_vs_niccfg :checked',section_networks).val();
select = $(this).val();
switch (select) {
case "network":
$('.niccfg',section_networks).hide();
@ -1648,6 +1657,8 @@ function setupCreateTemplateDialog(){
return false;
});
//preselect now hidden option
$('#network_vs_niccfg input#add_network',section_networks).trigger('click');
};
//Sets up the input section - basicly enabling adding and removing from box

View File

@ -197,7 +197,7 @@ var user_actions = {
"User.delete" : {
type: "multiple",
call: OpenNebula.User.delete,
call: OpenNebula.User.del,
callback: deleteUserElement,
elements: userElements,
error: onError,

View File

@ -63,6 +63,7 @@ var vms_tab_content =
<th>'+tr("CPU")+'</th>\
<th>'+tr("Memory")+'</th>\
<th>'+tr("Hostname")+'</th>\
<th>'+tr("IPs")+'</th>\
<th>'+tr("Start Time")+'</th>\
<th>'+tr("VNC Access")+'</th>\
</tr>\
@ -353,7 +354,7 @@ var vm_actions = {
"VM.delete" : {
type: "multiple",
call: OpenNebula.VM.delete,
call: OpenNebula.VM.del,
callback: deleteVMachineElement,
elements: vmElements,
error: onError,
@ -592,6 +593,10 @@ var vm_info_panel = {
"vm_log_tab" : {
title: tr("VM log"),
content: ""
},
"vm_history_tab" : {
title: tr("History information"),
content: "",
}
}
@ -619,7 +624,21 @@ function vmShow(req) {
// Returns a human readable running time for a VM
function str_start_time(vm){
return pretty_time(vm.STIME);
}
};
function ip_str(vm){
var nic = vm.TEMPLATE.NIC;
var ip = '--';
if ($.isArray(nic)) {
ip = '';
$.each(nic, function(index,value){
ip += value.IP+'<br />';
});
} else if (nic && nic.IP) {
ip = nic.IP;
};
return ip;
};
// Returns an array formed by the information contained in the vm_json
// and ready to be introduced in a dataTable
@ -650,10 +669,11 @@ function vMachineElementArray(vm_json){
vm.CPU,
humanize_size(vm.MEMORY),
hostname,
ip_str(vm),
str_start_time(vm),
vncIcon(vm)
];
}
};
//Creates a listener for the TDs of the VM table
@ -703,7 +723,69 @@ function updateVMachinesView(request, vmachine_list){
updateView(vmachine_list_array,dataTable_vMachines);
updateDashboard("vms",vmachine_list);
updateVResDashboard("vms",vmachine_list);
}
};
function generateHistoryTable(vm){
var html = ' <table id="vm_history_table" class="info_table" style="width:80%">\
<thead>\
<tr>\
<th>'+tr("Sequence")+'</th>\
<th>'+tr("Hostname")+'</th>\
<th>'+tr("Reason")+'</th>\
<th>'+tr("State change time")+'</th>\
<th>'+tr("Total time")+'</th>\
<th colspan="2">'+tr("Prolog time")+'</th>\
</tr>\
</thead>\
<tbody>';
var history = [];
if ($.isArray(vm.HISTORY_RECORDS.HISTORY))
history = vm.HISTORY_RECORDS.HISTORY;
else if (vm.HISTORY_RECORDS.HISTORY.SEQ)
history = [vm.HISTORY_RECORDS.HISTORY];
var now = Math.round(new Date().getTime() / 1000);
for (var i=0; i < history.length; i++){
// :TIME time calculations copied from onevm_helper.rb
var stime = parseInt(history[i].STIME, 10);
var etime = parseInt(history[i].ETIME, 10)
etime = etime == 0 ? now : etime;
var dtime = etime - stime;
// end :TIME
//:PTIME
var stime2 = parseInt(history[i].PSTIME, 10);
var etime2;
var ptime2 = parseInt(history[i].PETIME, 10);
if (stime2 == 0)
etime2 = 0;
else
etime2 = ptime2 == 0 ? now : ptime2;
var dtime2 = etime2 - stime2;
//end :PTIME
html += ' <tr>\
<td style="width:20%">'+history[i].SEQ+'</td>\
<td style="width:20%">'+history[i].HOSTNAME+'</td>\
<td style="width:16%">'+OpenNebula.Helper.resource_state("VM_MIGRATE_REASON",parseInt(history[i].REASON, 10))+'</td>\
<td style="width:16%">'+pretty_time(history[i].STIME)+'</td>\
<td style="width:16%">'+pretty_time_runtime(dtime)+'</td>\
<td style="width:16%">'+pretty_time_runtime(dtime2)+'</td>\
<td></td>\
</tr>'
};
html += '</tbody>\
</table>';
return html;
};
// Refreshes the information panel for a VM
@ -805,7 +887,7 @@ function updateVMInfo(request,vm){
</tr>\
</tbody>\
</table>'
}
};
var template_tab = {
title: tr("VM Template"),
@ -814,21 +896,27 @@ function updateVMInfo(request,vm){
<thead><tr><th colspan="2">'+tr("VM template")+'</th></tr></thead>'+
prettyPrintJSON(vm_info.TEMPLATE)+
'</table>'
}
};
var log_tab = {
title: tr("VM log"),
content: '<div>'+spinner+'</div>'
}
};
var monitoring_tab = {
title: tr("Monitoring information"),
content: generateMonitoringDivs(vm_graphs,"vm_monitor_")
}
};
var history_tab = {
title: tr("History information"),
content: generateHistoryTable(vm_info),
};
Sunstone.updateInfoPanelTab("vm_info_panel","vm_info_tab",info_tab);
Sunstone.updateInfoPanelTab("vm_info_panel","vm_template_tab",template_tab);
Sunstone.updateInfoPanelTab("vm_info_panel","vm_log_tab",log_tab);
Sunstone.updateInfoPanelTab("vm_info_panel","vm_history_tab",history_tab);
Sunstone.updateInfoPanelTab("vm_info_panel","vm_monitoring_tab",monitoring_tab);
//Pop up the info panel and asynchronously get vm_log and stats
@ -1284,9 +1372,9 @@ $(document).ready(function(){
"aoColumnDefs": [
{ "bSortable": false, "aTargets": ["check"] },
{ "sWidth": "60px", "aTargets": [0,6,7] },
{ "sWidth": "35px", "aTargets": [1,10] },
{ "sWidth": "150px", "aTargets": [5,9] },
{ "sWidth": "100px", "aTargets": [2,3] }
{ "sWidth": "35px", "aTargets": [1,11] },
{ "sWidth": "150px", "aTargets": [5,10] },
{ "sWidth": "100px", "aTargets": [2,3,9] }
],
"oLanguage": (datatable_lang != "") ?
{
@ -1297,7 +1385,7 @@ $(document).ready(function(){
dataTable_vMachines.fnClearTable();
addElement([
spinner,
'','','','','','','','','',''],dataTable_vMachines);
'','','','','','','','','','',''],dataTable_vMachines);
Sunstone.runAction("VM.list");
setupCreateVMDialog();

View File

@ -283,7 +283,7 @@ var vnet_actions = {
"Network.delete" : {
type: "multiple",
call: OpenNebula.Network.delete,
call: OpenNebula.Network.del,
callback: deleteVNetworkElement,
elements: vnElements,
error: onError,
@ -566,7 +566,7 @@ function updateVNetworkInfo(request,vn){
</tr>\
<tr>\
<td class="key_td">'+tr("Cluster")+'</td>\
<td class="value_td">'+(network.CLUSTER.length ? network.CLUSTER : "-")+'</td>\
<td class="value_td">'+(vn_info.CLUSTER.length ? vn_info.CLUSTER : "-")+'</td>\
</tr>\
<tr>\
<td class="key_td">'+tr("Owner")+'</td>\

View File

@ -60,6 +60,20 @@ function pretty_time_axis(time){
return hour + ":" + mins + ":" + secs;// + "&nbsp;" + month + "/" + day;
}
function pretty_time_runtime(time){
var d = new Date();
d.setTime(time*1000);
var secs = pad(d.getUTCSeconds(),2);
var hour = pad(d.getUTCHours(),2);
var mins = pad(d.getUTCMinutes(),2);
var day = d.getUTCDate()-1;
var month = pad(d.getUTCMonth()+1,2); //getMonths returns 0-11
var year = d.getUTCFullYear();
return day + "d " + hour + ":" + mins;// + ":" + secs;// + "&nbsp;" + month + "/" + day;
}
//returns a human readable size in Kilo, Mega, Giga or Tera bytes
function humanize_size(value) {
if (typeof(value) === "undefined") {

View File

@ -465,7 +465,19 @@ function insertTab(tab_name){
$('div#'+tab_name,main_tabs_context).html(tab_info.content);
$('div#menu ul#navigation').append('<li id="li_'+tab_name+'" class="'+tabClass+' '+parent+'">'+tab_info.title+'<span class="ui-icon ui-icon-circle-plus plusIcon"></span></li>');
var li_item = '<li id="li_'+tab_name+'" class="'+tabClass+' '+parent+'">'+tab_info.title+'<span class="ui-icon ui-icon-circle-plus plusIcon"></span></li>';
//if this is a submenu...
if (parent.length) {
var children = $('div#menu ul#navigation li.'+parent);
//if there are other submenus, insert after last of them
if (children.length)
$(children[children.length-1]).after(li_item);
else //instert after parent menu
$('div#menu ul#navigation li#li_'+parent).after(li_item);
} else { //not a submenu, instert in the end
$('div#menu ul#navigation').append(li_item);
};
if (parent){ //this is a subtab
$('div#menu li#li_'+tab_name).hide();//hide by default