1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-10 01:17:40 +03:00

B #5289: Sunstone with new schedule actions API (#1390)

Co-authored-by: Tino Vázquez <cvazquez@opennebula.io>
This commit is contained in:
Frederick Borges 2021-07-30 12:48:05 +02:00 committed by GitHub
parent f4d34de4f6
commit 2d922a4ad6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1067 additions and 1499 deletions

View File

@ -254,7 +254,6 @@ AllCops:
- src/sunstone/models/OpenNebulaJSON/UserJSON.rb
- src/sunstone/models/OpenNebulaJSON/AclJSON.rb
- src/sunstone/models/OpenNebulaJSON/JSONUtils.rb
- src/sunstone/models/OpenNebulaJSON/VirtualMachineJSON.rb
- src/sunstone/models/OpenNebulaJSON/MarketPlaceAppJSON.rb
- src/sunstone/models/OpenNebulaJSON/ImageJSON.rb
- src/sunstone/models/OpenNebulaJSON/ZoneJSON.rb

View File

@ -19,7 +19,9 @@ require 'opennebula/virtual_machine_ext'
module OpenNebulaJSON
# Sunstone VirtualMachineJSON class
class VirtualMachineJSON < OpenNebula::VirtualMachine
include JSONUtils
def create(template_json)
@ -34,158 +36,211 @@ module OpenNebulaJSON
template = template_to_str(vm_hash)
end
self.allocate(template)
allocate(template)
end
def perform_action(template_json)
action_hash = parse_json(template_json,'action')
action_hash = parse_json(template_json, 'action')
if OpenNebula.is_error?(action_hash)
return action_hash
end
rc = case action_hash['perform']
when "deploy" then self.deploy(action_hash['params'])
when "hold" then self.hold
when "livemigrate" then self.migrate(action_hash['params'], true, 0)
when "migrate" then self.migrate(action_hash['params'], false, 0)
when "migrate_poff" then self.migrate(action_hash['params'], false, 1)
when "migrate_poff_hard" then self.migrate(action_hash['params'], false, 2)
when "resume" then self.resume
when "release" then self.release
when "stop" then self.stop
when "suspend" then self.suspend
when "disk_saveas" then self.disk_saveas(action_hash['params'])
when "snapshot_create" then self.snapshot_create(action_hash['params'])
when "snapshot_revert" then self.snapshot_revert(action_hash['params'])
when "snapshot_delete" then self.snapshot_delete(action_hash['params'])
when "disk_snapshot_create" then self.disk_snapshot_create(action_hash['params'])
when "disk_snapshot_revert" then self.disk_snapshot_revert(action_hash['params'])
when "disk_snapshot_rename" then self.disk_snapshot_rename(action_hash['params'])
when "disk_snapshot_delete" then self.disk_snapshot_delete(action_hash['params'])
when "terminate" then self.terminate(action_hash['params'])
when "reboot" then self.reboot(action_hash['params'])
when "poweroff" then self.poweroff(action_hash['params'])
when "chown" then self.chown(action_hash['params'])
when "chmod" then self.chmod_octet(action_hash['params'])
when "resize" then self.resize(action_hash['params'])
when "attachdisk" then self.disk_attach(action_hash['params'])
when "detachdisk" then self.disk_detach(action_hash['params'])
when "attachnic" then self.nic_attach(action_hash['params'])
when "detachnic" then self.nic_detach(action_hash['params'])
when "update" then self.update(action_hash['params'])
when "updateconf" then self.updateconf(action_hash['params'])
when "rename" then self.rename(action_hash['params'])
when "undeploy" then self.undeploy(action_hash['params'])
when "resched" then self.resched
when "unresched" then self.unresched
when "recover" then self.recover(action_hash['params'])
when "save_as_template" then self.save_as_template(action_hash['params'])
when "disk_resize" then self.disk_resize(action_hash['params'])
when "lock" then self.lock(action_hash['params']['level'].to_i)
when "unlock" then self.unlock()
case action_hash['perform']
when 'deploy'
deploy(action_hash['params'])
when 'hold'
hold
when 'livemigrate'
migrate(0, action_hash['params'], true)
when 'migrate'
migrate(0, action_hash['params'], false)
when 'migrate_poff'
migrate(1, action_hash['params'], false)
when 'migrate_poff_hard'
migrate(2, action_hash['params'], false)
when 'resume'
resume
when 'release'
release
when 'stop'
stop
when 'suspend'
suspend
when 'disk_saveas'
disk_saveas(action_hash['params'])
when 'snapshot_create'
snapshot_create(action_hash['params'])
when 'snapshot_revert'
snapshot_revert(action_hash['params'])
when 'snapshot_delete'
snapshot_delete(action_hash['params'])
when 'disk_snapshot_create'
disk_snapshot_create(action_hash['params'])
when 'disk_snapshot_revert'
disk_snapshot_revert(action_hash['params'])
when 'disk_snapshot_rename'
disk_snapshot_rename(action_hash['params'])
when 'disk_snapshot_delete'
disk_snapshot_delete(action_hash['params'])
when 'terminate'
terminate(action_hash['params'])
when 'reboot'
reboot(action_hash['params'])
when 'poweroff'
poweroff(action_hash['params'])
when 'chown'
chown(action_hash['params'])
when 'chmod'
chmod_octet(action_hash['params'])
when 'resize'
resize(action_hash['params'])
when 'attachdisk'
disk_attach(action_hash['params'])
when 'detachdisk'
disk_detach(action_hash['params'])
when 'attachnic'
nic_attach(action_hash['params'])
when 'detachnic'
nic_detach(action_hash['params'])
when 'update'
update(action_hash['params'])
when 'updateconf'
updateconf(action_hash['params'])
when 'rename'
rename(action_hash['params'])
when 'undeploy'
undeploy(action_hash['params'])
when 'resched'
resched
when 'unresched'
unresched
when 'recover'
recover(action_hash['params'])
when 'save_as_template'
save_as_template(action_hash['params'])
when 'disk_resize'
disk_resize(action_hash['params'])
when 'lock'
lock(action_hash['params']['level'].to_i)
when 'unlock'
unlock
when 'sched_action_add'
sched_action_add(action_hash['params'])
when 'sched_action_update'
sched_action_update(action_hash['params'])
when 'sched_action_delete'
sched_action_delete(action_hash['params'])
else
error_msg = "#{action_hash['perform']} action not " <<
" available for this resource"
error_msg = "#{action_hash['perform']} action not " \
' available for this resource'
OpenNebula::Error.new(error_msg)
end
end
def deploy(params=Hash.new)
def deploy(params = {})
super(params['host_id'], params['enforce'], params['ds_id'])
end
def undeploy(params=Hash.new)
def undeploy(params = {})
super(params['hard'])
end
def terminate(params=Hash.new)
def terminate(params = {})
super(params['hard'])
end
def reboot(params=Hash.new)
def reboot(params = {})
super(params['hard'])
end
def poweroff(params=Hash.new)
def poweroff(params = {})
super(params['hard'])
end
def migrate(params=Hash.new, live=false, mtype)
super(params['host_id'], live, params['enforce'], params['ds_id'], mtype)
def migrate(mtype, params = {}, live = false)
super(params['host_id'],
live,
params['enforce'],
params['ds_id'],
mtype
)
end
def disk_saveas(params=Hash.new)
def disk_saveas(params = {})
super(params['disk_id'].to_i, params['image_name'],
params['type'], params['snapshot_id'].to_i)
end
def disk_resize(params=Hash.new)
def disk_resize(params = {})
super(params['disk_id'].to_i, params['new_size'])
end
def snapshot_create(params=Hash.new)
def snapshot_create(params = {})
super(params['snapshot_name'])
end
def snapshot_revert(params=Hash.new)
def snapshot_revert(params = {})
super(params['snapshot_id'].to_i)
end
def snapshot_delete(params=Hash.new)
def snapshot_delete(params = {})
super(params['snapshot_id'].to_i)
end
def disk_snapshot_create(params=Hash.new)
def disk_snapshot_create(params = {})
super(params['disk_id'].to_i, params['snapshot_name'])
end
def disk_snapshot_revert(params=Hash.new)
def disk_snapshot_revert(params = {})
super(params['disk_id'].to_i, params['snapshot_id'].to_i)
end
def disk_snapshot_rename(params=Hash.new)
super(params['disk_id'].to_i, params['snapshot_id'].to_i, params['new_name'])
def disk_snapshot_rename(params = {})
super(params['disk_id'].to_i,
params['snapshot_id'].to_i,
params['new_name']
)
end
def disk_snapshot_delete(params=Hash.new)
def disk_snapshot_delete(params = {})
super(params['disk_id'].to_i, params['snapshot_id'].to_i)
end
def chown(params=Hash.new)
super(params['owner_id'].to_i,params['group_id'].to_i)
def chown(params = {})
super(params['owner_id'].to_i, params['group_id'].to_i)
end
def chmod_octet(params=Hash.new)
def chmod_octet(params = {})
super(params['octet'])
end
def resize(params=Hash.new)
def resize(params = {})
template_json = params['vm_template']
template = template_to_str(template_json)
super(template, params['enforce'])
end
def disk_attach(params=Hash.new)
def disk_attach(params = {})
template_json = params['disk_template']
template = template_to_str(template_json)
super(template)
end
def disk_detach(params=Hash.new)
def disk_detach(params = {})
super(params['disk_id'].to_i)
end
def nic_attach(params=Hash.new)
def nic_attach(params = {})
template_json = params['nic_template']
template = template_to_str(template_json)
super(template)
end
def nic_detach(params=Hash.new)
def nic_detach(params = {})
super(params['nic_id'].to_i)
end
def update(params=Hash.new)
def update(params = {})
if !params['append'].nil?
super(params['template_raw'], params['append'])
else
@ -193,31 +248,47 @@ module OpenNebulaJSON
end
end
def updateconf(params=Hash.new)
def updateconf(params = {})
super(params['template_raw'])
end
def rename(params=Hash.new)
def rename(params = {})
super(params['name'])
end
def recover(params=Hash.new)
def recover(params = {})
super(params['result'].to_i)
end
def save_as_template(params=Hash.new)
def save_as_template(params = {})
begin
vm_new = VirtualMachine.new(VirtualMachine.build_xml(@pe_id), @client)
vm_new = VirtualMachine.new(
VirtualMachine.build_xml(@pe_id),
@client
)
vm_new.extend(VirtualMachineExt)
vm_new.save_as_template(params['name'],
params['description'],
:persistent => params['persistent'])
rescue Exception => e
rescue StandardError => e
OpenNebula::Error.new(e.message)
end
end
def sched_action_add(params = {})
super(params['sched_template'])
end
def sched_action_update(params = {})
super(params['sched_id'], params['sched_template'])
end
def sched_action_delete(params = {})
super(params['sched_id'])
end
end
end

View File

@ -14,6 +14,7 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
# Sunstone VMHelper module
module SunstoneVMHelper
class << self

View File

