mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-27 07:44:04 +02:00
Merge branch 'feature/javascript-autoload-components-4456'
resolves #4456
This commit is contained in:
commit
7af982aaa5
@ -30,6 +30,7 @@
|
|||||||
use \Zend_Controller_Action_Exception as ActionException;
|
use \Zend_Controller_Action_Exception as ActionException;
|
||||||
use \Icinga\Web\Controller\ActionController;
|
use \Icinga\Web\Controller\ActionController;
|
||||||
use \Icinga\Application\Icinga;
|
use \Icinga\Application\Icinga;
|
||||||
|
use \Icinga\Application\Logger;
|
||||||
|
|
||||||
class StaticController extends ActionController
|
class StaticController extends ActionController
|
||||||
{
|
{
|
||||||
@ -111,14 +112,25 @@ class StaticController extends ActionController
|
|||||||
$module = $this->_getParam('module_name');
|
$module = $this->_getParam('module_name');
|
||||||
$file = $this->_getParam('file');
|
$file = $this->_getParam('file');
|
||||||
|
|
||||||
|
if ($module == 'app') {
|
||||||
|
$basedir = Icinga::app()->getApplicationDir('../public/js/icinga/components/');
|
||||||
|
$filePath = $basedir . $file;
|
||||||
|
} else {
|
||||||
if (!Icinga::app()->getModuleManager()->hasEnabled($module)) {
|
if (!Icinga::app()->getModuleManager()->hasEnabled($module)) {
|
||||||
echo '/** Module not enabled **/';
|
Logger::error(
|
||||||
|
'Non-existing frontend component "' . $module . '/' . $file
|
||||||
|
. '" was requested. The module "' . $module . '" does not exist or is not active.');
|
||||||
|
echo "/** Module not enabled **/";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$basedir = Icinga::app()->getModuleManager()->getModule($module)->getBaseDir();
|
$basedir = Icinga::app()->getModuleManager()->getModule($module)->getBaseDir();
|
||||||
|
|
||||||
$filePath = $basedir . '/public/js/' . $file;
|
$filePath = $basedir . '/public/js/' . $file;
|
||||||
|
}
|
||||||
|
|
||||||
if (!file_exists($filePath)) {
|
if (!file_exists($filePath)) {
|
||||||
|
Logger::error(
|
||||||
|
'Non-existing frontend component "' . $module . '/' . $file
|
||||||
|
. '" was requested, which would resolve to the the path: ' . $filePath);
|
||||||
echo '/** Module has no js files **/';
|
echo '/** Module has no js files **/';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
130
doc/components.md
Normal file
130
doc/components.md
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# Frontend components
|
||||||
|
|
||||||
|
Frontend components are JavaScript modules that can be required directly through the HTML markup of
|
||||||
|
your view, to provide additional functionality for the user. Although its best practice to
|
||||||
|
make all features available without JavaScript, these components can be used to provide a richer
|
||||||
|
and more comfortable user experience in case JavaScript is available.
|
||||||
|
|
||||||
|
There is a certain set of frontend components which come directly with the Icinga2-Web core application,
|
||||||
|
but it is also possible to define new components directly in Icinga2-Web modules.
|
||||||
|
|
||||||
|
|
||||||
|
## How do components work?
|
||||||
|
|
||||||
|
Components are defined in JavaScript files that provide a set of functionality that will be added to the
|
||||||
|
targeted HTML node. Icinga2-Web uses [RequireJS](http://requirejs.org) to load
|
||||||
|
all frontend components, so each frontend component is in fact
|
||||||
|
[defined exactly like a RequireJS-Module](http://requirejs.org/docs/api.html#define) .
|
||||||
|
|
||||||
|
The important difference to plain RequireJS is, that the loading and execution of these components is
|
||||||
|
done automatically through the HTML markup. The attribute *data-icinga-component* in a DIV
|
||||||
|
element will indicate that this element is a container for a frontend component and will trigger
|
||||||
|
the component loader to create a component instance for this HTML node. The component loader
|
||||||
|
keeps track of all available components and makes it possible to retrieve this instance when needed.
|
||||||
|
|
||||||
|
|
||||||
|
### Component names
|
||||||
|
|
||||||
|
A component name consists of two parts: the namespace and the name of the component itself. The component
|
||||||
|
is named exactly like its JavaScript file, while the namespace is the name of the Icinga2-Web module that contains
|
||||||
|
the component. Each Icinga2-Web module can contain its own components in the folder *public/js*.
|
||||||
|
|
||||||
|
<module>/<component>
|
||||||
|
|
||||||
|
|
||||||
|
NOTE: The namespace used for modules defined in the Icinga2-Web core application is "app". In opposition to
|
||||||
|
the modules the core application keeps its modules located in *public/js/icinga/components*
|
||||||
|
instead of *public/js*.
|
||||||
|
|
||||||
|
|
||||||
|
#### Example Names
|
||||||
|
|
||||||
|
|
||||||
|
The full name for the component *modules/monitoring/public/js/someComponent.js* in the module "monitoring" would be:
|
||||||
|
|
||||||
|
"monitoring/someComponent"
|
||||||
|
|
||||||
|
|
||||||
|
The full component name for the component *public/js/icinga/components/datetime.js* in the Icinga2-Web
|
||||||
|
core application would:
|
||||||
|
|
||||||
|
"app/datetime"
|
||||||
|
|
||||||
|
|
||||||
|
## Creating a component
|
||||||
|
|
||||||
|
As described in the chapters above, components are defined exactly like RequireJS modules, but
|
||||||
|
with the additional requirement that they must always return a class constructor. The component below will
|
||||||
|
search all date pickers, set the time format and create a JavaScript date picker when there is no native one
|
||||||
|
available.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that our date/time controls will work on every browser (natively or javascript based)
|
||||||
|
*/
|
||||||
|
define(['jquery', 'datetimepicker'], function($) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var DateTimePicker = function(target) {
|
||||||
|
$(target).find('.datetime input')
|
||||||
|
.attr('data-format', 'yyyy-MM-dd hh:mm:ss');
|
||||||
|
|
||||||
|
$(target).find('.datetime')
|
||||||
|
.addClass('input-append')
|
||||||
|
.append('<span class="add-on">' +
|
||||||
|
'<i data-time-icon="icon-time" data-date-icon="icon-calendar"></i></span>')
|
||||||
|
.datetimepicker();
|
||||||
|
};
|
||||||
|
return DateTimePicker;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
## Loading a component
|
||||||
|
|
||||||
|
The following code will load the module *datetime*, which will ensure that there is always a datetime-picker
|
||||||
|
with right time-format available.
|
||||||
|
|
||||||
|
<div id="date-time-picker" data-icinga-component="app/datetime">
|
||||||
|
|
||||||
|
<div class="datetime">
|
||||||
|
<input data-format="dd/MM/yyyy hh:mm:ss" type="text"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
### Component ids
|
||||||
|
|
||||||
|
When an ID is assigned to the HTML element, it will be used by the component loader to reference this
|
||||||
|
component. Otherwise an ID in the form "icinga-component-<ID>" will be created and the ID attribute in the
|
||||||
|
HTML Element will be updated accordingly.
|
||||||
|
|
||||||
|
|
||||||
|
### Component "target"
|
||||||
|
|
||||||
|
The div-element with the *data-icinga-component* will be used as the "target" for the loaded component,
|
||||||
|
which means that the component will perform its actions on this HTML node.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Retrieving a component
|
||||||
|
|
||||||
|
Sometimes it can be necessary to retrieve the instances of the components itself, for example when they implement
|
||||||
|
additional functions that can be called. The component loader is available in the Icinga object and can be used
|
||||||
|
to retrieve component instances using their ID or their full component name.
|
||||||
|
|
||||||
|
|
||||||
|
## By component id
|
||||||
|
|
||||||
|
var component = Icinga.components.getById("component-id");
|
||||||
|
component.doSomething();
|
||||||
|
|
||||||
|
|
||||||
|
## By full component name
|
||||||
|
|
||||||
|
var components = Icinga.components.getByType("app/datetime");
|
||||||
|
// ... do something with every component of the type app/datetime
|
||||||
|
|
||||||
|
## All components
|
||||||
|
|
||||||
|
var components = Icinga.components.getComponents();
|
||||||
|
// ... do something with every component
|
148
public/js/icinga/componentLoader.js
Normal file
148
public/js/icinga/componentLoader.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A module to load and manage frontend components
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
define(['jquery', 'logging', 'icinga/componentRegistry'], function ($, log, registry) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var ComponentLoader = function() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the component with the given type and attach it to the target
|
||||||
|
*
|
||||||
|
* @param {String} cmpType The component type to load '<module>/<component>'
|
||||||
|
* @param {HTMLElement} target The targeted dom node
|
||||||
|
* @param {function} fin The called when the component was successfully loaded
|
||||||
|
* @param {function} err The error-callback
|
||||||
|
*/
|
||||||
|
var loadComponent = function(cmpType, target, fin, err) {
|
||||||
|
requirejs(
|
||||||
|
['modules/' + cmpType],
|
||||||
|
function (Cmp) {
|
||||||
|
var cmp;
|
||||||
|
try {
|
||||||
|
cmp = new Cmp(target);
|
||||||
|
} catch (e) {
|
||||||
|
log.emergency(e);
|
||||||
|
err(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fin) {
|
||||||
|
fin(cmp);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (ex) {
|
||||||
|
if (!ex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.emergency('Component "' + cmpType + '" could not be loaded.', ex);
|
||||||
|
if (err) {
|
||||||
|
err(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all new components and remove components that were removed from
|
||||||
|
* the DOM from the internal registry
|
||||||
|
*
|
||||||
|
* @param {function} fin Called when the loading is completed
|
||||||
|
*/
|
||||||
|
this.load = function(fin) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Count the amount of pending callbacks to make sure everything is loaded
|
||||||
|
* when calling the garbage collection.
|
||||||
|
*/
|
||||||
|
var pendingFns = 1;
|
||||||
|
|
||||||
|
var finalize = function() {
|
||||||
|
pendingFns--;
|
||||||
|
/*
|
||||||
|
* Only return when all components are loaded
|
||||||
|
*/
|
||||||
|
if (pendingFns === 0) {
|
||||||
|
registry.removeInactive();
|
||||||
|
if (fin) {
|
||||||
|
fin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.markAllInactive();
|
||||||
|
|
||||||
|
$('div[data-icinga-component]')
|
||||||
|
.each(function(index, el) {
|
||||||
|
var type = $(el).attr('data-icinga-component');
|
||||||
|
pendingFns++;
|
||||||
|
|
||||||
|
if (!el.id || !registry.getById(el.id)) {
|
||||||
|
loadComponent(
|
||||||
|
type,
|
||||||
|
el,
|
||||||
|
function(cmp) {
|
||||||
|
var id = registry.add(cmp, el.id, type);
|
||||||
|
registry.markActive(id);
|
||||||
|
el.id = id;
|
||||||
|
finalize();
|
||||||
|
},
|
||||||
|
finalize
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
registry.markActive(el.id);
|
||||||
|
finalize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
finalize();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the given component, if one is assigned
|
||||||
|
*
|
||||||
|
* @param {*} component The component of which the id should be retrieved
|
||||||
|
*
|
||||||
|
* @returns {String|null} The id of the component, or null
|
||||||
|
*/
|
||||||
|
this.getId = function(component) {
|
||||||
|
return registry.getId(component);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component that is assigned to the given id
|
||||||
|
*
|
||||||
|
* @param {String} id The id of the component
|
||||||
|
*
|
||||||
|
* @returns {*} The component or null
|
||||||
|
*/
|
||||||
|
this.getById = function(id) {
|
||||||
|
return registry.getById(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all components that match the given type
|
||||||
|
*
|
||||||
|
* @param {String} type The component type in the form '<module>/<component>'
|
||||||
|
*
|
||||||
|
* @returns {*|Array} The components or an empty array
|
||||||
|
*/
|
||||||
|
this.getByType = function(type) {
|
||||||
|
return registry.getByType(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all components
|
||||||
|
*
|
||||||
|
* @returns {*|Array} The components or an empty array
|
||||||
|
*/
|
||||||
|
this.getComponents = function() {
|
||||||
|
return registry.getComponents();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return new ComponentLoader();
|
||||||
|
});
|
137
public/js/icinga/componentRegistry.js
Normal file
137
public/js/icinga/componentRegistry.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component registry that maps components to unique IDs and keeps track
|
||||||
|
* of component types to allow easy querying
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
define(['jquery'], function($) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var ComponentRegistry = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map ids to components
|
||||||
|
*/
|
||||||
|
var components = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new component id
|
||||||
|
*/
|
||||||
|
var createId = (function() {
|
||||||
|
var id = 0;
|
||||||
|
return function() {
|
||||||
|
return 'icinga-component-' + id++;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the given component, if one is assigned
|
||||||
|
*
|
||||||
|
* @param {*} component The component of which the id should be retrieved
|
||||||
|
*
|
||||||
|
* @returns {String|null} The id of the component, or null
|
||||||
|
*/
|
||||||
|
this.getId = function(cmp) {
|
||||||
|
var id = null;
|
||||||
|
$.each(components, function(key, value) {
|
||||||
|
if (value && value.cmp === cmp) {
|
||||||
|
id = key;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component that is assigned to the given id
|
||||||
|
*
|
||||||
|
* @param {String} id The id of the component
|
||||||
|
*
|
||||||
|
* @returns {*} The component or null
|
||||||
|
*/
|
||||||
|
this.getById = function(id) {
|
||||||
|
return components[id] && components[id].cmp;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all components that match the given type
|
||||||
|
*
|
||||||
|
* @param {String} type The component type in the form '<module>/<component>'
|
||||||
|
*
|
||||||
|
* @returns {*|Array} The components or an empty array
|
||||||
|
*/
|
||||||
|
this.getByType = function(type) {
|
||||||
|
return $.map(components, function(entry) {
|
||||||
|
return entry.type === type ? entry.cmp : null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all components
|
||||||
|
*
|
||||||
|
* @returns {*|Array} The components or an empty array
|
||||||
|
*/
|
||||||
|
this.getComponents = function() {
|
||||||
|
return $.map(components, function(entry) {
|
||||||
|
return entry.cmp;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the given component to the registry and return the assigned id
|
||||||
|
*
|
||||||
|
* @param {*} cmp The component to add
|
||||||
|
* @param {String} id The optional id that should be assigned to that component
|
||||||
|
* @param {String} type The component type to load '<module>/<component>'
|
||||||
|
*
|
||||||
|
* @returns {*|Array}
|
||||||
|
*/
|
||||||
|
this.add = function(cmp, id, type) {
|
||||||
|
if (!id){
|
||||||
|
id = self.getId(cmp) || createId();
|
||||||
|
}
|
||||||
|
components[id] = {
|
||||||
|
cmp: cmp,
|
||||||
|
type: type,
|
||||||
|
active: true
|
||||||
|
};
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all components inactive
|
||||||
|
*/
|
||||||
|
this.markAllInactive = function() {
|
||||||
|
$.each(components,function(index, el){
|
||||||
|
if (el && el.active) {
|
||||||
|
el.active = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the component with the given id as active
|
||||||
|
*/
|
||||||
|
this.markActive = function(id) {
|
||||||
|
if (components[id]) {
|
||||||
|
components[id].active = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let the garbage collection remove all inactive components
|
||||||
|
*/
|
||||||
|
this.removeInactive = function() {
|
||||||
|
$.each(components, function(key,value) {
|
||||||
|
if (!value || !value.active) {
|
||||||
|
delete components[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return new ComponentRegistry();
|
||||||
|
});
|
@ -1,118 +0,0 @@
|
|||||||
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ActionTable behaviour as described in
|
|
||||||
* https://wiki.icinga.org/display/cranberry/Frontend+Components#FrontendComponents-ActionTable
|
|
||||||
*
|
|
||||||
* @TODO: Row selection
|
|
||||||
*/
|
|
||||||
define(['jquery','logging','icinga/util/async'],function($,log,async) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var ActionTableBehaviour = function() {
|
|
||||||
var onTableHeaderClick;
|
|
||||||
|
|
||||||
var TABLE_BASE_MATCHER = '.icinga-container table.action';
|
|
||||||
var linksInActionTable = TABLE_BASE_MATCHER+" tbody tr > a";
|
|
||||||
var actionTableRow = TABLE_BASE_MATCHER+" tbody tr";
|
|
||||||
var headerRow = TABLE_BASE_MATCHER+" > th a";
|
|
||||||
var searchField = ".icinga-container .actiontable.controls input[type=search]";
|
|
||||||
|
|
||||||
|
|
||||||
onTableHeaderClick = function (ev) {
|
|
||||||
var target = ev.currentTarget,
|
|
||||||
href = $(target).attr('href'),
|
|
||||||
destination;
|
|
||||||
if ($(target).parents('.layout-main-detail').length) {
|
|
||||||
if ($(target).parents("#icinga-main").length) {
|
|
||||||
destination = 'icinga-main';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
destination = 'icinga-detail';
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
destination = 'icinga-main';
|
|
||||||
}
|
|
||||||
async.loadToTarget(destination, href);
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var onLinkTagClick = function(ev) {
|
|
||||||
|
|
||||||
var target = ev.currentTarget,
|
|
||||||
href = $(target).attr('href'),
|
|
||||||
destination;
|
|
||||||
if ($(target).parents('.layout-main-detail').length) {
|
|
||||||
destination = 'icinga-detail';
|
|
||||||
} else {
|
|
||||||
destination = 'icinga-main';
|
|
||||||
}
|
|
||||||
async.loadToTarget(destination,href);
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
return false;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
var onTableRowClick = function(ev) {
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
|
|
||||||
var target = $(ev.currentTarget),
|
|
||||||
href = target.attr('href'),
|
|
||||||
destination;
|
|
||||||
$('tr.active',target.parent('tbody').first()).removeClass("active");
|
|
||||||
target.addClass('active');
|
|
||||||
|
|
||||||
// When the tr has a href, act like it is a link
|
|
||||||
if(href) {
|
|
||||||
ev.currentTarget = target.first();
|
|
||||||
return onLinkTagClick(ev);
|
|
||||||
}
|
|
||||||
// Try to find a designated row action
|
|
||||||
var links = $("a.row-action",target);
|
|
||||||
if(links.length) {
|
|
||||||
ev.currentTarget = links.first();
|
|
||||||
return onLinkTagClick(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise use the first anchor tag
|
|
||||||
links = $("a",target);
|
|
||||||
if(links.length) {
|
|
||||||
ev.currentTarget = links.first();
|
|
||||||
return onLinkTagClick(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("No target for this table row found");
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var onSearchInput = function(ev) {
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
var txt = $(this).val();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.eventHandler = {};
|
|
||||||
this.eventHandler[linksInActionTable] = {
|
|
||||||
'click' : onLinkTagClick
|
|
||||||
};
|
|
||||||
this.eventHandler[actionTableRow] = {
|
|
||||||
'click' : onTableRowClick
|
|
||||||
};
|
|
||||||
this.eventHandler[headerRow] = {
|
|
||||||
'click' : onTableHeaderClick
|
|
||||||
};
|
|
||||||
this.eventHandler[searchField] = {
|
|
||||||
'keyup' : onSearchInput
|
|
||||||
};
|
|
||||||
|
|
||||||
this.enable = function() {
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return new ActionTableBehaviour();
|
|
||||||
|
|
||||||
});
|
|
@ -6,16 +6,15 @@
|
|||||||
define(['jquery', 'datetimepicker'], function($) {
|
define(['jquery', 'datetimepicker'], function($) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var DateTimeBehaviour = function() {
|
var DateTimePicker = function(target) {
|
||||||
this.enable = function() {
|
$(target).find('.datetime input')
|
||||||
$('.datetime input')
|
|
||||||
.attr('data-format', 'yyyy-MM-dd hh:mm:ss');
|
.attr('data-format', 'yyyy-MM-dd hh:mm:ss');
|
||||||
$('.datetime')
|
|
||||||
|
$(target).find('.datetime')
|
||||||
.addClass('input-append')
|
.addClass('input-append')
|
||||||
.append('<span class="add-on">' +
|
.append('<span class="add-on">' +
|
||||||
'<i data-time-icon="icon-time" data-date-icon="icon-calendar"></i></span>')
|
'<i data-time-icon="icon-time" data-date-icon="icon-calendar"></i></span>')
|
||||||
.datetimepicker();
|
.datetimepicker();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
return new DateTimeBehaviour();
|
return DateTimePicker;
|
||||||
});
|
});
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main-Detail layout behaviour as described in
|
|
||||||
* https://wiki.icinga.org/display/cranberry/Frontend+Components#FrontendComponents-Behaviour
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
define(['jquery','logging','icinga/util/async'],function($,log,async) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var MainDetailBehaviour = function() {
|
|
||||||
|
|
||||||
var onOuterLinkClick = function(ev) {
|
|
||||||
var a = $(ev.currentTarget),
|
|
||||||
target = a.attr("target"),
|
|
||||||
href = a.attr("href");
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
collapseDetailView();
|
|
||||||
async.loadToTarget("icinga-main",href);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var onLinkTagClick = function(ev) {
|
|
||||||
|
|
||||||
var a = $(ev.currentTarget),
|
|
||||||
target = a.attr("target"),
|
|
||||||
href = a.attr("href");
|
|
||||||
|
|
||||||
// check for protocol://
|
|
||||||
if(/^[A-Z]{2,10}\:\/\//i.test(href)) {
|
|
||||||
window.open(href);
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for link in table header
|
|
||||||
if(a.parents('th').length > 0) {
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof target === "undefined") {
|
|
||||||
if(a.parents("#icinga-detail").length) {
|
|
||||||
log.debug("Parent is detail, loading into detail");
|
|
||||||
async.loadToTarget("icinga-detail",href);
|
|
||||||
} else {
|
|
||||||
log.debug("Parent is not detail, loading into main");
|
|
||||||
async.loadToTarget("icinga-main",href);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch(target) {
|
|
||||||
case "body":
|
|
||||||
async.loadToTarget("body", href);
|
|
||||||
break;
|
|
||||||
case "main":
|
|
||||||
async.loadToTarget("icinga-main",href);
|
|
||||||
collapseDetailView();
|
|
||||||
break;
|
|
||||||
case "detail":
|
|
||||||
log.debug("Target: detail");
|
|
||||||
async.loadToTarget("icinga-detail",href);
|
|
||||||
break;
|
|
||||||
case "popup":
|
|
||||||
log.debug("No target");
|
|
||||||
async.loadToTarget(null,href);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ev.stopImmediatePropagation();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var expandDetailView = function() {
|
|
||||||
$("#icinga-detail").parents(".collapsed").removeClass('collapsed');
|
|
||||||
};
|
|
||||||
|
|
||||||
var collapseDetailView = function(elementInDetailView) {
|
|
||||||
$("#icinga-detail").parents(".layout-main-detail").addClass('collapsed');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.expandDetailView = expandDetailView;
|
|
||||||
this.collapseDetailView = collapseDetailView;
|
|
||||||
|
|
||||||
this.eventHandler = {
|
|
||||||
'.layout-main-detail * a' : {
|
|
||||||
'click' : onLinkTagClick
|
|
||||||
},
|
|
||||||
|
|
||||||
'.layout-main-detail .icinga-container#icinga-detail' : {
|
|
||||||
'focus' : expandDetailView
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return new MainDetailBehaviour();
|
|
||||||
});
|
|
@ -1,113 +0,0 @@
|
|||||||
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var containerMgrInstance = null;
|
|
||||||
var async;
|
|
||||||
|
|
||||||
var ContainerMgr = function($,log,Widgets,SubTable) {
|
|
||||||
|
|
||||||
|
|
||||||
var enhanceDetachLinks = function() {
|
|
||||||
$('a[target=_blank]',this).each(function() {
|
|
||||||
$(this).attr("target","popup");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Loading Async directly via AMD would result in a circular dependency and return null
|
|
||||||
* @param asyncMgr
|
|
||||||
*/
|
|
||||||
this.registerAsyncMgr = function(asyncMgr) {
|
|
||||||
async = asyncMgr;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.updateContainer = function(id,content,req) {
|
|
||||||
var target = id;
|
|
||||||
if (typeof id === "string") {
|
|
||||||
target = this.getContainer(id);
|
|
||||||
}
|
|
||||||
var ctrl = $('.container-controls',target);
|
|
||||||
target.html(content);
|
|
||||||
if(ctrl.length) {
|
|
||||||
this.updateControlTargets(ctrl,req);
|
|
||||||
target.append(ctrl.first());
|
|
||||||
}
|
|
||||||
target.focus();
|
|
||||||
this.initializeContainers(target);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.updateControlTargets = function(ctrl, req) {
|
|
||||||
$('a',ctrl).each(function() {
|
|
||||||
$(this).attr("href",req.url);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initControlBehaviour = function(root) {
|
|
||||||
$('div[container-id] .container-controls',root).each(function() {
|
|
||||||
enhanceDetachLinks.apply(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initExpandables = function(root) {
|
|
||||||
$('div[container-id] .expandable',root).each(function() {
|
|
||||||
var ctr = this;
|
|
||||||
$('.expand-link',this).on("click",function() {
|
|
||||||
$(ctr).removeClass('collapsed');
|
|
||||||
});
|
|
||||||
$('.collapse-link',this).on("click",function() {
|
|
||||||
$(ctr).addClass('collapsed');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.drawImplicitWidgets = function(root) {
|
|
||||||
$('.icinga-widget[type="icinga/subTable"]',root).each(function() {
|
|
||||||
new SubTable(this);
|
|
||||||
});
|
|
||||||
$('div[container-id] .inlinepie',root).each(function() {
|
|
||||||
new Widgets.inlinePie(this,32,32);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.loadAsyncContainers = function(root) {
|
|
||||||
$('.icinga-container[icingaurl]',root).each(function() {
|
|
||||||
var el = $(this);
|
|
||||||
var url = el.attr('icingaurl');
|
|
||||||
el.attr('loaded',true);
|
|
||||||
async.loadToTarget(el,url);
|
|
||||||
});
|
|
||||||
log.debug("Loading async");
|
|
||||||
};
|
|
||||||
|
|
||||||
this.initializeContainers = function(root) {
|
|
||||||
this.initControlBehaviour(root);
|
|
||||||
this.initExpandables(root);
|
|
||||||
this.drawImplicitWidgets(root);
|
|
||||||
this.loadAsyncContainers(root);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.createPopupContainer = function(content,req) {
|
|
||||||
var closeButton = $('<button type="button" class="close" data-dismiss="modal" >×</button>');
|
|
||||||
var container = $('<div>').addClass('modal').attr('container-id','popup-'+req.url).attr("role","dialog")
|
|
||||||
.append($("<div>").addClass('modal-header').text('Header').append(closeButton))
|
|
||||||
.append($("<div>").addClass('modal-body').html(content)).appendTo(document.body);
|
|
||||||
|
|
||||||
closeButton.on("click",function() {container.remove();});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getContainer = function(id) {
|
|
||||||
if(id == 'body') {
|
|
||||||
return $(document.body);
|
|
||||||
}
|
|
||||||
return $('div[container-id='+id+']');
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
define(['jquery','logging','icinga/widgets/checkIcons','icinga/widgets/subTable'], function($,log,widgets,subTable) {
|
|
||||||
if (containerMgrInstance === null) {
|
|
||||||
containerMgrInstance = new ContainerMgr($,log,widgets,subTable);
|
|
||||||
}
|
|
||||||
return containerMgrInstance;
|
|
||||||
});
|
|
||||||
})();
|
|
@ -2,86 +2,25 @@
|
|||||||
define([
|
define([
|
||||||
'jquery',
|
'jquery',
|
||||||
'logging',
|
'logging',
|
||||||
'icinga/module',
|
|
||||||
'icinga/util/async',
|
'icinga/util/async',
|
||||||
'icinga/container',
|
'icinga/componentLoader'
|
||||||
'modules/list'
|
], function ($, log, async,components) {
|
||||||
], function ($, log, moduleMgr, async, containerMgr, modules) {
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icinga prototype
|
* Icinga prototype
|
||||||
*/
|
*/
|
||||||
var Icinga = function() {
|
var Icinga = function() {
|
||||||
var internalModules = ['icinga/components/actionTable',
|
|
||||||
'icinga/components/mainDetail',
|
|
||||||
'icinga/components/datetime'];
|
|
||||||
|
|
||||||
this.modules = {};
|
|
||||||
var failedModules = [];
|
|
||||||
|
|
||||||
var initialize = function () {
|
var initialize = function () {
|
||||||
registerLazyModuleLoading();
|
components.load();
|
||||||
enableInternalModules();
|
|
||||||
|
|
||||||
containerMgr.registerAsyncMgr(async);
|
|
||||||
containerMgr.initializeContainers(document);
|
|
||||||
log.debug("Initialization finished");
|
log.debug("Initialization finished");
|
||||||
|
|
||||||
enableModules();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var registerLazyModuleLoading = function() {
|
|
||||||
async.registerHeaderListener("X-Icinga-Enable-Module", loadModuleScript, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
var enableInternalModules = function() {
|
|
||||||
$.each(internalModules,function(idx,module) {
|
|
||||||
moduleMgr.enableModule(module, log.error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var loadModuleScript = function(name) {
|
|
||||||
moduleMgr.enableModule("modules/"+name+"/"+name, function(error) {
|
|
||||||
failedModules.push({
|
|
||||||
name: name,
|
|
||||||
errorMessage: error
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var enableModules = function(moduleList) {
|
|
||||||
moduleList = moduleList || modules;
|
|
||||||
|
|
||||||
$.each(modules,function(idx,module) {
|
|
||||||
loadModuleScript(module.name);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(initialize.bind(this));
|
$(document).ready(initialize.bind(this));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
components: components
|
||||||
*
|
|
||||||
*/
|
|
||||||
loadModule: function(blubb,bla) {
|
|
||||||
behaviour.registerBehaviour(blubb,bla);
|
|
||||||
},
|
|
||||||
|
|
||||||
loadIntoContainer: function(ctr) {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
loadUrl: function(url, target, params) {
|
|
||||||
target = target || "icinga-main";
|
|
||||||
async.loadToTarget(target, url, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
getFailedModules: function() {
|
|
||||||
return failedModules;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return new Icinga();
|
return new Icinga();
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
/*global Icinga:false, $: false, document: false, define:false requirejs:false base_url:false console:false */
|
|
||||||
|
|
||||||
/**
|
|
||||||
This prototype encapsulates the modules registered in the module folder
|
|
||||||
**/
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var loaded = {};
|
|
||||||
|
|
||||||
define(['logging'],function(log) {
|
|
||||||
|
|
||||||
var registerModuleFunctions = function(module) {
|
|
||||||
var enableFn = module.enable, disableFn = module.disable;
|
|
||||||
|
|
||||||
module.enable = (function(root) {
|
|
||||||
root = root || $('body');
|
|
||||||
for (var jqMatcher in this.eventHandler) {
|
|
||||||
for (var ev in this.eventHandler[jqMatcher]) {
|
|
||||||
log.debug("Registered module: ", "'"+ev+"'", jqMatcher);
|
|
||||||
$(root).on(ev,jqMatcher,this.eventHandler[jqMatcher][ev]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(enableFn) {
|
|
||||||
enableFn.apply(this,arguments);
|
|
||||||
}
|
|
||||||
}).bind(module);
|
|
||||||
|
|
||||||
module.disable = (function(root) {
|
|
||||||
root = root || $('body');
|
|
||||||
for (var jqMatcher in this.eventHandler) {
|
|
||||||
for (var ev in this.eventHandler[jqMatcher]) {
|
|
||||||
log.debug("Unregistered module: ", "'"+ev+"'", jqMatcher);
|
|
||||||
$(root).off(ev,jqMatcher,this.eventHandler[jqMatcher][ev]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (disableFn) {
|
|
||||||
disableFn.apply(this,arguments);
|
|
||||||
}
|
|
||||||
}).bind(module);
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
var CallInterface = function() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a module and calls successCallback with the module as the parameter on success, otherwise
|
|
||||||
* the errorCallback with the errorstring as the first parameter
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param errorCallback
|
|
||||||
* @param successCallback
|
|
||||||
*/
|
|
||||||
this.enableModule = function(name,errorCallback,successCallback) {
|
|
||||||
requirejs([name],function(module) {
|
|
||||||
if (typeof module === "undefined") {
|
|
||||||
return errorCallback(new Error("Unknown module: "+name));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof module.eventHandler === "object") {
|
|
||||||
registerModuleFunctions(module);
|
|
||||||
}
|
|
||||||
if (typeof module.enable === "function") {
|
|
||||||
module.enable();
|
|
||||||
}
|
|
||||||
loaded[name] = {
|
|
||||||
module: module,
|
|
||||||
active: true
|
|
||||||
};
|
|
||||||
if (typeof successCallback === "function") {
|
|
||||||
successCallback(module);
|
|
||||||
}
|
|
||||||
},function(err) {
|
|
||||||
errorCallback("Could not load module "+name+" "+err,err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.disableModule = function(name) {
|
|
||||||
if(loaded[name] && loaded[name].active) {
|
|
||||||
loaded[name].module.disable();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This should *ONLY* be called in testcases
|
|
||||||
**/
|
|
||||||
this.resetHard = function() {
|
|
||||||
if (typeof describe !== "function") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loaded = {};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return new CallInterface();
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
@ -3,7 +3,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var asyncMgrInstance = null;
|
var asyncMgrInstance = null;
|
||||||
|
|
||||||
define(['icinga/container','logging','jquery'],function(containerMgr,log,$) {
|
define(['logging','jquery'],function(log,$,containerMgr) {
|
||||||
|
|
||||||
var headerListeners = {};
|
var headerListeners = {};
|
||||||
|
|
||||||
|
@ -45,6 +45,10 @@ define(function() {
|
|||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
logTagged('error', arguments);
|
logTagged('error', arguments);
|
||||||
|
},
|
||||||
|
emergency: function() {
|
||||||
|
logTagged('emergency', arguments);
|
||||||
|
// TODO: log *emergency* errors to the backend
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
(function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var Timer = function() {
|
|
||||||
this.resolution = 1000; // 1 second resolution
|
|
||||||
this.containers = {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.registerContainer = function(container) {
|
|
||||||
this.containers[container.attr('container-id')] = container;
|
|
||||||
};
|
|
||||||
|
|
||||||
var tick = function() {
|
|
||||||
for(var container in this.containers) {
|
|
||||||
var el = this.containers[container];
|
|
||||||
// document does not exist anymore
|
|
||||||
if(!jQuery.contains(document.documentElement, el[0])) {
|
|
||||||
delete this.containers[container];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
@ -1,168 +0,0 @@
|
|||||||
/*global Icinga:false, $:true, document: false, define:false require:false base_url:false console:false */
|
|
||||||
define(['logging','raphael'], function(log,raphael) {
|
|
||||||
"use strict";
|
|
||||||
var rad = Math.PI / 180;
|
|
||||||
|
|
||||||
var getPaper = function(el,width,height) {
|
|
||||||
if (el[0]) {
|
|
||||||
el = el[0];
|
|
||||||
}
|
|
||||||
this.paper = raphael(el,width, height);
|
|
||||||
this.paper.customAttributes.arc = function (xloc, yloc, value, total, R) {
|
|
||||||
var alpha = 360 / total * value,
|
|
||||||
a = (90 - alpha) * Math.PI / 180,
|
|
||||||
x = xloc + R * Math.cos(a),
|
|
||||||
y = yloc - R * Math.sin(a),
|
|
||||||
path;
|
|
||||||
if (total === value) {
|
|
||||||
path = [
|
|
||||||
["M", xloc, yloc - R],
|
|
||||||
["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R]
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
path = [
|
|
||||||
["M", xloc, yloc - R],
|
|
||||||
["A", R, R, 0, +(alpha > 180), 1, x, y]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path: path
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var drawStatusArc = function(color,percentage,width,anim) {
|
|
||||||
anim = anim || 500;
|
|
||||||
// how much percentage this sub arc requires
|
|
||||||
var alpha = this.rot + percentage / 100 * 180; // this is the end angle for the arc
|
|
||||||
|
|
||||||
var coords = getCoordsForAngle.call(this,alpha);
|
|
||||||
var pos = getSection(alpha);
|
|
||||||
|
|
||||||
var subArc = this.paper.path().attr({
|
|
||||||
"stroke": color,
|
|
||||||
"stroke-width": width || (this.radius/4)+"px",
|
|
||||||
arc: [this.x, this.y, 0, 100, this.radius]
|
|
||||||
});
|
|
||||||
|
|
||||||
subArc.data("percentage",percentage);
|
|
||||||
subArc.transform("r" + this.rot + "," + this.x + "," + this.y).animate({
|
|
||||||
arc: [this.x, this.y, percentage, 100, this.radius]
|
|
||||||
}, anim, "easeOut");
|
|
||||||
|
|
||||||
//subArc.hover(indicateMouseOver,indicateMouseOut);
|
|
||||||
this.rot += percentage / 100 * 360;
|
|
||||||
};
|
|
||||||
|
|
||||||
var getSection = function(alpha) {
|
|
||||||
return {
|
|
||||||
right: alpha < 180,
|
|
||||||
left: alpha > 180,
|
|
||||||
top: alpha < 90 || alpha > 270,
|
|
||||||
bottom: alpha > 90 && alpha < 270
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var getCoordsForAngle = function(alpha) {
|
|
||||||
var a = (90 - alpha) * Math.PI / 180;
|
|
||||||
return {
|
|
||||||
tx : this.x + (this.radius) * Math.cos(a),
|
|
||||||
ty : this.y - (this.radius) * Math.sin(a)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var sector = function(startAngle, endAngle) {
|
|
||||||
var cx = this.x, cy = this.y, r = this.radius;
|
|
||||||
var x1 = cx + r * Math.cos(-startAngle * rad),
|
|
||||||
x2 = cx + r * Math.cos(-endAngle * rad),
|
|
||||||
y1 = cy + r * Math.sin(-startAngle * rad),
|
|
||||||
y2 = cy + r * Math.sin(-endAngle * rad);
|
|
||||||
|
|
||||||
return ["M", cx, cy, "L", x1, y1, "A", r, r, 0, +(endAngle - startAngle > 180), 0, x2, y2, "z"];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var inlinePie = function(targetEl,h,w) {
|
|
||||||
var colors = {
|
|
||||||
0: '#11ee11',
|
|
||||||
1: '#ffff00',
|
|
||||||
2: '#ff2222',
|
|
||||||
3: '#acacac'
|
|
||||||
};
|
|
||||||
targetEl = $(targetEl);
|
|
||||||
var height = h || targetEl.height();
|
|
||||||
var width = w || targetEl.width();
|
|
||||||
this.x = width/2;
|
|
||||||
this.y = height/2;
|
|
||||||
this.radius = Math.min(width,height)/2.5;
|
|
||||||
var values = $(targetEl).html().split(",");
|
|
||||||
targetEl.html("");
|
|
||||||
|
|
||||||
getPaper.call(this,targetEl.first(),width,height);
|
|
||||||
|
|
||||||
var total = 0;
|
|
||||||
for(var i=0;i<values.length;i++) {
|
|
||||||
total += parseInt(values[i],10);
|
|
||||||
values[i] = parseInt(values[i],10);
|
|
||||||
}
|
|
||||||
var degStart = 0;
|
|
||||||
var degEnd = 0;
|
|
||||||
|
|
||||||
for(i=0;i<values.length;i++) {
|
|
||||||
degEnd = degStart+(parseInt(values[i],10)/total*360);
|
|
||||||
if(degEnd >= 360) {
|
|
||||||
degEnd = 359.9;
|
|
||||||
}
|
|
||||||
log.debug(degStart,degEnd);
|
|
||||||
this.paper.path(sector.call(this,degStart,degEnd)).attr({
|
|
||||||
fill: colors[i],
|
|
||||||
"stroke-width": '0.5px'
|
|
||||||
});
|
|
||||||
degStart = degEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
var diskStatus = function(targetEl,full,ok,warning,critical) {
|
|
||||||
targetEl = $(targetEl);
|
|
||||||
this.rot = 0;
|
|
||||||
var height = targetEl.height();
|
|
||||||
var width = targetEl.width();
|
|
||||||
this.x = width/2;
|
|
||||||
this.y = height/2;
|
|
||||||
this.radius = Math.min(width,height)/3;
|
|
||||||
getPaper.call(this,targetEl.first(),width,height);
|
|
||||||
for(var i = 0;i<5;i++) {
|
|
||||||
this.paper
|
|
||||||
.ellipse(this.x,this.y+(height/10)-i*(height/20),width/5,height/10)
|
|
||||||
.attr({'fill' : '90-#ddd-#666'});
|
|
||||||
}
|
|
||||||
this.radius -= 7;
|
|
||||||
drawStatusArc.call(this,'#000000',100,1,1);
|
|
||||||
this.radius += 2;
|
|
||||||
drawStatusArc.call(this,'#00ff00',ok,3 );
|
|
||||||
drawStatusArc.call(this,'#ffff00',warning,3);
|
|
||||||
drawStatusArc.call(this,'#ff0000',critical,3);
|
|
||||||
this.radius += 2;
|
|
||||||
drawStatusArc.call(this,'#000000',100,2,1);
|
|
||||||
|
|
||||||
this.radius+=4;
|
|
||||||
this.rot = 0;
|
|
||||||
drawStatusArc.call(this,'#ff0000',full);
|
|
||||||
drawStatusArc.call(this,'#0f0',100-full);
|
|
||||||
this.rot = 0;
|
|
||||||
this.radius += 5;
|
|
||||||
drawStatusArc.call(this,'#000000',100,2,1);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
inlinePie: inlinePie,
|
|
||||||
diskStatus: diskStatus
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
|||||||
define(['jquery','raphael'], function($,Raphael) {
|
|
||||||
return function(el) {
|
|
||||||
this.el = $(el);
|
|
||||||
|
|
||||||
this.height = this.el.height();
|
|
||||||
this.width = this.el.width();
|
|
||||||
this.dataSet = {};
|
|
||||||
this.paper= null;
|
|
||||||
this.radius = 0;
|
|
||||||
this.total = 0;
|
|
||||||
|
|
||||||
var construct = (function(el,cfg) {
|
|
||||||
cfg = cfg || {}
|
|
||||||
this.radius = cfg.radius || Math.min(this.width,this.height)/4
|
|
||||||
this.x = cfg.x || this.width/2;
|
|
||||||
this.y = cfg.y || this.height/2;
|
|
||||||
this.paper = getPaper();
|
|
||||||
console.log(this.el);
|
|
||||||
}).bind(this);
|
|
||||||
|
|
||||||
var getSection = function(alpha) {
|
|
||||||
return {
|
|
||||||
right: alpha < 180,
|
|
||||||
left: alpha > 180,
|
|
||||||
top: alpha < 90 || alpha > 270,
|
|
||||||
bottom: alpha > 90 && alpha < 270
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var getCoordsForAngle = (function(alpha) {
|
|
||||||
var a = (90 - alpha) * Math.PI / 180;
|
|
||||||
return {
|
|
||||||
tx : this.x + (this.radius) * Math.cos(a),
|
|
||||||
ty : this.y - (this.radius) * Math.sin(a)
|
|
||||||
}
|
|
||||||
}).bind(this);
|
|
||||||
|
|
||||||
var drawSubCircle = (function(coords, color,percentage) {
|
|
||||||
this.paper.circle(coords.tx,coords.ty,0).attr({
|
|
||||||
fill: color,
|
|
||||||
stroke: 'none'
|
|
||||||
}).animate({r: this.radius/2},500, "easeOut");
|
|
||||||
}).bind(this);
|
|
||||||
|
|
||||||
var indicateMouseOver = function() {
|
|
||||||
this.animate({"stroke-width": 30 }, 400, "bounce");
|
|
||||||
}
|
|
||||||
|
|
||||||
var indicateMouseOut = function() {
|
|
||||||
this.animate({"stroke-width": 18}, 400, "bounce");
|
|
||||||
}
|
|
||||||
|
|
||||||
var drawSubArcFor = (function(elem,rot) {
|
|
||||||
var percentage = elem.items.length/this.total*100; // how much percentage this sub arc requires
|
|
||||||
var alpha = rot + percentage / 100 * 180; // this is the end angle for the arc
|
|
||||||
|
|
||||||
var coords = getCoordsForAngle(alpha);
|
|
||||||
var pos = getSection(alpha);
|
|
||||||
if(elem.items.length > 10)
|
|
||||||
drawSubCircle(coords,elem.color,percentage/100);
|
|
||||||
|
|
||||||
var subArc = this.paper.path().attr({
|
|
||||||
"stroke": elem.color,
|
|
||||||
"stroke-width": 18,
|
|
||||||
arc: [this.x, this.y, 0, 100, this.radius]
|
|
||||||
});
|
|
||||||
|
|
||||||
subArc.data("percentage",percentage);
|
|
||||||
subArc.data("item",elem);
|
|
||||||
subArc.transform("r" + rot + "," + this.x + "," + this.y).animate({
|
|
||||||
arc: [this.x, this.y, percentage, 100, this.radius]
|
|
||||||
}, 500, "easeOut");
|
|
||||||
|
|
||||||
var text = this.paper
|
|
||||||
.text(coords.tx,coords.ty,elem.items.length)
|
|
||||||
.attr({'text-anchor': alpha < 180 ? 'start' : 'end'});
|
|
||||||
|
|
||||||
subArc.hover(indicateMouseOver,indicateMouseOut);
|
|
||||||
return percentage / 100 * 360;
|
|
||||||
}).bind(this);
|
|
||||||
|
|
||||||
var drawContainerCircle = (function() {
|
|
||||||
|
|
||||||
var rot = 0;
|
|
||||||
this.total = 0;
|
|
||||||
for (var i = 0;i<this.dataSet.childs.length;i++) {
|
|
||||||
this.total += this.dataSet.childs[i].items.length
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0;i<this.dataSet.childs.length;i++) {
|
|
||||||
rot += drawSubArcFor(this.dataSet.childs[i],rot);
|
|
||||||
}
|
|
||||||
var innerCircleShadow = this.paper.circle(this.x, this.y + 1, this.radius - 3).attr({
|
|
||||||
fill: 'black',
|
|
||||||
stroke: 'none',
|
|
||||||
opacity: 0.4
|
|
||||||
});
|
|
||||||
var innerCircle = this.paper.circle(this.x, this.y, this.radius - 4).attr({
|
|
||||||
fill: this.dataSet.color,
|
|
||||||
stroke: 'none'
|
|
||||||
});
|
|
||||||
this.paper.text(this.x, this.y,this.dataSet.label);
|
|
||||||
}).bind(this);
|
|
||||||
|
|
||||||
var getPaper = (function() {
|
|
||||||
var paper = Raphael(this.el.first(),this.width, this.height);
|
|
||||||
|
|
||||||
paper.customAttributes.arc = function (xloc, yloc, value, total, R) {
|
|
||||||
var alpha = 360 / total * value,
|
|
||||||
a = (90 - alpha) * Math.PI / 180,
|
|
||||||
x = xloc + R * Math.cos(a),
|
|
||||||
y = yloc - R * Math.sin(a),
|
|
||||||
path;
|
|
||||||
if (total == value) {
|
|
||||||
path = [
|
|
||||||
["M", xloc, yloc - R],
|
|
||||||
["A", R, R, 0, 1, 1, xloc - 0.01, yloc - R]
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
path = [
|
|
||||||
["M", xloc, yloc - R],
|
|
||||||
["A", R, R, 0, +(alpha > 180), 1, x, y]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
path: path
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return paper;
|
|
||||||
}).bind(this);
|
|
||||||
|
|
||||||
this.drawFor = function(dataSet) {
|
|
||||||
this.dataSet = dataSet;
|
|
||||||
drawContainerCircle();
|
|
||||||
}
|
|
||||||
|
|
||||||
construct.apply(this,arguments);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,44 +0,0 @@
|
|||||||
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
|
||||||
|
|
||||||
define(['jquery','logging'], function($,log) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
this.count = 10;
|
|
||||||
this.offset = 0;
|
|
||||||
this.searchable = false;
|
|
||||||
|
|
||||||
var construct = function(el) {
|
|
||||||
this.el = $(el);
|
|
||||||
this.count = this.el.attr("count") || this.count;
|
|
||||||
this.searchable = this.el.attr("searchable") || false;
|
|
||||||
this.render();
|
|
||||||
};
|
|
||||||
|
|
||||||
var renderQuicksearch = (function() {
|
|
||||||
this.input = $("<input type='text' style='padding:0px;font-size:9pt;padding-left:1em;margin-bottom:2px;line-height:8px' class='search-query input-small pull-right' >");
|
|
||||||
|
|
||||||
$('.expand-title',this.el.parents('.expandable').first())
|
|
||||||
.append(this.input)
|
|
||||||
.append($("<i class='icon-search pull-right'></i>"));
|
|
||||||
|
|
||||||
this.input.on("keyup",this.updateVisible.bind(this));
|
|
||||||
}).bind(this);
|
|
||||||
|
|
||||||
this.updateVisible = function() {
|
|
||||||
var filter = this.input.val();
|
|
||||||
$("tbody tr",this.el).hide();
|
|
||||||
$("td",this.el).each(function() {
|
|
||||||
if($(this).text().match(filter)) {
|
|
||||||
$(this).parent("tbody tr").show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.render = function() {
|
|
||||||
renderQuicksearch();
|
|
||||||
};
|
|
||||||
|
|
||||||
construct.apply(this,arguments);
|
|
||||||
};
|
|
||||||
});
|
|
@ -31,5 +31,4 @@ describe('The async module', function() {
|
|||||||
},this);
|
},this);
|
||||||
var test = async.createRequest();
|
var test = async.createRequest();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
145
test/js/test/icinga/componentTest.js
Normal file
145
test/js/test/icinga/componentTest.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('should');
|
||||||
|
var rjsmock = require('requiremock.js');
|
||||||
|
|
||||||
|
GLOBAL.document = $('body');
|
||||||
|
var component;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the test fixture
|
||||||
|
*
|
||||||
|
* @param registry The optional registry mock that should be used.
|
||||||
|
*/
|
||||||
|
var setUp = function(registry)
|
||||||
|
{
|
||||||
|
rjsmock.purgeDependencies();
|
||||||
|
|
||||||
|
requireNew('icinga/componentRegistry.js');
|
||||||
|
registry = registry || rjsmock.getDefine();
|
||||||
|
|
||||||
|
rjsmock.registerDependencies({
|
||||||
|
'icinga/componentRegistry': registry,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Available components
|
||||||
|
*/
|
||||||
|
'modules/app/component1': function(cmp) {
|
||||||
|
cmp.test = 'changed-by-component-1';
|
||||||
|
this.type = function() {
|
||||||
|
return "app/component1";
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'modules/app/component2': function(cmp) {
|
||||||
|
cmp.test = 'changed-by-component-2';
|
||||||
|
this.type = function() {
|
||||||
|
return "app/component2";
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'modules/module/component3': function(cmp) {
|
||||||
|
cmp.test = 'changed-by-component-3-from-module';
|
||||||
|
this.type = function() {
|
||||||
|
return "module/component3";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('body').empty();
|
||||||
|
|
||||||
|
requireNew('icinga/componentLoader.js');
|
||||||
|
component = rjsmock.getDefine();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new component to the current test-DOM
|
||||||
|
*
|
||||||
|
* @param type {String} The type of the component in the form: "<module>/<type>"
|
||||||
|
* @param id {String} The optional id of the component
|
||||||
|
*/
|
||||||
|
var addComponent = function(type, id) {
|
||||||
|
var txt = '<div ' + ( id ? ( ' id= "' + id + '" ' ) : '' ) +
|
||||||
|
' data-icinga-component="' + type + '" >test</div>';
|
||||||
|
|
||||||
|
$('body').append(txt);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Component loader', function() {
|
||||||
|
|
||||||
|
it('Component loaded with automatic id', function() {
|
||||||
|
setUp();
|
||||||
|
addComponent('app/component1');
|
||||||
|
|
||||||
|
component.load(function() {
|
||||||
|
var cmpNode = $('#icinga-component-0');
|
||||||
|
cmpNode.length.should.equal(1);
|
||||||
|
cmpNode[0].test.should.equal('changed-by-component-1');
|
||||||
|
component.getById('icinga-component-0').type().should.equal('app/component1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Component load with user-defined id', function() {
|
||||||
|
setUp();
|
||||||
|
addComponent('app/component2','some-id');
|
||||||
|
|
||||||
|
component.load(function() {
|
||||||
|
var cmpNode = $('#some-id');
|
||||||
|
cmpNode.length.should.equal(1);
|
||||||
|
cmpNode[0].test.should.equal('changed-by-component-2');
|
||||||
|
component.getById('some-id').type().should.equal('app/component2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Garbage collection removes deleted components', function() {
|
||||||
|
setUp();
|
||||||
|
addComponent('app/component1');
|
||||||
|
addComponent('app/component2');
|
||||||
|
addComponent('app/component2');
|
||||||
|
addComponent('module/component3');
|
||||||
|
|
||||||
|
component.load(function() {
|
||||||
|
var components = component.getComponents();
|
||||||
|
components.length.should.equal(4);
|
||||||
|
$('body').empty();
|
||||||
|
component.load(function() {
|
||||||
|
var components = component.getComponents();
|
||||||
|
components.length.should.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Component queries are delegated to the registry correctly', function() {
|
||||||
|
var getByIdCalled = false;
|
||||||
|
var getByTypeCalled = false;
|
||||||
|
var getComponentsCalled = false;
|
||||||
|
|
||||||
|
var registryMock = {
|
||||||
|
getById: function(id) {
|
||||||
|
getByIdCalled = true;
|
||||||
|
id.should.equal('some-id');
|
||||||
|
},
|
||||||
|
getByType: function(type) {
|
||||||
|
getByTypeCalled = true;
|
||||||
|
type.should.equal('some-type');
|
||||||
|
},
|
||||||
|
getComponents: function() {
|
||||||
|
getComponentsCalled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setUp(registryMock);
|
||||||
|
|
||||||
|
component.getById('some-id');
|
||||||
|
getByIdCalled.should.be.true;
|
||||||
|
|
||||||
|
component.getByType('some-type');
|
||||||
|
getByTypeCalled.should.be.true;
|
||||||
|
|
||||||
|
component.getComponents();
|
||||||
|
getComponentsCalled.should.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,280 +0,0 @@
|
|||||||
/**
|
|
||||||
* Test cases for the module loading implementation
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
|
|
||||||
|
|
||||||
// {{LICENSE_HEADER}}
|
|
||||||
// {{LICENSE_HEADER}}
|
|
||||||
var should = require("should");
|
|
||||||
var rjsmock = require("requiremock.js");
|
|
||||||
var asyncMock = require("asyncmock.js");
|
|
||||||
|
|
||||||
requireNew("icinga/module.js");
|
|
||||||
var module = rjsmock.getDefine();
|
|
||||||
GLOBAL.document = $('body');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test module that only uses eventhandlers and
|
|
||||||
* no custom logic
|
|
||||||
**/
|
|
||||||
var eventOnlyModule = function() {
|
|
||||||
var thiz = this;
|
|
||||||
this.moduleLinkClick = false;
|
|
||||||
this.formFocus = false;
|
|
||||||
|
|
||||||
var onModuleLinkClick = function() {
|
|
||||||
thiz.moduleLinkClick = true;
|
|
||||||
};
|
|
||||||
var onFormFocus = function() {
|
|
||||||
thiz.formFocus = true;
|
|
||||||
};
|
|
||||||
this.eventHandler = {
|
|
||||||
'.myModule a' : {
|
|
||||||
'click': onModuleLinkClick
|
|
||||||
},
|
|
||||||
'.myModule div.test input' : {
|
|
||||||
'focus' : onFormFocus
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Module that defines an own enable and disable function
|
|
||||||
* that is called additionally to the eventhandler setup
|
|
||||||
*
|
|
||||||
**/
|
|
||||||
var customLogicModule = function() {
|
|
||||||
var thiz = this;
|
|
||||||
this.clicked = false;
|
|
||||||
this.customEnable = false;
|
|
||||||
this.customDisable = false;
|
|
||||||
|
|
||||||
var onClick = function() {
|
|
||||||
thiz.clicked = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.enable = function() {
|
|
||||||
thiz.customEnable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.disable = function() {
|
|
||||||
thiz.customDisable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.eventHandler = {
|
|
||||||
'.myModule a' : {
|
|
||||||
'click' : onClick
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var setupTestDOM = function() {
|
|
||||||
tearDownTestDOM();
|
|
||||||
$('body').append($('<div class="myModule" />')
|
|
||||||
.append($('<a href="test">funkey</a>'))
|
|
||||||
.append($('<div class="test" />')
|
|
||||||
.append('<input type="button" />')));
|
|
||||||
};
|
|
||||||
|
|
||||||
var tearDownTestDOM = function() {
|
|
||||||
$('body').off();
|
|
||||||
$('body').empty();
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Module loader', function() {
|
|
||||||
var err = null;
|
|
||||||
var errorCallback = function(error) {
|
|
||||||
err = error;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
it('Should call the errorCallback when module isn\'t found', function() {
|
|
||||||
err = null;
|
|
||||||
rjsmock.purgeDependencies();
|
|
||||||
module.resetHard();
|
|
||||||
module.enableModule("testModule", errorCallback);
|
|
||||||
should.exist(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('Should register model event handlers when an \'eventHandler\' attribute exists', function() {
|
|
||||||
rjsmock.purgeDependencies();
|
|
||||||
var testModule = new eventOnlyModule();
|
|
||||||
rjsmock.registerDependencies({
|
|
||||||
testModule: testModule
|
|
||||||
});
|
|
||||||
err = null;
|
|
||||||
var toTest = null;
|
|
||||||
|
|
||||||
// Test event handler
|
|
||||||
setupTestDOM();
|
|
||||||
|
|
||||||
// Enable the module and asser it is recognized and enabled
|
|
||||||
module.enableModule("testModule", errorCallback, function(enabled) {
|
|
||||||
toTest = enabled;
|
|
||||||
});
|
|
||||||
should.not.exist(err, "An error occured during loading: "+err);
|
|
||||||
should.exist(toTest, "The injected test module didn't work!");
|
|
||||||
should.exist(toTest.enable, "Implicit enable method wasn't created");
|
|
||||||
$('.myModule a').click();
|
|
||||||
should.equal(toTest.moduleLinkClick, true, "Click on link should trigger handler");
|
|
||||||
|
|
||||||
$('.myModule div.test input').focus();
|
|
||||||
should.equal(toTest.formFocus, true, "Form focus should trigger handler");
|
|
||||||
|
|
||||||
tearDownTestDOM();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be able to deregister events handlers when disable() is called', function() {
|
|
||||||
rjsmock.purgeDependencies();
|
|
||||||
var testModule = new eventOnlyModule();
|
|
||||||
rjsmock.registerDependencies({
|
|
||||||
testModule: testModule
|
|
||||||
});
|
|
||||||
err = null;
|
|
||||||
var toTest = null;
|
|
||||||
|
|
||||||
setupTestDOM();
|
|
||||||
|
|
||||||
module.enableModule("testModule", errorCallback, function(enabled) {
|
|
||||||
toTest = enabled;
|
|
||||||
});
|
|
||||||
should.not.exist(err, "An error occured during loading: "+err);
|
|
||||||
should.exist(toTest, "The injected test module didn't work!");
|
|
||||||
should.exist(toTest.enable, "Implicit enable method wasn't created");
|
|
||||||
|
|
||||||
$('.myModule a').click();
|
|
||||||
should.equal(toTest.moduleLinkClick, true, "Click on link should trigger handler");
|
|
||||||
toTest.moduleLinkClick = false;
|
|
||||||
|
|
||||||
module.disableModule("testModule");
|
|
||||||
$('.myModule a').click();
|
|
||||||
should.equal(toTest.moduleLinkClick, false, "Click on link shouldn't trigger handler when module is disabled");
|
|
||||||
tearDownTestDOM();
|
|
||||||
$('body').unbind();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should additionally call custom enable and disable functions', function() {
|
|
||||||
|
|
||||||
rjsmock.purgeDependencies();
|
|
||||||
var testModule = new customLogicModule();
|
|
||||||
rjsmock.registerDependencies({
|
|
||||||
testModule: testModule
|
|
||||||
});
|
|
||||||
err = null;
|
|
||||||
var toTest = null;
|
|
||||||
|
|
||||||
// Test event handler
|
|
||||||
setupTestDOM();
|
|
||||||
|
|
||||||
module.enableModule("testModule", errorCallback, function(enabled) {
|
|
||||||
toTest = enabled;
|
|
||||||
});
|
|
||||||
should.not.exist(err, "An error occured during loading: "+err);
|
|
||||||
should.exist(toTest, "The injected test module didn't work!");
|
|
||||||
should.exist(toTest.enable, "Implicit enable method wasn't created");
|
|
||||||
should.equal(toTest.customEnable, true, "Custom enable method wasn't called");
|
|
||||||
$('.myModule a').click();
|
|
||||||
should.equal(toTest.clicked, true, "Click on link should trigger handler");
|
|
||||||
toTest.clicked = false;
|
|
||||||
|
|
||||||
module.disableModule("testModule");
|
|
||||||
should.equal(toTest.customDisable, true, "Custom disable method wasn't called");
|
|
||||||
$('.myModule a').click();
|
|
||||||
should.equal(toTest.clicked, false, "Click on link shouldn't trigger handler when module is disabled");
|
|
||||||
tearDownTestDOM();
|
|
||||||
$('body').unbind();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('The icinga module bootstrap', function() {
|
|
||||||
it("Should automatically load all enabled modules", function() {
|
|
||||||
rjsmock.purgeDependencies();
|
|
||||||
var testClick = false;
|
|
||||||
rjsmock.registerDependencies({
|
|
||||||
"icinga/module": module,
|
|
||||||
"icinga/util/async" : {
|
|
||||||
registerHeaderListener: function() {}
|
|
||||||
},
|
|
||||||
"modules/test/test" : {
|
|
||||||
eventHandler: {
|
|
||||||
"a.test" : {
|
|
||||||
click : function() {
|
|
||||||
testClick = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"icinga/container" : {
|
|
||||||
registerAsyncMgr: function() {},
|
|
||||||
initializeContainers: function() {}
|
|
||||||
},
|
|
||||||
"modules/list" : [
|
|
||||||
{ name: 'test' },
|
|
||||||
{ name: 'test2'} // this one fails
|
|
||||||
]
|
|
||||||
});
|
|
||||||
tearDownTestDOM();
|
|
||||||
requireNew("icinga/icinga.js");
|
|
||||||
var icinga = rjsmock.getDefine();
|
|
||||||
$('body').append($("<a class='test'></a>"));
|
|
||||||
$('a.test').click();
|
|
||||||
should.equal(testClick, true, "Module wasn't automatically loaded!");
|
|
||||||
icinga.getFailedModules().should.have.length(1);
|
|
||||||
should.equal(icinga.getFailedModules()[0].name, "test2");
|
|
||||||
tearDownTestDOM();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should load modules lazily when discovering a X-Icinga-Enable-Module header", function() {
|
|
||||||
rjsmock.purgeDependencies();
|
|
||||||
|
|
||||||
requireNew("icinga/util/async.js");
|
|
||||||
var async = rjsmock.getDefine();
|
|
||||||
|
|
||||||
rjsmock.registerDependencies({
|
|
||||||
"icinga/module": module,
|
|
||||||
"icinga/util/async": async,
|
|
||||||
"modules/test/test" : {
|
|
||||||
eventHandler: {
|
|
||||||
"a.test" : {
|
|
||||||
click : function() {
|
|
||||||
testClick = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"icinga/container" : {
|
|
||||||
registerAsyncMgr: function() {},
|
|
||||||
initializeContainers: function() {}
|
|
||||||
},
|
|
||||||
"modules/list" : [
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDownTestDOM();
|
|
||||||
|
|
||||||
requireNew("icinga/icinga.js");
|
|
||||||
var icinga = rjsmock.getDefine();
|
|
||||||
|
|
||||||
var testClick = false;
|
|
||||||
// The module shouldn't be loaded
|
|
||||||
$('body').append($("<a class='test'></a>"));
|
|
||||||
$('a.test').click();
|
|
||||||
should.equal(testClick, false, "Unregistered module was loaded");
|
|
||||||
|
|
||||||
asyncMock.setNextAsyncResult(async,"result", false, {
|
|
||||||
"X-Icinga-Enable-Module" : "test"
|
|
||||||
});
|
|
||||||
async.createRequest();
|
|
||||||
// The module shouldn't be loaded
|
|
||||||
$('body').append($("<a class='test'></a>"));
|
|
||||||
$('a.test').click();
|
|
||||||
should.equal(testClick, true, "Module wasn't automatically loaded on header!");
|
|
||||||
|
|
||||||
|
|
||||||
tearDownTestDOM();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
117
test/js/test/icinga/registryTest.js
Normal file
117
test/js/test/icinga/registryTest.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
* {{LICENSE_HEADER}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var should = require('should');
|
||||||
|
var rjsmock = require('requiremock.js');
|
||||||
|
|
||||||
|
GLOBAL.document = $('body');
|
||||||
|
|
||||||
|
var registry;
|
||||||
|
var setUp = function() {
|
||||||
|
requireNew('icinga/componentRegistry.js');
|
||||||
|
registry = rjsmock.getDefine();
|
||||||
|
};
|
||||||
|
|
||||||
|
var cleanTestDom = function() {
|
||||||
|
$('body').empty();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
describe('Component registry',function() {
|
||||||
|
it('Ids are created automatically in the form "icinga-component-<id>"', function() {
|
||||||
|
setUp();
|
||||||
|
|
||||||
|
registry.add({}, null, null).should.equal('icinga-component-0');
|
||||||
|
registry.add({}, null, null).should.equal('icinga-component-1');
|
||||||
|
registry.add({}, null, null).should.equal('icinga-component-2');
|
||||||
|
|
||||||
|
cleanTestDom();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Existing ids are preserved', function() {
|
||||||
|
setUp();
|
||||||
|
|
||||||
|
registry.add({}, 'user-defined-id', null).should.equal('user-defined-id');
|
||||||
|
|
||||||
|
cleanTestDom();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Components are correctly added to the library', function() {
|
||||||
|
setUp();
|
||||||
|
|
||||||
|
var cmp1 = { component: "cmp1" };
|
||||||
|
registry.add(cmp1, 'user-defined-id', null);
|
||||||
|
registry.getById('user-defined-id').should.equal(cmp1);
|
||||||
|
|
||||||
|
var cmp2 = { component: "cmp2" };
|
||||||
|
registry.add(cmp2, null, null);
|
||||||
|
registry.getById('icinga-component-0').should.equal(cmp2);
|
||||||
|
|
||||||
|
cleanTestDom();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getId(component) should return the components assigned id.', function() {
|
||||||
|
setUp();
|
||||||
|
|
||||||
|
var cmp1 = { component: "cmp1" };
|
||||||
|
registry.add(cmp1, 'user-defined-id', null);
|
||||||
|
registry.getId(cmp1).should.equal('user-defined-id');
|
||||||
|
|
||||||
|
var cmp2 = { component: "cmp2" };
|
||||||
|
registry.add(cmp2, 'user-defined-id-2',null);
|
||||||
|
registry.getId(cmp2).should.equal('user-defined-id-2');
|
||||||
|
|
||||||
|
should.not.exist(registry.getId({}));
|
||||||
|
|
||||||
|
cleanTestDom();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getByType() should return all components of a certain type', function() {
|
||||||
|
setUp();
|
||||||
|
|
||||||
|
var cmp1 = { component: "some/type" };
|
||||||
|
registry.add(cmp1, null, 'some/type');
|
||||||
|
|
||||||
|
var cmp2 = { component: "some/type" };
|
||||||
|
registry.add(cmp2, null, "some/type");
|
||||||
|
|
||||||
|
var cmp3 = { component: "other/type" };
|
||||||
|
registry.add(cmp3, null, "other/type");
|
||||||
|
|
||||||
|
var cmps = registry.getByType('some/type');
|
||||||
|
cmps.length.should.equal(2);
|
||||||
|
cmps[0].component.should.equal('some/type');
|
||||||
|
cmps[1].component.should.equal('some/type');
|
||||||
|
|
||||||
|
cleanTestDom();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getComponents() should return all components', function() {
|
||||||
|
setUp();
|
||||||
|
|
||||||
|
var cmp1 = { component: "cmp1" };
|
||||||
|
registry.add(cmp1, null, null);
|
||||||
|
|
||||||
|
var cmp2 = { component: "cmp2" };
|
||||||
|
registry.add(cmp2, null, null);
|
||||||
|
|
||||||
|
var cmp3 = { component: "cmp3" };
|
||||||
|
registry.add(cmp3, null, null);
|
||||||
|
|
||||||
|
var cmps = registry.getComponents();
|
||||||
|
cmps.length.should.equal(3);
|
||||||
|
cmps[0].should.equal(cmp1);
|
||||||
|
cmps[1].should.equal(cmp2);
|
||||||
|
cmps[2].should.equal(cmp3);
|
||||||
|
|
||||||
|
cleanTestDom();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: The functionality of the garbage collection of this class is
|
||||||
|
* tested in the componentTest.js
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
@ -35,6 +35,16 @@ var requireJsMock = function(dependencies, fn) {
|
|||||||
fn.apply(this,fnArgs);
|
fn.apply(this,fnArgs);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock the Logger
|
||||||
|
*/
|
||||||
|
var logger = {
|
||||||
|
debug: function() {},
|
||||||
|
warn: function() {},
|
||||||
|
error: function() {},
|
||||||
|
emergency: function() {}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock for the 'define' function of requireJS, behaves exactly the same
|
* Mock for the 'define' function of requireJS, behaves exactly the same
|
||||||
* except that it looks up the dependencies in the list provided by registerDepencies()
|
* except that it looks up the dependencies in the list provided by registerDepencies()
|
||||||
@ -82,7 +92,7 @@ function initRequireMethods() {
|
|||||||
GLOBAL.define = defineMock;
|
GLOBAL.define = defineMock;
|
||||||
registeredDependencies = {
|
registeredDependencies = {
|
||||||
'jquery' : GLOBAL.$,
|
'jquery' : GLOBAL.$,
|
||||||
'logging' : console
|
'logging' : logger
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
initRequireMethods();
|
initRequireMethods();
|
||||||
@ -94,7 +104,7 @@ initRequireMethods();
|
|||||||
function purgeDependencies() {
|
function purgeDependencies() {
|
||||||
registeredDependencies = {
|
registeredDependencies = {
|
||||||
'jquery' : GLOBAL.$,
|
'jquery' : GLOBAL.$,
|
||||||
'logging' : console
|
'logging' : logger
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// helper to log debug messages with console
|
// helper to log debug messages with console
|
||||||
|
Loading…
x
Reference in New Issue
Block a user