mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
Feature #3748: Refactor vCenter template import, add template import form-panel
This commit is contained in:
parent
c849192b31
commit
1719050284
@ -11,8 +11,8 @@ define(function(require) {
|
||||
var ResourceSelect = require('utils/resource-select');
|
||||
var OpenNebulaError = require('opennebula/error');
|
||||
var OpenNebulaHost = require('opennebula/host');
|
||||
var OpenNebulaTemplate = require('opennebula/template');
|
||||
var OpenNebulaVM = require('opennebula/vm');
|
||||
var VCenterTemplates = require('utils/vcenter/templates');
|
||||
var VCenterNetworks = require('utils/vcenter/networks');
|
||||
|
||||
/*
|
||||
@ -44,6 +44,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
this.vCenterNetworks = new VCenterNetworks();
|
||||
this.vCenterTemplates = new VCenterTemplates();
|
||||
|
||||
BaseFormPanel.call(this);
|
||||
};
|
||||
@ -65,7 +66,8 @@ define(function(require) {
|
||||
function _htmlWizard() {
|
||||
return TemplateWizardHTML({
|
||||
'formPanelId': this.formPanelId,
|
||||
'vcenterNetworksHTML': this.vCenterNetworks.html()
|
||||
'vCenterTemplatesHTML': this.vCenterTemplates.html(),
|
||||
'vCenterNetworksHTML': this.vCenterNetworks.html()
|
||||
});
|
||||
}
|
||||
|
||||
@ -174,19 +176,18 @@ define(function(require) {
|
||||
}
|
||||
});
|
||||
|
||||
var templates_container = $(".vcenter_templates", context);
|
||||
var vms_container = $(".vcenter_vms", context);
|
||||
|
||||
var vcenter_user = $("#vcenter_user", context).val();
|
||||
var vcenter_password = $("#vcenter_password", context).val();
|
||||
var vcenter_host = $("#vcenter_host", context).val();
|
||||
|
||||
fillVCenterTemplates({
|
||||
container: templates_container,
|
||||
vcenter_user: vcenter_user,
|
||||
vcenter_password: vcenter_password,
|
||||
vcenter_host: vcenter_host
|
||||
});
|
||||
that.vCenterTemplates.insert({
|
||||
container: context,
|
||||
vcenter_user: vcenter_user,
|
||||
vcenter_password: vcenter_password,
|
||||
vcenter_host: vcenter_host
|
||||
});
|
||||
|
||||
that.vCenterNetworks.insert({
|
||||
container: context,
|
||||
@ -262,48 +263,6 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
$.each($(".template_name:checked", context), function() {
|
||||
var template_context = $(this).closest(".vcenter_template");
|
||||
|
||||
$(".vcenter_template_result:not(.success)", template_context).html(
|
||||
'<span class="fa-stack fa-2x" style="color: #dfdfdf">' +
|
||||
'<i class="fa fa-cloud fa-stack-2x"></i>' +
|
||||
'<i class="fa fa-spinner fa-spin fa-stack-1x fa-inverse"></i>' +
|
||||
'</span>');
|
||||
|
||||
var template_json = {
|
||||
"vmtemplate": {
|
||||
"template_raw": $(this).data("one_template")
|
||||
}
|
||||
};
|
||||
|
||||
OpenNebulaTemplate.create({
|
||||
timeout: true,
|
||||
data: template_json,
|
||||
success: function(request, response) {
|
||||
$(".vcenter_template_result", template_context).addClass("success").html(
|
||||
'<span class="fa-stack fa-2x" style="color: #dfdfdf">' +
|
||||
'<i class="fa fa-cloud fa-stack-2x"></i>' +
|
||||
'<i class="fa fa-check fa-stack-1x fa-inverse"></i>' +
|
||||
'</span>');
|
||||
|
||||
$(".vcenter_template_response", template_context).html('<p style="font-size:12px" class="running-color">' +
|
||||
Locale.tr("Template created successfully") + ' ID:' + response.VMTEMPLATE.ID +
|
||||
'</p>');
|
||||
},
|
||||
error: function (request, error_json) {
|
||||
$(".vcenter_template_result", template_context).html('<span class="fa-stack fa-2x" style="color: #dfdfdf">' +
|
||||
'<i class="fa fa-cloud fa-stack-2x"></i>' +
|
||||
'<i class="fa fa-warning fa-stack-1x fa-inverse"></i>' +
|
||||
'</span>');
|
||||
|
||||
$(".vcenter_template_response", template_context).html('<p style="font-size:12px" class="error-color">' +
|
||||
(error_json.error.message || Locale.tr("Cannot contact server: is it running and reachable?")) +
|
||||
'</p>');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$.each($(".vm_name:checked", context), function() {
|
||||
var vm_context = $(this).closest(".vcenter_vm");
|
||||
|
||||
@ -356,6 +315,8 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
that.vCenterTemplates.import();
|
||||
|
||||
that.vCenterNetworks.import();
|
||||
|
||||
return false;
|
||||
@ -442,91 +403,4 @@ define(function(require) {
|
||||
ResourceSelect.insert('#host_cluster_id', context, "Cluster", cluster_id, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieve the list of templates from vCenter and fill the container with them
|
||||
|
||||
opts = {
|
||||
datacenter: "Datacenter Name",
|
||||
cluster: "Cluster Name",
|
||||
container: Jquery div to inject the html,
|
||||
vcenter_user: vCenter Username,
|
||||
vcenter_password: vCenter Password,
|
||||
vcenter_host: vCenter Host
|
||||
}
|
||||
*/
|
||||
function fillVCenterTemplates(opts) {
|
||||
var path = '/vcenter/templates';
|
||||
|
||||
opts.container.show();
|
||||
|
||||
$(".accordion_advanced_toggle", opts.container).trigger("click");
|
||||
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: "GET",
|
||||
data: {timeout: false},
|
||||
dataType: "json",
|
||||
headers: {
|
||||
"X_VCENTER_USER": opts.vcenter_user,
|
||||
"X_VCENTER_PASSWORD": opts.vcenter_password,
|
||||
"X_VCENTER_HOST": opts.vcenter_host
|
||||
},
|
||||
success: function(response){
|
||||
$(".content", opts.container).html("");
|
||||
|
||||
$('<div class="row">' +
|
||||
'<div class="large-12 columns">' +
|
||||
'<p style="color: #999">' + Locale.tr("Please select the vCenter Templates to be imported to OpenNebula.") + '</p>' +
|
||||
'</div>' +
|
||||
'</div>').appendTo($(".content", opts.container))
|
||||
|
||||
$.each(response, function(datacenter_name, templates){
|
||||
$('<div class="row">' +
|
||||
'<div class="large-12 columns">' +
|
||||
'<h5>' +
|
||||
datacenter_name + ' ' + Locale.tr("DataCenter") +
|
||||
'</h5>' +
|
||||
'</div>' +
|
||||
'</div>').appendTo($(".content", opts.container))
|
||||
|
||||
if (templates.length == 0) {
|
||||
$('<div class="row">' +
|
||||
'<div class="large-12 columns">' +
|
||||
'<label>' +
|
||||
Locale.tr("No new templates found in this DataCenter") +
|
||||
'</label>' +
|
||||
'</div>' +
|
||||
'</div>').appendTo($(".content", opts.container))
|
||||
} else {
|
||||
$.each(templates, function(id, template){
|
||||
var trow = $('<div class="vcenter_template">' +
|
||||
'<div class="row">' +
|
||||
'<div class="large-10 columns">' +
|
||||
'<label>' +
|
||||
'<input type="checkbox" class="template_name" checked/> ' +
|
||||
template.name + ' <span style="color: #999">' + template.host + '</span>' +
|
||||
'</label>' +
|
||||
'<div class="large-12 columns vcenter_template_response">'+
|
||||
'</div>'+
|
||||
'</div>' +
|
||||
'<div class="large-2 columns vcenter_template_result">'+
|
||||
'</div>'+
|
||||
'</div>'+
|
||||
'</div>').appendTo($(".content", opts.container))
|
||||
|
||||
$(".template_name", trow).data("template_name", template.name)
|
||||
$(".template_name", trow).data("one_template", template.one)
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
error: function(response){
|
||||
opts.container.hide();
|
||||
Notifier.onError({}, OpenNebulaError(response));
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
@ -129,20 +129,15 @@
|
||||
{{/advancedSection}}
|
||||
</div>
|
||||
<br>
|
||||
<div class="vcenter_templates hidden">
|
||||
{{#advancedSection (tr "Templates") }}
|
||||
<span class="fa-stack fa-2x" style="color: #dfdfdf">
|
||||
<i class="fa fa-cloud fa-stack-2x"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
{{/advancedSection}}
|
||||
<div>
|
||||
{{{vCenterTemplatesHTML}}}
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div class="vcenter_vms"></div>
|
||||
<br>
|
||||
<div>
|
||||
{{{vcenterNetworksHTML}}}
|
||||
{{{vCenterNetworksHTML}}}
|
||||
</div>
|
||||
<div class="row import_vcenter_clusters_div hidden">
|
||||
<div class="large-12 columns">
|
||||
|
@ -18,7 +18,8 @@ define(function(require) {
|
||||
];
|
||||
|
||||
var _formPanels = [
|
||||
require('./templates-tab/form-panels/create')
|
||||
require('./templates-tab/form-panels/create'),
|
||||
require('./templates-tab/form-panels/import')
|
||||
]
|
||||
|
||||
var Tab = {
|
||||
|
@ -10,6 +10,8 @@ define(function(require) {
|
||||
var CREATE_DIALOG_ID = require('./form-panels/create/formPanelId');
|
||||
var CLONE_DIALOG_ID = require('./dialogs/clone/dialogId');
|
||||
var INSTANTIATE_DIALOG_ID = require('./dialogs/instantiate/dialogId');
|
||||
var IMPORT_DIALOG_ID = require('./form-panels/import/formPanelId');
|
||||
|
||||
var XML_ROOT = "VMTEMPLATE"
|
||||
var RESOURCE = "Template"
|
||||
|
||||
@ -41,10 +43,10 @@ define(function(require) {
|
||||
}
|
||||
},
|
||||
"Template.import_dialog" : {
|
||||
type: "create",
|
||||
type: "custom",
|
||||
call: function() {
|
||||
// TODO popUpTemplateImportDialog();
|
||||
}
|
||||
Sunstone.showFormPanel(TAB_ID, IMPORT_DIALOG_ID, "import");
|
||||
}
|
||||
},
|
||||
"Template.update_dialog" : {
|
||||
type: "custom",
|
||||
|
104
src/sunstone/public/app/tabs/templates-tab/form-panels/import.js
Normal file
104
src/sunstone/public/app/tabs/templates-tab/form-panels/import.js
Normal file
@ -0,0 +1,104 @@
|
||||
define(function(require) {
|
||||
/*
|
||||
DEPENDENCIES
|
||||
*/
|
||||
|
||||
require('foundation.tab');
|
||||
var BaseFormPanel = require('utils/form-panels/form-panel');
|
||||
var Sunstone = require('sunstone');
|
||||
var Locale = require('utils/locale');
|
||||
var VCenterTemplates = require('utils/vcenter/templates');
|
||||
|
||||
/*
|
||||
TEMPLATES
|
||||
*/
|
||||
|
||||
var TemplateHTML = require('hbs!./import/html');
|
||||
|
||||
/*
|
||||
CONSTANTS
|
||||
*/
|
||||
|
||||
var FORM_PANEL_ID = require('./import/formPanelId');
|
||||
var TAB_ID = require('../tabId');
|
||||
|
||||
/*
|
||||
CONSTRUCTOR
|
||||
*/
|
||||
|
||||
function FormPanel() {
|
||||
this.formPanelId = FORM_PANEL_ID;
|
||||
this.tabId = TAB_ID;
|
||||
this.actions = {
|
||||
'import': {
|
||||
'title': Locale.tr("Import vCenter VM Templates"),
|
||||
'buttonText': Locale.tr("Import"),
|
||||
'resetButton': true
|
||||
}
|
||||
};
|
||||
|
||||
this.vCenterTemplates = new VCenterTemplates();
|
||||
|
||||
BaseFormPanel.call(this);
|
||||
}
|
||||
|
||||
FormPanel.FORM_PANEL_ID = FORM_PANEL_ID;
|
||||
FormPanel.prototype = Object.create(BaseFormPanel.prototype);
|
||||
FormPanel.prototype.constructor = FormPanel;
|
||||
FormPanel.prototype.htmlWizard = _htmlWizard;
|
||||
FormPanel.prototype.submitWizard = _submitWizard;
|
||||
FormPanel.prototype.onShow = _onShow;
|
||||
FormPanel.prototype.setup = _setup;
|
||||
|
||||
return FormPanel;
|
||||
|
||||
/*
|
||||
FUNCTION DEFINITIONS
|
||||
*/
|
||||
|
||||
function _htmlWizard() {
|
||||
return TemplateHTML({
|
||||
'formPanelId': this.formPanelId,
|
||||
'vCenterTemplatesHTML': this.vCenterTemplates.html()
|
||||
});
|
||||
}
|
||||
|
||||
function _setup(context) {
|
||||
var that = this;
|
||||
|
||||
Sunstone.disableFormPanelSubmit(TAB_ID);
|
||||
|
||||
$("#get_vcenter_templates", context).on("click", function(){
|
||||
Sunstone.enableFormPanelSubmit(TAB_ID);
|
||||
|
||||
var vcenter_user = $("#vcenter_user", context).val();
|
||||
var vcenter_password = $("#vcenter_password", context).val();
|
||||
var vcenter_host = $("#vcenter_host", context).val();
|
||||
|
||||
that.vCenterTemplates.insert({
|
||||
container: context,
|
||||
vcenter_user: vcenter_user,
|
||||
vcenter_password: vcenter_password,
|
||||
vcenter_host: vcenter_host
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _submitWizard(context) {
|
||||
var that = this;
|
||||
|
||||
Sunstone.hideFormPanelLoading(TAB_ID);
|
||||
Sunstone.disableFormPanelSubmit(TAB_ID);
|
||||
|
||||
this.vCenterTemplates.import();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _onShow(context) {
|
||||
}
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
define(function(require){
|
||||
return 'importTemplateForm';
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
<form data-abide="ajax" id="{{formPanelId}}Wizard" class="custom creation">
|
||||
<div class="row collapse vcenter_credentials">
|
||||
<div class="row">
|
||||
<div class="large-6 columns">
|
||||
<label for="vcenter_user">{{tr "User"}}</label>
|
||||
<input type="text" name="vcenter_user" id="vcenter_user" />
|
||||
</div>
|
||||
<div class="large-6 columns">
|
||||
<label for="vcenter_host">{{tr "Hostname"}}</label>
|
||||
<input type="text" name="vcenter_host" id="vcenter_host" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="large-6 columns">
|
||||
<label for="vcenter_password">{{tr "Password"}}</label>
|
||||
<input type="password" name="vcenter_password" id="vcenter_password" />
|
||||
</div>
|
||||
<div class="large-6 columns">
|
||||
<br>
|
||||
<a class="button radius small right" id="get_vcenter_templates">
|
||||
{{tr "Get VM Templates"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row collapse">
|
||||
{{{vCenterTemplatesHTML}}}
|
||||
</div>
|
||||
</form>
|
@ -59,7 +59,7 @@ define(function(require) {
|
||||
function _htmlWizard() {
|
||||
return TemplateHTML({
|
||||
'formPanelId': this.formPanelId,
|
||||
'vcenterNetworksHTML': this.vCenterNetworks.html()
|
||||
'vCenterNetworksHTML': this.vCenterNetworks.html()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
<form data-abide="ajax" id="{{formPanelId}}Advanced" class="custom creation">
|
||||
<div class="row">
|
||||
<div class="large-12 columns">
|
||||
<p>{{tr "Write the Virtual Network template here"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="large-12 columns">
|
||||
<textarea id="template" rows="15" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -24,6 +24,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row collapse">
|
||||
{{{vcenterNetworksHTML}}}
|
||||
{{{vCenterNetworksHTML}}}
|
||||
</div>
|
||||
</form>
|
158
src/sunstone/public/app/utils/vcenter/templates.js
Normal file
158
src/sunstone/public/app/utils/vcenter/templates.js
Normal file
@ -0,0 +1,158 @@
|
||||
define(function(require) {
|
||||
// Dependencies
|
||||
var Locale = require('utils/locale');
|
||||
var OpenNebulaTemplate = require('opennebula/template');
|
||||
var OpenNebulaError = require('opennebula/error');
|
||||
|
||||
var TemplateHTML = require('hbs!./templates/html');
|
||||
|
||||
function VCenterTemplates() {
|
||||
return this;
|
||||
}
|
||||
|
||||
VCenterTemplates.prototype = {
|
||||
'html': _html,
|
||||
'insert': _fillVCenterTemplates,
|
||||
'import': _import
|
||||
};
|
||||
VCenterTemplates.prototype.constructor = VCenterTemplates;
|
||||
|
||||
return VCenterTemplates;
|
||||
|
||||
function _html() {
|
||||
return '<div class="vcenter_templates hidden"></div>';
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieve the list of templates from vCenter and fill the container with them
|
||||
|
||||
opts = {
|
||||
datacenter: "Datacenter Name",
|
||||
cluster: "Cluster Name",
|
||||
container: Jquery div to inject the html,
|
||||
vcenter_user: vCenter Username,
|
||||
vcenter_password: vCenter Password,
|
||||
vcenter_host: vCenter Host
|
||||
}
|
||||
*/
|
||||
function _fillVCenterTemplates(opts) {
|
||||
var path = '/vcenter/templates';
|
||||
|
||||
var context = $(".vcenter_templates", opts.container);
|
||||
|
||||
context.html( TemplateHTML({}) );
|
||||
|
||||
context.show();
|
||||
|
||||
$(".accordion_advanced_toggle", context).trigger("click");
|
||||
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: "GET",
|
||||
data: {timeout: false},
|
||||
dataType: "json",
|
||||
headers: {
|
||||
"X_VCENTER_USER": opts.vcenter_user,
|
||||
"X_VCENTER_PASSWORD": opts.vcenter_password,
|
||||
"X_VCENTER_HOST": opts.vcenter_host
|
||||
},
|
||||
success: function(response){
|
||||
$(".content", context).html("");
|
||||
|
||||
$('<div class="row">' +
|
||||
'<div class="large-12 columns">' +
|
||||
'<p style="color: #999">' + Locale.tr("Please select the vCenter Templates to be imported to OpenNebula.") + '</p>' +
|
||||
'</div>' +
|
||||
'</div>').appendTo($(".content", context))
|
||||
|
||||
$.each(response, function(datacenter_name, templates){
|
||||
$('<div class="row">' +
|
||||
'<div class="large-12 columns">' +
|
||||
'<h5>' +
|
||||
datacenter_name + ' ' + Locale.tr("DataCenter") +
|
||||
'</h5>' +
|
||||
'</div>' +
|
||||
'</div>').appendTo($(".content", context))
|
||||
|
||||
if (templates.length == 0) {
|
||||
$('<div class="row">' +
|
||||
'<div class="large-12 columns">' +
|
||||
'<label>' +
|
||||
Locale.tr("No new templates found in this DataCenter") +
|
||||
'</label>' +
|
||||
'</div>' +
|
||||
'</div>').appendTo($(".content", context))
|
||||
} else {
|
||||
$.each(templates, function(id, template){
|
||||
var trow = $('<div class="vcenter_template">' +
|
||||
'<div class="row">' +
|
||||
'<div class="large-10 columns">' +
|
||||
'<label>' +
|
||||
'<input type="checkbox" class="template_name" checked/> ' +
|
||||
template.name + ' <span style="color: #999">' + template.host + '</span>' +
|
||||
'</label>' +
|
||||
'<div class="large-12 columns vcenter_template_response">'+
|
||||
'</div>'+
|
||||
'</div>' +
|
||||
'<div class="large-2 columns vcenter_template_result">'+
|
||||
'</div>'+
|
||||
'</div>'+
|
||||
'</div>').appendTo($(".content", context))
|
||||
|
||||
$(".template_name", trow).data("template_name", template.name)
|
||||
$(".template_name", trow).data("one_template", template.one)
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
error: function(response){
|
||||
context.hide();
|
||||
Notifier.onError({}, OpenNebulaError(response));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _import(context) {
|
||||
$.each($(".template_name:checked", context), function() {
|
||||
var template_context = $(this).closest(".vcenter_template");
|
||||
|
||||
$(".vcenter_template_result:not(.success)", template_context).html(
|
||||
'<span class="fa-stack fa-2x" style="color: #dfdfdf">' +
|
||||
'<i class="fa fa-cloud fa-stack-2x"></i>' +
|
||||
'<i class="fa fa-spinner fa-spin fa-stack-1x fa-inverse"></i>' +
|
||||
'</span>');
|
||||
|
||||
var template_json = {
|
||||
"vmtemplate": {
|
||||
"template_raw": $(this).data("one_template")
|
||||
}
|
||||
};
|
||||
|
||||
OpenNebulaTemplate.create({
|
||||
timeout: true,
|
||||
data: template_json,
|
||||
success: function(request, response) {
|
||||
$(".vcenter_template_result", template_context).addClass("success").html(
|
||||
'<span class="fa-stack fa-2x" style="color: #dfdfdf">' +
|
||||
'<i class="fa fa-cloud fa-stack-2x"></i>' +
|
||||
'<i class="fa fa-check fa-stack-1x fa-inverse"></i>' +
|
||||
'</span>');
|
||||
|
||||
$(".vcenter_template_response", template_context).html('<p style="font-size:12px" class="running-color">' +
|
||||
Locale.tr("Template created successfully") + ' ID:' + response.VMTEMPLATE.ID +
|
||||
'</p>');
|
||||
},
|
||||
error: function (request, error_json) {
|
||||
$(".vcenter_template_result", template_context).html('<span class="fa-stack fa-2x" style="color: #dfdfdf">' +
|
||||
'<i class="fa fa-cloud fa-stack-2x"></i>' +
|
||||
'<i class="fa fa-warning fa-stack-1x fa-inverse"></i>' +
|
||||
'</span>');
|
||||
|
||||
$(".vcenter_template_response", template_context).html('<p style="font-size:12px" class="error-color">' +
|
||||
(error_json.error.message || Locale.tr("Cannot contact server: is it running and reachable?")) +
|
||||
'</p>');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
6
src/sunstone/public/app/utils/vcenter/templates/html.hbs
Normal file
6
src/sunstone/public/app/utils/vcenter/templates/html.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
{{#advancedSection (tr "Templates") }}
|
||||
<span class="fa-stack fa-2x" style="color: #dfdfdf">
|
||||
<i class="fa fa-cloud fa-stack-2x"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
{{/advancedSection}}
|
Loading…
x
Reference in New Issue
Block a user