@ -827,6 +827,18 @@ define(function(require) {
return OpenNebulaAction.getName(id, RESOURCE);
},
"isvCenterVM": isVCenterVM,
"sched_action_add" : function(params) {
var action_obj = {"sched_template": params.data.extra_param};
OpenNebulaAction.simple_action(params, RESOURCE, "sched_action_add", action_obj);
},
"sched_action_update" : function(params) {
var action_obj = params.data.extra_param;
OpenNebulaAction.simple_action(params, RESOURCE, "sched_action_update", action_obj);
},
"sched_action_delete" : function(params) {
var action_obj = { "sched_id" : params.data.extra_param };
OpenNebulaAction.simple_action(params, RESOURCE, "sched_action_delete", action_obj);
}
};
function hostnameStr(element, navigationLink = false) {

View File

@ -941,7 +941,7 @@ define(function(require) {
//can be use to run action depending on conditions and notify them
//if desired. Returns 1 if some problem has been detected: i.e
//the condition to run the action is not met, the action is not found
var _runAction = function(action, dataArg, extraParam) {
var _runAction = function(action, dataArg, extraParam, callback) {
var actions = SunstoneCfg["actions"];
if (!actions[action]) {
Notifier.notifyError("Action " + action + " not defined");
@ -962,7 +962,7 @@ define(function(require) {
}
var call = actionCfg["call"];
var callback = actionCfg["callback"];
var callback = callback || actionCfg["callback"];
var err = actionCfg["error"];
switch (actionCfg.type){

View File

@ -59,8 +59,6 @@ define(function(require) {
FormPanel.prototype.fill = _fill;
FormPanel.prototype.addRoleTab = _add_role_tab;
return FormPanel;
/*

View File

@ -1,11 +1,19 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2021, OpenNebula Project, OpenNebula Systems }} {{! }}
{{! 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. }} {{! --------------------------------------------------------------------------
}}
{{! Copyright 2002-2021, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<table>
<tr>
<td id="title" colspan="3">
@ -37,7 +45,7 @@ the License for the specific language governing permissions and }} {{! limitatio
</tr>
<tr>
<td>
<button style="margin-top: 15px;" id="add_{{res}}_action_json" class="button small success secondary radius">{{tr "Add"}}</button>
<button style="margin-top: 15px;" id="add_{{res}}_action_json" class="button small success secondary radius">{{tr "Perform"}}</button>
</td>
</tr>
</table>

View File

@ -27,7 +27,6 @@ define(function(require) {
var Notifier = require("utils/notifier");
var ScheduleActions = require("utils/schedule_action");
var TemplateUtils = require("utils/template-utils");
var Leases = require("utils/leases");
var OpenNebulaAction = require("../../../opennebula/action");
var Humanize = require("utils/humanize");
@ -56,28 +55,6 @@ define(function(require) {
this.id = (info && info.DOCUMENT && info.DOCUMENT.ID) || "0";
this.body = (info && info.DOCUMENT && info.DOCUMENT.TEMPLATE && info.DOCUMENT.TEMPLATE.BODY) || {};
this.data = (this.body && this.body.roles) || [];
this.actions = [
"terminate",
"terminate-hard",
"hold",
"release",
"stop",
"suspend",
"resume",
"reboot",
"reboot-hard",
"poweroff",
"poweroff-hard",
"undeploy",
"undeploy-hard",
"snapshot-create",
"snapshot-delete",
"snapshot-revert",
"disk-snapshot-create",
"disk-snapshot-delete",
"disk-snapshot-revert"
];
return this;
}
@ -91,93 +68,20 @@ define(function(require) {
FUNCTION DEFINITIONS
*/
function actionRow(scheduling_action) {
var done_str = scheduling_action.DONE ? (Humanize.prettyTime(scheduling_action.DONE)) : "";
var message_str = scheduling_action.MESSAGE ? scheduling_action.MESSAGE : "";
var action_id = scheduling_action.ID || "";
var update_sched = "";
if(action_id){
update_sched = "<button id='minus_"+scheduling_action.ID+ "' class='small button btn-warning edit_action_x' data_id='"+action_id+"'><i class='fas fa-edit'></i></button>";
}
var str = "<td class='done_row'>" + done_str + "</td>\
<td class='message_row'>" + TemplateUtils.htmlEncode(message_str) + "</td>\
<td colspan='3' style='text-align: right;'>\
<div style='display: flex;justify-content: flex-end;'>\
<div>\
<button id='minus_" + scheduling_action.ID + "' class='small button btn-danger remove_action_x'><i class='fas fa-trash-alt'></i></button>\
</div>\
<div>"+update_sched+"</div>\
</td>\
</tr>";
return str;
}
function actionsTable(parseData) {
var str = "";
var empty = "\
<tr id=\"no_actions_tr\">\
<td colspan=\"6\">" + Locale.tr("No actions to show") + "</td>\
</tr>";
if (!parseData) {
return empty;
} else {
var sched_actions = Array.isArray(parseData)? parseData : [parseData];
if (sched_actions.length <= 0) {
return empty;
}
$.each(sched_actions, function(index, scheduling_action) {
if(scheduling_action && scheduling_action.ID){
var className = "";
var idTD = "<td class=\"id_row\"></td>";
var id = 0;
if(scheduling_action.ID){
className = "_"+scheduling_action.ID;
idTD = "<td class=\"id_row\">" + TemplateUtils.htmlEncode(scheduling_action.ID) + "</td>";
id = scheduling_action.ID;
}
str += "<tr class='tr_action"+className+"' data='" + JSON.stringify(scheduling_action) + "'>";
str += idTD;
var actions = ScheduleActions.fromJSONtoActionsTable([scheduling_action], id, true);
str += actions;
str += actionRow(scheduling_action);
}
});
}
return str;
}
function _htmlSchedActions(vmTemplateContents){
var parseData = TemplateUtils.stringToTemplate(vmTemplateContents);
return "<div class='row'>\
<div class='large-12 columns'>\
<table id='scheduling_"+RESOURCE_SCHED_ACTIONS+"_actions_table' class='info_table dataTable'>\
<thead>\
<tr>\
<th>" + Locale.tr("ID") + "</th>\
<th>" + Locale.tr("Action") + "</th>\
<th>" + Locale.tr("Time") + "</th>\
<th>" + Locale.tr("Rep") + "</th>\
<th>" + Locale.tr("End") + "</th>\
<th></th>\
<th></th>\
<th></th>\
<th><button id='add_scheduling_"+RESOURCE_SCHED_ACTIONS+"_action' class='button small success right radius' >" + Locale.tr("Add action") + "</button></th>\
<th>" +
Leases.html() +
"</th>\
</tr>\
</thead>\
<tbody id='sched_vms_actions_body' class='scheduling_place'>"+
actionsTable(parseData && parseData.SCHED_ACTION) +
"</tbody>\
</table>\
</div>\
</div>";
function _htmlSchedActions(sched_actions_array){
return ScheduleActions.htmlTable(
resource = RESOURCE_SCHED_ACTIONS,
leases = true,
body = ScheduleActions.getScheduleActionTableContent(
sched_actions_array
),
isVM = false,
canAdd = true
);
}
function _html() {
var optionsActions = this.actions.map(function(ac){
var optionsActions = ScheduleActions.defaultActions.map(function(ac){
return "<option value='"+ac+"'>"+ac+"</option>";
}).join("");
@ -192,317 +96,30 @@ define(function(require) {
optionsRoles.unshift("<option value=''>"+Locale.tr("All Roles")+"</option>");
optionsRoles = optionsRoles.join("");
var that = this;
var selector = "#service_sched_action_tab .sched_place";
ScheduleActions.updateServiceHTMLTable(
that,
selector,
_htmlSchedActions
);
return TemplateHTML({
sched_actions_table: _htmlSchedActions(this.data[0].vm_template_contents),
sched_actions_table: _htmlSchedActions(),
actions: optionsActions,
res: RESOURCE,
roles: optionsRoles
});
}
function findMaxID(items) {
return items.reduce((acc, val) => {
acc = ( acc === undefined || val.ID > acc ) ? val.ID : acc;
return acc;
}, 0);
}
function addID(actns){
acc = findMaxID(actns);
return actns.map(el=>{
if(el && !el.ID){
acc = el.ID = parseInt(acc, 10)+1;
}
return el;
});
}
/*** update schedule actions ***/
function updateNodes(that, callback, type = "replace"){
if(callback && typeof callback === "function" && that && that.data){
var roles = Array.isArray(that.data)? that.data : [that.data];
var updateService = true;
roles.forEach(function(element){
if(element && element.vm_template_contents !== undefined && element.nodes){
var jsonTemplateContents = TemplateUtils.stringToTemplate(element.vm_template_contents);
var nodes = Array.isArray(element.nodes)? element.nodes : [element.nodes];
nodes.forEach(element => {
if(element && element.vm_info && element.vm_info.VM && element.vm_info.VM.ID){
OpenNebulaAction.show({
data:{
id: element.vm_info.VM.ID
},
success: function(req, res){
var executedCallback = callback(jsonTemplateContents && jsonTemplateContents.SCHED_ACTION);
var userTemplate = res && res.VM && res.VM.USER_TEMPLATE || {};
var newUserTemplate = {};
switch (type) {
case "add":
var userTemplateSchedActions = userTemplate && userTemplate.SCHED_ACTION || [];
if(userTemplateSchedActions && executedCallback){
var newActions = Array.isArray(userTemplateSchedActions) ? userTemplateSchedActions : [userTemplateSchedActions];
if(Array.isArray(executedCallback)){
newActions = newActions.concat(executedCallback);
}else{
newActions.push(executedCallback);
}
newUserTemplate = addID(newActions);
}
break;
default:
if(executedCallback){
newUserTemplate = addID(executedCallback);
}
break;
}
userTemplate.SCHED_ACTION = newUserTemplate;
//this update the VM
OpenNebulaAction.simple_action(
{
data:{
id: element.vm_info.VM.ID
},
success: function(req){
if(userTemplate && userTemplate.SCHED_ACTION){
$(".sched_place").empty().append(_htmlSchedActions(TemplateUtils.templateToString({SCHED_ACTION: userTemplate.SCHED_ACTION})));
//update service
if(updateService){
updateService = false;
//this set a new vm_template_contents in roles
var rols = that.data.forEach(
function(element){
element.vm_template_contents = TemplateUtils.templateToString({SCHED_ACTION: userTemplate.SCHED_ACTION});
return element;
}
);
Service.update(
{
data:{
id: that.id,
extra_param: JSON.stringify(that.body)
},
success: function(){
updateService = true;
Notifier.notifyCustom(
Locale.tr("Service Updated"),
""
);
},
error: function(req, resp){
updateService = true;
Notifier.notifyError(
(resp && resp.error && resp.error.message) || Locale.tr("Cannot update the Service: ")+(that.id || "")
);
}
}
);
}
}
},
error: function(req, resp){
Notifier.notifyError(
(resp && resp.error && resp.error.message) || Locale.tr("Cannot update the VM: ")+element.vm_info.VM.ID
);
}
},
"VM",
"update",
{
template_raw: TemplateUtils.templateToString(userTemplate)
}
);
},
error: function(){
Notifier.notifyError(Locale.tr("Cannot get information of the VM: ")+element.vm_info.VM.ID);
}
}, "VM");
}
});
}
});
}
}
function functionButtons(context, that, actions){
var CREATE = true;
function clear(){
CREATE = true;
}
function renderCreateForm(){
if(CREATE){
ScheduleActions.htmlNewAction(actions, context, RESOURCE_SCHED_ACTIONS);
ScheduleActions.setup(context);
CREATE = false;
}
return false;
};
//add
context.off("click", "#add_scheduling_"+RESOURCE_SCHED_ACTIONS+"_action").on("click" , "#add_scheduling_"+RESOURCE_SCHED_ACTIONS+"_action", function(e){
e.preventDefault();
renderCreateForm();
$("#edit_"+RESOURCE_SCHED_ACTIONS+"_action_json").hide();
$("#add_"+RESOURCE_SCHED_ACTIONS+"_action_json").show();
});
context.off("click", "#add_"+RESOURCE_SCHED_ACTIONS+"_action_json").on("click" , "#add_"+RESOURCE_SCHED_ACTIONS+"_action_json", function(e) {
e.preventDefault();
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
$("#no_actions_tr", context).remove();
$("#sched_vms_actions_body").prepend(ScheduleActions.fromJSONtoActionsTable(sched_action));
}
updateNodes(that, function(){
return sched_action;
},"add");
clear();
});
//update
context.off("click", ".edit_action_x").on("click", ".edit_action_x", function(e) {
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
renderCreateForm();
$("#edit_"+RESOURCE_SCHED_ACTIONS+"_action_json").show().attr("data_id", id);
$("#add_"+RESOURCE_SCHED_ACTIONS+"_action_json").hide();
ScheduleActions.fill($(this),context);
}
});
context.off("click" , "#edit_"+RESOURCE_SCHED_ACTIONS+"_action_json").on("click" , "#edit_"+RESOURCE_SCHED_ACTIONS+"_action_json", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
$(".wickedpicker").hide();
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
var sched_actions = ScheduleActions.retrieve(context);
if(Array.isArray(sched_actions)){
sched_actions = sched_actions.map(
function(action){
if(action && action.ID && action.ID===id){
return sched_action;
}else{
return action;
}
}
);
}
updateNodes(that, function(){
return sched_actions;
},"replace");
}
clear();
}
return false;
});
//delete
context.off("click", ".remove_action_x").on("click", ".remove_action_x", function(e) {
e.preventDefault();
var index = this.id.substring(6, this.id.length);
updateNodes(that, function(schedAction){
var actions = Array.isArray(schedAction)? schedAction : [schedAction];
actions.forEach(function(el, i){
if(el && el.ID && String(el.ID) === index){
delete actions[i];
return;
}
});
return actions;
}, "replace");
clear();
});
}
function _setup(context) {
var that = this;
that.formContext = context;
Leases.actions(that, RESOURCE_SCHED_ACTIONS, "add-service", function(schedActions){
updateNodes(that, function(){
return schedActions;
},"add");
});
var actions = ScheduleActions.defaultActions;
$("select#select_new_action").off("change").on("change",function(){
var snap_name = $("#snapname");
var snap_id = $("#snapid");
var disk_id = $("#diskid");
switch ($(this).val()) {
case "snapshot-create":
snap_name.removeClass("hide");
snap_id.addClass("hide").val("");
disk_id.addClass("hide").val("");
break;
case "snapshot-revert":
snap_name.addClass("hide").val("");
snap_id.removeClass("hide");
disk_id.addClass("hide").val("");
break;
case "snapshot-delete":
snap_name.addClass("hide").val("");
snap_id.removeClass("hide");
disk_id.addClass("hide").val("");
break;
case "disk-snapshot-create":
snap_name.removeClass("hide");
snap_id.addClass("hide").val("");
disk_id.removeClass("hide");
break;
case "disk-snapshot-revert":
snap_name.addClass("hide").val("");
snap_id.removeClass("hide");
disk_id.removeClass("hide");
break;
case "disk-snapshot-delete":
snap_name.addClass("hide").val("");
snap_id.removeClass("hide");
disk_id.removeClass("hide");
break;
default:
snap_name.addClass("hide").val("");
snap_id.addClass("hide").val("");
disk_id.addClass("hide").val("");
break;
}
});
context.off("click", "#add_"+RESOURCE+"_action_json").on("click", "#add_"+RESOURCE+"_action_json", function(){
var new_action = $("select#select_new_action").val();
var role = $("select#role_name").val();
var snap_name = $("#snapname").val();
var snap_id = $("#snapid").val();
var disk_id = $("#diskid").val();
if(new_action){
var actionJSON = {};
actionJSON.error = function(e){
Notifier.notifyError((e && e.error && e.error.message) || Locale.tr("Error"));
};
actionJSON.success = function(e){
Notifier.notifyMessage(Locale.tr("Bulk Action Created"));
};
actionJSON.data = {};
actionJSON.data.id = that.id;
actionJSON.data.action = {perform: new_action};
actionJSON.data.action.params = {};
if(that.actions.includes(new_action)){
var rawData = [disk_id,snap_id,snap_name];
var args = rawData.filter(function (e) {return e;}).join();
if(args){
actionJSON.data.action.params.args = args;
}
}
if(role!=="" && role!==undefined){
actionJSON.data.roleName = role;
}
Actions.addFlowAction(actionJSON,RESOURCE);
}
return false;
});
functionButtons(context, that, actions);
ScheduleActions.setupButtons(
RESOURCE_SCHED_ACTIONS,
context,
that
);
}
});

View File

@ -27,7 +27,6 @@ define(function(require) {
var TemplateUtils = require("utils/template-utils");
var UserInputs = require("utils/user-inputs");
var ScheduleActions = require("utils/schedule_action");
var Leases = require("utils/leases");
/*
TEMPLATES
@ -43,11 +42,7 @@ define(function(require) {
var FORM_PANEL_ID = require("./create/formPanelId");
var TAB_ID = require("../tabId");
var RESOURCE = "service_create";
var CREATE = true;
var actions = ScheduleActions.defaultActions;
function clear(){
CREATE = true;
}
/*
CONSTRUCTOR
*/
@ -102,75 +97,7 @@ define(function(require) {
});
}
function renderCreateForm(context) {
if(CREATE){
ScheduleActions.htmlNewAction(actions, context, RESOURCE);
ScheduleActions.setup(context);
CREATE=false;
}
}
function sched_actions_events(context){
$(".edit_action_x").off("click").on("click", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
contextRow = $(this).closest("tr.tr_action");
renderCreateForm(context);
$("#edit_"+RESOURCE+"_action_json").show().attr("data_id", id);
$("#add_"+RESOURCE+"_action_json").hide();
ScheduleActions.fill($(this),context);
sched_actions_events(context);
}
});
$("#add_scheduling_"+RESOURCE+"_action").off("click");
$("#add_scheduling_"+RESOURCE+"_action").on("click", function(e){
e.preventDefault();
renderCreateForm(context);
$("#edit_"+RESOURCE+"_action_json").hide();
$("#add_"+RESOURCE+"_action_json").show();
sched_actions_events(context);
});
$(".remove_action_x").off("click").on("click", function(){
$(this).parents("tr").remove();
sched_actions_events(context);
});
$("#add_"+RESOURCE+"_action_json").off("click").on("click", function(){
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action) {
$("#no_actions_tr", context).remove();
$("#sched_"+RESOURCE+"_actions_body").prepend(ScheduleActions.fromJSONtoActionsTable(sched_action));
}
$("#input_sched_action_form").remove();
sched_actions_events(context);
clear();
return false;
});
$("#edit_"+RESOURCE+"_action_json").off("click").on("click", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length && contextRow){
$(".wickedpicker").hide();
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
sched_action.ID = id;
contextRow.replaceWith(ScheduleActions.fromJSONtoActionsTable(sched_action));
contextRow = undefined;
$("#input_sched_action_form").remove();
}
sched_actions_events(context);
clear();
}
return false;
});
}
function _setup(context) {
clear();
this.networksType = [
{ value: "template_id", text: "Create", select: "vntemplates", extra: true },
{ value: "reserve_from", text: "Reserve", select: "networks", extra: true },
@ -181,23 +108,25 @@ define(function(require) {
this.roleTabObjects = {};
var numberOfNetworks = 0;
var roles_index = 0;
var that = this;
//this render a schedule action form
$(".service_schedule_actions").append(ScheduleActions.htmlTable(RESOURCE, Leases.html()));
//this are actions of Leases button
var objLeases = $.extend(true, {}, that);
objLeases.resource = "template";
objLeases.__proto__ = FormPanel.prototype;
Leases.actions(objLeases, "", "", function(){
sched_actions_events(context);
});
//end of render a schedule action form
$(".service_schedule_actions").append(
ScheduleActions.htmlTable(
resource = RESOURCE,
leases = true,
body = ScheduleActions.getScheduleActionTableContent(),
isVM = false,
canAdd = true
)
);
// this is a logic to add, remove and edit schedule_action
sched_actions_events(context);
ScheduleActions.setupButtons(
RESOURCE,
context,
that
);
// end of logic to add, remove and edit schedule_action
$(".add_service_network", context).unbind("click");
@ -449,7 +378,6 @@ define(function(require) {
json_template["labels"] = currentInfo.TEMPLATE.BODY.labels;
}
clear();
if (this.action == "create") {
Sunstone.runAction("ServiceTemplate.create", json_template );
return false;
@ -489,23 +417,29 @@ define(function(require) {
this.resourceId = element.ID;
this.old_template = element.TEMPLATE.BODY;
var arraySchedActions = [];
//fill schedule actions
if(this.old_template && this.old_template.roles && this.old_template.roles[0] && this.old_template.roles[0].vm_template_contents){
if(this.old_template &&
this.old_template.roles &&
this.old_template.roles[0] &&
this.old_template.roles[0].vm_template_contents
){
var vm_template_contents = TemplateUtils.stringToTemplate(this.old_template.roles[0].vm_template_contents);
if(vm_template_contents && vm_template_contents.SCHED_ACTION){
var arraySchedActions = Array.isArray(vm_template_contents.SCHED_ACTION)? vm_template_contents.SCHED_ACTION : [vm_template_contents.SCHED_ACTION];
arraySchedActions.forEach(function(schedAction){
$("#no_actions_tr", context).remove();
$("#sched_"+RESOURCE+"_actions_body").prepend(ScheduleActions.fromJSONtoActionsTable(schedAction));
});
sched_actions_events(context);
arraySchedActions = Array.isArray(vm_template_contents.SCHED_ACTION)? vm_template_contents.SCHED_ACTION : [vm_template_contents.SCHED_ACTION];
}
$("#sched_service_create_actions_body").html(
ScheduleActions.getScheduleActionTableContent(
arraySchedActions
)
);
}
// Populates the Avanced mode Tab
$("#template", context).val(JSON.stringify(element.TEMPLATE.BODY, null, " "));
$("#service_name", context).attr("disabled", "disabled");
$("#service_name", context).val(element.NAME);

View File

@ -33,6 +33,7 @@ define(function(require) {
var Config = require("sunstone-config");
var TemplateUtils = require("utils/template-utils");
var ServiceUtils = require("utils/service-utils");
var ScheduleActions = require("utils/schedule_action");
/*
TEMPLATES
@ -48,6 +49,7 @@ define(function(require) {
var TAB_ID = require("../tabId");
var vm_group = "VM_GROUP";
var classButton = "small button leases right radius";
var RESOURCE_SCHED_ACTIONS = "inst_flow";
/*
CONSTRUCTOR
@ -89,15 +91,13 @@ define(function(require) {
"userInputsHTML": UserInputs.html(),
};
if(config && config.system_config && config.system_config.leases){
values_hbs.userInputsCharters = $("<div/>").append(
$("<div/>",{style:"display:inline-block;clear:both;width:100%"}).append(
$("<button />", {class: classButton, id:"addCharters"}).append(
$("<i/>", {class: "fa fa-clock"})
)
).add(
$("<table/>", {class: "service-charters"})
)
).prop("outerHTML");
values_hbs.userInputsCharters = ScheduleActions.htmlTable(
resource = RESOURCE_SCHED_ACTIONS,
leases = true,
body = ScheduleActions.getScheduleActionTableContent(),
isVM = false,
canAdd = true
);
}
return TemplateHTML(values_hbs);
}
@ -105,50 +105,15 @@ define(function(require) {
function _setup(context) {
var that = this;
Tips.setup(context);
ScheduleActions.setupButtons(
RESOURCE_SCHED_ACTIONS,
context,
that
);
return false;
}
function _onShow(context) {
$(".service-charters", context).empty();
if(config && config.system_config && config.system_config.leases ){
function remove_leases(){
$(".remove-lease", context).off("click").on("click", function(e){
e.preventDefault();
e.stopPropagation();
$(this).closest("tr").remove();
});
}
remove_leases();
$("#addCharters",context).off("click").on("click", function(e){
e.preventDefault();
e.stopPropagation();
var leases = config.system_config.leases;
var confLeasesKeys = Object.keys(leases);
var last = 0;
confLeasesKeys.forEach(function(schedAction){
if(leases[schedAction] && leases[schedAction].time){
var schedActionTime = parseInt(leases[schedAction].time,10);
last = last + schedActionTime;
var time = (last>=0? "+":"-")+last;
$(".service-charters", context).append(
$("<tr/>").attr("data-time", time).attr("data-action", schedAction).append(
$("<td/>").text(schedAction).add(
$("<td/>").text(time)
).add(
$("<td>").append(
$("<button/>", {class: "remove-lease"}).append(
$("<i>",{class: "fas fa-trash-alt"})
)
)
)
)
);
remove_leases();
}
});
});
}
var vmgroups = OpenNebulaAction.cache(vm_group);
if(!vmgroups){
Sunstone.runAction("VMGroup.list");
@ -202,6 +167,19 @@ define(function(require) {
$("#"+div_id, context).empty();
//if (role.vm_template_contents){
roleTemplate = TemplateUtils.stringToTemplate(role.vm_template_contents);
if (roleTemplate.SCHED_ACTION){
$(".schedule_actions_values", context).html(
ScheduleActions.htmlTable(
resource = RESOURCE_SCHED_ACTIONS,
leases = true,
body = ScheduleActions.getScheduleActionTableContent(
roleTemplate.SCHED_ACTION
),
isVM = false,
canAdd = true
)
);
}
if(roleTemplate && roleTemplate.APPEND){
var append = roleTemplate.APPEND.split(",");
$.each(append, function(key, value){
@ -313,15 +291,10 @@ define(function(require) {
}
if(that.service_template_json.DOCUMENT.TEMPLATE.BODY.roles){
var charters = "";
if(config && config.system_config && config.system_config.leases ){
$(".service-charters", context).find("tr").each(function(index){
var time = $(this).attr("data-time");
var action = $(this).attr("data-action");
if(time.length>0 && action.length>0){
charters += TemplateUtils.templateToString({SCHED_ACTION:{ACTION:action, TIME: time, ID: index.toString()}});
}
var scheduleActionsList = ScheduleActions.retrieve(context, true)
$.each(scheduleActionsList, function(_,sched_action){
charters += TemplateUtils.templateToString(sched_action);
});
}
$.each(that.service_template_json.DOCUMENT.TEMPLATE.BODY.roles, function(index, role){
var temp_role = {};
$.extend( temp_role, role);

View File

@ -20,7 +20,6 @@ define(function(require) {
*/
var Config = require("sunstone-config");
var Leases = require("utils/leases");
var Locale = require("utils/locale");
var ScheduleActions = require("utils/schedule_action");
var UniqueId = require("utils/unique-id");
@ -65,84 +64,25 @@ define(function(require) {
function _html() {
return TemplateHTML({
"table_sched_actions": ScheduleActions.htmlTable(RESOURCE, Leases.html()),
"table_sched_actions": ScheduleActions.htmlTable(
resource = RESOURCE,
leases = true,
body = ScheduleActions.getScheduleActionTableContent(),
isVM = false,
canAdd = true
)
});
}
function _onShow(_, panelForm) {
Leases.actions(panelForm);
}
function _setup(context) {
if(!CREATE){
CREATE = true;
}
var actions = ScheduleActions.defaultActions;
function renderCreateForm() {
if(CREATE){
ScheduleActions.htmlNewAction(actions, context, "temp");
ScheduleActions.setup(context);
CREATE = false;
}
return false;
}
context.off("click", "#add_scheduling_temp_action");
context.on("click", "#add_scheduling_temp_action", function(e){
renderCreateForm();
e.preventDefault();
ScheduleActions.reset();
$("#edit_"+RESOURCE+"_action_json").hide();
$("#add_"+RESOURCE+"_action_json").show();
});
context.off("click", "#add_temp_action_json");
context.on("click" , "#add_temp_action_json", function(){
$(".wickedpicker").hide();
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
$("#sched_temp_actions_body").prepend(ScheduleActions.fromJSONtoActionsTable(sched_action));
}
$("#input_sched_action_form").remove();
clear();
return false;
});
context.off("click" , "#edit_temp_action_json").on("click" , "#edit_temp_action_json", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length && contextRow){
$(".wickedpicker").hide();
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
sched_action.ID = id;
contextRow.replaceWith(ScheduleActions.fromJSONtoActionsTable(sched_action));
contextRow = undefined;
$("#input_sched_action_form").remove();
}
clear();
}
return false;
});
context.off("click", ".remove_action_x");
context.on("click", ".remove_action_x", function () {
$(this).parents("tr").remove();
});
context.off("click", ".edit_action_x");
context.on("click", ".edit_action_x", function (e) {
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
contextRow = $(this).closest("tr.tr_action");
renderCreateForm();
$("#edit_"+RESOURCE+"_action_json").show().attr("data_id", id);
$("#add_"+RESOURCE+"_action_json").hide();
ScheduleActions.fill($(this),context);
}
});
ScheduleActions.setupButtons(
RESOURCE,
context,
that
)
}
function _retrieve(context) {
@ -151,13 +91,9 @@ define(function(require) {
return templateJSON;
}
function clear(){
CREATE = true;
}
function _fill(_, templateJSON) {
var actions = ScheduleActions.fromJSONtoActionsTable(templateJSON.SCHED_ACTION);
$("#sched_temp_actions_body").prepend(actions);
var actions = ScheduleActions.getScheduleActionTableContent(templateJSON.SCHED_ACTION)
$("#sched_temp_actions_body").html(actions);
delete templateJSON["SCHED_ACTION"];
}
});

View File

@ -26,7 +26,6 @@ define(function(require) {
var DisksResize = require("utils/disks-resize");
var GroupTable = require("tabs/groups-tab/datatable");
var HostsTable = require("tabs/hosts-tab/datatable");
var Leases = require("utils/leases");
var Locale = require("utils/locale");
var NicsSection = require("utils/nics-section");
var Notifier = require("utils/notifier");
@ -56,8 +55,6 @@ define(function(require) {
var FORM_PANEL_ID = require("./instantiate/formPanelId");
var TAB_ID = require("../tabId");
var RESOURCE = "inst";
var CREATE = true;
var contextRow;
var distinct = function(value, index, self){
return self.indexOf(value)===index;
@ -106,15 +103,10 @@ define(function(require) {
}
function _setup(context) {
if(!CREATE){
CREATE = true;
}
var actions = ScheduleActions.defaultActions;
var that = this;
var objLeases = $.extend(true, {}, that);
objLeases.resource = "template";
objLeases.__proto__ = FormPanel.prototype;
Leases.actions(objLeases);
if(Config.isFeatureEnabled("instantiate_persistent")){
$("input.instantiate_pers", context).on("change", function(){
@ -141,75 +133,11 @@ define(function(require) {
$("#vm_n_times", context).show();
}
function renderCreateForm() {
if(CREATE){
ScheduleActions.htmlNewAction(actions, context, RESOURCE);
ScheduleActions.setup(context);
CREATE=false;
}
}
context.off("click", "#add_scheduling_inst_action");
context.on("click", "#add_scheduling_inst_action", function(e){
e.preventDefault();
renderCreateForm();
$("#edit_"+RESOURCE+"_action_json").hide();
$("#add_"+RESOURCE+"_action_json").show();
});
context.off("click", "#add_inst_action_json");
context.on("click", "#add_inst_action_json", function(){
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
$("#no_actions_tr", context).remove();
$("#sched_inst_actions_body").prepend(ScheduleActions.fromJSONtoActionsTable(sched_action));
}
$("#input_sched_action_form").remove();
clear();
return false;
});
context.on("focusout" , "#time_input", function(){
$("#time_input").removeAttr("data-invalid");
$("#time_input").removeAttr("class");
});
context.off("click" , "#edit_inst_action_json")
context.on("click" , "#edit_inst_action_json", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length && contextRow){
$(".wickedpicker").hide();
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
sched_action.ID = id;
contextRow.replaceWith(ScheduleActions.fromJSONtoActionsTable(sched_action));
contextRow = undefined;
$("#input_sched_action_form").remove();
}
clear();
}
return false;
});
context.off("click", ".remove_action_x");
context.on("click", ".remove_action_x", function(){
$(this).parents("tr").remove();
});
context.off("click", ".edit_action_x");
context.on("click", ".edit_action_x", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
contextRow = $(this).closest("tr.tr_action");
renderCreateForm();
$("#edit_"+RESOURCE+"_action_json").show().attr("data_id", id);
$("#add_"+RESOURCE+"_action_json").hide();
ScheduleActions.fill($(this),context);
}
});
//----------------------------------------------------------------------------
// Boot order
//----------------------------------------------------------------------------
@ -537,31 +465,43 @@ define(function(require) {
that.groupTable = new GroupTable("GroupTable" + UniqueId.id(), options_unique);
templatesContext.append(
TemplateRowHTML(
{ element: template_json.VMTEMPLATE,
TemplateRowHTML({
element: template_json.VMTEMPLATE,
capacityInputsHTML: CapacityInputs.html(),
hostsDatatable: that.hostsTable.dataTableHTML,
dsDatatable: that.datastoresTable.dataTableHTML,
usersDatatable: that.usersTable.dataTableHTML,
groupDatatable: that.groupTable.dataTableHTML,
table_sched_actions: ScheduleActions.htmlTable(RESOURCE, Leases.html())
table_sched_actions: ScheduleActions.htmlTable(
resource = RESOURCE,
leases = true,
body = ScheduleActions.getScheduleActionTableContent(
template_json.VMTEMPLATE.TEMPLATE.SCHED_ACTION
),
isVM = true,
canAdd = true
)
}
)
);
ScheduleActions.setupButtons(
RESOURCE,
context,
that,
template_json.VMTEMPLATE.TEMPLATE.SCHED_ACTION
);
var objLeases = $.extend(true, {}, that);
objLeases.formContext = templatesContext;
objLeases.resource = "template";
objLeases.__proto__ = FormPanel.prototype;
Leases.actions(objLeases);
$(".provision_host_selector" + template_json.VMTEMPLATE.ID, context).data("hostsTable", that.hostsTable);
$(".provision_ds_selector" + template_json.VMTEMPLATE.ID, context).data("dsTable", that.datastoresTable);
$(".provision_uid_selector" + template_json.VMTEMPLATE.ID, context).data("usersTable", that.usersTable);
$(".provision_gid_selector" + template_json.VMTEMPLATE.ID, context).data("groupTable", that.groupTable);
var actions = ScheduleActions.fromJSONtoActionsTable(template_json.VMTEMPLATE.TEMPLATE.SCHED_ACTION);
$("#sched_inst_actions_body").prepend(actions);
var selectOptions = {
"selectOptions": {
"select_callback": function(aData, options) {
@ -757,14 +697,9 @@ define(function(require) {
var form = context.find("#sched_inst_actions_body");
form.find("tr.create,tr#schedule_base,tr#input_sched_action_form,tr#relative_time_form,tr#no_relative_time_form").remove();
}
clear();
return false;
}
function clear(){
CREATE = true;
}
function generateRequirements(hosts_table, ds_table, context, id) {
var req_string = [];
var req_ds_string = [];

View File

@ -116,6 +116,9 @@ define(function(require) {
"VM.disk_snapshot_rename": _commonActions.singleAction('disk_snapshot_rename'),
"VM.disk_snapshot_delete": _commonActions.singleAction('disk_snapshot_delete'),
"VM.disk_saveas" : _commonActions.singleAction('disk_saveas'),
"VM.sched_action_add" : _commonActions.singleAction('sched_action_add'),
"VM.sched_action_delete" : _commonActions.singleAction('sched_action_delete'),
"VM.sched_action_update" : _commonActions.singleAction('sched_action_update'),
"VM.create_dialog" : {
type: "custom",

View File

@ -23,7 +23,6 @@ define(function(require) {
var Locale = require("utils/locale");
var Tips = require("utils/tips");
var TemplatesTable = require("tabs/templates-tab/datatable");
var Leases = require("utils/leases");
var OpenNebulaAction = require("opennebula/action");
/*
@ -114,7 +113,6 @@ define(function(require) {
}
}
leasesThat.__proto__ = FormPanel.prototype;
Leases.actions(leasesThat);
}
});
Tips.setup(context);

View File

@ -19,9 +19,7 @@ define(function(require) {
DEPENDENCIES
*/
var Humanize = require("utils/humanize");
var Leases = require("utils/leases");
var Locale = require("utils/locale");
var ScheduleActions = require("utils/schedule_action");
var Sunstone = require("sunstone");
@ -60,185 +58,33 @@ define(function(require) {
function _html() {
var that = this;
var html = "<div class='row'>\
<div class='large-12 columns'>\
<table id='scheduling_vms_actions_table' class='info_table dataTable'>\
<thead>\
<tr>\
<th>" + Locale.tr("ID") + "</th>\
<th>" + Locale.tr("Action") + "</th>\
<th>" + Locale.tr("Time") + "</th>\
<th>" + Locale.tr("Rep") + "</th>\
<th>" + Locale.tr("End") + "</th>\
<th>" + Locale.tr("Done") + "</th>\
<th>" + Locale.tr("Message") + "</th>\
<th colspan=''> </th>\
<th><button id='add_scheduling_vms_action' class='button small success right radius' >" + Locale.tr("Add action") + "</button></th>\
<th>" + Leases.html() + "</th>\
</tr>\
</thead>\
<tbody id='sched_vms_actions_body' class='scheduling_place'>"+
vmsfromJSONtoActionsTable(that.element.USER_TEMPLATE.SCHED_ACTION) +
"</tbody>\
</table>\
</div>\
</div>";
ScheduleActions.htmlTable(RESOURCE_SCHED_ACTIONS); //only charge the resource attribute
var canAdd = !(
that &&
that.element &&
that.element.USER_TEMPLATE &&
that.element.USER_TEMPLATE.SERVICE_ID
);
var html = ScheduleActions.htmlTable(
resource = RESOURCE_SCHED_ACTIONS,
leases = canAdd,
body = ScheduleActions.getScheduleActionTableContent(
that.element.TEMPLATE.SCHED_ACTION,
that.element
),
isVM = true,
canAdd = canAdd,
);
return html;
}
function _setup(context) {
var CREATE = true;
var that = this;
that.formContext = context;
Leases.actions(that,"vm","update");
var actions = ScheduleActions.defaultActions;
function renderCreateForm(){
if(CREATE){
ScheduleActions.htmlNewAction(actions, context, "vms");
ScheduleActions.setup(context);
CREATE = false;
}
return false;
};
context.off("click", "#add_scheduling_vms_action");
context.on("click" , "#add_scheduling_vms_action", function(e){
e.preventDefault();
renderCreateForm();
$("#edit_"+RESOURCE_SCHED_ACTIONS+"_action_json").hide();
$("#add_"+RESOURCE_SCHED_ACTIONS+"_action_json").show();
});
context.off("click", "#add_vms_action_json");
context.on("click" , "#add_vms_action_json", function(){
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
$("#no_actions_tr", context).remove();
$("#sched_vms_actions_body").prepend(ScheduleActions.fromJSONtoActionsTable(sched_action));
} else {
return false;
}
that.element.USER_TEMPLATE.SCHED_ACTION = ScheduleActions.retrieve(context);
// Let OpenNebula know
var template_str = TemplateUtils.templateToString(that.element.USER_TEMPLATE);
Sunstone.runAction("VM.update_template", that.element.ID, template_str);
return false;
});
context.off("click" , "#edit_vms_action_json").on("click" , "#edit_vms_action_json", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
$(".wickedpicker").hide();
var sched_action = ScheduleActions.retrieveNewAction(context);
if (sched_action != false) {
sched_action.ID = id;
var sched_actions = ScheduleActions.retrieve(context);
if(Array.isArray(sched_actions)){
sched_actions = sched_actions.map(function(action){
if(action && action.ID && action.ID===id){
return sched_action;
}else{
return action;
}
});
}
that.element.USER_TEMPLATE.SCHED_ACTION = sched_actions;
var template_str = TemplateUtils.templateToString(that.element.USER_TEMPLATE);
Sunstone.runAction("VM.update_template", that.element.ID, template_str);
}
clear();
}
return false;
});
// Listener for key,value pair remove action
context.off("click", ".remove_action_x");
context.on("click", ".remove_action_x", function() {
var index = this.id.substring(6, this.id.length);
var tmp_tmpl = new Array();
$.each(that.element.USER_TEMPLATE.SCHED_ACTION, function(i, element) {
if (element.ID && element.ID != index)
tmp_tmpl[i] = element;
});
that.element.USER_TEMPLATE.SCHED_ACTION = tmp_tmpl;
var template_str = TemplateUtils.templateToString(that.element.USER_TEMPLATE);
Sunstone.runAction("VM.update_template", that.element.ID, template_str);
});
context.off("click", ".edit_action_x");
context.on("click", ".edit_action_x", function(e) {
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
renderCreateForm();
$("#edit_"+RESOURCE_SCHED_ACTIONS+"_action_json").show().attr("data_id", id);
$("#add_"+RESOURCE_SCHED_ACTIONS+"_action_json").hide();
ScheduleActions.fill($(this),context);
}
});
}
// Returns an HTML string with the json keys and values
function vmsfromJSONtoActionsTable(actions_array) {
var str = "";
var empty = "\
<tr id=\"no_actions_tr\">\
<td colspan=\"6\">" + Locale.tr("No actions to show") + "</td>\
</tr>" ;
if (!actions_array) {
return empty;
}
if (!Array.isArray(actions_array)) {
var tmp_array = new Array();
tmp_array[0] = actions_array;
actions_array = tmp_array;
}
if (!actions_array.length) {
return empty;
}
$.each(actions_array, function(_, scheduling_action) {
str += "<tr class='tr_action_"+scheduling_action.ID+"' data='" + JSON.stringify(scheduling_action) + "'>";
str += "<td class=\"id_row\">" + TemplateUtils.htmlEncode(scheduling_action.ID) + "</td>";
var actions = ScheduleActions.fromJSONtoActionsTable([scheduling_action], scheduling_action.ID, true);
str += actions;
str += vmsfromJSONtoActionRow(scheduling_action);
});
return str;
}
function clear(){
CREATE = true;
}
// Helper for fromJSONtoHTMLTable function
function vmsfromJSONtoActionRow(scheduling_action) {
var done_str = scheduling_action.DONE ? (Humanize.prettyTime(scheduling_action.DONE)) : "";
var message_str = scheduling_action.MESSAGE ? scheduling_action.MESSAGE : "";
var action_id = scheduling_action.ID || "";
var update_sched = "";
if(action_id){
update_sched = "<button id='minus_"+scheduling_action.ID+ "' class='small button btn-warning edit_action_x' data_id='"+action_id+"'><i class='fas fa-edit'></i></button>";
}
var str = "<td class='done_row'>" + done_str + "</td>\
<td class='message_row'>" + TemplateUtils.htmlEncode(message_str) + "</td>\
<td colspan='3' style='text-align: right;'>\
<div style='display: flex;justify-content: flex-end;'>\
<div>\
<button id='minus_" + scheduling_action.ID + "' class='small button btn-danger remove_action_x'><i class='fas fa-trash-alt'></i></button>\
</div>\
<div>"+update_sched+"</div>\
</td>\
</tr>";
return str;
ScheduleActions.setupButtons(
RESOURCE_SCHED_ACTIONS,
context,
that
);
}
});

View File

@ -148,16 +148,16 @@ define(function(require) {
if(
element &&
element.STIME &&
element.USER_TEMPLATE &&
element.USER_TEMPLATE.SCHED_ACTION &&
element.TEMPLATE &&
element.TEMPLATE.SCHED_ACTION &&
config &&
config.system_config &&
config.system_config.leases
){
var actionsArray = [];
var actions = element.USER_TEMPLATE.SCHED_ACTION;
var actions = element.TEMPLATE.SCHED_ACTION;
var leases = config.system_config.leases;
if(Array.isArray(element.USER_TEMPLATE.SCHED_ACTION)){
if(Array.isArray(actions)){
actionsArray = actions;
}else{
actionsArray.push(actions);

View File

@ -1,82 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
/* */
/* 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. */
/* -------------------------------------------------------------------------- */
define(function(require) {
var Locale = require("utils/locale");
var TemplateUtils = require("utils/template-utils");
var Humanize = require("utils/humanize");
function _fromJSONtoActionsTable(actions_array) {
var str = "";
if (!actions_array) {
return "";
}
if (!Array.isArray(actions_array)) {
var tmp_array = new Array();
tmp_array[0] = actions_array;
actions_array = tmp_array;
}
if (!actions_array.length) {
return "";
}
$.each(actions_array, function(index, scheduling_action) {
str += fromJSONtoActionRow(scheduling_action);
});
return str;
}
function fromJSONtoActionRow(scheduling_action) {
var time_str = Humanize.prettyTime(scheduling_action.TIME);
var str = "";
var action_id = scheduling_action.ID || '';
var update_sched = '';
if(action_id){
update_sched = "<button id='minus' class='small button btn-warning edit_action_x' data_id='"+action_id+"'><i class='fas fa-edit'></i></button>";
}
str += "<tr class='tr_action'>\
<td class='action_row'>" + TemplateUtils.htmlEncode(scheduling_action.ACTION) + "</td>\
<td nowrap class='time_row'>" + time_str + "</td>\
<td colspan='3' style='text-align: right;'>\
<div style='display: flex;justify-content: flex-end;'>\
<div>\
<button id='minus' class='small button btn-danger remove_action_x'><i class='fas fa-trash-alt'></i></button>\
</div>\
<div>"+update_sched+"</div>\
</div>\
</td>\
</tr>";
return str;
}
function _convertDate(date_string){
date_string = date_string.split("/");
return date_string[2] + "-" + date_string[1] + "-" + date_string[0];
}
return {
"fromJSONtoActionsTable": _fromJSONtoActionsTable,
"convertDate": _convertDate
};
});

View File

@ -62,7 +62,15 @@ define(function(require) {
}
};
var week_days_str = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
var week_days_str = [
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat"
];
/*
CONSTRUCTOR

View File

@ -1,277 +0,0 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
/* */
/* 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. */
/* -------------------------------------------------------------------------- */
define(function(require) {
/*
DEPENDENCIES
*/
var Locale = require("utils/locale");
var TemplateUtils = require("utils/template-utils");
var WizardFields = require("utils/wizard-fields");
var Sunstone = require("sunstone");
var ScheduleActions = require("utils/schedule_action");
var notifier = require("utils/notifier");
var CONFIRM_DIALOG_LEASES = require("utils/dialogs/leases/dialogId");
/*
CONSTANTS
*/
var classButton = "small button leases right radius";
var idElementSchedActions = "#sched_temp_actions_body, #sched_inst_actions_body, #sched_service_create_actions_body";
/*
CONSTRUCTOR
*/
return {
html: _html,
actions: _actions
};
/*
FUNCTION DEFINITIONS
*/
function _html(){
var rtn = "";
if(
config &&
config.system_config &&
config.system_config.leases &&
(config.system_config.leases.suspend || config.system_config.leases.terminate)
){
rtn = $("<button />", {class: classButton, type:"button"}).append(
$("<i/>", {class: "fa fa-clock"})
).prop("outerHTML");
}
return rtn;
}
function parseVarToJqueryClass(constant){
return "."+constant.replace(/ /g, ".");
}
function _actions(form, res, act, callback){
if(
form &&
form.constructor &&
form.constructor.name &&
(form.constructor.name === "FormPanel" || form.constructor.name === "Panel") &&
config &&
config.system_config &&
config.system_config.leases
){
form.formContext.off("click", parseVarToJqueryClass(classButton));
form.formContext.on("click", parseVarToJqueryClass(classButton), function(e){
e.preventDefault();
var confLeases = config.system_config.leases;
var confLeasesKeys = Object.keys(confLeases);
var type = form.constructor.name;
var resource = null;
var action = null;
var template = null;
var id = null;
var showLeaseMessage = function(){
notifier.notifyCustom(Locale.tr("Added scheduled actions"),"");
};
var nowEpoch = function(){
epochStr = new Date();
return parseInt(epochStr.getTime(),10) / 1000;
};
//render leases for modal
var renderLeasesForModal = function(template, now) {
var rtn = "";
var last = 0;
if(confLeasesKeys && Array.isArray(confLeasesKeys)){
rtn = $("<table/>");
confLeasesKeys.forEach(function(schedAction){
if(confLeases[schedAction] && confLeases[schedAction].time){
var schedActionTime = parseInt(confLeases[schedAction].time,10);
var startTime = Math.round(now) + schedActionTime;
var time = "+"+(last === 0? startTime.toString() : startTime+last);
var nameAction = schedAction;
rtn = rtn.append(
$("<tr/>").append(
$("<td/>").text(nameAction).add(
$("<td/>").text(ScheduleActions.parseTime(time))
)
)
);
last = schedActionTime;
}
});
rtn = rtn.prop("outerHTML");
}
return rtn;
};
// confirm modal
var displayAlertCreateLeases = function(typeSubmit, template){
var now = template && template.STIME? nowEpoch() - parseInt(template.STIME,10) : 0;
Sunstone.getDialog(CONFIRM_DIALOG_LEASES).setParams({
header: Locale.tr("Scheduled actions to add"),
body : renderLeasesForModal(template, now),
submit : function(params) {
addInTemplate(typeSubmit, template, now);
return false;
}
});
Sunstone.getDialog(CONFIRM_DIALOG_LEASES).reset();
Sunstone.getDialog(CONFIRM_DIALOG_LEASES).show();
};
var addInTemplate = function(typeSubmit, template, now){
var type = typeSubmit? typeSubmit : "add";
var last = 0;
var pass = false;
var newSchedActions =[];
var index = (
template && template.SCHED_ACTION ?
(Array.isArray(template.SCHED_ACTION)? template.SCHED_ACTION.length : 1)
:
0
);
confLeasesKeys.forEach(function(schedAction){
if(confLeases[schedAction] && confLeases[schedAction].time){
var schedActionTime = parseInt(confLeases[schedAction].time,10);
var startTime = Math.round(now) + schedActionTime;
switch (type) {
case "add":
var newAction = {
TIME: "+"+(last === 0? startTime.toString() : startTime+last),
ACTION: schedAction
};
$(idElementSchedActions).prepend(ScheduleActions.fromJSONtoActionsTable(newAction));
break;
case "submit":
var newAction = {
ACTION: schedAction,
TIME: "+"+(last === 0? startTime.toString() : startTime+last),
ID: (index++).toString()
};
newSchedActions.push(
newAction
);
break;
case "add-service":
var newAction = {
TIME: "+"+(last === 0? startTime.toString() : startTime+last),
ACTION: schedAction
};
newSchedActions.push(newAction);
break;
default:
break;
}
last = schedActionTime;
pass = true;
}
});
if(type==="submit"){
template.SCHED_ACTION = (
template.SCHED_ACTION?
(Array.isArray(template.SCHED_ACTION)?
template.SCHED_ACTION.concat(newSchedActions)
:
[template.SCHED_ACTION].concat(newSchedActions))
:
newSchedActions
);
template = TemplateUtils.templateToString(template);
Sunstone.runAction("VM.update_template", id, template);
}
if(typeof callback === "function"){
callback(newSchedActions);
}
if(pass){
showLeaseMessage();
}
};
switch (type) {
case "FormPanel":
resource = form.resource || null;
action = form.action || null;
template = ( form.jsonTemplate ?
form.jsonTemplate
:
(
form.wizardElement ?
WizardFields.retrieve(form.wizardElement)
:
null
)
);
id = form.resourceId || null;
break;
case "Panel":
resource = res || null;
action = act || null;
template = (form.element && form.element.USER_TEMPLATE? form.element.USER_TEMPLATE : null );
id = (form.element && form.element.ID? form.element.ID : null);
// get history
if(form.element &&
form.element.HISTORY_RECORDS &&
form.element.HISTORY_RECORDS.HISTORY &&
form.element.HISTORY_RECORDS.HISTORY.STIME && template){
template.STIME = form.element.HISTORY_RECORDS.HISTORY.STIME;
}
break;
default:
break;
}
if(resource && action){
switch (resource.toLowerCase()) {
case "template":
if(template){
displayAlertCreateLeases("add", template);
}
break;
case "vm":
if(template){
if(action.toLowerCase() === "update" && id){
displayAlertCreateLeases("submit", template);
}else{
displayAlertCreateLeases("add", template);
}
}
break;
case "flow":
displayAlertCreateLeases("add-service");
break;
default:
break;
}
}
});
}else{
$(parseVarToJqueryClass(classButton)).off("click").remove();
}
}
});

View File

@ -1,4 +1,3 @@
/* eslint-disable quotes */
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
/* */
@ -16,17 +15,59 @@
/* -------------------------------------------------------------------------- */
define(function (require) {
/*
IMPORTS
*/
var Config = require("sunstone-config");
var Humanize = require("utils/humanize");
var Locale = require("utils/locale");
var Notifier = require("utils/notifier");
var TemplateUtils = require("utils/template-utils");
var Tips = require("utils/tips");
var Sunstone = require("sunstone");
var OpenNebulaVM = require('opennebula/vm');
/*
TEMPLATES
*/
var TemplateHTML = require("hbs!./schedule_action/html");
var TemplateTableHTML = require("hbs!./schedule_action/table");
var TemplateTableRowHTML = require("hbs!./schedule_action/table-row");
/*
GLOBAL VARIABLES
*/
var currentSchedID = 0;
var scheduleActionsArray = [];
/*
CONSTANTS
*/
var CONFIRM_DIALOG_LEASES = require("utils/dialogs/leases/dialogId");
var defaultHour = "12:30";
var defaultActions = [
"terminate",
"terminate-hard",
"hold",
"release",
"stop",
"suspend",
"resume",
"reboot",
"reboot-hard",
"poweroff",
"poweroff-hard",
"undeploy",
"undeploy-hard",
"snapshot-create",
"snapshot-delete",
"snapshot-revert",
"disk-snapshot-create",
"disk-snapshot-delete",
"disk-snapshot-revert"
];
var actionsWithARGS = [
'snapshot-create',
'snapshot-revert',
@ -36,12 +77,48 @@ define(function (require) {
'disk-snapshot-delete'
];
function _html(resource, leases = null, header = true) {
var clearEmptySpaces = function(e){
var value = e.val().replace(/\s/g, "");
e.val(value);
};
var options_date_picker={
dateFormat: "yy-mm-dd",
minDate: new Date(),
showOptions: { direction: "down" }
};
var options_hour_picker = {
title: Locale.tr("Hour"),
twentyFour: "true",
timeSeparator: ":",
beforeShow: clearEmptySpaces,
now: defaultHour
};
/*
FUNCTIONS
*/
/**
* This functions returns the HTML string for the Schedule Action Table.
*
* @param {('vms'|'inst'|'temp'|'flow'|'service_create')} resource - Resource.
* @param {boolean} leases - Can add leases?
* @param {boolean} header - Should generate the table header?
* @param {string} body - Body HTML string.
* @param {boolean} isVM - is it the VM view?
* @param {boolean} canAdd - Can add schedule actions?
* @returns - HTML string with the schedule action table.
*/
function _html(resource, leases = true, body = null, isVM = false, canAdd=true) {
this.res = resource;
return TemplateTableHTML({
header: header,
res: resource,
leases: leases
leases: leases,
body: body,
isVM: isVM,
canAdd: canAdd
});
}
@ -75,24 +152,6 @@ define(function (require) {
return date.join('-');
}
var clearEmpySpaces = function(e){
var value = e.val().replace(/\s/g, "");
e.val(value);
};
var options_date_picker={
dateFormat: "yy-mm-dd",
minDate: new Date(),
showOptions: { direction: "down" }
};
var options_hour_picker = {
title: Locale.tr("Hour"),
twentyFour: "true",
timeSeparator: ":",
beforeShow: clearEmpySpaces,
now: defaultHour
};
function addPickers(schedule,context){
if(schedule && context){
//input periodic scheduled date
@ -145,7 +204,7 @@ define(function (require) {
}
});
var schedule = $("#scheduling_" + this.res + "_actions_table tbody", context).append(TemplateHTML({
var schedule = $("#sched_" + this.res + "_actions_table tbody", context).append(TemplateHTML({
"actions": options,
"res": this.res
}));
@ -253,7 +312,7 @@ define(function (require) {
<span class=\"tip\">"+ Locale.tr("Comma separated list of days of the month to repeat the action on. Ex: 1,15,25 repeats the action every first, 15th and 25th day of the month") + " </span></div>";
break;
case "year":
input_html = "<div style=\"display: -webkit-box;\"><input style=\"margin-right: 4px;\" id=\"days_year_value\" type=\"text\" placeholder=\"0,365\"/>\
input_html = "<div style=\"display: -webkit-box;\"><input style=\"margin-right: 4px;\" id=\"days_year_value\" type=\"text\" placeholder=\"1,365\"/>\
<span class=\"tip\">"+ Locale.tr("Comma separated list of days of the year to repeat the action on. Ex: 1,30,330 repeats the action every first, 30th and 330th day of the year") + " </span></div>";
break;
case "hour":
@ -300,9 +359,7 @@ define(function (require) {
$("#time_input").removeAttr("data-invalid");
$("#time_input").removeAttr("class");
});
} catch (error) {
}
} catch (error) {}
}
function _fill(element, context){
@ -462,7 +519,7 @@ define(function (require) {
$("#end_type_ever").prop("checked",true).click();
break;
case "1":
$("#end_type_n_rep[value=n_rep]").click();
$("#end_type_date[value=n_rep]").click();
if(dataJSON.END_VALUE && dataJSON.END_VALUE.length){
$("#end_value_n_rep").val(dataJSON.END_VALUE);
}
@ -520,11 +577,11 @@ define(function (require) {
$("#time_input").val(defaultHour);
}
function _retrieve(context) {
$("#scheduling_" + this.res + "_actions_table .create", context).remove();
function _retrieve(context, isService=false) {
$("#sched_" + this.res + "_actions_table .create", context).remove();
var actionsJSON = [];
$("#scheduling_" + this.res + "_actions_table tbody tr").each(function (index) {
$("#sched_" + this.res + "_actions_table tbody tr").each(function (index) {
var first = $(this).children("td")[0];
if (!$("select", first).html()) { //table header
var actionJSON = {};
@ -533,7 +590,14 @@ define(function (require) {
actionJSON.ID = String(index);
}
}
if (!$.isEmptyObject(actionJSON)) { actionsJSON.push(actionJSON); };
if (!$.isEmptyObject(actionJSON)) {
var sched_action = isService ?
{ SCHED_ACTION: actionJSON}
:
actionJSON;
actionsJSON.push(sched_action);
};
});
return actionsJSON;
}
@ -662,11 +726,11 @@ define(function (require) {
var rawData = [disk_id_val,snap_id_val,snap_name_val];
sched_action.ARGS = rawData.filter(function (e) {return e;}).join();
}
$("#scheduling_" + this.res + "_actions_table .create", context).remove();
$("#scheduling_" + this.res + "_actions_table #relative_time_form", context).remove();
$("#scheduling_" + this.res + "_actions_table #no_relative_time_form", context).remove();
$("#sched_" + this.res + "_actions_table .create", context).remove();
$("#sched_" + this.res + "_actions_table #relative_time_form", context).remove();
$("#sched_" + this.res + "_actions_table #no_relative_time_form", context).remove();
$("#no_relative_time_form", context).addClass("hide");
$("#add_scheduling_" + this.res + "_action", context).removeAttr("disabled");
$("#add_sched_" + this.res + "_action", context).removeAttr("disabled");
return sched_action;
}
@ -697,7 +761,8 @@ define(function (require) {
return rtn;
}
function _fromJSONtoActionsTable(actions_array, action_id, minus) {
function _fromJSONtoActionsTable(actions_array, canEdit=true, canDelete=true) {
currentSchedID = 0;
var str = "";
if (!actions_array) {
@ -714,8 +779,8 @@ define(function (require) {
return "";
}
$.each(actions_array, function (index, scheduling_action) {
str += _fromJSONtoActionRow(scheduling_action, action_id, minus);
$.each(actions_array, function (_, schedule_action) {
str += _fromJSONtoActionRow(schedule_action, canEdit, canDelete);
});
return str;
@ -758,100 +823,558 @@ define(function (require) {
}
}
function _fromJSONtoActionRow(scheduling_action, action_id, minus) {
var time_str = Humanize.prettyTime(scheduling_action.TIME);
var rep_str = "";
var end_str = "";
/**
* This functions creates the HTML for the schedule actions rows.
*
* @param {object} schedule_action - Schedule action object.
* @param {boolean} canEdit - Is edit allowed?
* @param {bool ean} canDelete - Is delete allowed?
* @returns {string} - Row HTML for the given schedule action.
*/
function _fromJSONtoActionRow(schedule_action, canEdit=true, canDelete=true) {
var sched_obj = {};
var time_str = Humanize.prettyTime(schedule_action.TIME);
if (scheduling_action.REPEAT !== undefined) {
if (scheduling_action.REPEAT == 0) {
rep_str = "Weekly ";
} else if (scheduling_action.REPEAT == 1) {
rep_str = "Monthly ";
} else if (scheduling_action.REPEAT == 2) {
rep_str = "Yearly ";
} else if (scheduling_action.REPEAT == 3) {
rep_str = "Each " + scheduling_action.DAYS + " hours";
switch (schedule_action.REPEAT) {
case "0":
sched_obj.rep_str = Locale.tr("Weekly") +
" " +
Humanize.week_days(schedule_action.DAYS);
break;
case "1":
sched_obj.rep_str = Locale.tr("Monthly") +
" " +
schedule_action.DAYS;
break;
case "2":
sched_obj.rep_str = Locale.tr("Yearly") +
" " +
Humanize.week_days(schedule_action.DAYS);
break;
case "3":
sched_obj.rep_str = Locale.tr("Each") +
" " +
schedule_action.DAYS +
" " +
Locale.tr("hours");
break;
default:
break;
}
if (scheduling_action.REPEAT != 3) {
if (scheduling_action.REPEAT != 0) {
rep_str += scheduling_action.DAYS;
} else {
rep_str += Humanize.week_days(scheduling_action.DAYS);
}
}
switch (schedule_action.END_TYPE) {
case "0":
sched_obj.end_str = Locale.tr("None");
break;
case "1":
sched_obj.end_str = Locale.tr("After") +
" " +
schedule_action.END_VALUE +
" " +
Locale.tr("times");
break;
case "2":
sched_obj.end_str = Locale.tr("On") +
" " +
Humanize.prettyTime(schedule_action.END_VALUE);
break;
default:
break;
}
if (scheduling_action.END_TYPE !== undefined) {
if (scheduling_action.END_TYPE == 0) {
end_str = "None";
} else if (scheduling_action.END_TYPE == 1) {
end_str = "After " + scheduling_action.END_VALUE + " times";
} else if (scheduling_action.END_TYPE == 2) {
end_str = "on " + Humanize.prettyTime(scheduling_action.END_VALUE);
if (schedule_action.ID){
sched_obj.id = schedule_action.ID;
} else{
sched_obj.id = currentSchedID.toString();
}
currentSchedID++;
var time = String(schedule_action.TIME);
sched_obj.time = isNaN(time) ? time_str : (time && time.match(/^\+(.*)/gi) ? _time(time) : time_str);
sched_obj.done_str = schedule_action.DONE ? (Humanize.prettyTime(schedule_action.DONE)) : "";
sched_obj.message_str = schedule_action.MESSAGE ? schedule_action.MESSAGE : "";
sched_obj.action = JSON.stringify(schedule_action);
sched_obj.name = schedule_action.ACTION;
sched_obj.canEdit = canEdit && sched_obj.id;
sched_obj.canDelete = canDelete && sched_obj.id;
sched_obj.canEditOrDelete = (canEdit || canDelete) && sched_obj.id;
return TemplateTableRowHTML(sched_obj);
}
var str = "";
if (action_id === undefined) {
str += "<tr class='tr_action' data='" + JSON.stringify(scheduling_action) + "'>";
}
var time = String(scheduling_action.TIME);
time = isNaN(time) ? time_str : (time && time.match(/^\+(.*)/gi) ? _time(time) : time_str);
str += "<td class='action_row'>" + TemplateUtils.htmlEncode(scheduling_action.ACTION) + "</td>\
<td nowrap class='time_row'>" + time + "</td>\
<td nowrap class='rep_row'>" + rep_str + "</td>\
<td nowrap class='end_row'>" + end_str + "</td>";
if (minus === undefined) {
var action_id = scheduling_action.ID || '';
var update_sched = '';
if(action_id){
update_sched = "<button id='edit' class='small button btn-warning edit_action_x' data_id='"+action_id+"'><i class='fas fa-edit'></i></button>";
}
str += "<td colspan='3' style='text-align: right;'>\
<div style='display: flex;justify-content: flex-end;'>\
<div>\
<button id='minus' class='small button btn-danger remove_action_x'><i class='fas fa-trash-alt'></i></button>\
</div>\
<div>\
"+update_sched+"\
</div>\
</div>\
</td>\
</tr>";
}
return str;
}
/**
* This function gets the function
*
* @param {object} data - Schedule action information.
* @returns {string} - Schedule action string.
*/
function parseToRequestString(data) {
return data ? TemplateUtils.templateToString({ SCHED_ACTION: data }) : "";
}
var defaultActions = [
"terminate",
"terminate-hard",
"hold",
"release",
"stop",
"suspend",
"resume",
"reboot",
"reboot-hard",
"poweroff",
"poweroff-hard",
"undeploy",
"undeploy-hard",
"snapshot-create",
"snapshot-delete",
"snapshot-revert",
"disk-snapshot-create",
"disk-snapshot-delete",
"disk-snapshot-revert"
];
/**
* This function send the schedule action to each role.
*
* @param {Array} roles - Service Roles.
* @param {string} action - Action name.
* @param {object} sched_obj - Schedule action object.
* @param {{
* sched_id: string,
* callback: Function
* }} extraParams - Extra parameters with functions to execute after
* runAction and schedule action id.
*/
function sendSchedActionToServiceRoles(roles, action, sched_obj, extraParams={}) {
var {sched_id, callback} = extraParams;
roles.forEach(function(role){
var nodes = Array.isArray(role.nodes)? role.nodes : [role.nodes];
nodes.forEach(function(node) {
if (node && node.vm_info && node.vm_info.VM && node.vm_info.VM.ID){
switch (action) {
case "VM.sched_action_add":
case "VM.sched_action_update":
Sunstone.runAction(action, node.vm_info.VM.ID , sched_obj, callback);
break
case "VM.sched_action_delete":
Sunstone.runAction(action, node.vm_info.VM.ID, sched_id, callback);
break;
default:
break;
}
}
})
});
}
/**
* This function updates the Service datatable with the services information
* of the first service vm.
*
* @param {object} that - Service object.
* @param {string} selector - JQuery selector text.
* @param {Function} htmlFunction - Function to execute to get the HTML.
*/
function _updateServiceHTMLTable(that, selector, htmlFunction){
if (that.data &&
that.data[0] &&
that.data[0].nodes &&
that.data[0].nodes[0] &&
that.data[0].nodes[0].deploy_id >= 0){
OpenNebulaVM.show({
data : {
id: that.data[0].nodes[0].deploy_id
},
success: function(_, vmTemplate){
$(selector).html(
htmlFunction(vmTemplate.VM.TEMPLATE.SCHED_ACTION)
);
},
error: function(error){
Notifier.onError("VM: " +error);
}
});
}
}
/**
* This function setup the buttons in the Actions view.
*
* @param {('vms'|'inst'|'temp'|'flow'|'service_create')} resource - Resource.
* @param {object} context - Context object.
* @param {object} that - Object.
*/
function _setupButtons(resource, context, that){
scheduleActionsArray = [];
var CREATE = true;
function clear(){
CREATE = true;
}
function renderCreateForm(){
if(CREATE){
_htmlNewAction(defaultActions, context, resource);
_setup(context);
CREATE = false;
}
return false;
};
// Show options to add a new Schedule Action
context.off("click", "#add_sched_"+resource+"_action");
context.on("click" , "#add_sched_"+resource+"_action", function(e){
e.preventDefault();
renderCreateForm();
$("#edit_"+resource+"_action_json").hide();
$("#add_"+resource+"_action_json").show();
});
// Add new Schedule action
context.off("click", "#add_"+resource+"_action_json");
context.on("click" , "#add_"+resource+"_action_json", function(e) {
e.preventDefault();
var sched_action = { SCHED_ACTION: _retrieveNewAction(context) };
if (sched_action['SCHED_ACTION'] == false) {
return false;
}
var sched_template = TemplateUtils.templateToString(sched_action);
switch (resource) {
case "vms":
Sunstone.runAction("VM.sched_action_add", that.element.ID, sched_template);
break;
case "inst":
case "inst_flow":
case "service_create":
case "temp":
validateAndInitVariables(resource);
scheduleActionsArray.push(sched_action['SCHED_ACTION']);
$("#sched_" + resource + "_actions_body").html(
_getScheduleActionTableContent(scheduleActionsArray)
);
break;
case "flow":
var roles = Array.isArray(that.data)? that.data : [that.data];
var extraParams = {
callback: function() {
var selector = "#sched_" + resource + "_actions_body";
_updateServiceHTMLTable(that, selector, _getScheduleActionTableContent);
}
}
sendSchedActionToServiceRoles(roles, "VM.sched_action_add", sched_template, extraParams);
break;
default:
break;
}
clear();
});
// Show options to edit a Schedule Action
context.off("click", ".edit_action_x");
context.on("click", ".edit_action_x", function(e) {
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
renderCreateForm();
$("#edit_"+resource+"_action_json").show().attr("data_id", id);
$("#add_"+resource+"_action_json").hide();
_fill($(this),context);
}
});
// Edit Schedule action
context.off("click" , "#edit_"+ resource +"_action_json")
context.on("click" , "#edit_"+ resource +"_action_json", function(e){
e.preventDefault();
var id = $(this).attr("data_id");
if(id && id.length){
$(".wickedpicker").hide();
var sched_action = { SCHED_ACTION: _retrieveNewAction(context) };
if (sched_action['SCHED_ACTION'] != false) {
sched_action.SCHED_ACTION.ID = id;
var obj = {
"sched_id" : id,
"sched_template" : TemplateUtils.templateToString(sched_action)
}
switch (resource) {
case "vms":
Sunstone.runAction("VM.sched_action_update", that.element.ID, obj);
break;
case "inst":
case "inst_flow":
case "service_create":
case "temp":
validateAndInitVariables(resource);
delete sched_action.SCHED_ACTION.ID;
scheduleActionsArray[id] = sched_action['SCHED_ACTION'];
$("#sched_" + resource + "_actions_body").html(
_getScheduleActionTableContent(scheduleActionsArray)
);
break;
case "flow":
var roles = Array.isArray(that.data)? that.data : [that.data];
var extraParams = {
callback: function() {
var selector = "#sched_" + resource + "_actions_body";
_updateServiceHTMLTable(that, selector, _getScheduleActionTableContent);
}
}
sendSchedActionToServiceRoles(roles, "VM.sched_action_update", obj, extraParams);
break;
default:
break;
}
}
clear();
}
return false;
});
// Remove Schedule Action
context.off("click", ".remove_action_x");
context.on("click", ".remove_action_x", function(e) {
e.preventDefault();
var id = $(this).attr("data_id");
switch (resource) {
case "vms":
Sunstone.runAction("VM.sched_action_delete", that.element.ID, id);
break;
case "inst":
case "inst_flow":
case "service_create":
case "temp":
validateAndInitVariables(resource);
scheduleActionsArray.splice(id, 1);
$("#sched_" + resource + "_actions_body").html(
_getScheduleActionTableContent(scheduleActionsArray)
);
break;
case "flow":
var roles = Array.isArray(that.data)? that.data : [that.data];
var extraParams = {
sched_id: id,
callback: function() {
var selector = "#sched_" + resource + "_actions_body";
_updateServiceHTMLTable(that, selector, _getScheduleActionTableContent);
}
}
sendSchedActionToServiceRoles(roles, "VM.sched_action_delete", null, extraParams);
break;
default:
break;
}
});
context.off("click", "#leases_btn");
context.on("click", "#leases_btn", function(e) {
var confLeases = config.system_config.leases;
displayAlertCreateLeases(resource, that, confLeases);
});
}
/**
* Returns an HTML string with the json keys and values
*
* @param {Object[]} actions_array - Schedule action array.
* @returns {string} - HTML string with the json keys and values
*/
function _getScheduleActionTableContent(actions_array, template={}) {
scheduleActionsArray = actions_array || [];
var empty = "\
<tr id=\"no_actions_tr\">\
<td colspan=\"6\">" + Locale.tr("No actions to show") + "</td>\
</tr>";
if (!actions_array) {
return empty;
}
var sched_actions = Array.isArray(actions_array) ? actions_array : [actions_array];
if (!sched_actions.length) {
return empty;
}
var canEditOrDelete = !(
template &&
template.USER_TEMPLATE &&
template.USER_TEMPLATE.SERVICE_ID
);
return _fromJSONtoActionsTable(sched_actions, canEditOrDelete, canEditOrDelete);
}
/*
* LEASES FUNCTIONS
* This functions are here because in the end they add schedule actions
* to elements.
*/
/**
* This function adds the leases to an instantiated VM.
*
* @param {object[]} leasesArray - Array with all the actions to be added.
* @param {string} vm_id - ID from the VM to send the action.
*/
function addLeasesToVM(leasesArray, vm_id){
$.each(leasesArray, function(_, sched_action){
var sched_template = TemplateUtils.templateToString(sched_action);
Sunstone.runAction("VM.sched_action_add", vm_id, sched_template);
});
}
/**
* This function adds the leases to an instantiated Service.
*
* @param {Object[]} leassesArray - Array with all the actions to be added.
* @param {Object[]} roles - Service roles.
*/
function addLeasesToService(leassesArray, roles){
$.each(leassesArray, function(_, sched_action){
var sched_template = TemplateUtils.templateToString(sched_action);
var extraParams = {
callback: function() {
Sunstone.runAction("Service.refresh");
}
}
sendSchedActionToServiceRoles(
roles,
"VM.sched_action_add",
sched_template,
extraParams
);
});
}
/**
* This function converts the current date to epoch.
*
* @returns - Current date and hour in epoch format.
*/
function nowEpoch(){
epochStr = new Date();
return parseInt(epochStr.getTime(),10) / 1000;
};
function _leasesToScheduleActions(confLeases, now){
var last = 0;
var newSchedActions =[];
var confLeasesKeys = Object.keys(confLeases);
confLeasesKeys.forEach(function(schedAction){
if(confLeases[schedAction].time){
var schedActionTime = parseInt(confLeases[schedAction].time,10);
var startTime = Math.round(now) + schedActionTime;
var newAction = {
SCHED_ACTION : {
TIME: "+"+ (startTime+last).toString(),
ACTION: schedAction
}
};
newSchedActions.push(newAction);
last = schedActionTime;
}
});
return newSchedActions;
}
function addSchedActionTable(leasesArray, resource){
$.each(leasesArray, function(_, sched_action){
scheduleActionsArray.push(sched_action['SCHED_ACTION']);
});
$("#sched_" + resource + "_actions_body").html(
_getScheduleActionTableContent(scheduleActionsArray)
);
}
/**
* This function shows the modal to confirm the leases creation.
*
* @param {('vms'|'inst'|'temp'|'flow'|'service_create')} resource - Resource.
* @param {object} template - Resource template.
*/
function displayAlertCreateLeases(resource, that, confLeases){
var template = that.element;
var now = template && template.STIME? nowEpoch() - parseInt(template.STIME,10) : 0;
if (resource === "inst" ||
resource === "inst_flow" ||
resource === "service_create" ||
resource === "temp"
){
validateAndInitVariables(resource);
addSchedActionTable(
_leasesToScheduleActions(confLeases, now),
resource
);
}
else{
Sunstone.getDialog(CONFIRM_DIALOG_LEASES).setParams({
header: Locale.tr("Scheduled actions to add"),
body : renderLeasesForModal(now, confLeases),
submit : function(params) {
switch (resource) {
case 'vms':
addLeasesToVM(
_leasesToScheduleActions(confLeases, now),
template.ID
);
break;
case 'flow':
var roles = Array.isArray(that.data)? that.data : [that.data];
addLeasesToService(
_leasesToScheduleActions(confLeases, now),
roles
);
break;
default:
break;
}
return false;
}
});
Sunstone.getDialog(CONFIRM_DIALOG_LEASES).reset();
Sunstone.getDialog(CONFIRM_DIALOG_LEASES).show();
}
}
/**
* This function generates the content for the confirm dialog body.
*
* @param {number} now - Now time.
* @param {Object} confLeases - Object with the configured leases.
* @returns - HTML content for the modal.
*/
function renderLeasesForModal(now, confLeases) {
var rtn = "";
var last = 0;
var confLeasesKeys = Object.keys(confLeases);
if(confLeasesKeys && Array.isArray(confLeasesKeys)){
rtn = $("<table/>");
confLeasesKeys.forEach(function(actionName){
if(confLeases[actionName] && confLeases[actionName].time){
var schedActionTime = parseInt(confLeases[actionName].time,10);
var startTime = Math.round(now) + schedActionTime;
var time = "+"+(last === 0? startTime.toString() : startTime+last);
rtn = rtn.append(
$("<tr/>").append(
$("<td/>").text(actionName).add(
$("<td/>").text(_time(time))
)
)
);
last = schedActionTime;
}
});
rtn = rtn.prop("outerHTML");
}
return rtn;
}
/**
* This functions initializes the global variables with the current
* schedule actions.
*
* @param {('vms'|'inst'|'temp'|'flow'|'service_create')} resource - Resource.
*/
function validateAndInitVariables(resource){
if (!scheduleActionsArray.length){
$("#sched_" + resource + "_actions_body tr").each(function(_, sched_action){
data = sched_action.getAttribute("data");
if (data){
scheduleActionsArray.push(
JSON.parse(data)
);
}
});
}
}
return {
"fromJSONtoActionRow": _fromJSONtoActionRow,
@ -865,6 +1388,10 @@ define(function (require) {
"parseTime": _time,
"parseToRequestString": parseToRequestString,
"reset": _reset,
"defaultActions": defaultActions
"defaultActions": defaultActions,
"setupButtons": _setupButtons,
"updateServiceHTMLTable": _updateServiceHTMLTable,
"getScheduleActionTableContent": _getScheduleActionTableContent,
"sendSchedActionToServiceRoles": sendSchedActionToServiceRoles,
};
});

View File

@ -1,11 +1,18 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2021, OpenNebula Project, OpenNebula Systems }} {{! }}
{{! 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. }} {{! --------------------------------------------------------------------------
}}
{{! Copyright 2002-2021, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<tr class="create">
<td id="title">
@ -58,7 +65,7 @@ the License for the specific language governing permissions and }} {{! limitatio
</td>
<td>
<input id="schedule_type" type="checkbox">
<label>{{tr "Periodic"}}</label>
<label for="schedule_type">{{tr "Periodic"}}</label>
</td>
</tr>
<tr class="periodic create">
@ -106,7 +113,7 @@ the License for the specific language governing permissions and }} {{! limitatio
</tr>
<tr class="periodic create">
<td>
<input type="radio" name="end_type" id="end_type_n_rep" value="date">
<input type="radio" name="end_type" id="end_type_date" value="date">
<label for="end_type_date">{{tr "On"}}</label>
</td>
<td id="td_end_value_date">

View File

@ -0,0 +1,45 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2021, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! 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. }}
{{! -------------------------------------------------------------------------- }}
<tr class='tr_action_{{id}}' data='{{action}}'>
<td class='id_row'>{{id}}</td>
<td nowrap class='action_row'>{{name}}</td>
<td nowrap class='time_row'>{{time}}</td>
<td nowrap class='rep_row'>{{rep_str}}</td>
<td nowrap class='end_row'>{{end_str}}</td>
<td nowrap class='done_row'>{{done_str}}</td>
<td class='message_row'>{{message_str}}</td>
{{#if canEditOrDelete}}
<td colspan='3' style='text-align: right;'>
<div style='display: flex;justify-content: flex-end;'>
{{#if canDelete}}
<div>
<button id='minus' class='small button btn-danger remove_action_x' data_id='{{id}}'>
<i class='fas fa-trash-alt'></i>
</button>
</div>
{{/if}}
{{#if canEdit}}
<div>
<button id='edit' class='small button btn-warning edit_action_x' data_id='{{id}}'>
<i class='fas fa-edit'></i>
</button>
</div>
{{/if}}
</div>
</td>
{{/if}}
</tr>

View File

@ -21,21 +21,30 @@
}
</style>
<table id="scheduling_{{res}}_actions_table" class="info_table dataTable">
<table id="sched_{{res}}_actions_table" class="info_table dataTable">
<thead>
{{#if header}}
<tr>
<th> {{tr "ID"}}</th>
<th> {{tr "Action"}} </th>
<th> {{tr "Time"}} </th>
<th> {{tr "Rep"}} </th>
<th> {{tr "Ends"}} </th>
<th> {{tr "Done" }} </th>
{{#if isVM}} <th> {{tr "Message" }} </th> {{/if}}
<th colspan=""> </th>
{{#if canAdd}}
<th>
<button id="add_scheduling_{{res}}_action" class="button small success right radius"> {{tr "Add action"}} </button>
<button id="add_sched_{{res}}_action" class="button small success right radius"> {{tr "Add action"}} </button>
</th>
{{#if leases}} <th>{{{leases}}}</th> {{/if}}
</tr>
{{/if}}
{{#if leases}}
<th>
<button id="leases_btn" class="small button leases right radius" type="button"><i class="fa fa-clock"></i></button>
</th>
{{/if}}
</tr>
</thead>
<tbody id="sched_{{res}}_actions_body"></tbody>
<tbody id="sched_{{res}}_actions_body" class="sched_place">
{{#if body}} {{{body}}} {{/if}}
</tbody>
</table>