Merge branch 'develop' of https://brutus.artica.lan:8081/artica/pandorafms into develop
Former-commit-id: 56da7f09fa90543ae4b07af79da5dd308a4aa395
This commit is contained in:
commit
da8507c4b4
|
@ -10,5 +10,16 @@
|
|||
"require": {
|
||||
"mpdf/mpdf": "^7.1",
|
||||
"swiftmailer/swiftmailer": "^6.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Models\\": "include/rest-api/models",
|
||||
"Enterprise\\Models\\": "enterprise/include/rest-api/models"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1090,10 +1090,11 @@ function readFields() {
|
|||
var text = tinymce.get("text-label").getContent();
|
||||
values["label"] = text;
|
||||
|
||||
values["percentile_label_color"] = $(
|
||||
"input[name=percentile_label_color]"
|
||||
).val();
|
||||
|
||||
if ($("input[name=percentile_label]").val().length > 0) {
|
||||
values["percentile_label_color"] = $(
|
||||
"input[name=percentile_label_color]"
|
||||
).val();
|
||||
values["label"] =
|
||||
"<span style='color:" +
|
||||
values["percentile_label_color"] +
|
||||
|
|
|
@ -70,10 +70,6 @@ $values[SECONDS_5MINUTES] = human_time_description_raw(SECONDS_5MINUTES);
|
|||
$values[SECONDS_10MINUTES] = human_time_description_raw(SECONDS_10MINUTES);
|
||||
$values[SECONDS_30MINUTES] = human_time_description_raw(SECONDS_30MINUTES);
|
||||
|
||||
$table_behaviour->data[$row][0] = __('Default interval for refresh on Visual Console').ui_print_help_tip(__('This interval will affect to Visual Console pages'), true);
|
||||
$table_behaviour->data[$row][1] = html_print_select($values, 'vc_refr', $config['vc_refr'], '', 'N/A', 0, true, false, false);
|
||||
$row++;
|
||||
|
||||
$table_behaviour->data[$row][0] = __('Paginated module view');
|
||||
$table_behaviour->data[$row][1] = html_print_checkbox_switch(
|
||||
'paginate_module',
|
||||
|
@ -908,6 +904,24 @@ $row++;
|
|||
$table_vc->size[0] = '50%';
|
||||
$table_vc->data = [];
|
||||
|
||||
// Remove when the new view reaches rock solid stability.
|
||||
$table_vc->data[$row][0] = __('Legacy Visual Console View');
|
||||
$table_vc->data[$row][0] .= ui_print_help_tip(
|
||||
__('To use the old view when using the Visual Console visor'),
|
||||
true
|
||||
);
|
||||
$table_vc->data[$row][1] = html_print_checkbox_switch(
|
||||
'legacy_vc',
|
||||
1,
|
||||
(bool) $config['legacy_vc'],
|
||||
true
|
||||
);
|
||||
$row++;
|
||||
|
||||
$table_vc->data[$row][0] = __('Default interval for refresh on Visual Console').ui_print_help_tip(__('This interval will affect to Visual Console pages'), true);
|
||||
$table_vc->data[$row][1] = html_print_select($values, 'vc_refr', (int) $config['vc_refr'], '', 'N/A', 0, true, false, false);
|
||||
$row++;
|
||||
|
||||
$vc_favourite_view_array[0] = __('Classic view');
|
||||
$vc_favourite_view_array[1] = __('View of favorites');
|
||||
$table_vc->data[$row][0] = __('Type of view of visual consoles').ui_print_help_tip(__('Allows you to directly display the list of favorite visual consoles'), true);
|
||||
|
@ -918,12 +932,8 @@ $row++;
|
|||
$table_vc->data[$row][1] = "<input type ='number' value=".$config['vc_menu_items']." size='5' name='vc_menu_items' min='0' max='25'>";
|
||||
$row++;
|
||||
|
||||
if (empty($config['vc_line_thickness'])) {
|
||||
$config['vc_line_thickness'] = 2;
|
||||
}
|
||||
|
||||
$table_vc->data[$row][0] = __('Default line thickness for the Visual Console').ui_print_help_tip(__('This interval will affect to the lines between elements on the Visual Console'), true);
|
||||
$table_vc->data[$row][1] = html_print_input_text('vc_line_thickness', $config['vc_line_thickness'], '', 5, 5, true);
|
||||
$table_vc->data[$row][1] = html_print_input_text('vc_line_thickness', (int) $config['vc_line_thickness'], '', 5, 5, true);
|
||||
|
||||
|
||||
echo '<fieldset>';
|
||||
|
|
|
@ -328,14 +328,21 @@ function agents_get_alerts_simple($id_agent=false, $filter='', $options=false, $
|
|||
*
|
||||
* By default, it will return all the agents where the user has reading access.
|
||||
*
|
||||
* @param array filter options in an indexed array. See
|
||||
* db_format_array_where_clause_sql()
|
||||
* @param array Fields to get.
|
||||
* @param string Access needed in the agents groups.
|
||||
* @param array $order The order of agents, by default is upward for field nombre.
|
||||
* @param boolean $return Whether to return array with agents or false, or sql string statement
|
||||
* @param array $filter Filter options in an indexed array.
|
||||
* See db_format_array_where_clause_sql().
|
||||
* @param array $fields DB fields to get.
|
||||
* @param string $access ACL level needed in the agents groups.
|
||||
* @param array $order The order of agents, by default is upward
|
||||
* for field nombre.
|
||||
* @param boolean $return Whether to return array with agents or
|
||||
* the sql string statement.
|
||||
* @param boolean $disabled_agent Whether to return only the enabled agents
|
||||
* or not.
|
||||
* @param boolean $use_meta_table Whether to use the regular or the meta table
|
||||
* to retrieve the agents.
|
||||
*
|
||||
* @return mixed An array with all alerts defined for an agent or false in case no allowed groups are specified.
|
||||
* @return mixed An array with all alerts defined for an agent
|
||||
* or false in case no allowed groups are specified.
|
||||
*/
|
||||
function agents_get_agents(
|
||||
$filter=false,
|
||||
|
@ -346,7 +353,8 @@ function agents_get_agents(
|
|||
'order' => 'ASC',
|
||||
],
|
||||
$return=false,
|
||||
$disabled_agent=0
|
||||
$disabled_agent=0,
|
||||
$use_meta_table=false
|
||||
) {
|
||||
global $config;
|
||||
|
||||
|
@ -563,11 +571,15 @@ function agents_get_agents(
|
|||
);
|
||||
}
|
||||
|
||||
$table_name = ($use_meta_table === true) ? 'tmetaconsole_agent' : 'tagente';
|
||||
$sql = sprintf(
|
||||
'SELECT DISTINCT %s
|
||||
FROM tagente LEFT JOIN tagent_secondary_group ON tagent_secondary_group.id_agent=tagente.id_agente
|
||||
FROM `%s` tagente
|
||||
LEFT JOIN tagent_secondary_group
|
||||
ON tagent_secondary_group.id_agent=tagente.id_agente
|
||||
WHERE %s %s',
|
||||
implode(',', $fields),
|
||||
$table_name,
|
||||
$where,
|
||||
$order
|
||||
);
|
||||
|
|
|
@ -980,7 +980,11 @@ function config_update_config()
|
|||
$error_update[] = __('Custom support url');
|
||||
}
|
||||
|
||||
if (!config_update_value('vc_refr', get_parameter('vc_refr'))) {
|
||||
if (!config_update_value('legacy_vc', (int) get_parameter('legacy_vc'))) {
|
||||
$error_update[] = __('Use the legacy Visual Console');
|
||||
}
|
||||
|
||||
if (!config_update_value('vc_refr', (int) get_parameter('vc_refr'))) {
|
||||
$error_update[] = __('Default interval for refresh on Visual Console');
|
||||
}
|
||||
|
||||
|
@ -2415,10 +2419,18 @@ function config_process_config()
|
|||
config_update_value('dbtype', 'mysql');
|
||||
}
|
||||
|
||||
if (!isset($config['legacy_vc'])) {
|
||||
config_update_value('legacy_vc', 0);
|
||||
}
|
||||
|
||||
if (!isset($config['vc_refr'])) {
|
||||
config_update_value('vc_refr', 300);
|
||||
}
|
||||
|
||||
if (!isset($config['vc_line_thickness'])) {
|
||||
config_update_value('vc_line_thickness', 2);
|
||||
}
|
||||
|
||||
if (!isset($config['agent_size_text_small'])) {
|
||||
config_update_value('agent_size_text_small', 18);
|
||||
}
|
||||
|
|
|
@ -3961,38 +3961,54 @@ function visual_map_translate_module_status($module_status)
|
|||
* layouts), and makes an AND operation to be sure that all the items
|
||||
* are OK. If any of them is down, then result is down (0)
|
||||
*
|
||||
* @param int Id of the layout
|
||||
* @param array Information about the status calculation of the item
|
||||
* @param int Depth (for recursion control)
|
||||
* @param integer $layout_id Id of the layout.
|
||||
* @param array $status_data Information about the status calculation of the
|
||||
* item.
|
||||
* @param integer $depth Depth (for recursion control).
|
||||
*
|
||||
* @return boolean The status of the given layout. True if it's OK, false if not.
|
||||
* @return integer The status of the given layout.
|
||||
*/
|
||||
function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
|
||||
{
|
||||
global $config;
|
||||
|
||||
// TODO: Implement this limit into the setup
|
||||
// TODO: Implement this limit into the setup.
|
||||
if ($depth > 10) {
|
||||
return VISUAL_MAP_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
$layout_items = db_get_all_rows_filter('tlayout_data', ['id_layout' => $layout_id]);
|
||||
$layout_items = db_get_all_rows_filter(
|
||||
'tlayout_data',
|
||||
['id_layout' => $layout_id]
|
||||
);
|
||||
|
||||
if ($layout_items === false) {
|
||||
return VISUAL_MAP_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
// Check for valid items to retrieve the status for
|
||||
// Check for valid items to retrieve the status for.
|
||||
$valid_layout_items = [];
|
||||
foreach ($layout_items as $layout_item_data) {
|
||||
if (($layout_item_data['type'] == GROUP_ITEM
|
||||
&& !empty($layout_item_data['id_group'])
|
||||
&& check_acl($config['id_user'], $layout_item_data['id_group'], 'VR')
|
||||
&& check_acl($config['id_user'], $layout_item_data['element_group'], 'VR'))
|
||||
&& check_acl(
|
||||
$config['id_user'],
|
||||
$layout_item_data['id_group'],
|
||||
'VR'
|
||||
)
|
||||
&& check_acl(
|
||||
$config['id_user'],
|
||||
$layout_item_data['element_group'],
|
||||
'VR'
|
||||
))
|
||||
|| ((!empty($layout_item_data['id_layout_linked'])
|
||||
|| !empty($layout_item_data['id_agente_modulo'])
|
||||
|| !empty($layout_item_data['id_agent']))
|
||||
&& check_acl($config['id_user'], $layout_item_data['element_group'], 'VR'))
|
||||
&& check_acl(
|
||||
$config['id_user'],
|
||||
$layout_item_data['element_group'],
|
||||
'VR'
|
||||
))
|
||||
) {
|
||||
$valid_layout_items[] = $layout_item_data;
|
||||
}
|
||||
|
@ -4002,7 +4018,7 @@ function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
|
|||
return VISUAL_MAP_STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
// Sort by node id to reduce the number of connections
|
||||
// Sort by node id to reduce the number of connections.
|
||||
if (is_metaconsole()) {
|
||||
sort_by_column($valid_layout_items, 'id_metaconsole');
|
||||
}
|
||||
|
@ -4021,14 +4037,14 @@ function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
|
|||
|
||||
if (empty($node_id) && $meta_connected_to) {
|
||||
metaconsole_restore_db();
|
||||
// Restore db connection
|
||||
// Restore db connection.
|
||||
$meta_connected_to = null;
|
||||
} else if (!empty($node_id) && ( empty($meta_connected_to)
|
||||
|| $meta_connected_to != $node_id)
|
||||
} else if (!empty($node_id)
|
||||
&& (empty($meta_connected_to) || $meta_connected_to != $node_id)
|
||||
) {
|
||||
if (!empty($meta_connected_to)) {
|
||||
metaconsole_restore_db();
|
||||
// Restore db connection
|
||||
// Restore db connection.
|
||||
}
|
||||
|
||||
$connection = metaconsole_get_connection_by_id($node_id);
|
||||
|
@ -4042,64 +4058,80 @@ function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
|
|||
|
||||
$status = VISUAL_MAP_STATUS_NORMAL;
|
||||
|
||||
$ent_element_status = enterprise_hook('enterprise_visual_map_get_status_element', [$layoutData]);
|
||||
$ent_element_status = enterprise_hook(
|
||||
'enterprise_visual_map_get_status_element',
|
||||
[$layoutData]
|
||||
);
|
||||
if ($ent_element_status === ENTERPRISE_NOT_HOOK) {
|
||||
$ent_element_status = false;
|
||||
}
|
||||
|
||||
// Enterprise element
|
||||
if ($ent_element_status !== false) {
|
||||
// Enterprise element.
|
||||
$status = $ent_element_status;
|
||||
}
|
||||
// Other
|
||||
else {
|
||||
} else {
|
||||
// Other.
|
||||
switch ($layout_item_data['type']) {
|
||||
case STATIC_GRAPH:
|
||||
case PERCENTILE_BAR:
|
||||
case PERCENTILE_BUBBLE:
|
||||
case CIRCULAR_PROGRESS_BAR:
|
||||
case CIRCULAR_INTERIOR_PROGRESS_BAR:
|
||||
// Linked layout
|
||||
if (!empty($layout_item_data['id_layout_linked'])) {
|
||||
$status = visual_map_get_layout_status($layout_item_data['id_layout_linked'], $layout_item_data, ($depth + 1));
|
||||
}
|
||||
// Module
|
||||
else if (!empty($layout_item_data['id_agente_modulo'])) {
|
||||
$module_status = modules_get_agentmodule_status($layout_item_data['id_agente_modulo']);
|
||||
$status = visual_map_translate_module_status($module_status);
|
||||
}
|
||||
// Agent
|
||||
else if (!empty($layout_item_data['id_agent'])) {
|
||||
$agent_status = agents_get_status($layout_item_data['id_agent'], true);
|
||||
$status = visual_map_translate_agent_status($agent_status);
|
||||
}
|
||||
// Unknown
|
||||
else {
|
||||
// Linked layout.
|
||||
$status = visual_map_get_layout_status(
|
||||
$layout_item_data['id_layout_linked'],
|
||||
$layout_item_data,
|
||||
($depth + 1)
|
||||
);
|
||||
} else if (!empty($layout_item_data['id_agente_modulo'])) {
|
||||
// Module.
|
||||
$module_status = modules_get_agentmodule_status(
|
||||
$layout_item_data['id_agente_modulo']
|
||||
);
|
||||
$status = visual_map_translate_module_status(
|
||||
$module_status
|
||||
);
|
||||
} else if (!empty($layout_item_data['id_agent'])) {
|
||||
// Agent.
|
||||
$agent_status = agents_get_status(
|
||||
$layout_item_data['id_agent'],
|
||||
true
|
||||
);
|
||||
$status = visual_map_translate_agent_status(
|
||||
$agent_status
|
||||
);
|
||||
} else {
|
||||
// Unknown.
|
||||
$status = VISUAL_MAP_STATUS_UNKNOWN;
|
||||
}
|
||||
break;
|
||||
|
||||
case GROUP_ITEM:
|
||||
$group_status = groups_get_status($layout_item_data['id_group']);
|
||||
$group_status = groups_get_status(
|
||||
$layout_item_data['id_group']
|
||||
);
|
||||
$status = visual_map_translate_agent_status($group_status);
|
||||
break;
|
||||
|
||||
default:
|
||||
// If it's a graph, a progress bar or a data tag, ALWAYS report status OK
|
||||
// (=0) to avoid confussions here.
|
||||
// If it's a graph, a progress bar or a data tag,
|
||||
// ALWAYS report status OK (=0) to avoid confussions here.
|
||||
$status = VISUAL_MAP_STATUS_NORMAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// When the status calculation type is 'default', only one critical element is required to
|
||||
// set the layout status as critical, so we can return the critical status right now.
|
||||
if ($status_data['linked_layout_status_type'] === 'default' && ( $status == VISUAL_MAP_STATUS_CRITICAL_BAD
|
||||
// When the status calculation type is 'default', only one critical
|
||||
// element is required to set the layout status as critical, so we can
|
||||
// return the critical status right now.
|
||||
if ($status_data['linked_layout_status_type'] === 'default'
|
||||
&& ($status == VISUAL_MAP_STATUS_CRITICAL_BAD
|
||||
|| $status == VISUAL_MAP_STATUS_CRITICAL_ALERT)
|
||||
) {
|
||||
if (is_metaconsole() && $meta_connected_to) {
|
||||
// Restore db connection.
|
||||
metaconsole_restore_db();
|
||||
// Restore db connection
|
||||
}
|
||||
|
||||
return $status;
|
||||
|
@ -4113,11 +4145,11 @@ function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
|
|||
}
|
||||
|
||||
if (is_metaconsole() && $meta_connected_to) {
|
||||
// Restore db connection.
|
||||
metaconsole_restore_db();
|
||||
// Restore db connection
|
||||
}
|
||||
|
||||
// Status calculation
|
||||
// Status calculation.
|
||||
switch ($status_data['linked_layout_status_type']) {
|
||||
default:
|
||||
case 'default':
|
||||
|
@ -4149,7 +4181,7 @@ function visual_map_get_layout_status($layout_id, $status_data=[], $depth=0)
|
|||
if ($num_items_critical > 0
|
||||
&& ((($num_items_critical_alert + $num_items_critical) * 100) / $num_items) >= $weight
|
||||
) {
|
||||
return $num_items_critical_alert > 0 ? VISUAL_MAP_STATUS_CRITICAL_ALERT : VISUAL_MAP_STATUS_CRITICAL_BAD;
|
||||
return ($num_items_critical_alert > 0) ? VISUAL_MAP_STATUS_CRITICAL_ALERT : VISUAL_MAP_STATUS_CRITICAL_BAD;
|
||||
} else if ($num_items_warning > 0
|
||||
&& (($num_items_warning * 100) / $num_items) >= $weight
|
||||
) {
|
||||
|
@ -4525,3 +4557,43 @@ function visual_map_get_color_cloud_element($data)
|
|||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the Visual Console Client files (js & css).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function visual_map_load_client_resources()
|
||||
{
|
||||
global $config;
|
||||
|
||||
$baseUrl = ui_get_full_url(false, false, false, false);
|
||||
$vcClientPath = 'include/visual-console-client';
|
||||
$dir = $config['homedir'].'/'.$vcClientPath;
|
||||
if (is_dir($dir)) {
|
||||
$dh = opendir($dir);
|
||||
if ($dh) {
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
preg_match('/.*.js$/', $file, $match, PREG_OFFSET_CAPTURE);
|
||||
if (empty($match) === false) {
|
||||
$url = $baseUrl.$vcClientPath.'/'.$match[0][0];
|
||||
echo '<script type="text/javascript" src="'.$url.'"></script>';
|
||||
continue;
|
||||
}
|
||||
|
||||
preg_match('/.*.css$/', $file, $match, PREG_OFFSET_CAPTURE);
|
||||
if (empty($match) === false) {
|
||||
$url = $baseUrl.$vcClientPath.'/'.$match[0][0];
|
||||
echo '<link type="text/css" rel="stylesheet" href="'.$url.'" />';
|
||||
}
|
||||
}
|
||||
|
||||
closedir($dh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -688,7 +688,7 @@ function visual_map_editor_print_item_palette($visualConsole_id, $background)
|
|||
'percentile_item',
|
||||
'datos',
|
||||
];
|
||||
$form_items['percentile_item_row_6']['html'] = '<td align="left">'.__('Label color').'</td>
|
||||
$form_items['percentile_item_row_6']['html'] = '<td align="left">'.__('Value color').'</td>
|
||||
<td align="left">'.html_print_input_text_extended(
|
||||
'percentile_label_color',
|
||||
'#ffffff',
|
||||
|
|
|
@ -2133,7 +2133,7 @@ function pandoraFlotArea(
|
|||
var plot = $.plot($("#" + graph_id), datas, options);
|
||||
|
||||
// Re-calculate the graph height with the legend height
|
||||
if (dashboard || vconsole) {
|
||||
if (dashboard) {
|
||||
$acum = 0;
|
||||
if (dashboard) $acum = 35;
|
||||
var hDiff =
|
||||
|
|
|
@ -1748,7 +1748,9 @@ function round_with_decimals(value, multiplier) {
|
|||
if (typeof multiplier === "undefined") multiplier = 1;
|
||||
|
||||
// Return non numeric types without modification
|
||||
if (typeof value !== "number") return value;
|
||||
if (typeof value !== "number" || Number.isNaN(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value * multiplier == 0) return 0;
|
||||
if (Math.abs(value) * multiplier >= 1) {
|
||||
|
|
|
@ -1,3 +1,268 @@
|
|||
// TODO: Add Artica ST header.
|
||||
/* globals jQuery, VisualConsole */
|
||||
|
||||
/*
|
||||
* *********************
|
||||
* * New VC functions. *
|
||||
* *********************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate a Visual Console client.
|
||||
* @param {HTMLElement} container Node which will be used to contain the VC.
|
||||
* @param {object} props VC container properties.
|
||||
* @param {object[]} items List of item definitions.
|
||||
* @param {string | null} baseUrl Base URL to perform API requests.
|
||||
* @param {number | null} updateInterval Time in milliseconds between VC updates.
|
||||
* @param {function | null} onUpdate Callback which will be execuded when the Visual Console.
|
||||
* is updated. It will receive two arguments with the old and the new Visual Console's
|
||||
* data structure.
|
||||
* @return {VisualConsole | null} The Visual Console instance or a null value.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function createVisualConsole(
|
||||
container,
|
||||
props,
|
||||
items,
|
||||
baseUrl,
|
||||
updateInterval,
|
||||
onUpdate
|
||||
) {
|
||||
var visualConsole = null;
|
||||
var linkedVCRequest = null;
|
||||
var updateVCRequest = null;
|
||||
|
||||
if (container == null || props == null || items == null) return null;
|
||||
if (baseUrl == null) baseUrl = "";
|
||||
|
||||
// Code which will be executed between intervals.
|
||||
var intervalRef = null;
|
||||
var stopInterval = function() {
|
||||
if (intervalRef !== null) window.clearInterval(intervalRef);
|
||||
};
|
||||
var startInterval = function() {
|
||||
if (updateInterval == null || updateInterval <= 0) return;
|
||||
stopInterval();
|
||||
|
||||
intervalRef = window.setInterval(function() {
|
||||
if (updateVCRequest !== null) updateVCRequest.abort();
|
||||
updateVCRequest = loadVisualConsoleData(
|
||||
baseUrl,
|
||||
visualConsole.props.id,
|
||||
function(error, data) {
|
||||
if (error) {
|
||||
console.log(
|
||||
"[ERROR]",
|
||||
"[VISUAL-CONSOLE-CLIENT]",
|
||||
"[API]",
|
||||
error.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace Visual Console.
|
||||
if (data != null && data.props != null && data.items != null) {
|
||||
try {
|
||||
var props =
|
||||
typeof data.props === "string"
|
||||
? JSON.parse(data.props)
|
||||
: data.props;
|
||||
var items =
|
||||
typeof data.items === "string"
|
||||
? JSON.parse(data.items)
|
||||
: data.items;
|
||||
|
||||
var prevProps = visualConsole.props;
|
||||
// Update the data structure.
|
||||
visualConsole.props = props;
|
||||
// Update the items.
|
||||
visualConsole.updateElements(items);
|
||||
// Emit the VC update event.
|
||||
if (onUpdate) onUpdate(prevProps, visualConsole.props);
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
}
|
||||
}
|
||||
);
|
||||
}, updateInterval);
|
||||
};
|
||||
|
||||
// Initialize the Visual Console.
|
||||
try {
|
||||
visualConsole = new VisualConsole(container, props, items);
|
||||
// VC Item clicked.
|
||||
visualConsole.onClick(function(e) {
|
||||
// Override the link to another VC if it isn't on remote console.
|
||||
if (
|
||||
e.data &&
|
||||
e.data.linkedLayoutId != null &&
|
||||
e.data.linkedLayoutId > 0 &&
|
||||
e.data.link != null &&
|
||||
e.data.link.length > 0 &&
|
||||
(e.data.metaconsoleId == null || e.data.metaconsoleId === 0)
|
||||
) {
|
||||
// Stop the current link behavior.
|
||||
e.nativeEvent.preventDefault();
|
||||
|
||||
// Fetch and update the old VC with the new.
|
||||
if (linkedVCRequest !== null) linkedVCRequest.abort();
|
||||
linkedVCRequest = loadVisualConsoleData(
|
||||
baseUrl,
|
||||
e.data.linkedLayoutId,
|
||||
function(error, data) {
|
||||
if (error) {
|
||||
console.log(
|
||||
"[ERROR]",
|
||||
"[VISUAL-CONSOLE-CLIENT]",
|
||||
"[API]",
|
||||
error.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace Visual Console.
|
||||
if (data != null && data.props != null && data.items != null) {
|
||||
// Cancel the old VC updates.
|
||||
stopInterval();
|
||||
|
||||
try {
|
||||
var props =
|
||||
typeof data.props === "string"
|
||||
? JSON.parse(data.props)
|
||||
: data.props;
|
||||
var items =
|
||||
typeof data.items === "string"
|
||||
? JSON.parse(data.items)
|
||||
: data.items;
|
||||
|
||||
if (updateVCRequest !== null) updateVCRequest.abort();
|
||||
// Save the old props.
|
||||
var prevProps = visualConsole.props;
|
||||
// Update the data structure.
|
||||
visualConsole.props = props;
|
||||
// Update the items.
|
||||
visualConsole.updateElements(items);
|
||||
// Emit the VC update event.
|
||||
if (onUpdate) onUpdate(prevProps, visualConsole.props);
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
|
||||
// Restart the updates.
|
||||
startInterval();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Start an interval to update the Visual Console.
|
||||
startInterval();
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", "[VISUAL-CONSOLE-CLIENT]", error.message);
|
||||
}
|
||||
|
||||
return visualConsole;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a Visual Console's structure and its items.
|
||||
* @param {string} baseUrl Base URL to build the API path.
|
||||
* @param {number} vcId Identifier of the Visual Console.
|
||||
* @param {function} callback Function to be executed on request success or fail.
|
||||
* On success, the function will receive an object with the next properties:
|
||||
* - `props`: object with the Visual Console's data structure.
|
||||
* - `items`: array of data structures of the Visual Console's items.
|
||||
* @return {Object} Cancellable. Object which include and .abort([statusText]) function.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function loadVisualConsoleData(baseUrl, vcId, callback) {
|
||||
// var apiPath = baseUrl + "/include/rest-api";
|
||||
var apiPath = baseUrl + "/ajax.php";
|
||||
var vcJqXHR = null;
|
||||
var itemsJqXHR = null;
|
||||
|
||||
// Initialize the final result.
|
||||
var result = {
|
||||
props: null,
|
||||
items: null
|
||||
};
|
||||
|
||||
// Cancel the ajax requests.
|
||||
var abort = function(textStatus) {
|
||||
if (textStatus == null) textStatus = "abort";
|
||||
|
||||
// -- XMLHttpRequest.readyState --
|
||||
// Value State Description
|
||||
// 0 UNSENT Client has been created. open() not called yet.
|
||||
// 4 DONE The operation is complete.
|
||||
|
||||
if (vcJqXHR.readyState !== 0 && vcJqXHR.readyState !== 4)
|
||||
vcJqXHR.abort(textStatus);
|
||||
if (itemsJqXHR.readyState !== 0 && itemsJqXHR.readyState !== 4)
|
||||
itemsJqXHR.abort(textStatus);
|
||||
};
|
||||
|
||||
// Check if the required data is complete.
|
||||
var checkResult = function() {
|
||||
return result.props !== null && result.items !== null;
|
||||
};
|
||||
|
||||
// Failed request handler.
|
||||
var handleFail = function(jqXHR, textStatus, errorThrown) {
|
||||
abort();
|
||||
// Manually aborted or not.
|
||||
if (textStatus === "abort") {
|
||||
callback();
|
||||
} else {
|
||||
var error = new Error(errorThrown);
|
||||
error.request = jqXHR;
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Curried function which handle success.
|
||||
var handleSuccess = function(key) {
|
||||
// Actual request handler.
|
||||
return function(data) {
|
||||
result[key] = data;
|
||||
if (checkResult()) callback(null, result);
|
||||
};
|
||||
};
|
||||
|
||||
// Visual Console container request.
|
||||
vcJqXHR = jQuery
|
||||
// .get(apiPath + "/visual-consoles/" + vcId, null, "json")
|
||||
.get(
|
||||
apiPath,
|
||||
{
|
||||
page: "include/rest-api/index",
|
||||
getVisualConsole: 1,
|
||||
visualConsoleId: vcId
|
||||
},
|
||||
"json"
|
||||
)
|
||||
.done(handleSuccess("props"))
|
||||
.fail(handleFail);
|
||||
// Visual Console items request.
|
||||
itemsJqXHR = jQuery
|
||||
// .get(apiPath + "/visual-consoles/" + vcId + "/items", null, "json")
|
||||
.get(
|
||||
apiPath,
|
||||
{
|
||||
page: "include/rest-api/index",
|
||||
getVisualConsoleItems: 1,
|
||||
visualConsoleId: vcId
|
||||
},
|
||||
"json"
|
||||
)
|
||||
.done(handleSuccess("items"))
|
||||
.fail(handleFail);
|
||||
|
||||
// Abortable.
|
||||
return {
|
||||
abort: abort
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Delete the functions below when you can.
|
||||
/**************************************
|
||||
These functions require jQuery library
|
||||
**************************************/
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
global $config;
|
||||
|
||||
if (!is_ajax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once $config['homedir'].'/vendor/autoload.php';
|
||||
|
||||
use Models\VisualConsole\Container as VisualConsole;
|
||||
|
||||
$visualConsoleId = (int) get_parameter('visualConsoleId');
|
||||
$getVisualConsole = (bool) get_parameter('getVisualConsole');
|
||||
$getVisualConsoleItems = (bool) get_parameter('getVisualConsoleItems');
|
||||
|
||||
// Check groups can access user.
|
||||
$aclUserGroups = [];
|
||||
if (!users_can_manage_group_all('AR')) {
|
||||
$aclUserGroups = array_keys(users_get_groups(false, 'AR'));
|
||||
}
|
||||
|
||||
ob_clean();
|
||||
|
||||
if ($getVisualConsole === true) {
|
||||
$visualConsole = VisualConsole::fromDB(['id' => $visualConsoleId]);
|
||||
$visualConsoleData = $visualConsole->toArray();
|
||||
$groupId = $visualConsoleData['groupId'];
|
||||
|
||||
// ACL.
|
||||
$aclRead = check_acl($config['id_user'], $groupId, 'VR');
|
||||
$aclWrite = check_acl($config['id_user'], $groupId, 'VW');
|
||||
$aclManage = check_acl($config['id_user'], $groupId, 'VM');
|
||||
|
||||
if (!$aclRead && !$aclWrite && !$aclManage) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without group access'
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo $visualConsole;
|
||||
} else if ($getVisualConsoleItems === true) {
|
||||
$vcItems = VisualConsole::getItemsFromDB($visualConsoleId, $aclUserGroups);
|
||||
echo '['.implode($vcItems, ',').']';
|
||||
}
|
||||
|
||||
exit;
|
|
@ -0,0 +1,231 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models;
|
||||
|
||||
/**
|
||||
* This class should be extended to add functionalities to
|
||||
* fetch, validate, transform and represent data entities.
|
||||
*/
|
||||
abstract class Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Internal data of the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data;
|
||||
|
||||
|
||||
/**
|
||||
* Validate the received data structure to ensure if we can extract the
|
||||
* values required to build the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException If any input value is considered
|
||||
* invalid.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
abstract protected function validateData(array $data): void;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
abstract protected function decode(array $data): array;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor of the model. It won't be public. The instances
|
||||
* will be created through factories which start with from*.
|
||||
*
|
||||
* @param array $unknownData Input data structure.
|
||||
*/
|
||||
protected function __construct(array $unknownData)
|
||||
{
|
||||
$this->validateData($unknownData);
|
||||
$this->data = $this->decode($unknownData);
|
||||
// Sort alphabetically.
|
||||
ksort($this->data, (SORT_NATURAL | SORT_FLAG_CASE));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instance the class with the unknown input data.
|
||||
*
|
||||
* @param array $data Unknown data structure.
|
||||
*
|
||||
* @return self Instance of the model.
|
||||
*/
|
||||
public static function fromArray(array $data)
|
||||
{
|
||||
// The reserved word static refers to the invoked class at runtime.
|
||||
return new static($data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter to retrieve the modeled element.
|
||||
*
|
||||
* @return array The modeled element data structure stored into the DB.
|
||||
* @throws \Exception When the data cannot be retrieved from the DB.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
abstract protected static function fetchDataFromDB(array $filter);
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a model's instance from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter to retrieve the modeled element.
|
||||
*
|
||||
* @return self A modeled element's instance.
|
||||
*/
|
||||
public static function fromDB(array $filter): self
|
||||
{
|
||||
// The reserved word static refers to the invoked class at runtime.
|
||||
return static::fromArray(static::fetchDataFromDB($filter));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JSON representation of the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* JSON representation of the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson(): string
|
||||
{
|
||||
return \json_encode($this->data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Text representation of the model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* -------------
|
||||
* - UTILITIES -
|
||||
* -------------
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* From a unknown value, it will try to extract a valid boolean value.
|
||||
*
|
||||
* @param mixed $value Unknown input.
|
||||
*
|
||||
* @return boolean Valid boolean value.
|
||||
*/
|
||||
protected static function parseBool($value): bool
|
||||
{
|
||||
if (\is_bool($value) === true) {
|
||||
return $value;
|
||||
} else if (\is_numeric($value) === true) {
|
||||
return $value > 0;
|
||||
} else if (\is_string($value) === true) {
|
||||
return $value === '1' || $value === 'true';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a not empty string or a default value from a unknown value.
|
||||
*
|
||||
* @param mixed $val Input value.
|
||||
* @param mixed $def Default value.
|
||||
*
|
||||
* @return mixed A valid string (not empty) extracted from the input
|
||||
* or the default value.
|
||||
*/
|
||||
protected static function notEmptyStringOr($val, $def)
|
||||
{
|
||||
return (\is_string($val) === true && strlen($val) > 0) ? $val : $def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a valid integer or a default value from a unknown value.
|
||||
*
|
||||
* @param mixed $val Input value.
|
||||
* @param mixed $def Default value.
|
||||
*
|
||||
* @return mixed A valid int extracted from the input or the default value.
|
||||
*/
|
||||
protected static function parseIntOr($val, $def)
|
||||
{
|
||||
return (is_numeric($val) === true) ? (int) $val : $def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a valid float or a default value from a unknown value.
|
||||
*
|
||||
* @param mixed $val Input value.
|
||||
* @param mixed $def Default value.
|
||||
*
|
||||
* @return mixed A valid float extracted from the input or the
|
||||
* default value.
|
||||
*/
|
||||
protected static function parseFloatOr($val, $def)
|
||||
{
|
||||
return (is_numeric($val) === true) ? (float) $val : $def;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a value from a dictionary from a possible pool of keys.
|
||||
*
|
||||
* @param array $dict Input array.
|
||||
* @param array $keys Possible keys.
|
||||
*
|
||||
* @return mixed The first value found with the pool of keys or null.
|
||||
*/
|
||||
protected static function issetInArray(array $dict, array $keys)
|
||||
{
|
||||
foreach ($keys as $key => $value) {
|
||||
if (isset($dict[$value]) === true) {
|
||||
return $dict[$value];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole;
|
||||
use Models\Model;
|
||||
|
||||
/**
|
||||
* Model of a Visual Console.
|
||||
*/
|
||||
final class Container extends Model
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Validate the received data structure to ensure if we can extract the
|
||||
* values required to build the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException If any input value is considered
|
||||
* invalid.
|
||||
*
|
||||
* @overrides Model::validateData.
|
||||
*/
|
||||
protected function validateData(array $data): void
|
||||
{
|
||||
if (isset($data['id']) === false
|
||||
|| \is_numeric($data['id']) === false
|
||||
) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the Id property is required and should be integer'
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($data['name']) === false
|
||||
|| \is_string($data['name']) === false
|
||||
|| empty($data['name']) === true
|
||||
) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the name property is required and should be string'
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($data['width']) === false
|
||||
|| \is_numeric($data['width']) === false
|
||||
|| $data['width'] <= 0
|
||||
) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the width property is required and should greater than 0'
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($data['height']) === false
|
||||
|| \is_numeric($data['height']) === false
|
||||
|| $data['height'] <= 0
|
||||
) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the height property is required and should greater than 0'
|
||||
);
|
||||
}
|
||||
|
||||
static::extractGroupId($data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Model::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $data['id'],
|
||||
'name' => $data['name'],
|
||||
'groupId' => static::extractGroupId($data),
|
||||
'backgroundImage' => static::extractBackgroundImage($data),
|
||||
'backgroundColor' => static::extractBackgroundColor($data),
|
||||
'isFavorite' => static::extractFavorite($data),
|
||||
'width' => (int) $data['width'],
|
||||
'height' => (int) $data['height'],
|
||||
'backgroundURL' => static::extractBackgroundUrl($data),
|
||||
'relationLineWidth' => (int) $data['relationLineWidth'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a group Id value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid identifier of a group.
|
||||
*
|
||||
* @throws \InvalidArgumentException When a valid group Id can't be found.
|
||||
*/
|
||||
private static function extractGroupId(array $data): int
|
||||
{
|
||||
$groupId = static::parseIntOr(
|
||||
static::issetInArray($data, ['id_group', 'groupId']),
|
||||
null
|
||||
);
|
||||
|
||||
if ($groupId === null || $groupId < 0) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the group Id property is required and should be integer'
|
||||
);
|
||||
}
|
||||
|
||||
return $groupId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a image name value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the image name (not empty) or null.
|
||||
*/
|
||||
private static function extractBackgroundImage(array $data)
|
||||
{
|
||||
$backgroundImage = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['background', 'backgroundURL']),
|
||||
null
|
||||
);
|
||||
|
||||
return ($backgroundImage === 'None.png') ? null : $backgroundImage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a image url value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the image url (not empty) or null.
|
||||
*/
|
||||
private static function extractBackgroundUrl(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['backgroundURL']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a background color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the color (not empty) or null.
|
||||
*/
|
||||
private static function extractBackgroundColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray(
|
||||
$data,
|
||||
[
|
||||
'backgroundColor',
|
||||
'background_color',
|
||||
]
|
||||
),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the "is favorite" switch value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return boolean If the item is favorite or not.
|
||||
*/
|
||||
private static function extractFavorite(array $data): bool
|
||||
{
|
||||
return static::parseBool(
|
||||
static::issetInArray($data, ['is_favourite', 'isFavorite'])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a container data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console.
|
||||
*
|
||||
* @return self A Visual Console Container instance.
|
||||
* @throws \Exception When the data cannot be retrieved from the DB.
|
||||
*
|
||||
* @override Model::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter)
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$row = \db_get_row_filter('tlayout', $filter);
|
||||
|
||||
if ($row === false) {
|
||||
throw new \Exception('error fetching the data from the DB');
|
||||
}
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_io.php';
|
||||
include_once $config['homedir'].'/include/functions_ui.php';
|
||||
|
||||
// Clean HTML entities.
|
||||
$row = \io_safe_output($row);
|
||||
|
||||
$row['relationLineWidth'] = (int) $config['vc_line_thickness'];
|
||||
|
||||
$backgroundUrl = static::extractBackgroundUrl($row);
|
||||
$backgroundImage = static::extractBackgroundImage($row);
|
||||
|
||||
if ($backgroundUrl === null && $backgroundImage !== null) {
|
||||
$backgroundPath = 'images/console/background/'.$backgroundImage;
|
||||
|
||||
$width = (int) $row['width'];
|
||||
$height = (int) $row['height'];
|
||||
|
||||
if ($width > 0 && $height > 0) {
|
||||
$q = [
|
||||
'getFile' => 1,
|
||||
'thumb' => 1,
|
||||
'thumb_size' => $width.'x'.$height,
|
||||
'file' => '../../'.$backgroundPath,
|
||||
];
|
||||
$row['backgroundURL'] = ui_get_full_url(
|
||||
'include/Image/image_functions.php?'.http_build_query($q),
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
$row['backgroundURL'] = ui_get_full_url(
|
||||
$backgroundPath,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return \io_safe_output($row);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a item's class.
|
||||
*
|
||||
* @param integer $type Type of the item of the Visual Console.
|
||||
*
|
||||
* @return mixed A reference to the item's class.
|
||||
*/
|
||||
public static function getItemClass(int $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case STATIC_GRAPH:
|
||||
return Items\StaticGraph::class;
|
||||
|
||||
case MODULE_GRAPH:
|
||||
return Items\ModuleGraph::class;
|
||||
|
||||
case SIMPLE_VALUE:
|
||||
case SIMPLE_VALUE_MAX:
|
||||
case SIMPLE_VALUE_MIN:
|
||||
case SIMPLE_VALUE_AVG:
|
||||
return Items\SimpleValue::class;
|
||||
|
||||
case PERCENTILE_BAR:
|
||||
case PERCENTILE_BUBBLE:
|
||||
case CIRCULAR_PROGRESS_BAR:
|
||||
case CIRCULAR_INTERIOR_PROGRESS_BAR:
|
||||
return Items\Percentile::class;
|
||||
|
||||
case LABEL:
|
||||
return Items\Label::class;
|
||||
|
||||
case ICON:
|
||||
return Items\Icon::class;
|
||||
|
||||
// Enterprise item. It may not exist.
|
||||
case SERVICE:
|
||||
return \class_exists('\Enterprise\Models\VisualConsole\Items\Service') ? \Enterprise\Models\VisualConsole\Items\Service::class : Item::class;
|
||||
|
||||
case GROUP_ITEM:
|
||||
return Items\Group::class;
|
||||
|
||||
case BOX_ITEM:
|
||||
return Items\Box::class;
|
||||
|
||||
case LINE_ITEM:
|
||||
return Items\Line::class;
|
||||
|
||||
case AUTO_SLA_GRAPH:
|
||||
return Items\EventsHistory::class;
|
||||
|
||||
case DONUT_GRAPH:
|
||||
return Items\DonutGraph::class;
|
||||
|
||||
case BARS_GRAPH:
|
||||
return Items\BarsGraph::class;
|
||||
|
||||
case CLOCK:
|
||||
return Items\Clock::class;
|
||||
|
||||
case COLOR_CLOUD:
|
||||
return Items\ColorCloud::class;
|
||||
|
||||
default:
|
||||
return Item::class;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a list of items which belong to the Visual Console.
|
||||
*
|
||||
* @param integer $layoutId Identifier of the Visual Console.
|
||||
* @param array $groupsFilter Groups can access user.
|
||||
*
|
||||
* @return array A list of items.
|
||||
* @throws \Exception When the data cannot be retrieved from the DB.
|
||||
*/
|
||||
public static function getItemsFromDB(
|
||||
int $layoutId,
|
||||
array $groupsFilter=[]
|
||||
): array {
|
||||
// Default filter.
|
||||
$filter = ['id_layout' => $layoutId];
|
||||
$fields = [
|
||||
'id',
|
||||
'type',
|
||||
];
|
||||
|
||||
// Override the filter if the groups filter is not empty.
|
||||
if (count($groupsFilter) > 0) {
|
||||
// Filter group for elements groups.
|
||||
$filter = [];
|
||||
$filter[] = \db_format_array_where_clause_sql(
|
||||
[
|
||||
'id_layout' => $layoutId,
|
||||
'element_group' => $groupsFilter,
|
||||
]
|
||||
);
|
||||
|
||||
// Filter groups for type groups.
|
||||
// Only true condition if type is GROUP_ITEM.
|
||||
$filter[] = '('.\db_format_array_where_clause_sql(
|
||||
[
|
||||
'type' => GROUP_ITEM,
|
||||
'id_group' => $groupsFilter,
|
||||
]
|
||||
).')';
|
||||
}
|
||||
|
||||
$rows = \db_get_all_rows_filter(
|
||||
'tlayout_data',
|
||||
$filter,
|
||||
$fields,
|
||||
'OR'
|
||||
);
|
||||
|
||||
if ($rows === false) {
|
||||
$rows = [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($rows as $data) {
|
||||
$itemId = (int) $data['id'];
|
||||
$itemType = (int) $data['type'];
|
||||
$class = static::getItemClass($itemType);
|
||||
|
||||
try {
|
||||
array_push($items, $class::fromDB(['id' => $itemId]));
|
||||
} catch (\Throwable $e) {
|
||||
// TODO: Log this?
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,346 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a bars graph item of the Visual Console.
|
||||
*/
|
||||
final class BarsGraph extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
/**
|
||||
* Used to enable validation, extraction and encodeing of the HTML output.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useHtmlOutput = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = BARS_GRAPH;
|
||||
$return['gridColor'] = $this->extractGridColor($data);
|
||||
$return['backgroundColor'] = $this->extractBackgroundColor($data);
|
||||
$return['typeGraph'] = $this->extractTypeGraph($data);
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a grid color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the grid color (not empty) or null.
|
||||
*/
|
||||
private function extractGridColor(array $data): string
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['gridColor', 'border_color']),
|
||||
'#000000'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a background color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string One of 'white', 'black' or 'transparent'.
|
||||
* 'white' by default.
|
||||
*/
|
||||
private function extractBackgroundColor(array $data): string
|
||||
{
|
||||
$backgroundColor = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['backgroundColor', 'image']),
|
||||
null
|
||||
);
|
||||
|
||||
switch ($backgroundColor) {
|
||||
case 'black':
|
||||
case 'transparent':
|
||||
return $backgroundColor;
|
||||
|
||||
default:
|
||||
return 'white';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a type graph value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string One of 'vertical' or 'horizontal'. 'vertical' by default.
|
||||
*/
|
||||
private function extractTypeGraph(array $data): string
|
||||
{
|
||||
$typeGraph = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['typeGraph', 'type_graph']),
|
||||
null
|
||||
);
|
||||
|
||||
switch ($typeGraph) {
|
||||
case 'horizontal':
|
||||
return 'horizontal';
|
||||
|
||||
default:
|
||||
return 'vertical';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load config.
|
||||
global $config;
|
||||
|
||||
// Load side libraries.
|
||||
include_once $config['homedir'].'/include/functions_ui.php';
|
||||
include_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
include_once $config['homedir'].'/include/graphs/fgraph.php';
|
||||
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
// Extract needed properties.
|
||||
$gridColor = static::extractGridColor($data);
|
||||
$backGroundColor = static::extractBackgroundColor($data);
|
||||
$typeGraph = static::extractTypeGraph($data);
|
||||
|
||||
// Get the linked agent and module Ids.
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$agentId = $linkedModule['agentId'];
|
||||
$moduleId = $linkedModule['moduleId'];
|
||||
$metaconsoleId = $linkedModule['metaconsoleId'];
|
||||
|
||||
if ($agentId === null) {
|
||||
throw new \InvalidArgumentException('missing agent Id');
|
||||
}
|
||||
|
||||
if ($moduleId === null) {
|
||||
throw new \InvalidArgumentException('missing module Id');
|
||||
}
|
||||
|
||||
// Add colors that will use the graphics.
|
||||
$color = [];
|
||||
|
||||
$color[0] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color1'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[1] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color2'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[2] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color3'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[3] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color4'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[4] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color5'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[5] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color6'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[6] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color7'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[7] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color8'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[8] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color9'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[9] = [
|
||||
'border' => '#000000',
|
||||
'color' => $config['graph_color10'],
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[11] = [
|
||||
'border' => '#000000',
|
||||
'color' => COL_GRAPH9,
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[12] = [
|
||||
'border' => '#000000',
|
||||
'color' => COL_GRAPH10,
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[13] = [
|
||||
'border' => '#000000',
|
||||
'color' => COL_GRAPH11,
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[14] = [
|
||||
'border' => '#000000',
|
||||
'color' => COL_GRAPH12,
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
$color[15] = [
|
||||
'border' => '#000000',
|
||||
'color' => COL_GRAPH13,
|
||||
'alpha' => CHART_DEFAULT_ALPHA,
|
||||
];
|
||||
|
||||
// Maybe connect to node.
|
||||
$nodeConnected = false;
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$nodeConnected = \metaconsole_connect(
|
||||
null,
|
||||
$metaconsoleId
|
||||
) === NOERR;
|
||||
|
||||
if ($nodeConnected === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error connecting to the node'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$moduleData = \get_bars_module_data($moduleId);
|
||||
|
||||
$waterMark = [
|
||||
'file' => $config['homedir'].'/images/logo_vertical_water.png',
|
||||
'url' => \ui_get_full_url(
|
||||
'images/logo_vertical_water.png',
|
||||
false,
|
||||
false,
|
||||
false
|
||||
),
|
||||
];
|
||||
|
||||
if ((int) $data['width'] === 0 || (int) $data['height'] === 0) {
|
||||
$width = 400;
|
||||
$height = 400;
|
||||
} else {
|
||||
$width = (int) $data['width'];
|
||||
$height = (int) $data['height'];
|
||||
}
|
||||
|
||||
if ($typeGraph === 'horizontal') {
|
||||
$graph = \hbar_graph(
|
||||
$moduleData,
|
||||
$width,
|
||||
$height,
|
||||
$color,
|
||||
[],
|
||||
[],
|
||||
\ui_get_full_url(
|
||||
'images/image_problem_area.png',
|
||||
false,
|
||||
false,
|
||||
false
|
||||
),
|
||||
'',
|
||||
'',
|
||||
$waterMark,
|
||||
$config['fontpath'],
|
||||
6,
|
||||
'',
|
||||
0,
|
||||
$config['homeurl'],
|
||||
$backGroundColor,
|
||||
$gridColor
|
||||
);
|
||||
} else {
|
||||
$graph = \vbar_graph(
|
||||
$moduleData,
|
||||
$width,
|
||||
$height,
|
||||
$color,
|
||||
[],
|
||||
[],
|
||||
\ui_get_full_url(
|
||||
'images/image_problem_area.png',
|
||||
false,
|
||||
false,
|
||||
false
|
||||
),
|
||||
'',
|
||||
'',
|
||||
$waterMark,
|
||||
$config['fontpath'],
|
||||
6,
|
||||
'',
|
||||
0,
|
||||
$config['homeurl'],
|
||||
$backGroundColor,
|
||||
true,
|
||||
false,
|
||||
$gridColor
|
||||
);
|
||||
}
|
||||
|
||||
// Restore connection.
|
||||
if ($nodeConnected === true) {
|
||||
\metaconsole_restore_db();
|
||||
}
|
||||
|
||||
$data['html'] = $graph;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a Box item of the Visual Console.
|
||||
*/
|
||||
final class Box extends Item
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$boxData = parent::decode($data);
|
||||
$boxData['type'] = BOX_ITEM;
|
||||
$boxData['parentId'] = null;
|
||||
$boxData['aclGroupId'] = null;
|
||||
$boxData['borderWidth'] = $this->extractBorderWidth($data);
|
||||
$boxData['borderColor'] = $this->extractBorderColor($data);
|
||||
$boxData['fillColor'] = $this->extractFillColor($data);
|
||||
return $boxData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a border width value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid border width. 0 by default.
|
||||
*/
|
||||
private function extractBorderWidth(array $data): int
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['borderWidth', 'border_width']),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a border color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the border color (not empty) or null.
|
||||
*/
|
||||
private function extractBorderColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['borderColor', 'border_color']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a fill color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the fill color (not empty) or null.
|
||||
*/
|
||||
private function extractFillColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['fillColor', 'fill_color']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a clock item of the Visual Console.
|
||||
*/
|
||||
final class Clock extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
* @throws \InvalidArgumentException When there is a problem with
|
||||
* the time management.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$clockData = parent::decode($data);
|
||||
$clockData['type'] = CLOCK;
|
||||
$clockData['clockType'] = static::extractClockType($data);
|
||||
$clockData['clockFormat'] = static::extractClockFormat($data);
|
||||
$clockData['clockTimezone'] = static::extractClockTimezone($data);
|
||||
|
||||
try {
|
||||
$timezone = new \DateTimeZone($clockData['clockTimezone']);
|
||||
$timezoneUTC = new \DateTimeZone('UTC');
|
||||
$dateTimeUtc = new \DateTime('now', $timezoneUTC);
|
||||
$clockData['clockTimezoneOffset'] = $timezone->getOffset(
|
||||
$dateTimeUtc
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
throw new \InvalidArgumentException($e->getMessage());
|
||||
}
|
||||
|
||||
// $clockData['showClockTimezone'] = static::parseBool(
|
||||
// static::issetInArray($data, ['showClockTimezone'])
|
||||
// );
|
||||
// TODO: Remove the true by default when added into the editor.
|
||||
$clockData['showClockTimezone'] = true;
|
||||
$clockData['color'] = static::extractColor($data);
|
||||
return $clockData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a clock type value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string One of 'digital' or 'analogic'. 'analogic' by default.
|
||||
*/
|
||||
private static function extractClockType(array $data): string
|
||||
{
|
||||
$clockType = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['clockType', 'clock_animation']),
|
||||
null
|
||||
);
|
||||
|
||||
switch ($clockType) {
|
||||
case 'digital':
|
||||
case 'digital_1':
|
||||
return 'digital';
|
||||
|
||||
default:
|
||||
return 'analogic';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a clock format value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string One of 'time' or 'datetime'. 'datetime' by default.
|
||||
*/
|
||||
private static function extractClockFormat(array $data): string
|
||||
{
|
||||
$clockFormat = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['clockFormat', 'time_format']),
|
||||
null
|
||||
);
|
||||
|
||||
switch ($clockFormat) {
|
||||
case 'time':
|
||||
return 'time';
|
||||
|
||||
default:
|
||||
return 'datetime';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a clock timezone value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException When a valid clock timezone cannot be
|
||||
* extracted.
|
||||
*/
|
||||
private static function extractClockTimezone(array $data): string
|
||||
{
|
||||
$clockTimezone = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['clockTimezone', 'timezone']),
|
||||
null
|
||||
);
|
||||
|
||||
if ($clockTimezone === null) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the clockTimezone property is required and should be string'
|
||||
);
|
||||
}
|
||||
|
||||
return $clockTimezone;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed returns a color or null.
|
||||
*/
|
||||
private static function extractColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['color', 'fill_color']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a color cloud item of the Visual Console.
|
||||
*/
|
||||
final class ColorCloud extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item->decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$decodedData = parent::decode($data);
|
||||
$decodedData['type'] = COLOR_CLOUD;
|
||||
$decodedData['label'] = null;
|
||||
$decodedData['defaultColor'] = static::extractDefaultColor($data);
|
||||
$decodedData['colorRanges'] = static::extractColorRanges($data);
|
||||
$decodedData['color'] = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['color']),
|
||||
null
|
||||
);
|
||||
|
||||
return $decodedData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the default color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string Default color.
|
||||
* @throws \InvalidArgumentException If the default color cannot be
|
||||
* extracted.
|
||||
*/
|
||||
private static function extractDefaultColor(array $data): string
|
||||
{
|
||||
if (isset($data['defaultColor'])) {
|
||||
$defaultColor = static::notEmptyStringOr(
|
||||
$data['defaultColor'],
|
||||
null
|
||||
);
|
||||
|
||||
if ($defaultColor === null) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the default color property is required and should be a not empty string'
|
||||
);
|
||||
}
|
||||
|
||||
return $defaultColor;
|
||||
} else {
|
||||
$dynamicData = static::extractDynamicData($data);
|
||||
return $dynamicData['defaultColor'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a list of color ranges.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return array Color ranges list.
|
||||
* @throws \InvalidArgumentException If any of the color ranges is invalid.
|
||||
*/
|
||||
private static function extractColorRanges(array $data): array
|
||||
{
|
||||
if (isset($data['colorRanges']) && \is_array($data['colorRanges'])) {
|
||||
// Validate the color ranges.
|
||||
foreach ($data['colorRanges'] as $colorRange) {
|
||||
if (\is_numeric($colorRange['fromValue']) === false
|
||||
|| \is_numeric($colorRange['toValue']) === false
|
||||
|| static::notEmptyStringOr($colorRange['color'], null) === null
|
||||
) {
|
||||
throw new \InvalidArgumentException('invalid color range');
|
||||
}
|
||||
}
|
||||
|
||||
return $data['colorRanges'];
|
||||
} else if (isset($data['label']) === true) {
|
||||
$dynamicData = static::extractDynamicData($data);
|
||||
return $dynamicData['colorRanges'];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a dynamic data structure from the 'label' field.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return array Dynamic data structure.
|
||||
* @throws \InvalidArgumentException If the structure cannot be built.
|
||||
*
|
||||
* @example [
|
||||
* 'defaultColor' => '#FFF',
|
||||
* 'colorRanges' => [
|
||||
* [
|
||||
* 'fromValue' => 50.0,
|
||||
* 'toValue' => 150.5,
|
||||
* 'color' => '#000',
|
||||
* ],
|
||||
* [
|
||||
* 'fromValue' => 200.0,
|
||||
* 'toValue' => 300.5,
|
||||
* 'color' => '#F0F0F0',
|
||||
* ],
|
||||
* ]
|
||||
* ]
|
||||
*/
|
||||
private static function extractDynamicData(array $data): array
|
||||
{
|
||||
$dynamicDataEncoded = static::notEmptyStringOr($data['label'], null);
|
||||
|
||||
if ($dynamicDataEncoded === null) {
|
||||
throw new \InvalidArgumentException('dynamic data not found');
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
try {
|
||||
$dynamicData = \json_decode($dynamicDataEncoded, true);
|
||||
|
||||
$result['defaultColor'] = $dynamicData['default_color'];
|
||||
$result['colorRanges'] = [];
|
||||
|
||||
if (\is_array($dynamicData['color_ranges']) === true) {
|
||||
foreach ($dynamicData['color_ranges'] as $colorRange) {
|
||||
if (\is_numeric($colorRange['from_value']) === true
|
||||
&& \is_numeric($colorRange['to_value']) === true
|
||||
&& static::notEmptyStringOr(
|
||||
$colorRange['color'],
|
||||
null
|
||||
) !== null
|
||||
) {
|
||||
$result['colorRanges'][] = [
|
||||
'color' => $colorRange['color'],
|
||||
'fromValue' => (float) $colorRange['from_value'],
|
||||
'toValue' => (float) $colorRange['to_value'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw new \InvalidArgumentException('invalid dynamic data');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_modules.php';
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
// Get the linked module Id.
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$moduleId = $linkedModule['moduleId'];
|
||||
$metaconsoleId = $linkedModule['metaconsoleId'];
|
||||
|
||||
if ($moduleId === null) {
|
||||
throw new \InvalidArgumentException('missing module Id');
|
||||
}
|
||||
|
||||
$dynamicData = static::extractDynamicData($data);
|
||||
// Set the initial color.
|
||||
$data['color'] = $dynamicData['defaultColor'];
|
||||
|
||||
// Search for a matching color range.
|
||||
if (empty($dynamicData['colorRanges']) === false) {
|
||||
// Connect to node.
|
||||
$nodeConnected = false;
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$nodeConnected = \metaconsole_connect(
|
||||
null,
|
||||
$metaconsoleId
|
||||
) === NOERR;
|
||||
|
||||
if ($nodeConnected === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error connecting to the node'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch module value.
|
||||
$value = false;
|
||||
if ($metaconsoleId === null
|
||||
|| ($metaconsoleId !== null && $nodeConnected)
|
||||
) {
|
||||
$value = \modules_get_last_value($moduleId);
|
||||
}
|
||||
|
||||
// Restore connection.
|
||||
if ($nodeConnected === true) {
|
||||
\metaconsole_restore_db();
|
||||
}
|
||||
|
||||
// Value found.
|
||||
if ($value !== false) {
|
||||
/*
|
||||
* TODO: It would be ok to give support to string values in the
|
||||
* future?
|
||||
*
|
||||
* It can be done by matching the range value with the value
|
||||
* if it is a string. I think the function to retrieve the value
|
||||
* only supports numeric values.
|
||||
*/
|
||||
|
||||
$value = (float) $value;
|
||||
foreach ($dynamicData['colorRanges'] as $colorRange) {
|
||||
if ($colorRange['fromValue'] <= $value
|
||||
&& $colorRange['toValue'] >= $value
|
||||
) {
|
||||
// Range matched. Use the range color.
|
||||
$data['color'] = $colorRange['color'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a Donut Graph item of the Visual Console.
|
||||
*/
|
||||
final class DonutGraph extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
/**
|
||||
* Used to enable validation, extraction and encodeing of the HTML output.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useHtmlOutput = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = DONUT_GRAPH;
|
||||
$return['legendBackgroundColor'] = static::extractLegendBackgroundColor(
|
||||
$data
|
||||
);
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a border color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the border color (not empty) or null.
|
||||
*/
|
||||
private static function extractLegendBackgroundColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray(
|
||||
$data,
|
||||
[
|
||||
'legendBackgroundColor',
|
||||
'border_color',
|
||||
]
|
||||
),
|
||||
'#000000'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
include_once $config['homedir'].'/include/graphs/functions_d3.php';
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
// Extract needed properties.
|
||||
$legendBackGroundColor = static::extractLegendBackgroundColor($data);
|
||||
// Get the linked agent and module Ids.
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$agentId = $linkedModule['agentId'];
|
||||
$moduleId = $linkedModule['moduleId'];
|
||||
$metaconsoleId = $linkedModule['metaconsoleId'];
|
||||
|
||||
if ($agentId === null) {
|
||||
throw new \InvalidArgumentException('missing agent Id');
|
||||
}
|
||||
|
||||
if ($moduleId === null) {
|
||||
throw new \InvalidArgumentException('missing module Id');
|
||||
}
|
||||
|
||||
// Maybe connect to node.
|
||||
$nodeConnected = false;
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$nodeConnected = \metaconsole_connect(
|
||||
null,
|
||||
$metaconsoleId
|
||||
) === NOERR;
|
||||
|
||||
if ($nodeConnected === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error connecting to the node'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
'SELECT COUNT(tam.id_agente_modulo)
|
||||
FROM tagente_modulo tam
|
||||
INNER JOIN ttipo_modulo ttm
|
||||
ON tam.id_tipo_modulo = ttm.id_tipo
|
||||
WHERE tam.id_agente = %d
|
||||
AND tam.id_agente_modulo = %d
|
||||
AND ttm.nombre LIKE \'%%_string\'',
|
||||
$agentId,
|
||||
$moduleId
|
||||
);
|
||||
$isString = (bool) \db_get_value_sql($sql);
|
||||
|
||||
// Restore connection.
|
||||
if ($nodeConnected === true) {
|
||||
\metaconsole_restore_db();
|
||||
}
|
||||
|
||||
if ($isString === true) {
|
||||
$graphData = \get_donut_module_data($moduleId);
|
||||
|
||||
$width = (int) $data['width'];
|
||||
$height = (int) $data['height'];
|
||||
|
||||
// Default width.
|
||||
if ($width <= 0) {
|
||||
$width = 300;
|
||||
}
|
||||
|
||||
// Default height.
|
||||
if ($height <= 0) {
|
||||
$height = 300;
|
||||
}
|
||||
|
||||
$data['html'] = \d3_donut_graph(
|
||||
(int) $data['id'],
|
||||
$width,
|
||||
$height,
|
||||
$graphData,
|
||||
$legendBackGroundColor
|
||||
);
|
||||
} else {
|
||||
$src = 'images/console/signes/wrong_donut_graph.png';
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$src = '../../'.$src;
|
||||
}
|
||||
|
||||
$data['html'] = '<img src="'.$src.'">';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a events history item of the Visual Console.
|
||||
*/
|
||||
final class EventsHistory extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
/**
|
||||
* Used to enable validation, extraction and encodeing of the HTML output.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useHtmlOutput = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = AUTO_SLA_GRAPH;
|
||||
$return['maxTime'] = static::extractMaxTime($data);
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a graph period value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed The time in seconds of the graph period or null.
|
||||
*/
|
||||
private static function extractMaxTime(array $data)
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['maxTime', 'period']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_graph.php';
|
||||
|
||||
// Get the linked agent and module Ids.
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$agentId = static::parseIntOr($linkedModule['agentId'], null);
|
||||
$moduleId = static::parseIntOr($linkedModule['moduleId'], null);
|
||||
|
||||
if ($agentId === null) {
|
||||
throw new \InvalidArgumentException('missing agent Id');
|
||||
}
|
||||
|
||||
// Default size.
|
||||
if ($data['width'] == 0 || $data['height'] == 0) {
|
||||
$data['width'] = 500;
|
||||
$data['height'] = 50;
|
||||
}
|
||||
|
||||
// Use the same HTML output as the old VC.
|
||||
$html = \graph_graphic_moduleevents(
|
||||
$agentId,
|
||||
$moduleId,
|
||||
(int) $data['width'],
|
||||
(int) $data['height'],
|
||||
static::extractMaxTime($data),
|
||||
'',
|
||||
true
|
||||
);
|
||||
|
||||
$data['html'] = $html;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,455 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a group item of the Visual Console.
|
||||
*/
|
||||
final class Group extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
/**
|
||||
* Enable the validation, extraction and encoding of HTML output.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useHtmlOutput = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = GROUP_ITEM;
|
||||
$return['groupId'] = static::extractGroupId($data);
|
||||
$return['imageSrc'] = static::extractImageSrc($data);
|
||||
$return['statusImageSrc'] = static::extractStatusImageSrc($data);
|
||||
$return['showStatistics'] = static::extractShowStatistics($data);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a group Id value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid identifier of a group.
|
||||
*
|
||||
* @throws \InvalidArgumentException When a valid group Id can't be found.
|
||||
*/
|
||||
private static function extractGroupId(array $data): int
|
||||
{
|
||||
$groupId = static::parseIntOr(
|
||||
static::issetInArray($data, ['groupId', 'id_group']),
|
||||
null
|
||||
);
|
||||
|
||||
if ($groupId === null || $groupId < 0) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the group Id property is required and should be integer'
|
||||
);
|
||||
}
|
||||
|
||||
return $groupId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a image src value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the image url (not empty) or null.
|
||||
*/
|
||||
private static function extractImageSrc(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['imageSrc', 'image']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a status image src value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the status image url (not empty)
|
||||
* or null.
|
||||
*/
|
||||
private static function extractStatusImageSrc(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['statusImageSrc']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the "show statistics" switch value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return boolean If the statistics should be shown or not.
|
||||
*/
|
||||
private static function extractShowStatistics(array $data): bool
|
||||
{
|
||||
return static::parseBool(
|
||||
static::issetInArray($data, ['showStatistics', 'show_statistics'])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_groups.php';
|
||||
include_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
include_once $config['homedir'].'/include/functions_ui.php';
|
||||
include_once $config['homedir'].'/include/functions_agents.php';
|
||||
include_once $config['homedir'].'/include/functions_users.php';
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
$groupId = static::extractGroupId($data);
|
||||
$showStatistics = static::extractShowStatistics($data);
|
||||
|
||||
if ($showStatistics === true) {
|
||||
$isMetaconsole = \is_metaconsole();
|
||||
// Retrieve the agent stats.
|
||||
$agentsCritical = \agents_get_agents(
|
||||
[
|
||||
'id_grupo' => $groupId,
|
||||
'status' => AGENT_STATUS_CRITICAL,
|
||||
],
|
||||
['COUNT(*) AS total'],
|
||||
'AR',
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
$isMetaconsole
|
||||
);
|
||||
$numCritical = $agentsCritical[0]['total'];
|
||||
$agentsWarning = \agents_get_agents(
|
||||
[
|
||||
'id_grupo' => $groupId,
|
||||
'status' => AGENT_STATUS_WARNING,
|
||||
],
|
||||
['COUNT(*) AS total'],
|
||||
'AR',
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
$isMetaconsole
|
||||
);
|
||||
$numWarning = $agentsWarning[0]['total'];
|
||||
$agentsUnknown = \agents_get_agents(
|
||||
[
|
||||
'id_grupo' => $groupId,
|
||||
'status' => AGENT_STATUS_UNKNOWN,
|
||||
],
|
||||
['COUNT(*) AS total'],
|
||||
'AR',
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
$isMetaconsole
|
||||
);
|
||||
$numUnknown = $agentsUnknown[0]['total'];
|
||||
$agentsOk = \agents_get_agents(
|
||||
[
|
||||
'id_grupo' => $groupId,
|
||||
'status' => AGENT_STATUS_OK,
|
||||
],
|
||||
['COUNT(*) AS total'],
|
||||
'AR',
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
$isMetaconsole
|
||||
);
|
||||
$numNormal = $agentsOk[0]['total'];
|
||||
|
||||
$numTotal = ($numCritical + $numWarning + $numUnknown + $numNormal);
|
||||
$agentStats = [
|
||||
'critical' => ($numCritical / $numTotal * 100),
|
||||
'warning' => ($numWarning / $numTotal * 100),
|
||||
'normal' => ($numNormal / $numTotal * 100),
|
||||
'unknown' => ($numUnknown / $numTotal * 100),
|
||||
];
|
||||
|
||||
$groupName = \groups_get_name($groupId, true);
|
||||
$data['html'] = static::printStatsTable(
|
||||
$groupName,
|
||||
$agentStats,
|
||||
(int) $data['width'],
|
||||
(int) $data['height']
|
||||
);
|
||||
} else {
|
||||
if (\is_metaconsole()) {
|
||||
$groupFilter = $groupId;
|
||||
if ($groupId === 0) {
|
||||
$groupFilter = implode(
|
||||
',',
|
||||
array_keys(\users_get_groups())
|
||||
);
|
||||
}
|
||||
|
||||
$sql = sprintf(
|
||||
'SELECT
|
||||
SUM(fired_count) AS fired,
|
||||
SUM(critical_count) AS critical,
|
||||
SUM(warning_count) AS warning,
|
||||
SUM(unknown_count) AS unknown
|
||||
FROM tmetaconsole_agent
|
||||
LEFT JOIN tmetaconsole_agent_secondary_group tasg
|
||||
ON id_agente = tasg.id_agent
|
||||
WHERE id_grupo IN (%s)
|
||||
OR tasg.id_group IN (%s)',
|
||||
$groupFilter,
|
||||
$groupFilter
|
||||
);
|
||||
|
||||
$countStatus = \db_get_row_sql($sql);
|
||||
|
||||
if ($countStatus['fired'] > 0) {
|
||||
$status = AGENT_STATUS_ALERT_FIRED;
|
||||
} else if ($countStatus['critical'] > 0) {
|
||||
$status = AGENT_STATUS_CRITICAL;
|
||||
} else if ($countStatus['warning'] > 0) {
|
||||
$status = AGENT_STATUS_WARNING;
|
||||
} else if ($countStatus['unknown'] > 0) {
|
||||
$status = AGENT_STATUS_UNKNOWN;
|
||||
} else {
|
||||
$status = AGENT_STATUS_NORMAL;
|
||||
}
|
||||
} else {
|
||||
// Get the status img src.
|
||||
$status = \groups_get_status($groupId);
|
||||
}
|
||||
|
||||
$imagePath = \visual_map_get_image_status_element($data, $status);
|
||||
$data['statusImageSrc'] = \ui_get_full_url(
|
||||
$imagePath,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
// If the width or the height are equal to 0 we will extract them
|
||||
// from the real image size.
|
||||
$width = (int) $data['width'];
|
||||
$height = (int) $data['height'];
|
||||
if ($width === 0 || $height === 0) {
|
||||
// TODO: This will be the default behaviour after we finish the
|
||||
// builder. Don't delete this code.
|
||||
// $sizeImage = getimagesize($config['homedir'].'/'.$imagePath);
|
||||
// $data['width'] = $sizeImage[0];
|
||||
// $data['height'] = $sizeImage[1];
|
||||
$sizeImage = getimagesize($config['homedir'].'/'.$imagePath);
|
||||
$imageHeight = $sizeImage[1];
|
||||
|
||||
if ($width === 0) {
|
||||
$data['width'] = 70;
|
||||
}
|
||||
|
||||
if ($height === 0) {
|
||||
$data['height'] = ($imageHeight > 70) ? 70 : $imageHeight;
|
||||
}
|
||||
}
|
||||
|
||||
$data['html'] = '<img src="'.$data['statusImageSrc'].'">';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* HTML representation for the agent stats of a group.
|
||||
*
|
||||
* @param string $groupName Group name.
|
||||
* @param array $agentStats Data structure with the agent statistics.
|
||||
* @param integer $width Width.
|
||||
* @param integer $height Height.
|
||||
*
|
||||
* @return string HTML representation.
|
||||
*/
|
||||
private static function printStatsTable(
|
||||
string $groupName,
|
||||
array $agentStats,
|
||||
int $width=520,
|
||||
int $height=80
|
||||
): string {
|
||||
$width = ($width > 0) ? $width : 520;
|
||||
$height = ($height > 0) ? $height : 80;
|
||||
|
||||
$tableStyle = \join(
|
||||
[
|
||||
'width:'.$width.'px;',
|
||||
'height:'.$height.'px;',
|
||||
'text-align:center;',
|
||||
]
|
||||
);
|
||||
$headStyle = \join(
|
||||
[
|
||||
'text-align:center;',
|
||||
'background-color:#9d9ea0;',
|
||||
'color:black;',
|
||||
'font-weight:bold;',
|
||||
]
|
||||
);
|
||||
$valueStyle = \join(
|
||||
[
|
||||
'margin-left: 2%;',
|
||||
'color: #FFF;',
|
||||
'font-size: 12px;',
|
||||
'display: inline;',
|
||||
'background-color: #FC4444;',
|
||||
'position: relative;',
|
||||
'height: 80%;',
|
||||
'width: 9.4%;',
|
||||
'height: 80%;',
|
||||
'border-radius: 2px;',
|
||||
'text-align: center;',
|
||||
'padding: 5px;',
|
||||
]
|
||||
);
|
||||
$nameStyle = \join(
|
||||
[
|
||||
'background-color: white;',
|
||||
'color: black;',
|
||||
'font-size: 12px;',
|
||||
'display: inline;',
|
||||
'display: inline;',
|
||||
'position:relative;',
|
||||
'width: 9.4%;',
|
||||
'height: 80%;',
|
||||
'border-radius: 2px;',
|
||||
'text-align: center;',
|
||||
'padding: 5px;',
|
||||
]
|
||||
);
|
||||
|
||||
$html = '<table class="databox" style="'.$tableStyle.'">';
|
||||
$html .= '<tr style="height:10%;">';
|
||||
$html .= '<th style="'.$headStyle.'">'.$groupName.'</th>';
|
||||
$html .= '</tr>';
|
||||
$html .= '<tr style="background-color:whitesmoke;height:90%;">';
|
||||
$html .= '<td>';
|
||||
|
||||
// Critical.
|
||||
$html .= '<div style="'.$valueStyle.'background-color: #FC4444;">';
|
||||
$html .= \number_format($agentStats['critical']).'%';
|
||||
$html .= '</div>';
|
||||
$html .= '<div style="'.$nameStyle.'">'.__('Critical').'</div>';
|
||||
// Warning.
|
||||
$html .= '<div style="'.$valueStyle.'background-color: #f8db3f;">';
|
||||
$html .= \number_format($agentStats['warning']).'%';
|
||||
$html .= '</div>';
|
||||
$html .= '<div style="'.$nameStyle.'">'.__('Warning').'</div>';
|
||||
// Normal.
|
||||
$html .= '<div style="'.$valueStyle.'background-color: #84b83c;">';
|
||||
$html .= \number_format($agentStats['normal']).'%';
|
||||
$html .= '</div>';
|
||||
$html .= '<div style="'.$nameStyle.'">'.__('Normal').'</div>';
|
||||
// Unknown.
|
||||
$html .= '<div style="'.$valueStyle.'background-color: #9d9ea0;">';
|
||||
$html .= \number_format($agentStats['unknown']).'%';
|
||||
$html .= '</div>';
|
||||
$html .= '<div style="'.$nameStyle.'">'.__('Unknown').'</div>';
|
||||
|
||||
$html .= '</td>';
|
||||
$html .= '</tr>';
|
||||
$html .= '</table>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a link to something related with the item.
|
||||
*
|
||||
* @param array $data Visual Console Item's data structure.
|
||||
*
|
||||
* @return mixed The link or a null value.
|
||||
*
|
||||
* @override Item::buildLink.
|
||||
*/
|
||||
protected static function buildLink(array $data)
|
||||
{
|
||||
// This will return the link to a linked VC if this item has one.
|
||||
$link = parent::buildLink($data);
|
||||
if ($link !== null) {
|
||||
return $link;
|
||||
}
|
||||
|
||||
global $config;
|
||||
|
||||
$groupId = static::extractGroupId($data);
|
||||
$baseUrl = $config['homeurl'].'index.php';
|
||||
|
||||
if (\is_metaconsole()) {
|
||||
return $baseUrl.'?'.http_build_query(
|
||||
[
|
||||
'sec' => 'monitoring',
|
||||
'sec2' => 'operation/tree',
|
||||
'group_id' => $groupId,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $baseUrl.'?'.http_build_query(
|
||||
[
|
||||
'sec' => 'estado',
|
||||
'sec2' => 'operation/agentes/estado_agente',
|
||||
'group_id' => $groupId,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a group item of the Visual Console.
|
||||
*/
|
||||
final class Icon extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = ICON;
|
||||
$return['image'] = static::extractImage($data);
|
||||
$return['imageSrc'] = static::extractImageSrc($data);
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a image value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the image url (not empty) or null.
|
||||
*
|
||||
* @throws \InvalidArgumentException When a valid image can't be found.
|
||||
*/
|
||||
private static function extractImage(array $data)
|
||||
{
|
||||
$image = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['image']),
|
||||
null
|
||||
);
|
||||
|
||||
if ($image === null) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the image property is required and should be a non empty string'
|
||||
);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a image src value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the image url (not empty) or null.
|
||||
*
|
||||
* @throws \InvalidArgumentException When a valid image src can't be found.
|
||||
*/
|
||||
private static function extractImageSrc(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['imageSrc']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 'images/console/icons/'.$imageSrc.'.png'
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_ui.php';
|
||||
include_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
|
||||
// Get the img src.
|
||||
$imagePath = \visual_map_get_image_status_element($data);
|
||||
$data['imageSrc'] = \ui_get_full_url($imagePath, false, false, false);
|
||||
|
||||
// If the width or the height are equal to 0 we will extract them
|
||||
// from the real image size.
|
||||
$width = (int) $data['width'];
|
||||
$height = (int) $data['height'];
|
||||
if ($width === 0 || $height === 0) {
|
||||
// TODO: This will be the default behaviour after we finish the
|
||||
// builder. Don't delete this code.
|
||||
// $sizeImage = getimagesize($config['homedir'].'/'.$imagePath);
|
||||
// $data['width'] = $sizeImage[0];
|
||||
// $data['height'] = $sizeImage[1];
|
||||
$sizeImage = getimagesize($config['homedir'].'/'.$imagePath);
|
||||
$imageHeight = $sizeImage[1];
|
||||
|
||||
if ($width === 0) {
|
||||
$data['width'] = 70;
|
||||
}
|
||||
|
||||
if ($height === 0) {
|
||||
$data['height'] = ($imageHeight > 70) ? 70 : $imageHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a label item of the Visual Console.
|
||||
*/
|
||||
final class Label extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
|
||||
/**
|
||||
* Validate the received data structure to ensure if we can extract the
|
||||
* values required to build the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException If any input value is considered
|
||||
* invalid.
|
||||
*
|
||||
* @overrides Item->validateData.
|
||||
*/
|
||||
protected function validateData(array $data): void
|
||||
{
|
||||
parent::validateData($data);
|
||||
if (static::notEmptyStringOr(static::issetInArray($data, ['label']), null) === null) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the label property is required and should be a not empty string'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item->decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = LABEL;
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\Model;
|
||||
|
||||
/**
|
||||
* Model of a line item of the Visual Console.
|
||||
*/
|
||||
final class Line extends Model
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Validate the received data structure to ensure if we can extract the
|
||||
* values required to build the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return void
|
||||
* @throws \InvalidArgumentException If any input value is considered
|
||||
* invalid.
|
||||
*
|
||||
* @overrides Model->validateData.
|
||||
*/
|
||||
protected function validateData(array $data): void
|
||||
{
|
||||
if (isset($data['id']) === false
|
||||
|| \is_numeric($data['id']) === false
|
||||
) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the Id property is required and should be integer'
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($data['type']) === false
|
||||
|| \is_numeric($data['type']) === false
|
||||
) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the Id property is required and should be integer'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Model->decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $data['id'],
|
||||
'type' => LINE_ITEM,
|
||||
'startX' => static::extractStartX($data),
|
||||
'startY' => static::extractStartY($data),
|
||||
'endX' => static::extractEndX($data),
|
||||
'endY' => static::extractEndY($data),
|
||||
'isOnTop' => static::extractIsOnTop($data),
|
||||
'borderWidth' => static::extractBorderWidth($data),
|
||||
'borderColor' => static::extractBorderColor($data),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a x axis value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid x axis of the start position of the line.
|
||||
*/
|
||||
private static function extractStartX(array $data): int
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['startX', 'pos_x']),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a y axis value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid y axis of the start position of the line.
|
||||
*/
|
||||
private static function extractStartY(array $data): int
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['startY', 'pos_y']),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a x axis value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid x axis of the end position of the line.
|
||||
*/
|
||||
private static function extractEndX(array $data): int
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['endX', 'width']),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a y axis value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid y axis of the end position of the line.
|
||||
*/
|
||||
private static function extractEndY(array $data): int
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['endY', 'height']),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a conditional value which tells if the item has visual priority.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return boolean If the item is on top or not.
|
||||
*/
|
||||
private static function extractIsOnTop(array $data): bool
|
||||
{
|
||||
return static::parseBool(
|
||||
static::issetInArray($data, ['isOnTop', 'show_on_top'])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a border width value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer Valid border width. 0 by default and minimum value.
|
||||
*/
|
||||
private static function extractBorderWidth(array $data): int
|
||||
{
|
||||
$borderWidth = static::parseIntOr(
|
||||
static::issetInArray($data, ['borderWidth', 'border_width']),
|
||||
0
|
||||
);
|
||||
|
||||
return ($borderWidth >= 0) ? $borderWidth : 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a border color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the border color (not empty) or null.
|
||||
*/
|
||||
private static function extractBorderColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['borderColor', 'border_color']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console line data structure stored into the DB.
|
||||
* @throws \Exception When the data cannot be retrieved from the DB.
|
||||
*
|
||||
* @override Model::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$row = \db_get_row_filter('tlayout_data', $filter);
|
||||
|
||||
if ($row === false) {
|
||||
throw new \Exception('error fetching the data from the DB');
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a module graph item of the Visual Console.
|
||||
*/
|
||||
final class ModuleGraph extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
/**
|
||||
* Used to enable validation, extraction and encodeing of the HTML output.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useHtmlOutput = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = MODULE_GRAPH;
|
||||
$return['backgroundType'] = static::extractBackgroundType($data);
|
||||
$return['period'] = static::extractPeriod($data);
|
||||
|
||||
$customGraphId = static::extractCustomGraphId($data);
|
||||
|
||||
if (empty($customGraphId) === false) {
|
||||
$return['customGraphId'] = $customGraphId;
|
||||
} else {
|
||||
$return['graphType'] = static::extractGraphType($data);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a background type value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string 'none', 'white' or 'black'. 'none' by default.
|
||||
*/
|
||||
private static function extractBackgroundType(array $data): string
|
||||
{
|
||||
$value = static::issetInArray($data, ['backgroundType', 'image']);
|
||||
|
||||
switch ($value) {
|
||||
case 'none':
|
||||
case 'white':
|
||||
case 'black':
|
||||
return $value;
|
||||
|
||||
default:
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a graph period value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed The time in seconds of the graph period or null.
|
||||
*/
|
||||
private static function extractPeriod(array $data)
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['period']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a custom graph Id value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed The custom graph Id (int) or null.
|
||||
*/
|
||||
private static function extractCustomGraphId(array $data)
|
||||
{
|
||||
return static::parseIntOr(
|
||||
static::issetInArray($data, ['customGraphId', 'id_custom_graph']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a graph type value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string 'line' or 'area'. 'line' by default.
|
||||
*/
|
||||
private static function extractGraphType(array $data): string
|
||||
{
|
||||
$value = static::issetInArray($data, ['graphType', 'type_graph']);
|
||||
|
||||
switch ($value) {
|
||||
case 'line':
|
||||
case 'area':
|
||||
return $value;
|
||||
|
||||
default:
|
||||
return 'line';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_graph.php';
|
||||
include_once $config['homedir'].'/include/functions_modules.php';
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
$imageOnly = false;
|
||||
|
||||
$backgroundType = static::extractBackgroundType($data);
|
||||
$period = static::extractPeriod($data);
|
||||
$customGraphId = static::extractCustomGraphId($data);
|
||||
$graphType = static::extractGraphType($data);
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$moduleId = $linkedModule['moduleId'];
|
||||
$metaconsoleId = $linkedModule['metaconsoleId'];
|
||||
|
||||
// Maybe connect to node.
|
||||
$nodeConnected = false;
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$nodeConnected = \metaconsole_connect(
|
||||
null,
|
||||
$metaconsoleId
|
||||
) === NOERR;
|
||||
|
||||
if ($nodeConnected === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error connecting to the node'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom graph.
|
||||
if (empty($customGraphId) === false) {
|
||||
$customGraph = \db_get_row_filter(
|
||||
'tgraph',
|
||||
'id_graph',
|
||||
$customGraphId
|
||||
);
|
||||
|
||||
$params = [
|
||||
'period' => $period,
|
||||
'width' => $data['width'],
|
||||
'height' => $data['height'],
|
||||
'title' => '',
|
||||
'unit_name' => null,
|
||||
'show_alerts' => false,
|
||||
'only_image' => $imageOnly,
|
||||
'vconsole' => true,
|
||||
'backgroundColor' => $backgroundType,
|
||||
];
|
||||
|
||||
$paramsCombined = [
|
||||
'id_graph' => $customGraphId,
|
||||
'stacked' => $graph['stacked'],
|
||||
'summatory' => $graph['summatory_series'],
|
||||
'average' => $graph['average_series'],
|
||||
'modules_series' => $graph['modules_series'],
|
||||
];
|
||||
|
||||
$data['html'] = \graphic_combined_module(
|
||||
false,
|
||||
$params,
|
||||
$paramsCombined
|
||||
);
|
||||
} else {
|
||||
// Module graph.
|
||||
if ($moduleId === null) {
|
||||
throw new \InvalidArgumentException('missing module Id');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'agent_module_id' => $moduleId,
|
||||
'period' => $period,
|
||||
'show_events' => false,
|
||||
'width' => $data['width'],
|
||||
'height' => $data['height'],
|
||||
'title' => \modules_get_agentmodule_name($moduleId),
|
||||
'unit' => \modules_get_unit($moduleId),
|
||||
'only_image' => $imageOnly,
|
||||
'menu' => false,
|
||||
'backgroundColor' => $backgroundType,
|
||||
'type_graph' => $graphType,
|
||||
'vconsole' => true,
|
||||
];
|
||||
|
||||
$data['html'] = \grafico_modulo_sparse($params);
|
||||
}
|
||||
|
||||
// Restore connection.
|
||||
if ($nodeConnected === true) {
|
||||
\metaconsole_restore_db();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a percentile item of the Visual Console.
|
||||
*/
|
||||
final class Percentile extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item::decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = PERCENTILE_BAR;
|
||||
$return['percentileType'] = static::extractPercentileType($data);
|
||||
$return['valueType'] = static::extractValueType($data);
|
||||
// TODO: Add min value to the database.
|
||||
$return['minValue'] = static::parseFloatOr(
|
||||
static::issetInArray($data, ['minValue']),
|
||||
null
|
||||
);
|
||||
$return['maxValue'] = static::parseFloatOr(
|
||||
static::issetInArray($data, ['maxValue', 'height']),
|
||||
null
|
||||
);
|
||||
$return['color'] = static::extractColor($data);
|
||||
$return['labelColor'] = static::extractLabelColor($data);
|
||||
$return['value'] = static::parseFloatOr(
|
||||
static::issetInArray($data, ['value']),
|
||||
null
|
||||
);
|
||||
$return['unit'] = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['unit']),
|
||||
null
|
||||
);
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a percentile type value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string 'progress-bar', 'bubble', 'circular-progress-bar'
|
||||
* or 'circular-progress-bar-alt'. 'progress-bar' by default.
|
||||
*/
|
||||
private static function extractPercentileType(array $data): string
|
||||
{
|
||||
if (isset($data['percentileType']) === true) {
|
||||
switch ($data['percentileType']) {
|
||||
case 'progress-bar':
|
||||
case 'bubble':
|
||||
case 'circular-progress-bar':
|
||||
case 'circular-progress-bar-alt':
|
||||
return $data['percentileType'];
|
||||
|
||||
default:
|
||||
return 'progress-bar';
|
||||
}
|
||||
}
|
||||
|
||||
switch ($data['type']) {
|
||||
case PERCENTILE_BUBBLE:
|
||||
return 'bubble';
|
||||
|
||||
case CIRCULAR_PROGRESS_BAR:
|
||||
return 'circular-progress-bar';
|
||||
|
||||
case CIRCULAR_INTERIOR_PROGRESS_BAR:
|
||||
return 'circular-progress-bar-alt';
|
||||
|
||||
default:
|
||||
case PERCENTILE_BAR:
|
||||
return 'progress-bar';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a value type value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string 'percent' or 'value'. 'percent' by default.
|
||||
*/
|
||||
private static function extractValueType(array $data): string
|
||||
{
|
||||
$rawValueType = static::issetInArray($data, ['valueType', 'image']);
|
||||
|
||||
switch ($rawValueType) {
|
||||
case 'percent':
|
||||
case 'value':
|
||||
return $rawValueType;
|
||||
|
||||
default:
|
||||
return 'percent';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed The color or null.
|
||||
*/
|
||||
private static function extractColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['color', 'border_color']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a label color value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed The label color or null.
|
||||
*/
|
||||
private static function extractLabelColor(array $data)
|
||||
{
|
||||
return static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['labelColor', 'fill_color']),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_graph.php';
|
||||
include_once $config['homedir'].'/include/functions_modules.php';
|
||||
include_once $config['homedir'].'/include/functions_io.php';
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
// Get the linked module Id.
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$moduleId = static::parseIntOr($linkedModule['moduleId'], null);
|
||||
$metaconsoleId = static::parseIntOr(
|
||||
$linkedModule['metaconsoleId'],
|
||||
null
|
||||
);
|
||||
|
||||
// Get the value type.
|
||||
$valueType = static::extractValueType($data);
|
||||
|
||||
if ($moduleId === null) {
|
||||
throw new \InvalidArgumentException('missing module Id');
|
||||
}
|
||||
|
||||
// Maybe connect to node.
|
||||
$nodeConnected = false;
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$nodeConnected = \metaconsole_connect(
|
||||
null,
|
||||
$metaconsoleId
|
||||
) === NOERR;
|
||||
|
||||
if ($nodeConnected === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error connecting to the node'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$moduleValue = \modules_get_last_value($moduleId);
|
||||
if ($moduleValue === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error fetching the module value'
|
||||
);
|
||||
}
|
||||
|
||||
// Cast to float.
|
||||
$moduleValue = (float) $moduleValue;
|
||||
|
||||
// Store the module value.
|
||||
$data['value'] = $moduleValue;
|
||||
|
||||
$unit = \modules_get_unit($moduleId);
|
||||
if (empty($unit) === false) {
|
||||
$data['unit'] = \io_safe_output($unit);
|
||||
}
|
||||
|
||||
// Restore connection.
|
||||
if ($nodeConnected === true) {
|
||||
\metaconsole_restore_db();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a simple value item of the Visual Console.
|
||||
*/
|
||||
final class SimpleValue extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
|
||||
/**
|
||||
* Validate the received data structure to ensure if we can extract the
|
||||
* values required to build the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException If any input value is considered
|
||||
* invalid.
|
||||
*
|
||||
* @overrides Item->validateData.
|
||||
*/
|
||||
protected function validateData(array $data): void
|
||||
{
|
||||
parent::validateData($data);
|
||||
if (isset($data['value']) === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the value property is required and should be string'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item->decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = SIMPLE_VALUE;
|
||||
$return['processValue'] = static::extractProcessValue($data);
|
||||
$return['valueType'] = static::extractValueType($data);
|
||||
$return['value'] = $data['value'];
|
||||
|
||||
if ($return['processValue'] !== 'none') {
|
||||
$return['period'] = static::extractPeriod($data);
|
||||
}
|
||||
|
||||
// Clear the size, as this element always have a dynamic size.
|
||||
$return['width'] = 0;
|
||||
$return['height'] = 0;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a process value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string One of 'none', 'avg', 'max' or 'min'. 'none' by default.
|
||||
*/
|
||||
private static function extractProcessValue(array $data): string
|
||||
{
|
||||
if (isset($data['processValue'])) {
|
||||
switch ($data['processValue']) {
|
||||
case 'none':
|
||||
case 'avg':
|
||||
case 'max':
|
||||
case 'min':
|
||||
return $data['processValue'];
|
||||
|
||||
default:
|
||||
return 'none';
|
||||
}
|
||||
} else {
|
||||
switch ($data['type']) {
|
||||
case SIMPLE_VALUE_MAX:
|
||||
return 'max';
|
||||
|
||||
case SIMPLE_VALUE_MIN:
|
||||
return 'min';
|
||||
|
||||
case SIMPLE_VALUE_AVG:
|
||||
return 'avg';
|
||||
|
||||
default:
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the value of period.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return integer The period in seconds. 0 is the minimum value.
|
||||
*/
|
||||
private static function extractPeriod(array $data): int
|
||||
{
|
||||
$period = static::parseIntOr(
|
||||
static::issetInArray($data, ['period']),
|
||||
0
|
||||
);
|
||||
return ($period >= 0) ? $period : 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a value type.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string One of 'string' or 'image'. 'string' by default.
|
||||
*/
|
||||
private static function extractValueType(array $data): string
|
||||
{
|
||||
switch ($data['valueType']) {
|
||||
case 'string':
|
||||
case 'image':
|
||||
return $data['valueType'];
|
||||
|
||||
default:
|
||||
return 'string';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When a module Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
// Get the linked module Id.
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$moduleId = static::parseIntOr($linkedModule['moduleId'], null);
|
||||
$metaconsoleId = static::parseIntOr(
|
||||
$linkedModule['metaconsoleId'],
|
||||
null
|
||||
);
|
||||
|
||||
if ($moduleId === null) {
|
||||
throw new \InvalidArgumentException('missing module Id');
|
||||
}
|
||||
|
||||
// Maybe connect to node.
|
||||
$nodeConnected = false;
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$nodeConnected = \metaconsole_connect(
|
||||
null,
|
||||
$metaconsoleId
|
||||
) === NOERR;
|
||||
|
||||
if ($nodeConnected === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error connecting to the node'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the formatted value.
|
||||
$value = \visual_map_get_simple_value(
|
||||
$data['type'],
|
||||
$moduleId,
|
||||
static::extractPeriod($data)
|
||||
);
|
||||
|
||||
// Restore connection.
|
||||
if ($nodeConnected === true) {
|
||||
\metaconsole_restore_db();
|
||||
}
|
||||
|
||||
// Some modules are image based. Extract the base64 image if needed.
|
||||
$matches = [];
|
||||
if (\preg_match('/src=\"(data:image.*)"/', $value, $matches) === 1) {
|
||||
$data['valueType'] = 'image';
|
||||
$data['value'] = $matches[1];
|
||||
} else {
|
||||
$data['valueType'] = 'string';
|
||||
$data['value'] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Models\VisualConsole\Items;
|
||||
use Models\VisualConsole\Item;
|
||||
|
||||
/**
|
||||
* Model of a static graph item of the Visual Console.
|
||||
*/
|
||||
final class StaticGraph extends Item
|
||||
{
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked visual console.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedVisualConsole = true;
|
||||
|
||||
/**
|
||||
* Used to enable the fetching, validation and extraction of information
|
||||
* about the linked module.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $useLinkedModule = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a valid representation of the model.
|
||||
*
|
||||
* @param array $data Input data.
|
||||
*
|
||||
* @return array Data structure representing the model.
|
||||
*
|
||||
* @overrides Item->decode.
|
||||
*/
|
||||
protected function decode(array $data): array
|
||||
{
|
||||
$return = parent::decode($data);
|
||||
$return['type'] = STATIC_GRAPH;
|
||||
$return['imageSrc'] = static::extractImageSrc($data);
|
||||
$return['showLastValueTooltip'] = static::extractShowLastValueTooltip(
|
||||
$data
|
||||
);
|
||||
$return['statusImageSrc'] = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['statusImageSrc']),
|
||||
null
|
||||
);
|
||||
$return['lastValue'] = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['lastValue']),
|
||||
null
|
||||
);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract a image src value.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return mixed String representing the image url (not empty) or null.
|
||||
*
|
||||
* @throws \InvalidArgumentException When a valid image src can't be found.
|
||||
*/
|
||||
private static function extractImageSrc(array $data): string
|
||||
{
|
||||
$imageSrc = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['imageSrc', 'image']),
|
||||
null
|
||||
);
|
||||
|
||||
if ($imageSrc === null) {
|
||||
throw new \InvalidArgumentException(
|
||||
'the image src property is required and should be a non empty string'
|
||||
);
|
||||
}
|
||||
|
||||
return $imageSrc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract the value of showLastValueTooltip and
|
||||
* return 'default', 'enabled' or 'disabled'.
|
||||
*
|
||||
* @param array $data Unknown input data structure.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function extractShowLastValueTooltip(array $data): string
|
||||
{
|
||||
$showLastValueTooltip = static::notEmptyStringOr(
|
||||
static::issetInArray($data, ['showLastValueTooltip']),
|
||||
null
|
||||
);
|
||||
|
||||
if ($showLastValueTooltip === null) {
|
||||
$showLastValueTooltip = static::parseIntOr(
|
||||
static::issetInArray($data, ['show_last_value']),
|
||||
null
|
||||
);
|
||||
switch ($showLastValueTooltip) {
|
||||
case 1:
|
||||
return 'enabled';
|
||||
|
||||
case 2:
|
||||
return 'disabled';
|
||||
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
} else {
|
||||
switch ($showLastValueTooltip) {
|
||||
case 'enabled':
|
||||
return 'enabled';
|
||||
|
||||
case 'disabled':
|
||||
return 'disabled';
|
||||
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a vc item data structure from the database using a filter.
|
||||
*
|
||||
* @param array $filter Filter of the Visual Console Item.
|
||||
*
|
||||
* @return array The Visual Console Item data structure stored into the DB.
|
||||
* @throws \InvalidArgumentException When an agent Id cannot be found.
|
||||
*
|
||||
* @override Item::fetchDataFromDB.
|
||||
*/
|
||||
protected static function fetchDataFromDB(array $filter): array
|
||||
{
|
||||
// Due to this DB call, this function cannot be unit tested without
|
||||
// a proper mock.
|
||||
$data = parent::fetchDataFromDB($filter);
|
||||
|
||||
/*
|
||||
* Retrieve extra data.
|
||||
*/
|
||||
|
||||
// Load side libraries.
|
||||
global $config;
|
||||
include_once $config['homedir'].'/include/functions_ui.php';
|
||||
include_once $config['homedir'].'/include/functions_io.php';
|
||||
include_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
include_once $config['homedir'].'/include/functions_modules.php';
|
||||
if (is_metaconsole()) {
|
||||
\enterprise_include_once('include/functions_metaconsole.php');
|
||||
}
|
||||
|
||||
// Get the linked module Id.
|
||||
$linkedModule = static::extractLinkedModule($data);
|
||||
$moduleId = $linkedModule['moduleId'];
|
||||
$metaconsoleId = $linkedModule['metaconsoleId'];
|
||||
|
||||
if ($moduleId === null) {
|
||||
throw new \InvalidArgumentException('missing module Id');
|
||||
}
|
||||
|
||||
// Get the img src.
|
||||
// There's no need to connect to the metaconsole before searching for
|
||||
// the image status cause the function itself does that for us.
|
||||
$imagePath = \visual_map_get_image_status_element($data);
|
||||
$data['statusImageSrc'] = \ui_get_full_url(
|
||||
$imagePath,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
// If the width or the height are equal to 0 we will extract them
|
||||
// from the real image size.
|
||||
$width = (int) $data['width'];
|
||||
$height = (int) $data['height'];
|
||||
if ($width === 0 || $height === 0) {
|
||||
// TODO: This will be the default behaviour after we finish the
|
||||
// builder. Don't delete this code.
|
||||
// $sizeImage = getimagesize($config['homedir'].'/'.$imagePath);
|
||||
// $data['width'] = $sizeImage[0];
|
||||
// $data['height'] = $sizeImage[1];
|
||||
// Default value. Will be replaced by a dynamic image size
|
||||
// calculation after the phase 3.
|
||||
$sizeImage = getimagesize($config['homedir'].'/'.$imagePath);
|
||||
$imageHeight = $sizeImage[1];
|
||||
|
||||
if ($width === 0) {
|
||||
$data['width'] = 70;
|
||||
}
|
||||
|
||||
if ($height === 0) {
|
||||
$data['height'] = ($imageHeight > 70) ? 70 : $imageHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Get last value.
|
||||
$showLastValueTooltip = static::extractShowLastValueTooltip($data);
|
||||
if ($showLastValueTooltip !== 'disabled' && $moduleId > 0) {
|
||||
// Maybe connect to node.
|
||||
$nodeConnected = false;
|
||||
if (\is_metaconsole() === true && $metaconsoleId !== null) {
|
||||
$nodeConnected = \metaconsole_connect(
|
||||
null,
|
||||
$metaconsoleId
|
||||
) === NOERR;
|
||||
|
||||
if ($nodeConnected === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'error connecting to the node'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$imgTitle = '';
|
||||
|
||||
$unit = \trim(\io_safe_output(\modules_get_unit($moduleId)));
|
||||
$value = \modules_get_last_value($moduleId);
|
||||
|
||||
$isBooleanModule = \modules_is_boolean($moduleId);
|
||||
if (!$isBooleanModule
|
||||
|| ($isBooleanModule && $showLastValueTooltip !== 'default')
|
||||
) {
|
||||
if (is_numeric($value)) {
|
||||
$imgTitle .= __('Last value: ').\remove_right_zeros($value);
|
||||
} else {
|
||||
$imgTitle .= __('Last value: ').$value;
|
||||
}
|
||||
|
||||
if (empty($unit) === false && empty($imgTitle) === false) {
|
||||
$imgTitle .= ' '.$unit;
|
||||
}
|
||||
|
||||
$data['lastValue'] = $imgTitle;
|
||||
}
|
||||
|
||||
// Restore connection.
|
||||
if ($nodeConnected === true) {
|
||||
\metaconsole_restore_db();
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,89 @@
|
|||
#visual-console-container {
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.visual-console-item {
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: initial;
|
||||
-webkit-box-direction: initial;
|
||||
-ms-flex-direction: initial;
|
||||
flex-direction: initial;
|
||||
justify-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Alarm Clock;
|
||||
src: url(alarm-clock.ttf);
|
||||
}
|
||||
|
||||
/* Digital clock */
|
||||
|
||||
.visual-console-item .digital-clock {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
-ms-flex-line-pack: center;
|
||||
align-content: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span {
|
||||
font-family: "Alarm Clock", "Courier New", Courier, monospace;
|
||||
font-size: 50px;
|
||||
|
||||
/* To improve legibility */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
text-shadow: rgba(0, 0, 0, 0.01) 0 0 1px;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span.date {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span.timezone {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
/* Analog clock */
|
||||
|
||||
.visual-console-item .analogic-clock .hour-hand {
|
||||
-webkit-animation: rotate-hour 43200s infinite linear;
|
||||
animation: rotate-hour 43200s infinite linear;
|
||||
}
|
||||
|
||||
.visual-console-item .analogic-clock .minute-hand {
|
||||
-webkit-animation: rotate-minute 3600s infinite linear;
|
||||
animation: rotate-minute 3600s infinite linear;
|
||||
}
|
||||
|
||||
.visual-console-item .analogic-clock .second-hand {
|
||||
-webkit-animation: rotate-second 60s infinite linear;
|
||||
animation: rotate-second 60s infinite linear;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=vc.main.css.map*/
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"sources":["webpack:///main.css","webpack:///styles.css"],"names":[],"mappings":"AAAA;EACE,gBAAgB;EAChB,kBAAkB;EAClB,4BAA4B;EAC5B,wBAAwB;AAC1B;;AAEA;EACE,kBAAkB;EAClB,oBAAa;EAAb,oBAAa;EAAb,aAAa;EACb,2BAAuB;EAAvB,8BAAuB;MAAvB,2BAAuB;UAAvB,uBAAuB;EACvB,qBAAqB;EACrB,yBAAmB;MAAnB,sBAAmB;UAAnB,mBAAmB;EACnB,yBAAiB;KAAjB,sBAAiB;MAAjB,qBAAiB;UAAjB,iBAAiB;AACnB;;ACdA;EACE,wBAAwB;EACxB,0BAA2B;AAC7B;;AAEA,kBAAkB;;AAElB;EACE,oBAAa;EAAb,oBAAa;EAAb,aAAa;EACb,4BAAsB;EAAtB,6BAAsB;MAAtB,0BAAsB;UAAtB,sBAAsB;EACtB,wBAAuB;MAAvB,qBAAuB;UAAvB,uBAAuB;EACvB,qBAAqB;EACrB,0BAAqB;MAArB,qBAAqB;EACrB,yBAAmB;MAAnB,sBAAmB;UAAnB,mBAAmB;AACrB;;AAEA;EACE,6DAA6D;EAC7D,eAAe;;EAEf,0BAA0B;EAC1B,mCAAmC;EACnC,kCAAkC;EAClC,kCAAkC;EAClC,wCAAwC;AAC1C;;AAEA;EACE,eAAe;AACjB;;AAEA;EACE,eAAe;AACjB;;AAEA,iBAAiB;;AAEjB;EACE,qDAA6C;UAA7C,6CAA6C;AAC/C;;AAEA;EACE,sDAA8C;UAA9C,8CAA8C;AAChD;;AAEA;EACE,oDAA4C;UAA5C,4CAA4C;AAC9C","file":"vc.main.css","sourcesContent":["#visual-console-container {\n margin: 0px auto;\n position: relative;\n background-repeat: no-repeat;\n background-size: contain;\n}\n\n.visual-console-item {\n position: absolute;\n display: flex;\n flex-direction: initial;\n justify-items: center;\n align-items: center;\n user-select: text;\n}\n","@font-face {\n font-family: Alarm Clock;\n src: url(./alarm-clock.ttf);\n}\n\n/* Digital clock */\n\n.visual-console-item .digital-clock {\n display: flex;\n flex-direction: column;\n justify-content: center;\n justify-items: center;\n align-content: center;\n align-items: center;\n}\n\n.visual-console-item .digital-clock > span {\n font-family: \"Alarm Clock\", \"Courier New\", Courier, monospace;\n font-size: 50px;\n\n /* To improve legibility */\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n text-shadow: rgba(0, 0, 0, 0.01) 0 0 1px;\n}\n\n.visual-console-item .digital-clock > span.date {\n font-size: 25px;\n}\n\n.visual-console-item .digital-clock > span.timezone {\n font-size: 25px;\n}\n\n/* Analog clock */\n\n.visual-console-item .analogic-clock .hour-hand {\n animation: rotate-hour 43200s infinite linear;\n}\n\n.visual-console-item .analogic-clock .minute-hand {\n animation: rotate-minute 3600s infinite linear;\n}\n\n.visual-console-item .analogic-clock .second-hand {\n animation: rotate-second 60s infinite linear;\n}\n"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
|
||||
// Pandora FMS - http://pandorafms.com
|
||||
// ==================================================
|
||||
// Copyright (c) 20012 Artica Soluciones Tecnologicas
|
||||
// Please see http://pandorafms.org for full contribution list
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation for version 2.
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// Don't start a session before this import.
|
||||
// The session is configured and started inside the config process.
|
||||
require_once '../../include/config.php';
|
||||
|
||||
// Set root on homedir, as defined in setup
|
||||
chdir($config['homedir']);
|
||||
|
||||
ob_start();
|
||||
// Enterprise support
|
||||
if (file_exists(ENTERPRISE_DIR.'/load_enterprise.php')) {
|
||||
include_once ENTERPRISE_DIR.'/load_enterprise.php';
|
||||
}
|
||||
|
||||
if (file_exists(ENTERPRISE_DIR.'/include/functions_login.php')) {
|
||||
include_once ENTERPRISE_DIR.'/include/functions_login.php';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n";
|
||||
echo '<html xmlns="http://www.w3.org/1999/xhtml">'."\n";
|
||||
echo '<head>';
|
||||
|
||||
global $vc_public_view;
|
||||
$vc_public_view = true;
|
||||
// This starts the page head. In the call back function,
|
||||
// things from $page['head'] array will be processed into the head
|
||||
ob_start('ui_process_page_head');
|
||||
// Enterprise main
|
||||
enterprise_include('index.php');
|
||||
|
||||
require_once 'include/functions_visual_map.php';
|
||||
|
||||
$hash = get_parameter('hash');
|
||||
$id_layout = (int) get_parameter('id_layout');
|
||||
$graph_javascript = (bool) get_parameter('graph_javascript');
|
||||
$config['id_user'] = get_parameter('id_user');
|
||||
|
||||
$myhash = md5($config['dbpass'].$id_layout.$config['id_user']);
|
||||
|
||||
// Check input hash
|
||||
if ($myhash != $hash) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$refr = (int) get_parameter('refr', 0);
|
||||
$layout = db_get_row('tlayout', 'id', $id_layout);
|
||||
|
||||
if (! $layout) {
|
||||
db_pandora_audit('ACL Violation', 'Trying to access visual console without id layout');
|
||||
include $config['homedir'].'/general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($config['pure'])) {
|
||||
$config['pure'] = 0;
|
||||
}
|
||||
|
||||
// ~ $xhr = (bool) get_parameter('xhr');
|
||||
if ($layout) {
|
||||
$id_group = $layout['id_group'];
|
||||
$layout_name = $layout['name'];
|
||||
$background = $layout['background'];
|
||||
$bwidth = $layout['width'];
|
||||
$bheight = $layout['height'];
|
||||
// ~ $width = (int) get_parameter('width');
|
||||
// ~ if ($width <= 0) $width = null;
|
||||
// ~ $height = (int) get_parameter('height');
|
||||
// ~ if ($height <= 0) $height = null;
|
||||
// ~ ob_start();
|
||||
// ~ // Render map
|
||||
visual_map_print_visual_map(
|
||||
$id_layout,
|
||||
true,
|
||||
true,
|
||||
$width,
|
||||
$height,
|
||||
'../../',
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
// ~ return;
|
||||
} else {
|
||||
echo '<div id="vc-container"></div>';
|
||||
}
|
||||
|
||||
// Floating menu - Start
|
||||
echo '<div id="vc-controls" style="z-index:300;">';
|
||||
|
||||
echo '<div id="menu_tab">';
|
||||
echo '<ul class="mn">';
|
||||
|
||||
// QR code
|
||||
echo '<li class="nomn">';
|
||||
echo '<a href="javascript: show_dialog_qrcode();">';
|
||||
echo '<img class="vc-qr" src="../../images/qrcode_icon_2.jpg"/>';
|
||||
echo '</a>';
|
||||
echo '</li>';
|
||||
|
||||
// Countdown
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-refr">';
|
||||
echo '<div class="vc-countdown"></div>';
|
||||
echo '<div id="vc-refr-form">';
|
||||
echo __('Refresh').':';
|
||||
echo html_print_select(get_refresh_time_array(), 'refr', $refr, '', '', 0, true, false, false);
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</li>';
|
||||
|
||||
// Console name
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-title">'.$layout_name.'</div>';
|
||||
echo '</li>';
|
||||
|
||||
echo '</ul>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
// Floating menu - End
|
||||
// QR code dialog
|
||||
echo '<div style="display: none;" id="qrcode_container" title="'.__('QR code of the page').'">';
|
||||
echo '<div id="qrcode_container_image"></div>';
|
||||
echo '</div>';
|
||||
|
||||
ui_require_jquery_file('countdown');
|
||||
ui_require_javascript_file('wz_jsgraphics');
|
||||
ui_require_javascript_file('pandora_visual_console');
|
||||
$ignored_params['refr'] = '';
|
||||
?>
|
||||
|
||||
<style type="text/css">
|
||||
svg {
|
||||
stroke: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script language="javascript" type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
var refr = <?php echo (int) $refr; ?>;
|
||||
var href = "<?php echo ui_get_url_refresh($ignored_params); ?>";
|
||||
|
||||
var startCountDown = function (duration, cb) {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
if (!duration) return;
|
||||
var t = new Date();
|
||||
t.setTime(t.getTime() + duration * 1000);
|
||||
$('div.vc-countdown').countdown({
|
||||
until: t,
|
||||
format: 'MS',
|
||||
layout: '(%M%nn%M:%S%nn%S <?php echo __('Until refresh'); ?>) ',
|
||||
alwaysExpire: true,
|
||||
onExpiry: function () {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
//~ cb();
|
||||
url = js_html_entity_decode( href ) + duration;
|
||||
$(document).attr ("location", url);
|
||||
}
|
||||
});
|
||||
}
|
||||
startCountDown(refr, false);
|
||||
// Auto hide controls
|
||||
var controls = document.getElementById('vc-controls');
|
||||
autoHideElement(controls, 1000);
|
||||
|
||||
$('select#refr').change(function (event) {
|
||||
refr = Number.parseInt(event.target.value, 10);
|
||||
startCountDown(refr, false);
|
||||
});
|
||||
|
||||
|
||||
$('body').css('background-color','<?php echo $layout['background_color']; ?>');
|
||||
$('body').css('margin','0');
|
||||
$('body').css('height','100%');
|
||||
$('body').css('overflow','hidden');
|
||||
$(".module_graph .menu_graph").css('display','none');
|
||||
|
||||
$(".parent_graph").each(function(){
|
||||
|
||||
if($(this).css('background-color') != 'rgb(255, 255, 255)'){
|
||||
$(this).css('color', '#999');
|
||||
}
|
||||
});
|
||||
|
||||
$(".overlay").removeClass("overlay").addClass("overlaydisabled");
|
||||
|
||||
// Start the map fetch
|
||||
//~ fetchMap();
|
||||
});
|
||||
|
||||
$(window).on('load', function () {
|
||||
$('.item:not(.icon) img:not(.b64img)').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
if(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2 < 0){
|
||||
$(this).css('margin-left','');
|
||||
$(this).css('margin-top','');
|
||||
} else {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if($(this).attr('id').indexOf('clock') || $(this).attr('id').indexOf('overlay')){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else{
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2-15)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > a > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2-5)+'px');
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$(".graph:not([class~='noresizevc'])").each(function(){
|
||||
height = parseInt($(this).css("height")) - 30;
|
||||
$(this).css('height', height);
|
||||
});
|
||||
|
||||
// Start the map fetch
|
||||
//~ fetchMap();
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,386 @@
|
|||
<?php
|
||||
|
||||
// Pandora FMS - http://pandorafms.com
|
||||
// ==================================================
|
||||
// Copyright (c) 2005-2009 Artica Soluciones Tecnologicas
|
||||
// Please see http://pandorafms.org for full contribution list
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation for version 2.
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
global $config;
|
||||
|
||||
// Login check
|
||||
require_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
|
||||
check_login();
|
||||
|
||||
if (!defined('METACONSOLE')) {
|
||||
$id_layout = (int) get_parameter('id');
|
||||
} else {
|
||||
$id_layout = (int) get_parameter('id_visualmap');
|
||||
}
|
||||
|
||||
if ($id_layout) {
|
||||
$default_action = 'edit';
|
||||
} else {
|
||||
$default_action = 'new';
|
||||
}
|
||||
|
||||
if (!defined('METACONSOLE')) {
|
||||
$action = get_parameterBetweenListValues(
|
||||
'action',
|
||||
[
|
||||
'new',
|
||||
'save',
|
||||
'edit',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
$default_action
|
||||
);
|
||||
} else {
|
||||
$action = get_parameterBetweenListValues(
|
||||
'action2',
|
||||
[
|
||||
'new',
|
||||
'save',
|
||||
'edit',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
$default_action
|
||||
);
|
||||
}
|
||||
|
||||
$refr = (int) get_parameter('refr', $config['vc_refr']);
|
||||
$graph_javascript = (bool) get_parameter('graph_javascript', true);
|
||||
$vc_refr = false;
|
||||
|
||||
if (isset($config['vc_refr']) and $config['vc_refr'] != 0) {
|
||||
$view_refresh = $config['vc_refr'];
|
||||
} else {
|
||||
$view_refresh = '300';
|
||||
}
|
||||
|
||||
// Get input parameter for layout id
|
||||
if (! $id_layout) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without id layout'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$layout = db_get_row('tlayout', 'id', $id_layout);
|
||||
|
||||
if (! $layout) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without id layout'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_group = $layout['id_group'];
|
||||
$layout_name = $layout['name'];
|
||||
$background = $layout['background'];
|
||||
$bwidth = $layout['width'];
|
||||
$bheight = $layout['height'];
|
||||
|
||||
$pure_url = '&pure='.$config['pure'];
|
||||
|
||||
// ACL
|
||||
$vconsole_read = check_acl($config['id_user'], $id_group, 'VR');
|
||||
$vconsole_write = check_acl($config['id_user'], $id_group, 'VW');
|
||||
$vconsole_manage = check_acl($config['id_user'], $id_group, 'VM');
|
||||
|
||||
if (! $vconsole_read && !$vconsole_write && !$vconsole_manage) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without group access'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Render map
|
||||
$options = [];
|
||||
|
||||
$options['consoles_list']['text'] = '<a href="index.php?sec=network&sec2=godmode/reporting/map_builder&refr='.$refr.'">'.html_print_image(
|
||||
'images/visual_console.png',
|
||||
true,
|
||||
['title' => __('Visual consoles list')]
|
||||
).'</a>';
|
||||
|
||||
if ($vconsole_write || $vconsole_manage) {
|
||||
$url_base = 'index.php?sec=network&sec2=godmode/reporting/visual_console_builder&action=';
|
||||
|
||||
$hash = md5($config['dbpass'].$id_layout.$config['id_user']);
|
||||
|
||||
$options['public_link']['text'] = '<a href="'.ui_get_full_url(
|
||||
'operation/visual_console/public_console.php?hash='.$hash.'&id_layout='.$id_layout.'&id_user='.$config['id_user']
|
||||
).'" target="_blank">'.html_print_image(
|
||||
'images/camera_mc.png',
|
||||
true,
|
||||
['title' => __('Show link to public Visual Console')]
|
||||
).'</a>';
|
||||
$options['public_link']['active'] = false;
|
||||
|
||||
$options['data']['text'] = '<a href="'.$url_base.$action.'&tab=data&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/op_reporting.png',
|
||||
true,
|
||||
['title' => __('Main data')]
|
||||
).'</a>';
|
||||
$options['list_elements']['text'] = '<a href="'.$url_base.$action.'&tab=list_elements&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/list.png',
|
||||
true,
|
||||
['title' => __('List elements')]
|
||||
).'</a>';
|
||||
|
||||
if (enterprise_installed()) {
|
||||
$options['wizard_services']['text'] = '<a href="'.$url_base.$action.'&tab=wizard_services&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/wand_services.png',
|
||||
true,
|
||||
['title' => __('Services wizard')]
|
||||
).'</a>';
|
||||
}
|
||||
|
||||
$options['wizard']['text'] = '<a href="'.$url_base.$action.'&tab=wizard&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/wand.png',
|
||||
true,
|
||||
['title' => __('Wizard')]
|
||||
).'</a>';
|
||||
$options['editor']['text'] = '<a href="'.$url_base.$action.'&tab=editor&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/builder.png',
|
||||
true,
|
||||
['title' => __('Builder')]
|
||||
).'</a>';
|
||||
}
|
||||
|
||||
$options['view']['text'] = '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$id_layout.'&refr='.$view_refresh.'">'.html_print_image('images/operation.png', true, ['title' => __('View')]).'</a>';
|
||||
$options['view']['active'] = true;
|
||||
|
||||
if (!is_metaconsole()) {
|
||||
if (!$config['pure']) {
|
||||
$options['pure']['text'] = '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$id_layout.'&refr='.$refr.'&pure=1">'.html_print_image('images/full_screen.png', true, ['title' => __('Full screen mode')]).'</a>';
|
||||
ui_print_page_header($layout_name, 'images/visual_console.png', false, '', false, $options);
|
||||
}
|
||||
|
||||
// Set the hidden value for the javascript
|
||||
html_print_input_hidden('metaconsole', 0);
|
||||
} else {
|
||||
// Set the hidden value for the javascript
|
||||
html_print_input_hidden('metaconsole', 1);
|
||||
}
|
||||
|
||||
if ($config['pure']) {
|
||||
// Container of the visual map (ajax loaded)
|
||||
echo '<div id="vc-container">'.visual_map_print_visual_map(
|
||||
$id_layout,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
'',
|
||||
false,
|
||||
true
|
||||
).'</div>';
|
||||
|
||||
// Floating menu - Start
|
||||
echo '<div id="vc-controls" style="z-index: 999">';
|
||||
|
||||
echo '<div id="menu_tab">';
|
||||
echo '<ul class="mn">';
|
||||
|
||||
// Quit fullscreen
|
||||
echo '<li class="nomn">';
|
||||
echo '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$id_layout.'&refr='.$refr.'">';
|
||||
echo html_print_image('images/normal_screen.png', true, ['title' => __('Back to normal mode')]);
|
||||
echo '</a>';
|
||||
echo '</li>';
|
||||
|
||||
// Countdown
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-refr">';
|
||||
echo '<div class="vc-countdown"></div>';
|
||||
echo '<div id="vc-refr-form">';
|
||||
echo __('Refresh').':';
|
||||
echo html_print_select(get_refresh_time_array(), 'refr', $refr, '', '', 0, true, false, false);
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</li>';
|
||||
|
||||
// Console name
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-title">'.$layout_name.'</div>';
|
||||
echo '</li>';
|
||||
|
||||
echo '</ul>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
// Floating menu - End
|
||||
ui_require_jquery_file('countdown');
|
||||
|
||||
?>
|
||||
<style type="text/css">
|
||||
/* Avoid the main_pure container 1000px height */
|
||||
body.pure {
|
||||
min-height: 100px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
<?php
|
||||
echo 'background-color: '.$layout['background_color'].';';
|
||||
?>
|
||||
}
|
||||
div#main_pure {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
<?php
|
||||
echo 'background-color: '.$layout['background_color'].';';
|
||||
?>
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
} else {
|
||||
visual_map_print_visual_map($id_layout, true, true, null, null, '', false, true, true);
|
||||
}
|
||||
|
||||
ui_require_javascript_file('wz_jsgraphics');
|
||||
ui_require_javascript_file('pandora_visual_console');
|
||||
$ignored_params['refr'] = '';
|
||||
?>
|
||||
|
||||
<style type="text/css">
|
||||
svg {
|
||||
stroke: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script language="javascript" type="text/javascript">
|
||||
$(document).ready (function () {
|
||||
var refr = <?php echo (int) $refr; ?>;
|
||||
var pure = <?php echo (int) $config['pure']; ?>;
|
||||
var href = "<?php echo ui_get_url_refresh($ignored_params); ?>";
|
||||
if (pure) {
|
||||
var startCountDown = function (duration, cb) {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
if (!duration) return;
|
||||
var t = new Date();
|
||||
t.setTime(t.getTime() + duration * 1000);
|
||||
$('div.vc-countdown').countdown({
|
||||
until: t,
|
||||
format: 'MS',
|
||||
layout: '(%M%nn%M:%S%nn%S <?php echo __('Until refresh'); ?>) ',
|
||||
alwaysExpire: true,
|
||||
onExpiry: function () {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
//cb();
|
||||
url = js_html_entity_decode( href ) + duration;
|
||||
$(document).attr ("location", url);
|
||||
/*$.post(window.location.href.replace("refr=300","refr="+new_count), function(respuestaSolicitud){
|
||||
$('#background_<?php echo $id_layout; ?>').html(respuestaSolicitud);
|
||||
});
|
||||
*/
|
||||
$("#main_pure").css('background-color','<?php echo $layout['background_color']; ?>');
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startCountDown(refr, false);
|
||||
|
||||
var controls = document.getElementById('vc-controls');
|
||||
autoHideElement(controls, 1000);
|
||||
|
||||
$('select#refr').change(function (event) {
|
||||
refr = Number.parseInt(event.target.value, 10);
|
||||
new_count = event.target.value;
|
||||
startCountDown(refr, false);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#refr').change(function () {
|
||||
$('#hidden-vc_refr').val($('#refr option:selected').val());
|
||||
});
|
||||
}
|
||||
|
||||
$(".module_graph .menu_graph").css('display','none');
|
||||
|
||||
$(".parent_graph").each( function() {
|
||||
if ($(this).css('background-color') != 'rgb(255, 255, 255)')
|
||||
$(this).css('color', '#999');
|
||||
});
|
||||
|
||||
$(".overlay").removeClass("overlay").addClass("overlaydisabled");
|
||||
|
||||
});
|
||||
|
||||
$(window).on('load', function () {
|
||||
$('.item:not(.icon) img:not(.b64img)').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
if(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2 < 0){
|
||||
$(this).css('margin-left','');
|
||||
$(this).css('margin-top','');
|
||||
} else {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if($(this).attr('id').indexOf('clock') || $(this).attr('id').indexOf('overlay')){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else{
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2-15)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > a > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2-5)+'px');
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$(".graph:not([class~='noresizevc'])").each(function(){
|
||||
height = parseInt($(this).css("height")) - 30;
|
||||
$(this).css('height', height);
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
|
@ -15,245 +15,9 @@
|
|||
// The session is configured and started inside the config process.
|
||||
require_once '../../include/config.php';
|
||||
|
||||
// Set root on homedir, as defined in setup
|
||||
chdir($config['homedir']);
|
||||
|
||||
ob_start();
|
||||
// Enterprise support
|
||||
if (file_exists(ENTERPRISE_DIR.'/load_enterprise.php')) {
|
||||
include_once ENTERPRISE_DIR.'/load_enterprise.php';
|
||||
}
|
||||
|
||||
if (file_exists(ENTERPRISE_DIR.'/include/functions_login.php')) {
|
||||
include_once ENTERPRISE_DIR.'/include/functions_login.php';
|
||||
}
|
||||
|
||||
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n";
|
||||
echo '<html xmlns="http://www.w3.org/1999/xhtml">'."\n";
|
||||
echo '<head>';
|
||||
|
||||
global $vc_public_view;
|
||||
$vc_public_view = true;
|
||||
// This starts the page head. In the call back function,
|
||||
// things from $page['head'] array will be processed into the head
|
||||
ob_start('ui_process_page_head');
|
||||
// Enterprise main
|
||||
enterprise_include('index.php');
|
||||
|
||||
require_once 'include/functions_visual_map.php';
|
||||
|
||||
$hash = get_parameter('hash');
|
||||
$id_layout = (int) get_parameter('id_layout');
|
||||
$graph_javascript = (bool) get_parameter('graph_javascript');
|
||||
$config['id_user'] = get_parameter('id_user');
|
||||
|
||||
$myhash = md5($config['dbpass'].$id_layout.$config['id_user']);
|
||||
|
||||
// Check input hash
|
||||
if ($myhash != $hash) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$refr = (int) get_parameter('refr', 0);
|
||||
$layout = db_get_row('tlayout', 'id', $id_layout);
|
||||
|
||||
if (! $layout) {
|
||||
db_pandora_audit('ACL Violation', 'Trying to access visual console without id layout');
|
||||
include $config['homedir'].'/general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($config['pure'])) {
|
||||
$config['pure'] = 0;
|
||||
}
|
||||
|
||||
// ~ $xhr = (bool) get_parameter('xhr');
|
||||
if ($layout) {
|
||||
$id_group = $layout['id_group'];
|
||||
$layout_name = $layout['name'];
|
||||
$background = $layout['background'];
|
||||
$bwidth = $layout['width'];
|
||||
$bheight = $layout['height'];
|
||||
// ~ $width = (int) get_parameter('width');
|
||||
// ~ if ($width <= 0) $width = null;
|
||||
// ~ $height = (int) get_parameter('height');
|
||||
// ~ if ($height <= 0) $height = null;
|
||||
// ~ ob_start();
|
||||
// ~ // Render map
|
||||
visual_map_print_visual_map(
|
||||
$id_layout,
|
||||
true,
|
||||
true,
|
||||
$width,
|
||||
$height,
|
||||
'../../',
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
// ~ return;
|
||||
$legacy = (bool) get_parameter('legacy', $config['legacy_vc']);
|
||||
if ($legacy === false) {
|
||||
include_once $config['homedir'].'/operation/visual_console/public_view.php';
|
||||
} else {
|
||||
echo '<div id="vc-container"></div>';
|
||||
include_once $config['homedir'].'/operation/visual_console/legacy_public_view.php';
|
||||
}
|
||||
|
||||
// Floating menu - Start
|
||||
echo '<div id="vc-controls" style="z-index:300;">';
|
||||
|
||||
echo '<div id="menu_tab">';
|
||||
echo '<ul class="mn">';
|
||||
|
||||
// QR code
|
||||
echo '<li class="nomn">';
|
||||
echo '<a href="javascript: show_dialog_qrcode();">';
|
||||
echo '<img class="vc-qr" src="../../images/qrcode_icon_2.jpg"/>';
|
||||
echo '</a>';
|
||||
echo '</li>';
|
||||
|
||||
// Countdown
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-refr">';
|
||||
echo '<div class="vc-countdown"></div>';
|
||||
echo '<div id="vc-refr-form">';
|
||||
echo __('Refresh').':';
|
||||
echo html_print_select(get_refresh_time_array(), 'refr', $refr, '', '', 0, true, false, false);
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</li>';
|
||||
|
||||
// Console name
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-title">'.$layout_name.'</div>';
|
||||
echo '</li>';
|
||||
|
||||
echo '</ul>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
// Floating menu - End
|
||||
// QR code dialog
|
||||
echo '<div style="display: none;" id="qrcode_container" title="'.__('QR code of the page').'">';
|
||||
echo '<div id="qrcode_container_image"></div>';
|
||||
echo '</div>';
|
||||
|
||||
ui_require_jquery_file('countdown');
|
||||
ui_require_javascript_file('wz_jsgraphics');
|
||||
ui_require_javascript_file('pandora_visual_console');
|
||||
$ignored_params['refr'] = '';
|
||||
?>
|
||||
|
||||
<script language="javascript" type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
var refr = <?php echo (int) $refr; ?>;
|
||||
var href = "<?php echo ui_get_url_refresh($ignored_params); ?>";
|
||||
|
||||
var startCountDown = function (duration, cb) {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
if (!duration) return;
|
||||
var t = new Date();
|
||||
t.setTime(t.getTime() + duration * 1000);
|
||||
$('div.vc-countdown').countdown({
|
||||
until: t,
|
||||
format: 'MS',
|
||||
layout: '(%M%nn%M:%S%nn%S <?php echo __('Until refresh'); ?>) ',
|
||||
alwaysExpire: true,
|
||||
onExpiry: function () {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
//~ cb();
|
||||
url = js_html_entity_decode( href ) + duration;
|
||||
$(document).attr ("location", url);
|
||||
}
|
||||
});
|
||||
}
|
||||
startCountDown(refr, false);
|
||||
// Auto hide controls
|
||||
var controls = document.getElementById('vc-controls');
|
||||
autoHideElement(controls, 1000);
|
||||
|
||||
$('select#refr').change(function (event) {
|
||||
refr = Number.parseInt(event.target.value, 10);
|
||||
startCountDown(refr, false);
|
||||
});
|
||||
|
||||
|
||||
$('body').css('background-color','<?php echo $layout['background_color']; ?>');
|
||||
$('body').css('margin','0');
|
||||
$('body').css('height','100%');
|
||||
$('body').css('overflow','hidden');
|
||||
$(".module_graph .menu_graph").css('display','none');
|
||||
|
||||
$(".parent_graph").each(function(){
|
||||
|
||||
if($(this).css('background-color') != 'rgb(255, 255, 255)'){
|
||||
$(this).css('color', '#999');
|
||||
}
|
||||
});
|
||||
|
||||
$(".overlay").removeClass("overlay").addClass("overlaydisabled");
|
||||
|
||||
// Start the map fetch
|
||||
//~ fetchMap();
|
||||
});
|
||||
|
||||
$(window).on('load', function () {
|
||||
$('.item:not(.icon) img:not(.b64img)').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
if(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2 < 0){
|
||||
$(this).css('margin-left','');
|
||||
$(this).css('margin-top','');
|
||||
} else {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if($(this).attr('id').indexOf('clock') || $(this).attr('id').indexOf('overlay')){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else{
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2-15)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > a > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2-5)+'px');
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$(".graph:not([class~='noresizevc'])").each(function(){
|
||||
height = parseInt($(this).css("height")) - 30;
|
||||
$(this).css('height', height);
|
||||
});
|
||||
|
||||
// Start the map fetch
|
||||
//~ fetchMap();
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
// Pandora FMS - http://pandorafms.com
|
||||
// ==================================================
|
||||
// Copyright (c) 2005-2019 Artica Soluciones Tecnologicas
|
||||
// Please see http://pandorafms.org for full contribution list
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; version 2
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
require_once '../../include/config.php';
|
||||
|
||||
// Set root on homedir, as defined in setup.
|
||||
chdir($config['homedir']);
|
||||
|
||||
ob_start();
|
||||
// Enterprise support.
|
||||
if (file_exists(ENTERPRISE_DIR.'/load_enterprise.php')) {
|
||||
include_once ENTERPRISE_DIR.'/load_enterprise.php';
|
||||
}
|
||||
|
||||
if (file_exists(ENTERPRISE_DIR.'/include/functions_login.php')) {
|
||||
include_once ENTERPRISE_DIR.'/include/functions_login.php';
|
||||
}
|
||||
|
||||
require_once $config['homedir'].'/vendor/autoload.php';
|
||||
|
||||
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n";
|
||||
echo '<html xmlns="http://www.w3.org/1999/xhtml">'."\n";
|
||||
echo '<head>';
|
||||
|
||||
global $vc_public_view;
|
||||
$vc_public_view = true;
|
||||
$config['public_view'] = true;
|
||||
|
||||
// This starts the page head. In the call back function,
|
||||
// things from $page['head'] array will be processed into the head.
|
||||
ob_start('ui_process_page_head');
|
||||
// Enterprise main.
|
||||
enterprise_include('index.php');
|
||||
|
||||
require_once 'include/functions_visual_map.php';
|
||||
|
||||
$hash = (string) get_parameter('hash');
|
||||
$visualConsoleId = (int) get_parameter('id_layout');
|
||||
$config['id_user'] = (string) get_parameter('id_user');
|
||||
$refr = (int) get_parameter('refr', $config['refr']);
|
||||
|
||||
if (!isset($config['pure'])) {
|
||||
$config['pure'] = 0;
|
||||
}
|
||||
|
||||
$myhash = md5($config['dbpass'].$visualConsoleId.$config['id_user']);
|
||||
|
||||
// Check input hash.
|
||||
if ($myhash != $hash) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Load Visual Console.
|
||||
use Models\VisualConsole\Container as VisualConsole;
|
||||
$visualConsole = null;
|
||||
try {
|
||||
$visualConsole = VisualConsole::fromDB(['id' => $visualConsoleId]);
|
||||
} catch (Throwable $e) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without Id'
|
||||
);
|
||||
include $config['homedir'].'/general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$visualConsoleData = $visualConsole->toArray();
|
||||
$visualConsoleName = $visualConsoleData['name'];
|
||||
|
||||
echo '<div id="visual-console-container"></div>';
|
||||
|
||||
// Floating menu - Start.
|
||||
echo '<div id="vc-controls" style="z-index:300;">';
|
||||
|
||||
echo '<div id="menu_tab">';
|
||||
echo '<ul class="mn">';
|
||||
|
||||
// QR code.
|
||||
echo '<li class="nomn">';
|
||||
echo '<a href="javascript: show_dialog_qrcode();">';
|
||||
echo '<img class="vc-qr" src="../../images/qrcode_icon_2.jpg"/>';
|
||||
echo '</a>';
|
||||
echo '</li>';
|
||||
|
||||
// Console name.
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-title">'.$visualConsoleName.'</div>';
|
||||
echo '</li>';
|
||||
|
||||
echo '</ul>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
// QR code dialog.
|
||||
echo '<div style="display: none;" id="qrcode_container" title="'.__('QR code of the page').'">';
|
||||
echo '<div id="qrcode_container_image"></div>';
|
||||
echo '</div>';
|
||||
|
||||
// Check groups can access user.
|
||||
$aclUserGroups = [];
|
||||
if (!users_can_manage_group_all('AR')) {
|
||||
$aclUserGroups = array_keys(users_get_groups(false, 'AR'));
|
||||
}
|
||||
|
||||
ui_require_javascript_file('pandora_visual_console');
|
||||
include_javascript_d3();
|
||||
visual_map_load_client_resources();
|
||||
|
||||
// Load Visual Console Items.
|
||||
$visualConsoleItems = VisualConsole::getItemsFromDB(
|
||||
$visualConsoleId,
|
||||
$aclUserGroups
|
||||
);
|
||||
|
||||
?>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: <?php echo $visualConsoleData['backgroundColor']; ?>;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
var container = document.getElementById("visual-console-container");
|
||||
var props = <?php echo (string) $visualConsole; ?>;
|
||||
var items = <?php echo '['.implode($visualConsoleItems, ',').']'; ?>;
|
||||
var baseUrl = "<?php echo ui_get_full_url('/', false, false, false); ?>";
|
||||
var handleUpdate = function (prevProps, newProps) {
|
||||
if (!newProps) return;
|
||||
|
||||
// Change the background color when the fullscreen mode is enabled.
|
||||
if (prevProps
|
||||
&& prevProps.backgroundColor != newProps.backgroundColor
|
||||
) {
|
||||
var body = document.querySelector("body");
|
||||
if (body !== null) {
|
||||
body.style.backgroundColor = newProps.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
// Change the title.
|
||||
if (prevProps && prevProps.name != newProps.name) {
|
||||
var title = document.querySelector("div.vc-title");
|
||||
if (title !== null) {
|
||||
title.textContent = newProps.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Change the links.
|
||||
if (prevProps && prevProps.id !== newProps.id) {
|
||||
var regex = /(id=|id_visual_console=|id_layout=)\d+(&?)/gi;
|
||||
var replacement = '$1' + newProps.id + '$2';
|
||||
|
||||
// Tab links.
|
||||
var menuLinks = document.querySelectorAll("div#menu_tab a");
|
||||
if (menuLinks !== null) {
|
||||
menuLinks.forEach(function (menuLink) {
|
||||
menuLink.href = menuLink.href.replace(regex, replacement);
|
||||
});
|
||||
}
|
||||
|
||||
// Change the URL (if the browser has support).
|
||||
if ("history" in window) {
|
||||
var href = window.location.href.replace(regex, replacement);
|
||||
window.history.replaceState({}, document.title, href);
|
||||
}
|
||||
}
|
||||
}
|
||||
var visualConsole = createVisualConsole(
|
||||
container,
|
||||
props,
|
||||
items,
|
||||
baseUrl,
|
||||
<?php echo ($refr * 1000); ?>,
|
||||
handleUpdate
|
||||
);
|
||||
|
||||
$(document).ready(function () {
|
||||
var controls = document.getElementById('vc-controls');
|
||||
if (controls) autoHideElement(controls, 1000);
|
||||
});
|
||||
</script>
|
|
@ -13,368 +13,9 @@
|
|||
// GNU General Public License for more details.
|
||||
global $config;
|
||||
|
||||
// Login check
|
||||
require_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
|
||||
check_login();
|
||||
|
||||
if (!defined('METACONSOLE')) {
|
||||
$id_layout = (int) get_parameter('id');
|
||||
$legacy = (bool) get_parameter('legacy', $config['legacy_vc']);
|
||||
if ($legacy === false) {
|
||||
include_once $config['homedir'].'/operation/visual_console/view.php';
|
||||
} else {
|
||||
$id_layout = (int) get_parameter('id_visualmap');
|
||||
include_once $config['homedir'].'/operation/visual_console/legacy_view.php';
|
||||
}
|
||||
|
||||
if ($id_layout) {
|
||||
$default_action = 'edit';
|
||||
} else {
|
||||
$default_action = 'new';
|
||||
}
|
||||
|
||||
if (!defined('METACONSOLE')) {
|
||||
$action = get_parameterBetweenListValues(
|
||||
'action',
|
||||
[
|
||||
'new',
|
||||
'save',
|
||||
'edit',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
$default_action
|
||||
);
|
||||
} else {
|
||||
$action = get_parameterBetweenListValues(
|
||||
'action2',
|
||||
[
|
||||
'new',
|
||||
'save',
|
||||
'edit',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
$default_action
|
||||
);
|
||||
}
|
||||
|
||||
$refr = (int) get_parameter('refr', $config['vc_refr']);
|
||||
$graph_javascript = (bool) get_parameter('graph_javascript', true);
|
||||
$vc_refr = false;
|
||||
|
||||
if (isset($config['vc_refr']) and $config['vc_refr'] != 0) {
|
||||
$view_refresh = $config['vc_refr'];
|
||||
} else {
|
||||
$view_refresh = '300';
|
||||
}
|
||||
|
||||
// Get input parameter for layout id
|
||||
if (! $id_layout) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without id layout'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$layout = db_get_row('tlayout', 'id', $id_layout);
|
||||
|
||||
if (! $layout) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without id layout'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$id_group = $layout['id_group'];
|
||||
$layout_name = $layout['name'];
|
||||
$background = $layout['background'];
|
||||
$bwidth = $layout['width'];
|
||||
$bheight = $layout['height'];
|
||||
|
||||
$pure_url = '&pure='.$config['pure'];
|
||||
|
||||
// ACL
|
||||
$vconsole_read = check_acl($config['id_user'], $id_group, 'VR');
|
||||
$vconsole_write = check_acl($config['id_user'], $id_group, 'VW');
|
||||
$vconsole_manage = check_acl($config['id_user'], $id_group, 'VM');
|
||||
|
||||
if (! $vconsole_read && !$vconsole_write && !$vconsole_manage) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without group access'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Render map
|
||||
$options = [];
|
||||
|
||||
$options['consoles_list']['text'] = '<a href="index.php?sec=network&sec2=godmode/reporting/map_builder&refr='.$refr.'">'.html_print_image(
|
||||
'images/visual_console.png',
|
||||
true,
|
||||
['title' => __('Visual consoles list')]
|
||||
).'</a>';
|
||||
|
||||
if ($vconsole_write || $vconsole_manage) {
|
||||
$url_base = 'index.php?sec=network&sec2=godmode/reporting/visual_console_builder&action=';
|
||||
|
||||
$hash = md5($config['dbpass'].$id_layout.$config['id_user']);
|
||||
|
||||
$options['public_link']['text'] = '<a href="'.ui_get_full_url(
|
||||
'operation/visual_console/public_console.php?hash='.$hash.'&id_layout='.$id_layout.'&id_user='.$config['id_user']
|
||||
).'" target="_blank">'.html_print_image(
|
||||
'images/camera_mc.png',
|
||||
true,
|
||||
['title' => __('Show link to public Visual Console')]
|
||||
).'</a>';
|
||||
$options['public_link']['active'] = false;
|
||||
|
||||
$options['data']['text'] = '<a href="'.$url_base.$action.'&tab=data&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/op_reporting.png',
|
||||
true,
|
||||
['title' => __('Main data')]
|
||||
).'</a>';
|
||||
$options['list_elements']['text'] = '<a href="'.$url_base.$action.'&tab=list_elements&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/list.png',
|
||||
true,
|
||||
['title' => __('List elements')]
|
||||
).'</a>';
|
||||
|
||||
if (enterprise_installed()) {
|
||||
$options['wizard_services']['text'] = '<a href="'.$url_base.$action.'&tab=wizard_services&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/wand_services.png',
|
||||
true,
|
||||
['title' => __('Services wizard')]
|
||||
).'</a>';
|
||||
}
|
||||
|
||||
$options['wizard']['text'] = '<a href="'.$url_base.$action.'&tab=wizard&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/wand.png',
|
||||
true,
|
||||
['title' => __('Wizard')]
|
||||
).'</a>';
|
||||
$options['editor']['text'] = '<a href="'.$url_base.$action.'&tab=editor&id_visual_console='.$id_layout.'">'.html_print_image(
|
||||
'images/builder.png',
|
||||
true,
|
||||
['title' => __('Builder')]
|
||||
).'</a>';
|
||||
}
|
||||
|
||||
$options['view']['text'] = '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$id_layout.'&refr='.$view_refresh.'">'.html_print_image('images/operation.png', true, ['title' => __('View')]).'</a>';
|
||||
$options['view']['active'] = true;
|
||||
|
||||
if (!is_metaconsole()) {
|
||||
if (!$config['pure']) {
|
||||
$options['pure']['text'] = '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$id_layout.'&refr='.$refr.'&pure=1">'.html_print_image('images/full_screen.png', true, ['title' => __('Full screen mode')]).'</a>';
|
||||
ui_print_page_header($layout_name, 'images/visual_console.png', false, '', false, $options);
|
||||
}
|
||||
|
||||
// Set the hidden value for the javascript
|
||||
html_print_input_hidden('metaconsole', 0);
|
||||
} else {
|
||||
// Set the hidden value for the javascript
|
||||
html_print_input_hidden('metaconsole', 1);
|
||||
}
|
||||
|
||||
if ($config['pure']) {
|
||||
// Container of the visual map (ajax loaded)
|
||||
echo '<div id="vc-container">'.visual_map_print_visual_map(
|
||||
$id_layout,
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
'',
|
||||
false,
|
||||
true
|
||||
).'</div>';
|
||||
|
||||
// Floating menu - Start
|
||||
echo '<div id="vc-controls" style="z-index: 999">';
|
||||
|
||||
echo '<div id="menu_tab">';
|
||||
echo '<ul class="mn">';
|
||||
|
||||
// Quit fullscreen
|
||||
echo '<li class="nomn">';
|
||||
echo '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$id_layout.'&refr='.$refr.'">';
|
||||
echo html_print_image('images/normal_screen.png', true, ['title' => __('Back to normal mode')]);
|
||||
echo '</a>';
|
||||
echo '</li>';
|
||||
|
||||
// Countdown
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-refr">';
|
||||
echo '<div class="vc-countdown"></div>';
|
||||
echo '<div id="vc-refr-form">';
|
||||
echo __('Refresh').':';
|
||||
echo html_print_select(get_refresh_time_array(), 'refr', $refr, '', '', 0, true, false, false);
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</li>';
|
||||
|
||||
// Console name
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-title">'.$layout_name.'</div>';
|
||||
echo '</li>';
|
||||
|
||||
echo '</ul>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
// Floating menu - End
|
||||
ui_require_jquery_file('countdown');
|
||||
|
||||
?>
|
||||
<style type="text/css">
|
||||
/* Avoid the main_pure container 1000px height */
|
||||
body.pure {
|
||||
min-height: 100px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
<?php
|
||||
echo 'background-color: '.$layout['background_color'].';';
|
||||
?>
|
||||
}
|
||||
div#main_pure {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
<?php
|
||||
echo 'background-color: '.$layout['background_color'].';';
|
||||
?>
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
} else {
|
||||
visual_map_print_visual_map($id_layout, true, true, null, null, '', false, true, true);
|
||||
}
|
||||
|
||||
ui_require_javascript_file('wz_jsgraphics');
|
||||
ui_require_javascript_file('pandora_visual_console');
|
||||
$ignored_params['refr'] = '';
|
||||
?>
|
||||
|
||||
<script language="javascript" type="text/javascript">
|
||||
$(document).ready (function () {
|
||||
var refr = <?php echo (int) $refr; ?>;
|
||||
var pure = <?php echo (int) $config['pure']; ?>;
|
||||
var href = "<?php echo ui_get_url_refresh($ignored_params); ?>";
|
||||
if (pure) {
|
||||
var startCountDown = function (duration, cb) {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
if (!duration) return;
|
||||
var t = new Date();
|
||||
t.setTime(t.getTime() + duration * 1000);
|
||||
$('div.vc-countdown').countdown({
|
||||
until: t,
|
||||
format: 'MS',
|
||||
layout: '(%M%nn%M:%S%nn%S <?php echo __('Until refresh'); ?>) ',
|
||||
alwaysExpire: true,
|
||||
onExpiry: function () {
|
||||
$('div.vc-countdown').countdown('destroy');
|
||||
//cb();
|
||||
url = js_html_entity_decode( href ) + duration;
|
||||
$(document).attr ("location", url);
|
||||
/*$.post(window.location.href.replace("refr=300","refr="+new_count), function(respuestaSolicitud){
|
||||
$('#background_<?php echo $id_layout; ?>').html(respuestaSolicitud);
|
||||
});
|
||||
*/
|
||||
$("#main_pure").css('background-color','<?php echo $layout['background_color']; ?>');
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startCountDown(refr, false);
|
||||
|
||||
var controls = document.getElementById('vc-controls');
|
||||
autoHideElement(controls, 1000);
|
||||
|
||||
$('select#refr').change(function (event) {
|
||||
refr = Number.parseInt(event.target.value, 10);
|
||||
new_count = event.target.value;
|
||||
startCountDown(refr, false);
|
||||
});
|
||||
}
|
||||
else {
|
||||
$('#refr').change(function () {
|
||||
$('#hidden-vc_refr').val($('#refr option:selected').val());
|
||||
});
|
||||
}
|
||||
|
||||
$(".module_graph .menu_graph").css('display','none');
|
||||
|
||||
$(".parent_graph").each( function() {
|
||||
if ($(this).css('background-color') != 'rgb(255, 255, 255)')
|
||||
$(this).css('color', '#999');
|
||||
});
|
||||
|
||||
$(".overlay").removeClass("overlay").addClass("overlaydisabled");
|
||||
|
||||
});
|
||||
|
||||
$(window).on('load', function () {
|
||||
$('.item:not(.icon) img:not(.b64img)').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
if(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2 < 0){
|
||||
$(this).css('margin-left','');
|
||||
$(this).css('margin-top','');
|
||||
} else {
|
||||
if( $(this).parent()[0].tagName == 'DIV'){
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
else if ( $(this).parent()[0].tagName == 'A') {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
}
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
if($(this).attr('id').indexOf('clock') || $(this).attr('id').indexOf('overlay')){
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2)+'px');
|
||||
}
|
||||
else{
|
||||
$(this).css('margin-top',(parseInt($(this).parent().css('height'))/2-parseInt($(this).css('height'))/2-15)+'px');
|
||||
}
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$('.item > a > div').each( function() {
|
||||
if ($(this).css('float')=='left' || $(this).css('float')=='right') {
|
||||
$(this).css('margin-top',(parseInt($(this).parent().parent().css('height'))/2-parseInt($(this).css('height'))/2-5)+'px');
|
||||
$(this).css('margin-left','');
|
||||
}
|
||||
else {
|
||||
$(this).css('margin-left',(parseInt($(this).parent().parent().css('width'))/2-parseInt($(this).css('width'))/2)+'px');
|
||||
$(this).css('margin-top','');
|
||||
}
|
||||
});
|
||||
|
||||
$(".graph:not([class~='noresizevc'])").each(function(){
|
||||
height = parseInt($(this).css("height")) - 30;
|
||||
$(this).css('height', height);
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
|
||||
// Pandora FMS - http://pandorafms.com
|
||||
// ==================================================
|
||||
// Copyright (c) 2005-2019 Artica Soluciones Tecnologicas
|
||||
// Please see http://pandorafms.org for full contribution list
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation for version 2.
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
global $config;
|
||||
|
||||
// Login check.
|
||||
check_login();
|
||||
|
||||
require_once $config['homedir'].'/vendor/autoload.php';
|
||||
require_once $config['homedir'].'/include/functions_visual_map.php';
|
||||
|
||||
// Query parameters.
|
||||
$visualConsoleId = (int) get_parameter(!is_metaconsole() ? 'id' : 'id_visualmap');
|
||||
// To hide the menus.
|
||||
$pure = (bool) get_parameter('pure', $config['pure']);
|
||||
// Refresh interval in seconds.
|
||||
$refr = (int) get_parameter('refr', $config['vc_refr']);
|
||||
|
||||
// Load Visual Console.
|
||||
use Models\VisualConsole\Container as VisualConsole;
|
||||
$visualConsole = null;
|
||||
try {
|
||||
$visualConsole = VisualConsole::fromDB(['id' => $visualConsoleId]);
|
||||
} catch (Throwable $e) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without Id'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$visualConsoleData = $visualConsole->toArray();
|
||||
$groupId = $visualConsoleData['groupId'];
|
||||
$visualConsoleName = $visualConsoleData['name'];
|
||||
|
||||
// ACL.
|
||||
$aclRead = check_acl($config['id_user'], $groupId, 'VR');
|
||||
$aclWrite = check_acl($config['id_user'], $groupId, 'VW');
|
||||
$aclManage = check_acl($config['id_user'], $groupId, 'VM');
|
||||
|
||||
if (!$aclRead && !$aclWrite && !$aclManage) {
|
||||
db_pandora_audit(
|
||||
'ACL Violation',
|
||||
'Trying to access visual console without group access'
|
||||
);
|
||||
include 'general/noaccess.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Render map.
|
||||
$options = [];
|
||||
|
||||
$options['consoles_list']['text'] = '<a href="index.php?sec=network&sec2=godmode/reporting/map_builder">'.html_print_image(
|
||||
'images/visual_console.png',
|
||||
true,
|
||||
['title' => __('Visual consoles list')]
|
||||
).'</a>';
|
||||
|
||||
if ($aclWrite || $aclManage) {
|
||||
$action = get_parameterBetweenListValues(
|
||||
is_metaconsole() ? 'action2' : 'action',
|
||||
[
|
||||
'new',
|
||||
'save',
|
||||
'edit',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
'edit'
|
||||
);
|
||||
|
||||
$baseUrl = 'index.php?sec=network&sec2=godmode/reporting/visual_console_builder&action='.$action;
|
||||
|
||||
$hash = md5($config['dbpass'].$visualConsoleId.$config['id_user']);
|
||||
|
||||
$options['public_link']['text'] = '<a href="'.ui_get_full_url(
|
||||
'operation/visual_console/public_console.php?hash='.$hash.'&id_layout='.$visualConsoleId.'&id_user='.$config['id_user']
|
||||
).'" target="_blank">'.html_print_image(
|
||||
'images/camera_mc.png',
|
||||
true,
|
||||
['title' => __('Show link to public Visual Console')]
|
||||
).'</a>';
|
||||
$options['public_link']['active'] = false;
|
||||
|
||||
$options['data']['text'] = '<a href="'.$baseUrl.'&tab=data&id_visual_console='.$visualConsoleId.'">'.html_print_image(
|
||||
'images/op_reporting.png',
|
||||
true,
|
||||
['title' => __('Main data')]
|
||||
).'</a>';
|
||||
$options['list_elements']['text'] = '<a href="'.$baseUrl.'&tab=list_elements&id_visual_console='.$visualConsoleId.'">'.html_print_image(
|
||||
'images/list.png',
|
||||
true,
|
||||
['title' => __('List elements')]
|
||||
).'</a>';
|
||||
|
||||
if (enterprise_installed()) {
|
||||
$options['wizard_services']['text'] = '<a href="'.$baseUrl.'&tab=wizard_services&id_visual_console='.$visualConsoleId.'">'.html_print_image(
|
||||
'images/wand_services.png',
|
||||
true,
|
||||
['title' => __('Services wizard')]
|
||||
).'</a>';
|
||||
}
|
||||
|
||||
$options['wizard']['text'] = '<a href="'.$baseUrl.'&tab=wizard&id_visual_console='.$visualConsoleId.'">'.html_print_image(
|
||||
'images/wand.png',
|
||||
true,
|
||||
['title' => __('Wizard')]
|
||||
).'</a>';
|
||||
$options['editor']['text'] = '<a href="'.$baseUrl.'&tab=editor&id_visual_console='.$visualConsoleId.'">'.html_print_image(
|
||||
'images/builder.png',
|
||||
true,
|
||||
['title' => __('Builder')]
|
||||
).'</a>';
|
||||
}
|
||||
|
||||
$options['view']['text'] = '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$visualConsoleId.'">'.html_print_image(
|
||||
'images/operation.png',
|
||||
true,
|
||||
['title' => __('View')]
|
||||
).'</a>';
|
||||
$options['view']['active'] = true;
|
||||
|
||||
if (!is_metaconsole()) {
|
||||
if (!$config['pure']) {
|
||||
$options['pure']['text'] = '<a href="index.php?sec=network&sec2=operation/visual_console/render_view&id='.$visualConsoleId.'&pure=1">'.html_print_image(
|
||||
'images/full_screen.png',
|
||||
true,
|
||||
['title' => __('Full screen mode')]
|
||||
).'</a>';
|
||||
ui_print_page_header(
|
||||
$visualConsoleName,
|
||||
'images/visual_console.png',
|
||||
false,
|
||||
'',
|
||||
false,
|
||||
$options
|
||||
);
|
||||
}
|
||||
|
||||
// Set the hidden value for the javascript.
|
||||
html_print_input_hidden('metaconsole', 0);
|
||||
} else {
|
||||
// Set the hidden value for the javascript.
|
||||
html_print_input_hidden('metaconsole', 1);
|
||||
}
|
||||
|
||||
echo '<div id="visual-console-container"></div>';
|
||||
|
||||
if ($pure === true) {
|
||||
// Floating menu - Start.
|
||||
echo '<div id="vc-controls" style="z-index: 999">';
|
||||
|
||||
echo '<div id="menu_tab">';
|
||||
echo '<ul class="mn">';
|
||||
|
||||
// Quit fullscreen.
|
||||
echo '<li class="nomn">';
|
||||
$urlNoFull = 'index.php?sec=network&sec2=operation/visual_console/render_view&id='.$visualConsoleId;
|
||||
echo '<a class="vc-btn-no-fullscreen" href="'.$urlNoFull.'">';
|
||||
echo html_print_image('images/normal_screen.png', true, ['title' => __('Back to normal mode')]);
|
||||
echo '</a>';
|
||||
echo '</li>';
|
||||
|
||||
// Console name.
|
||||
echo '<li class="nomn">';
|
||||
echo '<div class="vc-title">'.$visualConsoleName.'</div>';
|
||||
echo '</li>';
|
||||
|
||||
echo '</ul>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
// Floating menu - End.
|
||||
?>
|
||||
<style type="text/css">
|
||||
/* Avoid the main_pure container 1000px height */
|
||||
body.pure {
|
||||
min-height: 100px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
background-color: <?php echo $visualConsoleData['backgroundColor']; ?>;
|
||||
}
|
||||
div#main_pure {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
background-color: <?php echo $visualConsoleData['backgroundColor']; ?>;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Check groups can access user.
|
||||
$aclUserGroups = [];
|
||||
if (!users_can_manage_group_all('AR')) {
|
||||
$aclUserGroups = array_keys(users_get_groups(false, 'AR'));
|
||||
}
|
||||
|
||||
ui_require_javascript_file('pandora_visual_console');
|
||||
include_javascript_d3();
|
||||
visual_map_load_client_resources();
|
||||
|
||||
// Load Visual Console Items.
|
||||
$visualConsoleItems = VisualConsole::getItemsFromDB(
|
||||
$visualConsoleId,
|
||||
$aclUserGroups
|
||||
);
|
||||
?>
|
||||
|
||||
<script type="text/javascript">
|
||||
var container = document.getElementById("visual-console-container");
|
||||
var props = <?php echo (string) $visualConsole; ?>;
|
||||
var items = <?php echo '['.implode($visualConsoleItems, ',').']'; ?>;
|
||||
var baseUrl = "<?php echo ui_get_full_url('/', false, false, false); ?>";
|
||||
var handleUpdate = function (prevProps, newProps) {
|
||||
if (!newProps) return;
|
||||
|
||||
// Change the background color when the fullscreen mode is enabled.
|
||||
if (prevProps
|
||||
&& prevProps.backgroundColor != newProps.backgroundColor
|
||||
&& <?php echo json_encode($pure); ?>
|
||||
) {
|
||||
var pureBody = document.querySelector("body.pure");
|
||||
var pureContainer = document.querySelector("div#main_pure");
|
||||
|
||||
if (pureBody !== null) {
|
||||
pureBody.style.backgroundColor = newProps.backgroundColor
|
||||
}
|
||||
if (pureContainer !== null) {
|
||||
pureContainer.style.backgroundColor = newProps.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
// Change the title.
|
||||
if (prevProps && prevProps.name != newProps.name) {
|
||||
// View title.
|
||||
var title = document.querySelector(
|
||||
"div#menu_tab_frame_view > div#menu_tab_left span"
|
||||
);
|
||||
if (title !== null) {
|
||||
title.textContent = newProps.name;
|
||||
}
|
||||
// Fullscreen view title.
|
||||
var fullscreenTitle = document.querySelector("div.vc-title");
|
||||
if (fullscreenTitle !== null) {
|
||||
fullscreenTitle.textContent = newProps.name;
|
||||
}
|
||||
// TODO: Change the metaconsole title.
|
||||
}
|
||||
|
||||
// Change the links.
|
||||
if (prevProps && prevProps.id !== newProps.id) {
|
||||
var regex = /(id=|id_visual_console=|id_layout=)\d+(&?)/gi;
|
||||
var replacement = '$1' + newProps.id + '$2';
|
||||
|
||||
// Tab links.
|
||||
var menuLinks = document.querySelectorAll("div#menu_tab a");
|
||||
if (menuLinks !== null) {
|
||||
menuLinks.forEach(function (menuLink) {
|
||||
menuLink.href = menuLink.href.replace(regex, replacement);
|
||||
});
|
||||
}
|
||||
|
||||
// Go back from fullscreen button.
|
||||
var btnNoFull = document.querySelector("a.vc-btn-no-fullscreen");
|
||||
if (btnNoFull !== null) {
|
||||
btnNoFull.href = btnNoFull.href.replace(regex, replacement);
|
||||
}
|
||||
|
||||
// Change the URL (if the browser has support).
|
||||
if ("history" in window) {
|
||||
var href = window.location.href.replace(regex, replacement);
|
||||
window.history.replaceState({}, document.title, href);
|
||||
}
|
||||
}
|
||||
}
|
||||
var visualConsole = createVisualConsole(
|
||||
container,
|
||||
props,
|
||||
items,
|
||||
baseUrl,
|
||||
<?php echo ($refr * 1000); ?>,
|
||||
handleUpdate
|
||||
);
|
||||
|
||||
$(document).ready(function () {
|
||||
var controls = document.getElementById('vc-controls');
|
||||
if (controls) autoHideElement(controls, 1000);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Container as VisualConsole;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Container.
|
||||
*/
|
||||
class ContainerTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
VisualConsole::class,
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'foo',
|
||||
'groupId' => 1,
|
||||
'backgroundURL' => 'aaa',
|
||||
'backgroundColor' => 'bbb',
|
||||
'width' => 800,
|
||||
'height' => 800,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
VisualConsole::class,
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'name' => 'New visual console',
|
||||
'groupId' => 0,
|
||||
'background' => 'globalmap.jpg',
|
||||
'background_color' => 'white',
|
||||
'is_favourite' => 1,
|
||||
'width' => 100,
|
||||
'height' => 200,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
VisualConsole::class,
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1030,
|
||||
'name' => 'console2',
|
||||
'groupId' => 12,
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidId(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid id.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 'bar',
|
||||
'name' => 'foo',
|
||||
'groupId' => 0,
|
||||
'is_favourite' => 1,
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
// Missing id.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'name' => 'foo',
|
||||
'groupId' => 0,
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidName(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Empty name.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => '',
|
||||
'groupId' => 0,
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
// Missing name.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'groupId' => 8,
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid group id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidGroupId(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid group id.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'test',
|
||||
'groupId' => 'Hi',
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
|
||||
// Missing group id.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'test',
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid width.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidWidth(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid width.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'test',
|
||||
'groupId' => 10,
|
||||
'width' => 0,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
|
||||
// Missing width.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'test',
|
||||
'groupId' => 10,
|
||||
'height' => 768,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid height.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidHeigth(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid height.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'test',
|
||||
'groupId' => 10,
|
||||
'width' => 1024,
|
||||
'height' => -1,
|
||||
]
|
||||
);
|
||||
|
||||
// Missing height.
|
||||
VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'test',
|
||||
'groupId' => 10,
|
||||
'width' => 1024,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"backgroundColor":null,"backgroundURL":null,"groupId":0,"height":768,"id":1,"isFavorite":false,"name":"foo","width":1024}',
|
||||
(string) VisualConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'foo',
|
||||
'groupId' => 0,
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the item's instance is returned properly.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testItemClassIsReturned(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
VisualConsole::getItemClass(STATIC_GRAPH),
|
||||
Models\VisualConsole\Items\StaticGraph::class
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
VisualConsole::getItemClass(COLOR_CLOUD),
|
||||
Models\VisualConsole\Items\ColorCloud::class
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
VisualConsole::getItemClass(LABEL),
|
||||
Models\VisualConsole\Items\Label::class
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Item as ItemConsole;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Item model.
|
||||
*/
|
||||
class ItemTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
ItemConsole::class,
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'type' => 5,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
ItemConsole::class,
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'type' => 5,
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidId(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 'foo',
|
||||
'type' => 5,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
// Missing id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'type' => 5,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidType(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 15,
|
||||
'type' => 'clock',
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
// Missing id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 6,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid width.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidWidth(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 15,
|
||||
'type' => 3,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => -1,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
// Missing id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 15,
|
||||
'type' => 3,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid height.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidHeight(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 15,
|
||||
'type' => 3,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => -1,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
// Missing id.
|
||||
ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 15,
|
||||
'type' => 3,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testItemIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":12,"height":600,"id":15,"isLinkEnabled":false,"isOnTop":true,"label":"test","labelPosition":"down","parentId":0,"type":3,"width":800,"x":0,"y":0}',
|
||||
(string) ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 15,
|
||||
'type' => 3,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'down',
|
||||
'isLinkEnabled' => false,
|
||||
'isOnTop' => true,
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":12,"height":600,"id":15,"isLinkEnabled":false,"isOnTop":false,"label":null,"labelPosition":"down","parentId":0,"type":3,"width":800,"x":0,"y":0}',
|
||||
(string) ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 15,
|
||||
'type' => 3,
|
||||
'label' => '',
|
||||
'labelPosition' => 'test',
|
||||
'parentId' => 0,
|
||||
'aclGroupId' => 12,
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
'x' => 0,
|
||||
'y' => 0,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":69,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","parentId":null,"type":20,"width":0,"x":-666,"y":76}',
|
||||
(string) ItemConsole::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => 20,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\BarsGraph;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Bars Graph Item model.
|
||||
*/
|
||||
class BarsGraphTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
BarsGraph::class,
|
||||
BarsGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => BARS_GRAPH,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'typeGraph' => 'horizontal',
|
||||
'backgroundColor' => 'white',
|
||||
'gridColor' => '#33CCFF',
|
||||
'encodedHtml' => '<h1>Foo</h1>',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
BarsGraph::class,
|
||||
BarsGraph::fromArray(
|
||||
[
|
||||
'id' => 23,
|
||||
'type' => BARS_GRAPH,
|
||||
'width' => '800',
|
||||
'height' => '600',
|
||||
'type_graph' => 'vertical',
|
||||
'image' => 'transparent',
|
||||
'border_color' => '#33CCFF',
|
||||
'html' => '<h1>Foo</h1>',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"backgroundColor":"transparent","encodedHtml":"PGgxPkZvbzwvaDE+","gridColor":"#33CCFF","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","moduleId":null,"moduleName":null,"parentId":null,"type":18,"typeGraph":"vertical","width":0,"x":-666,"y":76}',
|
||||
(string) BarsGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => DONUT_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'type_graph' => 'vertical',
|
||||
'image' => 'transparent',
|
||||
'border_color' => '#33CCFF',
|
||||
'html' => '<h1>Foo</h1>',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"backgroundColor":"white","encodedHtml":"PGgxPkZvbzwvaDE+","gridColor":"#33CCFF","height":300,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":"test","labelPosition":"left","moduleId":null,"moduleName":null,"parentId":null,"type":18,"typeGraph":"horizontal","width":300,"x":-666,"y":76}',
|
||||
(string) BarsGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => DONUT_GRAPH,
|
||||
'label' => 'test',
|
||||
'labelPosition' => 'left',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '300',
|
||||
'height' => '300',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'typeGraph' => 'horizontal',
|
||||
'backgroundColor' => 'white',
|
||||
'gridColor' => '#33CCFF',
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\Box;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Box Item model.
|
||||
*/
|
||||
class BoxTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Box::class,
|
||||
Box::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => 12,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
Box::class,
|
||||
Box::fromArray(
|
||||
[
|
||||
'id' => 1000,
|
||||
'type' => 8,
|
||||
'name' => 'test',
|
||||
'width' => 100,
|
||||
'height' => 900,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"borderColor":null,"borderWidth":0,"fillColor":null,"height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","parentId":null,"type":12,"width":0,"x":-666,"y":76}',
|
||||
(string) Box::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => 10,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\Clock;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Clock Item model.
|
||||
*/
|
||||
class ClockTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Clock::class,
|
||||
Clock::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => CLOCK,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'clockType' => 'digital',
|
||||
'clockFormat' => 'time',
|
||||
'clockTimezone' => 'Europe/Madrid',
|
||||
'showClockTimezone' => false,
|
||||
'color' => 'white',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
Clock::class,
|
||||
Clock::fromArray(
|
||||
[
|
||||
'id' => 1000,
|
||||
'type' => CLOCK,
|
||||
'width' => 100,
|
||||
'height' => 900,
|
||||
'clockType' => 'analogic',
|
||||
'clockFormat' => 'datetime',
|
||||
'clockTimezone' => 'Asia/Tokyo',
|
||||
'showClockTimezone' => true,
|
||||
'color' => 'red',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid clockTimezone.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidClockTimezone(): void
|
||||
{
|
||||
$this->expectException(Exception::class);
|
||||
// Invalid clockTimezone.
|
||||
Clock::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => CLOCK,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'clockType' => 'digital',
|
||||
'clockFormat' => 'time',
|
||||
'clockTimezone' => 'Europe/Tokyo',
|
||||
'showClockTimezone' => false,
|
||||
'color' => 'white',
|
||||
]
|
||||
);
|
||||
|
||||
// Invalid clockTimezone.
|
||||
Clock::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => CLOCK,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'clockType' => 'digital',
|
||||
'clockFormat' => 'time',
|
||||
'clockTimezone' => 'Europe/Tokyo',
|
||||
'showClockTimezone' => false,
|
||||
'color' => 'white',
|
||||
]
|
||||
);
|
||||
|
||||
// Missing clockTimezone.
|
||||
Clock::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => CLOCK,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'clockType' => 'digital',
|
||||
'clockFormat' => 'time',
|
||||
'showClockTimezone' => false,
|
||||
'color' => 'white',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"clockFormat":"time","clockTimezone":"Europe\/Madrid","clockTimezoneOffset":7200,"clockType":"digital","color":"white","height":0,"id":69,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","parentId":null,"showClockTimezone":false,"type":19,"width":0,"x":-666,"y":76}',
|
||||
(string) Clock::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => CLOCK,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'clockType' => 'digital',
|
||||
'clockFormat' => 'time',
|
||||
'clockTimezone' => 'Europe/Madrid',
|
||||
'showClockTimezone' => false,
|
||||
'color' => 'white',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\ColorCloud;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console color cloud item model.
|
||||
*/
|
||||
class ColorCloudTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
ColorCloud::class,
|
||||
ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 345,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => null,
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'defaultColor' => '#FFF',
|
||||
'colorRanges' => [],
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
ColorCloud::class,
|
||||
ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 1000,
|
||||
'type' => COLOR_CLOUD,
|
||||
'width' => 100,
|
||||
'height' => 900,
|
||||
'label' => 'eyJkZWZhdWx0X2NvbG9yIjoiI0ZGRiIsImNvbG9yX3JhbmdlcyI6W3siY29sb3IiOiIjMDAwIiwiZnJvbV92YWx1ZSI6MTAuMDUsInRvX3ZhbHVlIjoxMDAuMH1dfQ==',
|
||||
'colorRanges' => [
|
||||
[
|
||||
'color' => '#000',
|
||||
'fromValue' => 10.05,
|
||||
'toValue' => 100.0,
|
||||
],
|
||||
],
|
||||
'color' => '#000',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
ColorCloud::class,
|
||||
ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 1000,
|
||||
'type' => COLOR_CLOUD,
|
||||
'width' => 100,
|
||||
'height' => 900,
|
||||
'label' => 'eyJkZWZhdWx0X2NvbG9yIjoiI0ZGRiIsImNvbG9yX3JhbmdlcyI6W3siY29sb3IiOiIjMDAwIiwiZnJvbV92YWx1ZSI6MTAuMDUsInRvX3ZhbHVlIjoxMDAuMH1dfQ==',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":"#000","colorRanges":[{"color":"#000","fromValue":10.05,"toValue":100}],"defaultColor":"#FFF","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"type":20,"width":0,"x":-666,"y":76}',
|
||||
(string) ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'defaultColor' => '#FFF',
|
||||
'colorRanges' => [
|
||||
[
|
||||
'color' => '#000',
|
||||
'fromValue' => 10.05,
|
||||
'toValue' => 100.0,
|
||||
],
|
||||
],
|
||||
'color' => '#000',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":null,"colorRanges":[],"defaultColor":"#FFF","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"type":20,"width":0,"x":-666,"y":76}',
|
||||
(string) ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'defaultColor' => '#FFF',
|
||||
'colorRanges' => [],
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":"#000","colorRanges":[{"color":"#000","fromValue":10.05,"toValue":100}],"defaultColor":"#FFF","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"default","metaconsoleId":5,"moduleId":null,"moduleName":null,"parentId":null,"type":20,"width":0,"x":-666,"y":76}',
|
||||
(string) ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => 'eyJkZWZhdWx0X2NvbG9yIjoiI0ZGRiIsImNvbG9yX3JhbmdlcyI6W3siY29sb3IiOiIjMDAwIiwiZnJvbV92YWx1ZSI6MTAuMDUsInRvX3ZhbHVlIjoxMDAuMH1dfQ==',
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'color' => '#000',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":"#000","colorRanges":[{"color":"#000","fromValue":10.05,"toValue":100}],"defaultColor":"#FFF","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":1,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"type":20,"width":0,"x":-666,"y":76}',
|
||||
(string) ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => 'eyJkZWZhdWx0X2NvbG9yIjoiI0ZGRiIsImNvbG9yX3JhbmdlcyI6W3siY29sb3IiOiIjMDAwIiwiZnJvbV92YWx1ZSI6MTAuMDUsInRvX3ZhbHVlIjoxMDAuMH1dfQ==',
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'defaultColor' => '#FFF',
|
||||
'color' => '#000',
|
||||
'id_layout_linked' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":"#000","colorRanges":[{"color":"#000","fromValue":10.05,"toValue":100}],"defaultColor":"#FFF","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":2,"linkedLayoutStatusType":"service","linkedLayoutStatusTypeCriticalThreshold":80,"linkedLayoutStatusTypeWarningThreshold":50,"moduleId":null,"moduleName":null,"parentId":null,"type":20,"width":0,"x":-666,"y":76}',
|
||||
(string) ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => 'eyJkZWZhdWx0X2NvbG9yIjoiI0ZGRiIsImNvbG9yX3JhbmdlcyI6W3siY29sb3IiOiIjMDAwIiwiZnJvbV92YWx1ZSI6MTAuMDUsInRvX3ZhbHVlIjoxMDAuMH1dfQ==',
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'colorRanges' => [
|
||||
[
|
||||
'color' => '#000',
|
||||
'fromValue' => 10.05,
|
||||
'toValue' => 100.0,
|
||||
],
|
||||
],
|
||||
'color' => '#000',
|
||||
'linkedLayoutId' => 2,
|
||||
'linked_layout_status_type' => 'service',
|
||||
'linkedLayoutStatusTypeWarningThreshold' => 50,
|
||||
'linked_layout_status_as_service_critical' => 80,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid dynamic data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidDynamicData(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid dynamic data.
|
||||
ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => null,
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '330',
|
||||
'height' => '0',
|
||||
'x' => 511,
|
||||
'y' => 76,
|
||||
]
|
||||
);
|
||||
// Missing dynamic data.
|
||||
ColorCloud::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => COLOR_CLOUD,
|
||||
'label' => null,
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '330',
|
||||
'height' => '0',
|
||||
'x' => 511,
|
||||
'y' => 76,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\DonutGraph;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Donut Graph Item model.
|
||||
*/
|
||||
class DonutGraphTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
DonutGraph::class,
|
||||
DonutGraph::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => DONUT_GRAPH,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'legendBackgroundColor' => '#33CCFF',
|
||||
'html' => '<h1>Foo</h1>',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
DonutGraph::class,
|
||||
DonutGraph::fromArray(
|
||||
[
|
||||
'id' => 14,
|
||||
'type' => DONUT_GRAPH,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'border_color' => '#000000',
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"encodedHtml":"PGgxPkZvbzwvaDE+","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","legendBackgroundColor":"#33CCFF","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"type":17,"width":0,"x":-666,"y":76}',
|
||||
(string) DonutGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => DONUT_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'border_color' => '#33CCFF',
|
||||
'html' => '<h1>Foo</h1>',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"encodedHtml":"PGgxPkZvbzwvaDE+","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"left","legendBackgroundColor":"#000000","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"type":17,"width":0,"x":-666,"y":76}',
|
||||
(string) DonutGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => DONUT_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'left',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'legendBackgroundColor' => '#000000',
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\EventsHistory;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console events history Item model.
|
||||
*/
|
||||
class EventsHistoryTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
EventsHistory::class,
|
||||
EventsHistory::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => AUTO_SLA_GRAPH,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'maxTime' => null,
|
||||
'html' => '<h1>Foo</h1>',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
EventsHistory::class,
|
||||
EventsHistory::fromArray(
|
||||
[
|
||||
'id' => 14,
|
||||
'type' => AUTO_SLA_GRAPH,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'maxTime' => 12800,
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"encodedHtml":"PGgxPkZvbzwvaDE+","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","maxTime":null,"moduleId":null,"moduleName":null,"parentId":null,"type":14,"width":0,"x":-666,"y":76}',
|
||||
(string) EventsHistory::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => AUTO_SLA_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => null,
|
||||
'html' => '<h1>Foo</h1>',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"encodedHtml":"PGgxPkZvbzwvaDE+","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","maxTime":12800,"moduleId":null,"moduleName":null,"parentId":null,"type":14,"width":0,"x":-666,"y":76}',
|
||||
(string) EventsHistory::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => AUTO_SLA_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => 12800,
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"encodedHtml":"PGgxPkZvbzwvaDE+","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":1,"linkedLayoutStatusType":"default","maxTime":null,"moduleId":null,"moduleName":null,"parentId":null,"type":14,"width":0,"x":-666,"y":76}',
|
||||
(string) EventsHistory::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => AUTO_SLA_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => null,
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
'id_layout_linked' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"encodedHtml":"PGgxPkZvbzwvaDE+","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"default","maxTime":12800,"metaconsoleId":5,"moduleId":null,"moduleName":null,"parentId":null,"type":14,"width":0,"x":-666,"y":76}',
|
||||
(string) EventsHistory::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => AUTO_SLA_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => 12800,
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":21,"agentName":null,"encodedHtml":"PGgxPkZvbzwvaDE+","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":15,"linkedLayoutId":3,"linkedLayoutStatusType":"default","maxTime":12800,"metaconsoleId":2,"moduleId":385,"moduleName":"module_test","parentId":null,"type":14,"width":0,"x":-666,"y":76}',
|
||||
(string) EventsHistory::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => AUTO_SLA_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => 12800,
|
||||
'encodedHtml' => 'PGgxPkZvbzwvaDE+',
|
||||
'id_metaconsole' => 2,
|
||||
'linked_layout_node_id' => 15,
|
||||
'linkedLayoutId' => 3,
|
||||
'agentId' => 21,
|
||||
'moduleId' => 385,
|
||||
'moduleName' => 'module_test',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\Group;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Box Group Item model.
|
||||
*/
|
||||
class GroupTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Group::class,
|
||||
Group::fromArray(
|
||||
[
|
||||
'id' => 13,
|
||||
'type' => GROUP_ITEM,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'imageSrc' => 'image.jpg',
|
||||
'groupId' => 12,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
Group::class,
|
||||
Group::fromArray(
|
||||
[
|
||||
'id' => 1004,
|
||||
'type' => GROUP_ITEM,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'image' => 'test_image.png',
|
||||
'id_group' => 0,
|
||||
'statusImageSrc' => 'test_image.bad.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid image src.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidImageSrc(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid imageSrc.
|
||||
Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => '',
|
||||
'groupId' => 0,
|
||||
]
|
||||
);
|
||||
// Missing imageSrc.
|
||||
Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'id_group' => 11,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid group Id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidGroupId(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid groupId.
|
||||
Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'test.jpg',
|
||||
'groupId' => 'bar',
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
]
|
||||
);
|
||||
// Missing groupId.
|
||||
Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'test.jpg',
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"groupId":12,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","parentId":null,"statusImageSrc":"image.bad.jpg","type":11,"width":0,"x":-666,"y":76}',
|
||||
(string) Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'groupId' => 12,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
// With a linked layout.
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"groupId":12,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":1,"linkedLayoutStatusType":"default","parentId":null,"statusImageSrc":"image.bad.jpg","type":11,"width":0,"x":-666,"y":76}',
|
||||
(string) Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'groupId' => 12,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
'id_layout_linked' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"groupId":12,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"default","metaconsoleId":5,"parentId":null,"statusImageSrc":"image.bad.jpg","type":11,"width":0,"x":-666,"y":76}',
|
||||
(string) Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'groupId' => 12,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
]
|
||||
)
|
||||
);
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"groupId":12,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"weight","linkedLayoutStatusTypeWeight":80,"metaconsoleId":5,"parentId":null,"statusImageSrc":"image.bad.jpg","type":11,"width":0,"x":-666,"y":76}',
|
||||
(string) Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'groupId' => 12,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
'linkedLayoutStatusType' => 'weight',
|
||||
'linkedLayoutStatusTypeWeight' => 80,
|
||||
]
|
||||
)
|
||||
);
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"groupId":12,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":2,"linkedLayoutStatusType":"service","linkedLayoutStatusTypeCriticalThreshold":80,"linkedLayoutStatusTypeWarningThreshold":50,"parentId":null,"statusImageSrc":"image.bad.jpg","type":11,"width":0,"x":-666,"y":76}',
|
||||
(string) Group::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => GROUP_ITEM,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'groupId' => 12,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
'linkedLayoutId' => 2,
|
||||
'linked_layout_status_type' => 'service',
|
||||
'linkedLayoutStatusTypeWarningThreshold' => 50,
|
||||
'linked_layout_status_as_service_critical' => 80,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\Icon;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Box Icon Item model.
|
||||
*/
|
||||
class IconTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Icon::class,
|
||||
Icon::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => ICON,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'imageSrc' => 'image.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid image src.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidImageSrc(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid imageSrc.
|
||||
Icon::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => ICON,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => '',
|
||||
]
|
||||
);
|
||||
// Missing imageSrc.
|
||||
Icon::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => ICON,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","parentId":null,"type":5,"width":0,"x":-666,"y":76}',
|
||||
(string) Icon::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => ICON,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
// With a linked layout.
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":1,"linkedLayoutStatusType":"default","parentId":null,"type":5,"width":0,"x":-666,"y":76}',
|
||||
(string) Icon::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => ICON,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'id_layout_linked' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"default","metaconsoleId":5,"parentId":null,"type":5,"width":0,"x":-666,"y":76}',
|
||||
(string) Icon::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => ICON,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"weight","linkedLayoutStatusTypeWeight":80,"metaconsoleId":5,"parentId":null,"type":5,"width":0,"x":-666,"y":76}',
|
||||
(string) Icon::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => ICON,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
'linkedLayoutStatusType' => 'weight',
|
||||
'linkedLayoutStatusTypeWeight' => 80,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"service","linkedLayoutStatusTypeCriticalThreshold":80,"linkedLayoutStatusTypeWarningThreshold":50,"metaconsoleId":5,"parentId":null,"type":5,"width":0,"x":-666,"y":76}',
|
||||
(string) Icon::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => ICON,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
'linked_layout_status_type' => 'service',
|
||||
'linkedLayoutStatusTypeWarningThreshold' => 50,
|
||||
'linked_layout_status_as_service_critical' => 80,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\Label;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Label Item model.
|
||||
*/
|
||||
class LabelTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Label::class,
|
||||
Label::fromArray(
|
||||
[
|
||||
'id' => 69,
|
||||
'type' => LABEL,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'label' => 'Label',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid label.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidLabel(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Missing label.
|
||||
Label::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => LABEL,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
]
|
||||
);
|
||||
// Empty label.
|
||||
Label::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => LABEL,
|
||||
'label' => '',
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":"Label","labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","parentId":null,"type":4,"width":0,"x":-666,"y":76}',
|
||||
(string) Label::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => LABEL,
|
||||
'label' => 'Label',
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
// With a linked layout.
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":"Label","labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":1,"linkedLayoutStatusType":"default","parentId":null,"type":4,"width":0,"x":-666,"y":76}',
|
||||
(string) Label::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => LABEL,
|
||||
'label' => 'Label',
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'id_layout_linked' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\Line;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Box Icon Item model.
|
||||
*/
|
||||
class LineTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Line::class,
|
||||
Line::fromArray(
|
||||
[
|
||||
'id' => 10,
|
||||
'type' => LINE_ITEM,
|
||||
'startX' => 50,
|
||||
'startY' => 100,
|
||||
'endX' => 0,
|
||||
'endY' => 10,
|
||||
'isOnTop' => false,
|
||||
'borderWidth' => 0,
|
||||
'borderColor' => 'white',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
Line::class,
|
||||
Line::fromArray(
|
||||
[
|
||||
'id' => 10,
|
||||
'type' => LINE_ITEM,
|
||||
'startX' => 50,
|
||||
'endY' => 10,
|
||||
'borderColor' => 'black',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid Id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidId(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid id.
|
||||
Line::fromArray(
|
||||
[
|
||||
'id' => 'foo',
|
||||
'type' => LINE_ITEM,
|
||||
'startX' => 50,
|
||||
'startY' => 100,
|
||||
'endX' => 0,
|
||||
'endY' => 10,
|
||||
'isOnTop' => false,
|
||||
'borderWidth' => 0,
|
||||
'borderColor' => 'white',
|
||||
]
|
||||
);
|
||||
// Missing id.
|
||||
Line::fromArray(
|
||||
[
|
||||
'type' => LINE_ITEM,
|
||||
'startX' => 50,
|
||||
'startY' => 100,
|
||||
'endX' => 0,
|
||||
'endY' => 10,
|
||||
'isOnTop' => false,
|
||||
'borderWidth' => 0,
|
||||
'borderColor' => 'white',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidtype(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid type.
|
||||
Line::fromArray(
|
||||
[
|
||||
'id' => 13,
|
||||
'type' => 'test',
|
||||
'startX' => 50,
|
||||
'startY' => 100,
|
||||
'endX' => 0,
|
||||
'endY' => 10,
|
||||
'isOnTop' => false,
|
||||
'borderWidth' => 0,
|
||||
'borderColor' => 'white',
|
||||
]
|
||||
);
|
||||
// Missing type.
|
||||
Line::fromArray(
|
||||
[
|
||||
'id' => 13,
|
||||
'startX' => 50,
|
||||
'startY' => 100,
|
||||
'endX' => 0,
|
||||
'endY' => 10,
|
||||
'isOnTop' => true,
|
||||
'borderWidth' => 0,
|
||||
'borderColor' => 'white',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"borderColor":"white","borderWidth":0,"endX":0,"endY":10,"id":1,"isOnTop":false,"startX":50,"startY":100,"type":13}',
|
||||
(string) Line::fromArray(
|
||||
[
|
||||
'id' => 1,
|
||||
'type' => LINE_ITEM,
|
||||
'startX' => 50,
|
||||
'startY' => 100,
|
||||
'endX' => 0,
|
||||
'endY' => 10,
|
||||
'isOnTop' => false,
|
||||
'borderWidth' => 0,
|
||||
'borderColor' => 'white',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\Percentile;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Percentile Item model.
|
||||
*/
|
||||
class PercentileTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
Percentile::class,
|
||||
Percentile::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => PERCENTILE_BAR,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'maxTime' => null,
|
||||
'valueType' => 'value',
|
||||
'value' => '123ms',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
Percentile::class,
|
||||
Percentile::fromArray(
|
||||
[
|
||||
'id' => 14,
|
||||
'type' => PERCENTILE_BUBBLE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'maxTime' => 12800,
|
||||
'valueType' => 'image',
|
||||
'value' => 'data:image;asdasoih==',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":null,"height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelColor":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","maxValue":0,"minValue":null,"moduleId":null,"moduleName":null,"parentId":null,"percentileType":"progress-bar","type":3,"unit":null,"value":null,"valueType":"percent","width":0,"x":-666,"y":76}',
|
||||
(string) Percentile::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => PERCENTILE_BAR,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => null,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":null,"height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelColor":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","maxValue":0,"minValue":null,"moduleId":null,"moduleName":null,"parentId":null,"percentileType":"bubble","type":3,"unit":null,"value":null,"valueType":"percent","width":0,"x":-666,"y":76}',
|
||||
(string) Percentile::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => PERCENTILE_BUBBLE,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => 12800,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":null,"height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelColor":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","maxValue":0,"minValue":null,"moduleId":null,"moduleName":null,"parentId":null,"percentileType":"circular-progress-bar","type":3,"unit":null,"value":1,"valueType":"value","width":0,"x":-666,"y":76}',
|
||||
(string) Percentile::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => CIRCULAR_PROGRESS_BAR,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => 12800,
|
||||
'valueType' => 'value',
|
||||
'value' => '1',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"color":"#FFF","height":0,"id":7,"isLinkEnabled":true,"isOnTop":false,"label":null,"labelColor":"#000","labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","maxValue":0,"minValue":null,"moduleId":null,"moduleName":null,"parentId":null,"percentileType":"circular-progress-bar","type":3,"unit":null,"value":80,"valueType":"percent","width":0,"x":-666,"y":76}',
|
||||
(string) Percentile::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => CIRCULAR_PROGRESS_BAR,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'maxTime' => 12800,
|
||||
'valueType' => 'percent',
|
||||
'value' => '80',
|
||||
'color' => '#FFF',
|
||||
'labelColor' => '#000',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\SimpleValue;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console Simple Value Item model.
|
||||
*/
|
||||
class SimpleValueTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
SimpleValue::class,
|
||||
SimpleValue::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => SIMPLE_VALUE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'valueType' => 'string',
|
||||
'value' => 57,
|
||||
'processValue' => 'avg',
|
||||
'period' => 12800,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
SimpleValue::class,
|
||||
SimpleValue::fromArray(
|
||||
[
|
||||
'id' => 14,
|
||||
'type' => SIMPLE_VALUE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'valueType' => 'image',
|
||||
'value' => 3598,
|
||||
'processValue' => 'max',
|
||||
'period' => 9000,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":500,"id":3,"isLinkEnabled":false,"isOnTop":false,"label":null,"labelPosition":"down","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"period":12800,"processValue":"avg","type":2,"value":57,"valueType":"string","width":600,"x":0,"y":0}',
|
||||
(string) SimpleValue::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => SIMPLE_VALUE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'valueType' => 'string',
|
||||
'value' => 57,
|
||||
'processValue' => 'avg',
|
||||
'period' => 12800,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":500,"id":3,"isLinkEnabled":false,"isOnTop":false,"label":null,"labelPosition":"down","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"processValue":"none","type":2,"value":57,"valueType":"string","width":600,"x":0,"y":0}',
|
||||
(string) SimpleValue::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => SIMPLE_VALUE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'valueType' => 'string',
|
||||
'value' => 57,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":500,"id":3,"isLinkEnabled":false,"isOnTop":false,"label":null,"labelPosition":"down","linkedLayoutAgentId":null,"linkedLayoutId":1,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"processValue":"none","type":2,"value":57,"valueType":"string","width":600,"x":0,"y":0}',
|
||||
(string) SimpleValue::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => SIMPLE_VALUE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'valueType' => 'string',
|
||||
'value' => 57,
|
||||
'id_layout_linked' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":500,"id":3,"isLinkEnabled":false,"isOnTop":false,"label":null,"labelPosition":"down","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"default","metaconsoleId":5,"moduleId":null,"moduleName":null,"parentId":null,"processValue":"none","type":2,"value":57,"valueType":"string","width":600,"x":0,"y":0}',
|
||||
(string) SimpleValue::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => SIMPLE_VALUE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'valueType' => 'string',
|
||||
'value' => 57,
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":21,"agentName":null,"height":500,"id":3,"isLinkEnabled":false,"isOnTop":false,"label":null,"labelPosition":"down","linkedLayoutAgentId":15,"linkedLayoutId":3,"linkedLayoutStatusType":"default","metaconsoleId":2,"moduleId":385,"moduleName":"module_test","parentId":null,"processValue":"none","type":2,"value":57,"valueType":"string","width":600,"x":0,"y":0}',
|
||||
(string) SimpleValue::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => SIMPLE_VALUE,
|
||||
'width' => '600',
|
||||
'height' => '500',
|
||||
'valueType' => 'string',
|
||||
'value' => 57,
|
||||
'id_metaconsole' => 2,
|
||||
'linked_layout_node_id' => 15,
|
||||
'linkedLayoutId' => 3,
|
||||
'agentId' => 21,
|
||||
'moduleId' => 385,
|
||||
'moduleName' => 'module_test',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Models\VisualConsole\Items\StaticGraph;
|
||||
|
||||
/**
|
||||
* Test for the Visual Console static graph Item model.
|
||||
*/
|
||||
class StaticGraphTest extends TestCase
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is created using a valid data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCanBeCreatedFromValidUserStructure(): void
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
StaticGraph::class,
|
||||
StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 345,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'aaaaa',
|
||||
'showLastValueTooltip' => 'enabled',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
StaticGraph::class,
|
||||
StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 1000,
|
||||
'type' => STATIC_GRAPH,
|
||||
'width' => 100,
|
||||
'height' => 900,
|
||||
'image' => 'test.jpg',
|
||||
'show_last_value' => 2,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the model has a valid JSON representation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testContainerIsRepresentedAsJson(): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"showLastValueTooltip":"default","statusImageSrc":null,"type":0,"width":0,"x":-666,"y":76}',
|
||||
(string) StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":null,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"showLastValueTooltip":"disabled","statusImageSrc":null,"type":0,"width":0,"x":-666,"y":76}',
|
||||
(string) StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'image' => 'image.jpg',
|
||||
'showLastValueTooltip' => 'disabled',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":3,"linkedLayoutId":2,"linkedLayoutStatusType":"default","metaconsoleId":5,"moduleId":null,"moduleName":null,"parentId":null,"showLastValueTooltip":"default","statusImageSrc":"image.bad.jpg","type":0,"width":0,"x":-666,"y":76}',
|
||||
(string) StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'imageSrc' => 'image.jpg',
|
||||
'id_metaconsole' => 5,
|
||||
'linked_layout_node_id' => 3,
|
||||
'linkedLayoutId' => 2,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":1,"linkedLayoutStatusType":"default","moduleId":null,"moduleName":null,"parentId":null,"showLastValueTooltip":"default","statusImageSrc":null,"type":0,"width":0,"x":-666,"y":76}',
|
||||
(string) StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'image' => 'image.jpg',
|
||||
'id_layout_linked' => 1,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"aclGroupId":null,"agentId":null,"agentName":null,"height":0,"id":7,"imageSrc":"image.jpg","isLinkEnabled":true,"isOnTop":false,"label":null,"labelPosition":"up","linkedLayoutAgentId":null,"linkedLayoutId":2,"linkedLayoutStatusType":"service","linkedLayoutStatusTypeCriticalThreshold":80,"linkedLayoutStatusTypeWarningThreshold":50,"moduleId":null,"moduleName":null,"parentId":null,"showLastValueTooltip":"default","statusImageSrc":"image.bad.jpg","type":0,"width":0,"x":-666,"y":76}',
|
||||
(string) StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 7,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'labelPosition' => 'up',
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '0',
|
||||
'height' => '0',
|
||||
'x' => -666,
|
||||
'y' => 76,
|
||||
'image' => 'image.jpg',
|
||||
'linkedLayoutId' => 2,
|
||||
'linked_layout_status_type' => 'service',
|
||||
'linkedLayoutStatusTypeWarningThreshold' => 50,
|
||||
'linked_layout_status_as_service_critical' => 80,
|
||||
'statusImageSrc' => 'image.bad.jpg',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if the instance is not created when using a invalid image src.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testCannotBeCreatedWithInvalidImageSrc(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
// Invalid imageSrc.
|
||||
StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '330',
|
||||
'height' => '0',
|
||||
'x' => 511,
|
||||
'y' => 76,
|
||||
'imageSrc' => 45,
|
||||
'showLastValueTooltip' => 'disabled',
|
||||
]
|
||||
);
|
||||
|
||||
// Missing imageSrc.
|
||||
StaticGraph::fromArray(
|
||||
[
|
||||
'id' => 3,
|
||||
'type' => STATIC_GRAPH,
|
||||
'label' => null,
|
||||
'isLinkEnabled' => true,
|
||||
'isOnTop' => false,
|
||||
'parentId' => null,
|
||||
'width' => '330',
|
||||
'height' => '0',
|
||||
'x' => 511,
|
||||
'y' => 76,
|
||||
'showLastValueTooltip' => 'enabled',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
require_once __DIR__.'/../include/constants.php';
|
|
@ -279,7 +279,7 @@ class ClassLoader
|
|||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,8 +6,11 @@ $vendorDir = dirname(dirname(__FILE__));
|
|||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Tests\\' => array($baseDir . '/tests'),
|
||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
|
||||
'Mpdf\\' => array($vendorDir . '/mpdf/mpdf/src'),
|
||||
'Models\\' => array($baseDir . '/include/rest-api/models'),
|
||||
'Enterprise\\Models\\' => array($baseDir . '/enterprise/include/rest-api/models'),
|
||||
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/EmailValidator'),
|
||||
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
|
||||
);
|
||||
|
|
|
@ -12,6 +12,10 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
|
|||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'T' =>
|
||||
array (
|
||||
'Tests\\' => 6,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\Log\\' => 8,
|
||||
|
@ -19,9 +23,11 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
|
|||
'M' =>
|
||||
array (
|
||||
'Mpdf\\' => 5,
|
||||
'Models\\' => 7,
|
||||
),
|
||||
'E' =>
|
||||
array (
|
||||
'Enterprise\\Models\\' => 18,
|
||||
'Egulias\\EmailValidator\\' => 23,
|
||||
),
|
||||
'D' =>
|
||||
|
@ -31,6 +37,10 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
|
|||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Tests\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/tests',
|
||||
),
|
||||
'Psr\\Log\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
|
||||
|
@ -39,6 +49,14 @@ class ComposerStaticInitfdecadadce22e6dde51e9535fe4ad7aa
|
|||
array (
|
||||
0 => __DIR__ . '/..' . '/mpdf/mpdf/src',
|
||||
),
|
||||
'Models\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/include/rest-api/models',
|
||||
),
|
||||
'Enterprise\\Models\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/enterprise/include/rest-api/models',
|
||||
),
|
||||
'Egulias\\EmailValidator\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/egulias/email-validator/EmailValidator',
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
|
||||
# editor
|
||||
# .vscode
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// Use IntelliSense para saber los atributos posibles.
|
||||
// Mantenga el puntero para ver las descripciones de los existentes atributos
|
||||
// Para más información, visite: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# Visual Console Client
|
|
@ -0,0 +1 @@
|
|||
module.exports = "test-file-stub";
|
|
@ -0,0 +1 @@
|
|||
module.exports = {};
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = {
|
||||
roots: ["<rootDir>/src"],
|
||||
transform: {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
// This configuration is used to mock the css and file imports used by Webpack.
|
||||
// https://jestjs.io/docs/en/webpack.html
|
||||
moduleNameMapper: {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||
"<rootDir>/__mocks__/fileMock.js",
|
||||
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "pandora-fms-visual-console",
|
||||
"version": "1.0.0",
|
||||
"description": "Visual Console",
|
||||
"scripts": {
|
||||
"build": "BUILD_PATH=\"pandora_console/include/visual-console-client\" NODE_ENV=production webpack",
|
||||
"build:dev": "BUILD_PATH=\"pandora_console/include/visual-console-client\" NODE_ENV=development webpack",
|
||||
"build:watch": "npm run build -- --watch",
|
||||
"format": "prettier",
|
||||
"lint": "eslint \"src/**/*.ts\"",
|
||||
"start": "webpack-dev-server --color --mode=development",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pandorafms/pandorafms.git"
|
||||
},
|
||||
"author": "Alejandro Gallardo Escobar <alejandro.gallardo@artica.es>",
|
||||
"license": "GPL2",
|
||||
"private": true,
|
||||
"bugs": {
|
||||
"url": "https://github.com/pandorafms/pandorafms/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pandorafms/pandorafms#readme",
|
||||
"dependencies": {
|
||||
"@types/d3-shape": "^1.3.1",
|
||||
"@types/jest": "^24.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^1.6.0",
|
||||
"@typescript-eslint/parser": "^1.6.0",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"clean-webpack-plugin": "^2.0.1",
|
||||
"css-loader": "^2.1.1",
|
||||
"d3-shape": "^1.3.5",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-prettier": "^4.1.0",
|
||||
"eslint-plugin-prettier": "^3.0.1",
|
||||
"file-loader": "^3.0.1",
|
||||
"jest": "^24.7.1",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"prettier": "^1.16.1",
|
||||
"ts-jest": "^24.0.2",
|
||||
"typescript": "^3.4.3",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.29.6",
|
||||
"webpack-cli": "^3.3.0",
|
||||
"webpack-dev-server": "^3.3.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Visual Console Sandbox</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="vc.main.css" />
|
||||
</head>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="visual-console-container"></div>
|
||||
</body>
|
||||
<script src="vc.main.min.js"></script>
|
||||
<script>
|
||||
var container = document.getElementById("visual-console-container");
|
||||
|
||||
if (container != null) {
|
||||
var props = {
|
||||
id: 1,
|
||||
groupId: 0,
|
||||
name: "Test Visual Console",
|
||||
width: 1000,
|
||||
height: 400,
|
||||
backgroundURL: null,
|
||||
backgroundColor: "rgb(86, 86, 86)",
|
||||
isFavorite: false
|
||||
};
|
||||
|
||||
var staticGraphRawProps = {
|
||||
// Generic props.
|
||||
id: 1,
|
||||
type: 0, // Static graph = 0
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 100,
|
||||
y: 50,
|
||||
// Size props.
|
||||
width: 70,
|
||||
height: 70,
|
||||
// Agent props.
|
||||
agentId: null,
|
||||
agentName: null,
|
||||
// Module props.
|
||||
moduleId: null,
|
||||
moduleName: null,
|
||||
// Custom props.
|
||||
imageSrc:
|
||||
"http://localhost/pandora_console/images/console/icons/bridge_ok.png",
|
||||
showLastValueTooltip: "default"
|
||||
};
|
||||
|
||||
var colorCloudRawProps = {
|
||||
// Generic props.
|
||||
id: 2,
|
||||
type: 20, // Color cloud = 20
|
||||
label: null,
|
||||
labelText: "CLOUD",
|
||||
isLinkEnabled: true,
|
||||
isOnTop: false,
|
||||
parentId: 10,
|
||||
aclGroupId: null,
|
||||
link: "https://google.es",
|
||||
// Position props.
|
||||
x: 300,
|
||||
y: 50,
|
||||
// Size props.
|
||||
width: 150,
|
||||
height: 150,
|
||||
// Agent props.
|
||||
agentId: null,
|
||||
agentName: null,
|
||||
// Module props.
|
||||
moduleId: null,
|
||||
moduleName: null,
|
||||
// Custom props.
|
||||
color: "rgb(100, 50, 245)"
|
||||
};
|
||||
|
||||
var digitalClockRawProps = {
|
||||
// Generic props.
|
||||
id: 3,
|
||||
type: 19, // Clock = 19
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 60,
|
||||
y: 150,
|
||||
// Size props.
|
||||
width: 300,
|
||||
height: 150,
|
||||
// Custom props.
|
||||
clockType: "digital",
|
||||
clockFormat: "datetime",
|
||||
clockTimezone: "Madrid",
|
||||
clockTimezoneOffset: 60,
|
||||
showClockTimezone: true,
|
||||
color: "white"
|
||||
};
|
||||
|
||||
var digitalClockRawProps2 = {
|
||||
// Generic props.
|
||||
id: 4,
|
||||
type: 19, // Clock = 19
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 10,
|
||||
y: 250,
|
||||
// Size props.
|
||||
width: 100,
|
||||
height: 50,
|
||||
// Custom props.
|
||||
clockType: "digital",
|
||||
clockFormat: "datetime",
|
||||
clockTimezone: "Madrid",
|
||||
clockTimezoneOffset: 60,
|
||||
showClockTimezone: true,
|
||||
color: "#82B92E"
|
||||
};
|
||||
|
||||
var analogicClockRawProps = {
|
||||
// Generic props.
|
||||
id: 5,
|
||||
type: 19, // Clock = 19
|
||||
label: "<h1>Analogic clock</h1>",
|
||||
labelPosition: "up",
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 500,
|
||||
y: 50,
|
||||
// Size props.
|
||||
width: 200,
|
||||
height: 200,
|
||||
// Custom props.
|
||||
clockType: "analogic",
|
||||
clockFormat: "datetime",
|
||||
clockTimezone: "Copenhagen",
|
||||
clockTimezoneOffset: 60,
|
||||
showClockTimezone: true
|
||||
};
|
||||
|
||||
var boxRawProps = {
|
||||
// Generic props.
|
||||
id: 6,
|
||||
type: 12, // Box = 12
|
||||
// Position props.
|
||||
x: 720,
|
||||
y: 20,
|
||||
// Size props.
|
||||
width: 50,
|
||||
height: 50,
|
||||
// Custom props.
|
||||
borderWidth: 10,
|
||||
borderColor: "white",
|
||||
fillColor: "black"
|
||||
};
|
||||
|
||||
var lineRawProps = {
|
||||
// Generic props.
|
||||
id: 7,
|
||||
type: 13, // Line = 13
|
||||
// Position props.
|
||||
x: 720,
|
||||
y: 20,
|
||||
// Size props.
|
||||
width: 50,
|
||||
height: 50,
|
||||
// Custom props.
|
||||
startX: 200,
|
||||
startY: 100,
|
||||
endX: 350,
|
||||
endY: 30,
|
||||
lineWidth: 2,
|
||||
color: "white"
|
||||
};
|
||||
|
||||
var labelRawProps = {
|
||||
// Generic props.
|
||||
id: 8,
|
||||
type: 4, // Label = 4
|
||||
label: '<h1 style="color: #FDFD96;">I\'m a label</h1>',
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 410,
|
||||
y: 0,
|
||||
// Size props.
|
||||
width: 200,
|
||||
height: 200
|
||||
};
|
||||
|
||||
var simpleValueRawProps = {
|
||||
// Generic props.
|
||||
id: 9,
|
||||
type: 2, // Simple value = 2
|
||||
label: '<h3 style="color: #FDFD96;">Simple Value: (_VALUE_)</h3>',
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 10,
|
||||
y: 10,
|
||||
// Size props.
|
||||
width: 50,
|
||||
height: 50,
|
||||
// Custom props.
|
||||
valueType: "string",
|
||||
value: "10",
|
||||
processValue: "none"
|
||||
};
|
||||
|
||||
var percentileRawProps = {
|
||||
// Generic props.
|
||||
id: 10,
|
||||
type: 3, // Percentile = 3
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 790,
|
||||
y: 10,
|
||||
// Size props.
|
||||
width: 200,
|
||||
height: 40,
|
||||
// Custom props.
|
||||
percentileType: "progress-bar",
|
||||
valueType: "percent",
|
||||
minValue: 100,
|
||||
maxValue: 500,
|
||||
color: null,
|
||||
labelColor: "#82B92E",
|
||||
value: 245,
|
||||
unit: "seconds"
|
||||
};
|
||||
|
||||
var percentileBubbleRawProps = {
|
||||
// Generic props.
|
||||
id: 11,
|
||||
type: 3, // Percentile = 3
|
||||
label: null,
|
||||
isLinkEnabled: true,
|
||||
isOnTop: false,
|
||||
parentId: 1,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 830,
|
||||
y: 60,
|
||||
// Size props.
|
||||
width: 100,
|
||||
height: 100,
|
||||
// Custom props.
|
||||
percentileType: "bubble",
|
||||
valueType: "value",
|
||||
minValue: 100,
|
||||
maxValue: 500,
|
||||
color: null,
|
||||
labelColor: "#82B92E",
|
||||
value: 245,
|
||||
unit: "seconds"
|
||||
};
|
||||
|
||||
var percentileDonutRawProps = {
|
||||
// Generic props.
|
||||
id: 12,
|
||||
type: 3, // Percentile = 3
|
||||
label: null,
|
||||
isLinkEnabled: true,
|
||||
isOnTop: false,
|
||||
parentId: 1,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 830,
|
||||
y: 170,
|
||||
// Size props.
|
||||
width: 100,
|
||||
height: 100,
|
||||
// Custom props.
|
||||
percentileType: "circular-progress-bar",
|
||||
valueType: "value",
|
||||
minValue: 100,
|
||||
maxValue: 500,
|
||||
color: null,
|
||||
labelColor: "#82B92E",
|
||||
value: 245,
|
||||
unit: "seconds"
|
||||
};
|
||||
|
||||
var percentileDonutAltRawProps = {
|
||||
// Generic props.
|
||||
id: 12,
|
||||
type: 3, // Percentile = 3
|
||||
label: null,
|
||||
isLinkEnabled: true,
|
||||
isOnTop: false,
|
||||
parentId: 1,
|
||||
aclGroupId: null,
|
||||
// Position props.
|
||||
x: 830,
|
||||
y: 280,
|
||||
// Size props.
|
||||
width: 100,
|
||||
height: 100,
|
||||
// Custom props.
|
||||
percentileType: "circular-progress-bar-alt",
|
||||
valueType: "value",
|
||||
minValue: 100,
|
||||
maxValue: 500,
|
||||
color: null,
|
||||
labelColor: "#82B92E",
|
||||
value: 245,
|
||||
unit: "seconds"
|
||||
};
|
||||
|
||||
var items = [
|
||||
staticGraphRawProps,
|
||||
colorCloudRawProps,
|
||||
digitalClockRawProps,
|
||||
digitalClockRawProps2,
|
||||
analogicClockRawProps,
|
||||
boxRawProps,
|
||||
lineRawProps,
|
||||
labelRawProps,
|
||||
simpleValueRawProps,
|
||||
percentileRawProps,
|
||||
percentileBubbleRawProps,
|
||||
percentileDonutRawProps,
|
||||
percentileDonutAltRawProps
|
||||
];
|
||||
|
||||
try {
|
||||
var visualConsole = new VisualConsole(container, props, items);
|
||||
console.log(visualConsole);
|
||||
|
||||
visualConsole.onClick(function(e) {
|
||||
console.log("[CLICK]", e);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("ERROR", error.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</html>
|
|
@ -0,0 +1,545 @@
|
|||
import { Position, Size, UnknownObject, WithModuleProps } from "./types";
|
||||
import {
|
||||
sizePropsDecoder,
|
||||
positionPropsDecoder,
|
||||
parseIntOr,
|
||||
parseBoolean,
|
||||
notEmptyStringOr,
|
||||
replaceMacros,
|
||||
humanDate,
|
||||
humanTime
|
||||
} from "./lib";
|
||||
import TypedEvent, { Listener, Disposable } from "./TypedEvent";
|
||||
|
||||
// Enum: https://www.typescriptlang.org/docs/handbook/enums.html.
|
||||
export const enum ItemType {
|
||||
STATIC_GRAPH = 0,
|
||||
MODULE_GRAPH = 1,
|
||||
SIMPLE_VALUE = 2,
|
||||
PERCENTILE_BAR = 3,
|
||||
LABEL = 4,
|
||||
ICON = 5,
|
||||
SIMPLE_VALUE_MAX = 6,
|
||||
SIMPLE_VALUE_MIN = 7,
|
||||
SIMPLE_VALUE_AVG = 8,
|
||||
PERCENTILE_BUBBLE = 9,
|
||||
SERVICE = 10,
|
||||
GROUP_ITEM = 11,
|
||||
BOX_ITEM = 12,
|
||||
LINE_ITEM = 13,
|
||||
AUTO_SLA_GRAPH = 14,
|
||||
CIRCULAR_PROGRESS_BAR = 15,
|
||||
CIRCULAR_INTERIOR_PROGRESS_BAR = 16,
|
||||
DONUT_GRAPH = 17,
|
||||
BARS_GRAPH = 18,
|
||||
CLOCK = 19,
|
||||
COLOR_CLOUD = 20
|
||||
}
|
||||
|
||||
// Base item properties. This interface should be extended by the item implementations.
|
||||
export interface ItemProps extends Position, Size {
|
||||
readonly id: number;
|
||||
readonly type: ItemType;
|
||||
label: string | null;
|
||||
labelPosition: "up" | "right" | "down" | "left";
|
||||
isLinkEnabled: boolean;
|
||||
link: string | null;
|
||||
isOnTop: boolean;
|
||||
parentId: number | null;
|
||||
aclGroupId: number | null;
|
||||
}
|
||||
|
||||
// FIXME: Fix type compatibility.
|
||||
export interface ItemClickEvent<Props extends ItemProps> {
|
||||
// data: Props;
|
||||
data: UnknownObject;
|
||||
nativeEvent: Event;
|
||||
}
|
||||
|
||||
// FIXME: Fix type compatibility.
|
||||
export interface ItemRemoveEvent<Props extends ItemProps> {
|
||||
// data: Props;
|
||||
data: UnknownObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw label positi9on value.
|
||||
* @param labelPosition Raw value.
|
||||
*/
|
||||
const parseLabelPosition = (
|
||||
labelPosition: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): ItemProps["labelPosition"] => {
|
||||
switch (labelPosition) {
|
||||
case "up":
|
||||
case "right":
|
||||
case "down":
|
||||
case "left":
|
||||
return labelPosition;
|
||||
default:
|
||||
return "down";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the item props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function itemBasePropsDecoder(data: UnknownObject): ItemProps | never {
|
||||
if (data.id == null || isNaN(parseInt(data.id))) {
|
||||
throw new TypeError("invalid id.");
|
||||
}
|
||||
if (data.type == null || isNaN(parseInt(data.type))) {
|
||||
throw new TypeError("invalid type.");
|
||||
}
|
||||
|
||||
return {
|
||||
id: parseInt(data.id),
|
||||
type: parseInt(data.type),
|
||||
label: notEmptyStringOr(data.label, null),
|
||||
labelPosition: parseLabelPosition(data.labelPosition),
|
||||
isLinkEnabled: parseBoolean(data.isLinkEnabled),
|
||||
link: notEmptyStringOr(data.link, null),
|
||||
isOnTop: parseBoolean(data.isOnTop),
|
||||
parentId: parseIntOr(data.parentId, null),
|
||||
aclGroupId: parseIntOr(data.aclGroupId, null),
|
||||
...sizePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
...positionPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class of the visual console items. Should be extended to use its capabilities.
|
||||
*/
|
||||
abstract class VisualConsoleItem<Props extends ItemProps> {
|
||||
// Properties of the item.
|
||||
private itemProps: Props;
|
||||
// Reference to the DOM element which will contain the item.
|
||||
public elementRef: HTMLElement;
|
||||
public readonly labelElementRef: HTMLElement;
|
||||
// Reference to the DOM element which will contain the view of the item which extends this class.
|
||||
protected readonly childElementRef: HTMLElement;
|
||||
// Event manager for click events.
|
||||
private readonly clickEventManager = new TypedEvent<ItemClickEvent<Props>>();
|
||||
// Event manager for remove events.
|
||||
private readonly removeEventManager = new TypedEvent<
|
||||
ItemRemoveEvent<Props>
|
||||
>();
|
||||
// List of references to clean the event listeners.
|
||||
private readonly disposables: Disposable[] = [];
|
||||
|
||||
/**
|
||||
* To create a new element which will be inside the item box.
|
||||
* @return Item.
|
||||
*/
|
||||
protected abstract createDomElement(): HTMLElement;
|
||||
|
||||
public constructor(props: Props) {
|
||||
this.itemProps = props;
|
||||
|
||||
/*
|
||||
* Get a HTMLElement which represents the container box
|
||||
* of the Visual Console item. This element will manage
|
||||
* all the common things like click events, show a border
|
||||
* when hovered, etc.
|
||||
*/
|
||||
this.elementRef = this.createContainerDomElement();
|
||||
this.labelElementRef = this.createLabelDomElement();
|
||||
|
||||
/*
|
||||
* Get a HTMLElement which represents the custom view
|
||||
* of the Visual Console item. This element will be
|
||||
* different depending on the item implementation.
|
||||
*/
|
||||
this.childElementRef = this.createDomElement();
|
||||
|
||||
// Insert the elements into the container.
|
||||
this.elementRef.append(this.childElementRef, this.labelElementRef);
|
||||
|
||||
// Resize element.
|
||||
this.resizeElement(props.width, props.height);
|
||||
// Set label position.
|
||||
this.changeLabelPosition(props.labelPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* To create a new box for the visual console item.
|
||||
* @return Item box.
|
||||
*/
|
||||
private createContainerDomElement(): HTMLElement {
|
||||
let box;
|
||||
if (this.props.isLinkEnabled) {
|
||||
box = document.createElement("a");
|
||||
box as HTMLAnchorElement;
|
||||
if (this.props.link) box.href = this.props.link;
|
||||
} else {
|
||||
box = document.createElement("div");
|
||||
box as HTMLDivElement;
|
||||
}
|
||||
|
||||
box.className = "visual-console-item";
|
||||
box.style.zIndex = this.props.isOnTop ? "2" : "1";
|
||||
box.style.left = `${this.props.x}px`;
|
||||
box.style.top = `${this.props.y}px`;
|
||||
box.onclick = e =>
|
||||
this.clickEventManager.emit({ data: this.props, nativeEvent: e });
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
/**
|
||||
* To create a new label for the visual console item.
|
||||
* @return Item label.
|
||||
*/
|
||||
protected createLabelDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "visual-console-item-label";
|
||||
// Add the label if it exists.
|
||||
const label = this.getLabelWithMacrosReplaced();
|
||||
if (label.length > 0) {
|
||||
// Ugly table we need to use to replicate the legacy style.
|
||||
const table = document.createElement("table");
|
||||
const row = document.createElement("tr");
|
||||
const emptyRow1 = document.createElement("tr");
|
||||
const emptyRow2 = document.createElement("tr");
|
||||
const cell = document.createElement("td");
|
||||
|
||||
cell.innerHTML = label;
|
||||
row.append(cell);
|
||||
table.append(emptyRow1, row, emptyRow2);
|
||||
table.style.textAlign = "center";
|
||||
|
||||
// Change the table size depending on its position.
|
||||
switch (this.props.labelPosition) {
|
||||
case "up":
|
||||
case "down":
|
||||
if (this.props.width > 0) {
|
||||
table.style.width = `${this.props.width}px`;
|
||||
table.style.height = null;
|
||||
}
|
||||
break;
|
||||
case "left":
|
||||
case "right":
|
||||
if (this.props.height > 0) {
|
||||
table.style.width = null;
|
||||
table.style.height = `${this.props.height}px`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// element.innerHTML = this.props.label;
|
||||
element.append(table);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the label stored into the props with some macros replaced.
|
||||
*/
|
||||
protected getLabelWithMacrosReplaced(): string {
|
||||
// We assert that the props may have some needed properties.
|
||||
const props = this.props as Partial<WithModuleProps>;
|
||||
|
||||
return replaceMacros(
|
||||
[
|
||||
{
|
||||
macro: "_date_",
|
||||
value: humanDate(new Date())
|
||||
},
|
||||
{
|
||||
macro: "_time_",
|
||||
value: humanTime(new Date())
|
||||
},
|
||||
{
|
||||
macro: "_agent_",
|
||||
value: props.agentAlias != null ? props.agentAlias : ""
|
||||
},
|
||||
{
|
||||
macro: "_agentdescription_",
|
||||
value: props.agentDescription != null ? props.agentDescription : ""
|
||||
},
|
||||
{
|
||||
macro: "_address_",
|
||||
value: props.agentAddress != null ? props.agentAddress : ""
|
||||
},
|
||||
{
|
||||
macro: "_module_",
|
||||
value: props.moduleName != null ? props.moduleName : ""
|
||||
},
|
||||
{
|
||||
macro: "_moduledescription_",
|
||||
value: props.moduleDescription != null ? props.moduleDescription : ""
|
||||
}
|
||||
],
|
||||
this.props.label || ""
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* To update the content element.
|
||||
* @return Item.
|
||||
*/
|
||||
protected updateDomElement(element: HTMLElement): void {
|
||||
element.innerHTML = this.createDomElement().innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public accessor of the `props` property.
|
||||
* @return Properties.
|
||||
*/
|
||||
public get props(): Props {
|
||||
return { ...this.itemProps }; // Return a copy.
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter of the `props` property.
|
||||
* If the new props are different enough than the
|
||||
* stored props, a render would be fired.
|
||||
* @param newProps
|
||||
*/
|
||||
public set props(newProps: Props) {
|
||||
const prevProps = this.props;
|
||||
// Update the internal props.
|
||||
this.itemProps = newProps;
|
||||
|
||||
// From this point, things which rely on this.props can access to the changes.
|
||||
|
||||
// Check if we should re-render.
|
||||
if (this.shouldBeUpdated(prevProps, newProps)) this.render(prevProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* To compare the previous and the new props and returns a boolean value
|
||||
* in case the difference is meaningfull enough to perform DOM changes.
|
||||
*
|
||||
* Here, the only comparision is done by reference.
|
||||
*
|
||||
* Override this function to perform a different comparision depending on the item needs.
|
||||
*
|
||||
* @param prevProps
|
||||
* @param newProps
|
||||
* @return Whether the difference is meaningful enough to perform DOM changes or not.
|
||||
*/
|
||||
protected shouldBeUpdated(prevProps: Props, newProps: Props): boolean {
|
||||
return prevProps !== newProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* To recreate or update the HTMLElement which represents the item into the DOM.
|
||||
* @param prevProps If exists it will be used to only perform DOM updates instead of a full replace.
|
||||
*/
|
||||
public render(prevProps: Props | null = null): void {
|
||||
this.updateDomElement(this.childElementRef);
|
||||
|
||||
// Move box.
|
||||
if (!prevProps || this.positionChanged(prevProps, this.props)) {
|
||||
this.moveElement(this.props.x, this.props.y);
|
||||
}
|
||||
// Resize box.
|
||||
if (!prevProps || this.sizeChanged(prevProps, this.props)) {
|
||||
this.resizeElement(this.props.width, this.props.height);
|
||||
}
|
||||
// Change label.
|
||||
const oldLabelHtml = this.labelElementRef.innerHTML;
|
||||
const newLabelHtml = this.createLabelDomElement().innerHTML;
|
||||
if (oldLabelHtml !== newLabelHtml) {
|
||||
this.labelElementRef.innerHTML = newLabelHtml;
|
||||
}
|
||||
// Change label position.
|
||||
if (!prevProps || prevProps.labelPosition !== this.props.labelPosition) {
|
||||
this.changeLabelPosition(this.props.labelPosition);
|
||||
}
|
||||
// Change link.
|
||||
if (
|
||||
prevProps &&
|
||||
(prevProps.isLinkEnabled !== this.props.isLinkEnabled ||
|
||||
(this.props.isLinkEnabled && prevProps.link !== this.props.link))
|
||||
) {
|
||||
const container = this.createContainerDomElement();
|
||||
container.innerHTML = this.elementRef.innerHTML;
|
||||
|
||||
if (this.elementRef.parentNode !== null) {
|
||||
this.elementRef.parentNode.replaceChild(container, this.elementRef);
|
||||
}
|
||||
|
||||
// Changed the reference to the main element. It's ugly, but needed.
|
||||
this.elementRef = container;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To remove the event listeners and the elements from the DOM.
|
||||
*/
|
||||
public remove(): void {
|
||||
// Call the remove event.
|
||||
this.removeEventManager.emit({ data: this.props });
|
||||
// Event listeners.
|
||||
this.disposables.forEach(disposable => {
|
||||
try {
|
||||
disposable.dispose();
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
});
|
||||
// VisualConsoleItem DOM element.
|
||||
this.elementRef.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the previous and the new position and return
|
||||
* a boolean value in case the position changed.
|
||||
* @param prevPosition
|
||||
* @param newPosition
|
||||
* @return Whether the position changed or not.
|
||||
*/
|
||||
protected positionChanged(
|
||||
prevPosition: Position,
|
||||
newPosition: Position
|
||||
): boolean {
|
||||
return prevPosition.x !== newPosition.x || prevPosition.y !== newPosition.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the label around the item content.
|
||||
* @param position Label position.
|
||||
*/
|
||||
protected changeLabelPosition(position: Props["labelPosition"]): void {
|
||||
switch (position) {
|
||||
case "up":
|
||||
this.elementRef.style.flexDirection = "column-reverse";
|
||||
break;
|
||||
case "left":
|
||||
this.elementRef.style.flexDirection = "row-reverse";
|
||||
break;
|
||||
case "right":
|
||||
this.elementRef.style.flexDirection = "row";
|
||||
break;
|
||||
case "down":
|
||||
default:
|
||||
this.elementRef.style.flexDirection = "column";
|
||||
break;
|
||||
}
|
||||
|
||||
// Ugly table to show the label as its legacy counterpart.
|
||||
const tables = this.labelElementRef.getElementsByTagName("table");
|
||||
const table = tables.length > 0 ? tables.item(0) : null;
|
||||
// Change the table size depending on its position.
|
||||
if (table) {
|
||||
switch (this.props.labelPosition) {
|
||||
case "up":
|
||||
case "down":
|
||||
if (this.props.width > 0) {
|
||||
table.style.width = `${this.props.width}px`;
|
||||
table.style.height = null;
|
||||
}
|
||||
break;
|
||||
case "left":
|
||||
case "right":
|
||||
if (this.props.height > 0) {
|
||||
table.style.width = null;
|
||||
table.style.height = `${this.props.height}px`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the DOM container.
|
||||
* @param x Horizontal axis position.
|
||||
* @param y Vertical axis position.
|
||||
*/
|
||||
protected moveElement(x: number, y: number): void {
|
||||
this.elementRef.style.left = `${x}px`;
|
||||
this.elementRef.style.top = `${y}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position into the properties and move the DOM container.
|
||||
* @param x Horizontal axis position.
|
||||
* @param y Vertical axis position.
|
||||
*/
|
||||
public move(x: number, y: number): void {
|
||||
this.moveElement(x, y);
|
||||
this.itemProps = {
|
||||
...this.props, // Object spread: http://es6-features.org/#SpreadOperator
|
||||
x,
|
||||
y
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the previous and the new size and return
|
||||
* a boolean value in case the size changed.
|
||||
* @param prevSize
|
||||
* @param newSize
|
||||
* @return Whether the size changed or not.
|
||||
*/
|
||||
protected sizeChanged(prevSize: Size, newSize: Size): boolean {
|
||||
return (
|
||||
prevSize.width !== newSize.width || prevSize.height !== newSize.height
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the DOM content container.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
protected resizeElement(width: number, height: number): void {
|
||||
// The most valuable size is the content size.
|
||||
this.childElementRef.style.width = width > 0 ? `${width}px` : null;
|
||||
this.childElementRef.style.height = height > 0 ? `${height}px` : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the size into the properties and resize the DOM container.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public resize(width: number, height: number): void {
|
||||
this.resizeElement(width, height);
|
||||
this.itemProps = {
|
||||
...this.props, // Object spread: http://es6-features.org/#SpreadOperator
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* To add an event handler to the click of the linked visual console elements.
|
||||
* @param listener Function which is going to be executed when a linked console is clicked.
|
||||
*/
|
||||
public onClick(listener: Listener<ItemClickEvent<Props>>): Disposable {
|
||||
/*
|
||||
* The '.on' function returns a function which will clean the event
|
||||
* listener when executed. We store all the 'dispose' functions to
|
||||
* call them when the item should be cleared.
|
||||
*/
|
||||
const disposable = this.clickEventManager.on(listener);
|
||||
this.disposables.push(disposable);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
|
||||
/**
|
||||
* To add an event handler to the removal of the item.
|
||||
* @param listener Function which is going to be executed when a item is removed.
|
||||
*/
|
||||
public onRemove(listener: Listener<ItemRemoveEvent<Props>>): Disposable {
|
||||
/*
|
||||
* The '.on' function returns a function which will clean the event
|
||||
* listener when executed. We store all the 'dispose' functions to
|
||||
* call them when the item should be cleared.
|
||||
*/
|
||||
const disposable = this.removeEventManager.on(listener);
|
||||
this.disposables.push(disposable);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
}
|
||||
|
||||
export default VisualConsoleItem;
|
|
@ -0,0 +1,40 @@
|
|||
export interface Listener<T> {
|
||||
(event: T): void;
|
||||
}
|
||||
|
||||
export interface Disposable {
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
/** passes through events as they happen. You will not get events from before you start listening */
|
||||
export default class TypedEvent<T> {
|
||||
private listeners: Listener<T>[] = [];
|
||||
private listenersOncer: Listener<T>[] = [];
|
||||
|
||||
public on = (listener: Listener<T>): Disposable => {
|
||||
this.listeners.push(listener);
|
||||
return {
|
||||
dispose: () => this.off(listener)
|
||||
};
|
||||
};
|
||||
|
||||
public once = (listener: Listener<T>): void => {
|
||||
this.listenersOncer.push(listener);
|
||||
};
|
||||
|
||||
public off = (listener: Listener<T>): void => {
|
||||
const callbackIndex = this.listeners.indexOf(listener);
|
||||
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
|
||||
};
|
||||
|
||||
public emit = (event: T): void => {
|
||||
/** Update any general listeners */
|
||||
this.listeners.forEach(listener => listener(event));
|
||||
|
||||
/** Clear the `once` queue */
|
||||
this.listenersOncer.forEach(listener => listener(event));
|
||||
this.listenersOncer = [];
|
||||
};
|
||||
|
||||
public pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e));
|
||||
}
|
|
@ -0,0 +1,560 @@
|
|||
import { UnknownObject, Size } from "./types";
|
||||
import {
|
||||
parseBoolean,
|
||||
sizePropsDecoder,
|
||||
parseIntOr,
|
||||
notEmptyStringOr
|
||||
} from "./lib";
|
||||
import Item, {
|
||||
ItemType,
|
||||
ItemProps,
|
||||
ItemClickEvent,
|
||||
ItemRemoveEvent
|
||||
} from "./Item";
|
||||
import StaticGraph, { staticGraphPropsDecoder } from "./items/StaticGraph";
|
||||
import Icon, { iconPropsDecoder } from "./items/Icon";
|
||||
import ColorCloud, { colorCloudPropsDecoder } from "./items/ColorCloud";
|
||||
import Group, { groupPropsDecoder } from "./items/Group";
|
||||
import Clock, { clockPropsDecoder } from "./items/Clock";
|
||||
import Box, { boxPropsDecoder } from "./items/Box";
|
||||
import Line, { linePropsDecoder } from "./items/Line";
|
||||
import Label, { labelPropsDecoder } from "./items/Label";
|
||||
import SimpleValue, { simpleValuePropsDecoder } from "./items/SimpleValue";
|
||||
import EventsHistory, {
|
||||
eventsHistoryPropsDecoder
|
||||
} from "./items/EventsHistory";
|
||||
import Percentile, { percentilePropsDecoder } from "./items/Percentile";
|
||||
import TypedEvent, { Disposable, Listener } from "./TypedEvent";
|
||||
import DonutGraph, { donutGraphPropsDecoder } from "./items/DonutGraph";
|
||||
import BarsGraph, { barsGraphPropsDecoder } from "./items/BarsGraph";
|
||||
import ModuleGraph, { moduleGraphPropsDecoder } from "./items/ModuleGraph";
|
||||
import Service, { servicePropsDecoder } from "./items/Service";
|
||||
|
||||
// TODO: Document.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
function itemInstanceFrom(data: UnknownObject) {
|
||||
const type = parseIntOr(data.type, null);
|
||||
if (type == null) throw new TypeError("missing item type.");
|
||||
|
||||
switch (type as ItemType) {
|
||||
case ItemType.STATIC_GRAPH:
|
||||
return new StaticGraph(staticGraphPropsDecoder(data));
|
||||
case ItemType.MODULE_GRAPH:
|
||||
return new ModuleGraph(moduleGraphPropsDecoder(data));
|
||||
case ItemType.SIMPLE_VALUE:
|
||||
case ItemType.SIMPLE_VALUE_MAX:
|
||||
case ItemType.SIMPLE_VALUE_MIN:
|
||||
case ItemType.SIMPLE_VALUE_AVG:
|
||||
return new SimpleValue(simpleValuePropsDecoder(data));
|
||||
case ItemType.PERCENTILE_BAR:
|
||||
case ItemType.PERCENTILE_BUBBLE:
|
||||
case ItemType.CIRCULAR_PROGRESS_BAR:
|
||||
case ItemType.CIRCULAR_INTERIOR_PROGRESS_BAR:
|
||||
return new Percentile(percentilePropsDecoder(data));
|
||||
case ItemType.LABEL:
|
||||
return new Label(labelPropsDecoder(data));
|
||||
case ItemType.ICON:
|
||||
return new Icon(iconPropsDecoder(data));
|
||||
case ItemType.SERVICE:
|
||||
return new Service(servicePropsDecoder(data));
|
||||
case ItemType.GROUP_ITEM:
|
||||
return new Group(groupPropsDecoder(data));
|
||||
case ItemType.BOX_ITEM:
|
||||
return new Box(boxPropsDecoder(data));
|
||||
case ItemType.LINE_ITEM:
|
||||
return new Line(linePropsDecoder(data));
|
||||
case ItemType.AUTO_SLA_GRAPH:
|
||||
return new EventsHistory(eventsHistoryPropsDecoder(data));
|
||||
case ItemType.DONUT_GRAPH:
|
||||
return new DonutGraph(donutGraphPropsDecoder(data));
|
||||
case ItemType.BARS_GRAPH:
|
||||
return new BarsGraph(barsGraphPropsDecoder(data));
|
||||
case ItemType.CLOCK:
|
||||
return new Clock(clockPropsDecoder(data));
|
||||
case ItemType.COLOR_CLOUD:
|
||||
return new ColorCloud(colorCloudPropsDecoder(data));
|
||||
default:
|
||||
throw new TypeError("item not found");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Document.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
function decodeProps(data: UnknownObject) {
|
||||
const type = parseIntOr(data.type, null);
|
||||
if (type == null) throw new TypeError("missing item type.");
|
||||
|
||||
switch (type as ItemType) {
|
||||
case ItemType.STATIC_GRAPH:
|
||||
return staticGraphPropsDecoder(data);
|
||||
case ItemType.MODULE_GRAPH:
|
||||
return moduleGraphPropsDecoder(data);
|
||||
case ItemType.SIMPLE_VALUE:
|
||||
case ItemType.SIMPLE_VALUE_MAX:
|
||||
case ItemType.SIMPLE_VALUE_MIN:
|
||||
case ItemType.SIMPLE_VALUE_AVG:
|
||||
return simpleValuePropsDecoder(data);
|
||||
case ItemType.PERCENTILE_BAR:
|
||||
case ItemType.PERCENTILE_BUBBLE:
|
||||
case ItemType.CIRCULAR_PROGRESS_BAR:
|
||||
case ItemType.CIRCULAR_INTERIOR_PROGRESS_BAR:
|
||||
return percentilePropsDecoder(data);
|
||||
case ItemType.LABEL:
|
||||
return labelPropsDecoder(data);
|
||||
case ItemType.ICON:
|
||||
return iconPropsDecoder(data);
|
||||
case ItemType.SERVICE:
|
||||
return servicePropsDecoder(data);
|
||||
case ItemType.GROUP_ITEM:
|
||||
return groupPropsDecoder(data);
|
||||
case ItemType.BOX_ITEM:
|
||||
return boxPropsDecoder(data);
|
||||
case ItemType.LINE_ITEM:
|
||||
return linePropsDecoder(data);
|
||||
case ItemType.AUTO_SLA_GRAPH:
|
||||
return eventsHistoryPropsDecoder(data);
|
||||
case ItemType.DONUT_GRAPH:
|
||||
return donutGraphPropsDecoder(data);
|
||||
case ItemType.BARS_GRAPH:
|
||||
return barsGraphPropsDecoder(data);
|
||||
case ItemType.CLOCK:
|
||||
return clockPropsDecoder(data);
|
||||
case ItemType.COLOR_CLOUD:
|
||||
return colorCloudPropsDecoder(data);
|
||||
default:
|
||||
throw new TypeError("decoder not found");
|
||||
}
|
||||
}
|
||||
|
||||
// Base properties.
|
||||
export interface VisualConsoleProps extends Size {
|
||||
readonly id: number;
|
||||
name: string;
|
||||
groupId: number;
|
||||
backgroundURL: string | null; // URL?
|
||||
backgroundColor: string | null;
|
||||
isFavorite: boolean;
|
||||
relationLineWidth: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the Visual Console props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function visualConsolePropsDecoder(
|
||||
data: UnknownObject
|
||||
): VisualConsoleProps | never {
|
||||
// Object destructuring: http://es6-features.org/#ObjectMatchingShorthandNotation
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
groupId,
|
||||
backgroundURL,
|
||||
backgroundColor,
|
||||
isFavorite,
|
||||
relationLineWidth
|
||||
} = data;
|
||||
|
||||
if (id == null || isNaN(parseInt(id))) {
|
||||
throw new TypeError("invalid Id.");
|
||||
}
|
||||
if (typeof name !== "string" || name.length === 0) {
|
||||
throw new TypeError("invalid name.");
|
||||
}
|
||||
if (groupId == null || isNaN(parseInt(groupId))) {
|
||||
throw new TypeError("invalid group Id.");
|
||||
}
|
||||
|
||||
return {
|
||||
id: parseInt(id),
|
||||
name,
|
||||
groupId: parseInt(groupId),
|
||||
backgroundURL: notEmptyStringOr(backgroundURL, null),
|
||||
backgroundColor: notEmptyStringOr(backgroundColor, null),
|
||||
isFavorite: parseBoolean(isFavorite),
|
||||
relationLineWidth: parseIntOr(relationLineWidth, 0),
|
||||
...sizePropsDecoder(data)
|
||||
};
|
||||
}
|
||||
|
||||
export default class VisualConsole {
|
||||
// Reference to the DOM element which will contain the items.
|
||||
private readonly containerRef: HTMLElement;
|
||||
// Properties.
|
||||
private _props: VisualConsoleProps;
|
||||
// Visual Console Item instances by their Id.
|
||||
private elementsById: {
|
||||
[key: number]: Item<ItemProps>;
|
||||
} = {};
|
||||
// Visual Console Item Ids.
|
||||
private elementIds: ItemProps["id"][] = [];
|
||||
// Dictionary which store the created lines.
|
||||
private relations: {
|
||||
[key: string]: Line;
|
||||
} = {};
|
||||
// Event manager for click events.
|
||||
private readonly clickEventManager = new TypedEvent<
|
||||
ItemClickEvent<ItemProps>
|
||||
>();
|
||||
// List of references to clean the event listeners.
|
||||
private readonly disposables: Disposable[] = [];
|
||||
|
||||
/**
|
||||
* React to a click on an element.
|
||||
* @param e Event object.
|
||||
*/
|
||||
private handleElementClick: (e: ItemClickEvent<ItemProps>) => void = e => {
|
||||
this.clickEventManager.emit(e);
|
||||
// console.log(`Clicked element #${e.data.id}`, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear some element references.
|
||||
* @param e Event object.
|
||||
*/
|
||||
private handleElementRemove: (e: ItemRemoveEvent<ItemProps>) => void = e => {
|
||||
// Remove the element from the list and its relations.
|
||||
this.elementIds = this.elementIds.filter(id => id !== e.data.id);
|
||||
delete this.elementsById[e.data.id];
|
||||
this.clearRelations(e.data.id);
|
||||
};
|
||||
|
||||
public constructor(
|
||||
container: HTMLElement,
|
||||
props: UnknownObject,
|
||||
items: UnknownObject[]
|
||||
) {
|
||||
this.containerRef = container;
|
||||
this._props = visualConsolePropsDecoder(props);
|
||||
|
||||
// Force the first render.
|
||||
this.render();
|
||||
|
||||
// Sort by isOnTop, id ASC
|
||||
items = items.sort(function(a, b) {
|
||||
if (
|
||||
a.isOnTop == null ||
|
||||
b.isOnTop == null ||
|
||||
a.id == null ||
|
||||
b.id == null
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a.isOnTop && !b.isOnTop) return 1;
|
||||
else if (!a.isOnTop && b.isOnTop) return -1;
|
||||
else if (a.id < b.id) return 1;
|
||||
else return -1;
|
||||
});
|
||||
|
||||
// Initialize the items.
|
||||
items.forEach(item => {
|
||||
try {
|
||||
const itemInstance = itemInstanceFrom(item);
|
||||
// Add the item to the list.
|
||||
this.elementsById[itemInstance.props.id] = itemInstance;
|
||||
this.elementIds.push(itemInstance.props.id);
|
||||
// Item event handlers.
|
||||
itemInstance.onClick(this.handleElementClick);
|
||||
itemInstance.onRemove(this.handleElementRemove);
|
||||
// Add the item to the DOM.
|
||||
this.containerRef.append(itemInstance.elementRef);
|
||||
} catch (error) {
|
||||
console.log("Error creating a new element:", error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Create lines.
|
||||
this.buildRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public accessor of the `elements` property.
|
||||
* @return Properties.
|
||||
*/
|
||||
public get elements(): Item<ItemProps>[] {
|
||||
// Ensure the type cause Typescript doesn't know the filter removes null items.
|
||||
return this.elementIds
|
||||
.map(id => this.elementsById[id])
|
||||
.filter(_ => _ != null) as Item<ItemProps>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter of the `elements` property.
|
||||
* @param items.
|
||||
*/
|
||||
public updateElements(items: UnknownObject[]): void {
|
||||
const itemIds = items.map(item => item.id || null).filter(id => id != null);
|
||||
itemIds as number[]; // Tell the type system to rely on us.
|
||||
// Get the elements we should delete.
|
||||
const deletedIds: number[] = this.elementIds.filter(
|
||||
id => itemIds.indexOf(id) < 0
|
||||
);
|
||||
// Delete the elements.
|
||||
deletedIds.forEach(id => {
|
||||
if (this.elementsById[id] != null) {
|
||||
this.elementsById[id].remove();
|
||||
delete this.elementsById[id];
|
||||
}
|
||||
});
|
||||
// Replace the element ids.
|
||||
this.elementIds = itemIds;
|
||||
|
||||
// Initialize the items.
|
||||
items.forEach(item => {
|
||||
if (item.id) {
|
||||
if (this.elementsById[item.id] == null) {
|
||||
// New item.
|
||||
try {
|
||||
const itemInstance = itemInstanceFrom(item);
|
||||
// Add the item to the list.
|
||||
this.elementsById[itemInstance.props.id] = itemInstance;
|
||||
// Item event handlers.
|
||||
itemInstance.onClick(this.handleElementClick);
|
||||
itemInstance.onRemove(this.handleElementRemove);
|
||||
// Add the item to the DOM.
|
||||
this.containerRef.append(itemInstance.elementRef);
|
||||
} catch (error) {
|
||||
console.log("Error creating a new element:", error.message);
|
||||
}
|
||||
} else {
|
||||
// Update item.
|
||||
try {
|
||||
this.elementsById[item.id].props = decodeProps(item);
|
||||
} catch (error) {
|
||||
console.log("Error updating an element:", error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Re-build relations.
|
||||
this.buildRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public accessor of the `props` property.
|
||||
* @return Properties.
|
||||
*/
|
||||
public get props(): VisualConsoleProps {
|
||||
return { ...this._props }; // Return a copy.
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter of the `props` property.
|
||||
* If the new props are different enough than the
|
||||
* stored props, a render would be fired.
|
||||
* @param newProps
|
||||
*/
|
||||
public set props(newProps: VisualConsoleProps) {
|
||||
const prevProps = this.props;
|
||||
// Update the internal props.
|
||||
this._props = newProps;
|
||||
|
||||
// From this point, things which rely on this.props can access to the changes.
|
||||
|
||||
// Re-render.
|
||||
this.render(prevProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate or update the HTMLElement which represents the Visual Console into the DOM.
|
||||
* @param prevProps If exists it will be used to only DOM updates instead of a full replace.
|
||||
*/
|
||||
public render(prevProps: VisualConsoleProps | null = null): void {
|
||||
if (prevProps) {
|
||||
if (prevProps.backgroundURL !== this.props.backgroundURL) {
|
||||
this.containerRef.style.backgroundImage =
|
||||
this.props.backgroundURL !== null
|
||||
? `url(${this.props.backgroundURL})`
|
||||
: null;
|
||||
}
|
||||
if (prevProps.backgroundColor !== this.props.backgroundColor) {
|
||||
this.containerRef.style.backgroundColor = this.props.backgroundColor;
|
||||
}
|
||||
if (this.sizeChanged(prevProps, this.props)) {
|
||||
this.resizeElement(this.props.width, this.props.height);
|
||||
}
|
||||
} else {
|
||||
this.containerRef.style.backgroundImage =
|
||||
this.props.backgroundURL !== null
|
||||
? `url(${this.props.backgroundURL})`
|
||||
: null;
|
||||
|
||||
this.containerRef.style.backgroundColor = this.props.backgroundColor;
|
||||
this.resizeElement(this.props.width, this.props.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the previous and the new size and return
|
||||
* a boolean value in case the size changed.
|
||||
* @param prevSize
|
||||
* @param newSize
|
||||
* @return Whether the size changed or not.
|
||||
*/
|
||||
public sizeChanged(prevSize: Size, newSize: Size): boolean {
|
||||
return (
|
||||
prevSize.width !== newSize.width || prevSize.height !== newSize.height
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the DOM container.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public resizeElement(width: number, height: number): void {
|
||||
this.containerRef.style.width = `${width}px`;
|
||||
this.containerRef.style.height = `${height}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the size into the properties and resize the DOM container.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public resize(width: number, height: number): void {
|
||||
this.props = {
|
||||
...this.props, // Object spread: http://es6-features.org/#SpreadOperator
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* To remove the event listeners and the elements from the DOM.
|
||||
*/
|
||||
public remove(): void {
|
||||
this.disposables.forEach(d => d.dispose()); // Arrow function.
|
||||
this.elements.forEach(e => e.remove()); // Arrow function.
|
||||
this.elementsById = {};
|
||||
this.elementIds = [];
|
||||
// Clear relations.
|
||||
this.clearRelations();
|
||||
// Clean container.
|
||||
this.containerRef.innerHTML = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create line elements which connect the elements with their parents.
|
||||
*/
|
||||
private buildRelations(): void {
|
||||
// Clear relations.
|
||||
this.clearRelations();
|
||||
// Add relations.
|
||||
this.elements.forEach(item => {
|
||||
if (item.props.parentId !== null) {
|
||||
const parent = this.elementsById[item.props.parentId];
|
||||
const child = this.elementsById[item.props.id];
|
||||
if (parent && child) this.addRelationLine(parent, child);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemId Optional identifier of a parent or child item.
|
||||
* Remove the line elements which connect the elements with their parents.
|
||||
*/
|
||||
private clearRelations(itemId?: number): void {
|
||||
if (itemId != null) {
|
||||
for (let key in this.relations) {
|
||||
const ids = key.split("|");
|
||||
const parentId = Number.parseInt(ids[0]);
|
||||
const childId = Number.parseInt(ids[1]);
|
||||
|
||||
if (itemId === parentId || itemId === childId) {
|
||||
this.relations[key].remove();
|
||||
delete this.relations[key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let key in this.relations) {
|
||||
this.relations[key].remove();
|
||||
delete this.relations[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the line element which represent the relation between items.
|
||||
* @param parentId Identifier of the parent item.
|
||||
* @param childId Itentifier of the child item.
|
||||
* @return The line element or nothing.
|
||||
*/
|
||||
private getRelationLine(parentId: number, childId: number): Line | null {
|
||||
const identifier = `${parentId}|${childId}`;
|
||||
return this.relations[identifier] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new line item to represent a relation between the items.
|
||||
* @param parent Parent item.
|
||||
* @param child Child item.
|
||||
* @return Whether the line was added or not.
|
||||
*/
|
||||
private addRelationLine(
|
||||
parent: Item<ItemProps>,
|
||||
child: Item<ItemProps>
|
||||
): Line {
|
||||
const identifier = `${parent.props.id}|${child.props.id}`;
|
||||
if (this.relations[identifier] != null) {
|
||||
this.relations[identifier].remove();
|
||||
}
|
||||
|
||||
// Get the items center.
|
||||
const startX = parent.props.x + parent.elementRef.clientWidth / 2;
|
||||
const startY =
|
||||
parent.props.y +
|
||||
(parent.elementRef.clientHeight - parent.labelElementRef.clientHeight) /
|
||||
2;
|
||||
const endX = child.props.x + child.elementRef.clientWidth / 2;
|
||||
const endY =
|
||||
child.props.y +
|
||||
(child.elementRef.clientHeight - child.labelElementRef.clientHeight) / 2;
|
||||
|
||||
const line = new Line(
|
||||
linePropsDecoder({
|
||||
id: 0,
|
||||
type: ItemType.LINE_ITEM,
|
||||
startX,
|
||||
startY,
|
||||
endX,
|
||||
endY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
lineWidth: this.props.relationLineWidth,
|
||||
color: "#CCCCCC"
|
||||
})
|
||||
);
|
||||
// Save a reference to the line item.
|
||||
this.relations[identifier] = line;
|
||||
|
||||
// Add the line to the DOM.
|
||||
line.elementRef.style.zIndex = "0";
|
||||
this.containerRef.append(line.elementRef);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event handler to the click of the linked visual console elements.
|
||||
* @param listener Function which is going to be executed when a linked console is clicked.
|
||||
*/
|
||||
public onClick(listener: Listener<ItemClickEvent<ItemProps>>): Disposable {
|
||||
/*
|
||||
* The '.on' function returns a function which will clean the event
|
||||
* listener when executed. We store all the 'dispose' functions to
|
||||
* call them when the item should be cleared.
|
||||
*/
|
||||
const disposable = this.clickEventManager.on(listener);
|
||||
this.disposables.push(disposable);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Useful resources.
|
||||
* http://es6-features.org/
|
||||
* http://exploringjs.com/es6
|
||||
* https://www.typescriptlang.org/
|
||||
*/
|
||||
|
||||
import "./main.css"; // CSS import.
|
||||
import VisualConsole from "./VisualConsole";
|
||||
|
||||
// Export the VisualConsole class to the global object.
|
||||
|
||||
// eslint-disable-next-line
|
||||
(window as any).VisualConsole = VisualConsole;
|
|
@ -0,0 +1,67 @@
|
|||
import { UnknownObject, WithModuleProps } from "../types";
|
||||
import { modulePropsDecoder, decodeBase64, stringIsEmpty } from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type BarsGraphProps = {
|
||||
type: ItemType.BARS_GRAPH;
|
||||
html: string;
|
||||
} & ItemProps &
|
||||
WithModuleProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the bars graph props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function barsGraphPropsDecoder(
|
||||
data: UnknownObject
|
||||
): BarsGraphProps | never {
|
||||
if (stringIsEmpty(data.html) && stringIsEmpty(data.encodedHtml)) {
|
||||
throw new TypeError("missing html content.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.BARS_GRAPH,
|
||||
html: !stringIsEmpty(data.html)
|
||||
? data.html
|
||||
: decodeBase64(data.encodedHtml),
|
||||
...modulePropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class BarsGraph extends Item<BarsGraphProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "bars-graph";
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const scripts = element.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
setTimeout(() => {
|
||||
if (scripts[i].src.length === 0) eval(scripts[i].innerHTML.trim());
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
protected updateDomElement(element: HTMLElement): void {
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const aux = document.createElement("div");
|
||||
aux.innerHTML = this.props.html;
|
||||
const scripts = aux.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.length === 0) {
|
||||
eval(scripts[i].innerHTML.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import { UnknownObject } from "../types";
|
||||
import { parseIntOr, notEmptyStringOr } from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
interface BoxProps extends ItemProps {
|
||||
// Overrided properties.
|
||||
readonly type: ItemType.BOX_ITEM;
|
||||
label: null;
|
||||
isLinkEnabled: false;
|
||||
parentId: null;
|
||||
aclGroupId: null;
|
||||
// Custom properties.
|
||||
borderWidth: number;
|
||||
borderColor: string | null;
|
||||
fillColor: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the item props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function boxPropsDecoder(data: UnknownObject): BoxProps | never {
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.BOX_ITEM,
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Custom properties.
|
||||
borderWidth: parseIntOr(data.borderWidth, 0),
|
||||
borderColor: notEmptyStringOr(data.borderColor, null),
|
||||
fillColor: notEmptyStringOr(data.fillColor, null)
|
||||
};
|
||||
}
|
||||
|
||||
export default class Box extends Item<BoxProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const box: HTMLDivElement = document.createElement("div");
|
||||
box.className = "box";
|
||||
// To prevent this item to expand beyond its parent.
|
||||
box.style.boxSizing = "border-box";
|
||||
|
||||
if (this.props.fillColor) {
|
||||
box.style.backgroundColor = this.props.fillColor;
|
||||
}
|
||||
|
||||
// Border.
|
||||
if (this.props.borderWidth > 0) {
|
||||
box.style.borderStyle = "solid";
|
||||
// Control the max width to prevent this item to expand beyond its parent.
|
||||
const maxBorderWidth = Math.min(this.props.width, this.props.height) / 2;
|
||||
const borderWidth = Math.min(this.props.borderWidth, maxBorderWidth);
|
||||
box.style.borderWidth = `${borderWidth}px`;
|
||||
|
||||
if (this.props.borderColor) {
|
||||
box.style.borderColor = this.props.borderColor;
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,601 @@
|
|||
import "./styles.css";
|
||||
|
||||
import { LinkedVisualConsoleProps, UnknownObject, Size } from "../../types";
|
||||
import {
|
||||
linkedVCPropsDecoder,
|
||||
parseIntOr,
|
||||
parseBoolean,
|
||||
prefixedCssRules,
|
||||
notEmptyStringOr,
|
||||
humanDate,
|
||||
humanTime
|
||||
} from "../../lib";
|
||||
import Item, { ItemProps, itemBasePropsDecoder, ItemType } from "../../Item";
|
||||
|
||||
export type ClockProps = {
|
||||
type: ItemType.CLOCK;
|
||||
clockType: "analogic" | "digital";
|
||||
clockFormat: "datetime" | "time";
|
||||
clockTimezone: string;
|
||||
clockTimezoneOffset: number; // Offset of the timezone to UTC in seconds.
|
||||
showClockTimezone: boolean;
|
||||
color?: string | null;
|
||||
} & ItemProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw unknown value.
|
||||
* @param clockType Raw value.
|
||||
*/
|
||||
const parseClockType = (
|
||||
clockType: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): ClockProps["clockType"] => {
|
||||
switch (clockType) {
|
||||
case "analogic":
|
||||
case "digital":
|
||||
return clockType;
|
||||
default:
|
||||
return "analogic";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw unknown value.
|
||||
* @param clockFormat Raw value.
|
||||
*/
|
||||
const parseClockFormat = (
|
||||
clockFormat: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): ClockProps["clockFormat"] => {
|
||||
switch (clockFormat) {
|
||||
case "datetime":
|
||||
case "date":
|
||||
case "time":
|
||||
return clockFormat;
|
||||
default:
|
||||
return "datetime";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the clock props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function clockPropsDecoder(data: UnknownObject): ClockProps | never {
|
||||
if (
|
||||
typeof data.clockTimezone !== "string" ||
|
||||
data.clockTimezone.length === 0
|
||||
) {
|
||||
throw new TypeError("invalid timezone.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.CLOCK,
|
||||
clockType: parseClockType(data.clockType),
|
||||
clockFormat: parseClockFormat(data.clockFormat),
|
||||
clockTimezone: data.clockTimezone,
|
||||
clockTimezoneOffset: parseIntOr(data.clockTimezoneOffset, 0),
|
||||
showClockTimezone: parseBoolean(data.showClockTimezone),
|
||||
color: notEmptyStringOr(data.color, null),
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class Clock extends Item<ClockProps> {
|
||||
public static readonly TICK_INTERVAL = 1000; // In ms.
|
||||
private intervalRef: number | null = null;
|
||||
|
||||
public constructor(props: ClockProps) {
|
||||
// Call the superclass constructor.
|
||||
super(props);
|
||||
|
||||
/* The item is already loaded and inserted into the DOM.
|
||||
* The class properties are now initialized.
|
||||
* Now you can modify the item, add event handlers, timers, etc.
|
||||
*/
|
||||
|
||||
/* The use of the arrow function is important here. startTick will
|
||||
* use the function passed as an argument to call the global setInterval
|
||||
* function. The interval, timeout or event functions, among other, are
|
||||
* called into another execution loop and using a different context.
|
||||
* The arrow functions, unlike the classic functions, doesn't create
|
||||
* their own context (this), so their context at execution time will be
|
||||
* use the current context at the declaration time.
|
||||
* http://es6-features.org/#Lexicalthis
|
||||
*/
|
||||
this.startTick(
|
||||
() => {
|
||||
// Replace the old element with the updated date.
|
||||
this.childElementRef.innerHTML = this.createClock().innerHTML;
|
||||
},
|
||||
/* The analogic clock doesn't need to tick,
|
||||
* but it will be refreshed every 20 seconds
|
||||
* to avoid a desync caused by page freezes.
|
||||
*/
|
||||
this.props.clockType === "analogic" ? 20000 : Clock.TICK_INTERVAL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a window.clearInterval call.
|
||||
*/
|
||||
private stopTick(): void {
|
||||
if (this.intervalRef !== null) {
|
||||
window.clearInterval(this.intervalRef);
|
||||
this.intervalRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a window.setInterval call.
|
||||
* @param handler Function to be called every time the interval
|
||||
* timer is reached.
|
||||
* @param interval Number in milliseconds for the interval timer.
|
||||
*/
|
||||
private startTick(
|
||||
handler: TimerHandler,
|
||||
interval: number = Clock.TICK_INTERVAL
|
||||
): void {
|
||||
this.stopTick();
|
||||
this.intervalRef = window.setInterval(handler, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a element which contains the DOM representation of the item.
|
||||
* @return DOM Element.
|
||||
* @override
|
||||
*/
|
||||
protected createDomElement(): HTMLElement | never {
|
||||
return this.createClock();
|
||||
}
|
||||
|
||||
/**
|
||||
* To remove the event listeners and the elements from the DOM.
|
||||
* @override
|
||||
*/
|
||||
public remove(): void {
|
||||
// Clear the interval.
|
||||
this.stopTick();
|
||||
// Call to the parent clean function.
|
||||
super.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override Item.resizeElement
|
||||
* Resize the DOM content container.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
protected resizeElement(width: number, height: number): void {
|
||||
const { width: newWidth, height: newHeight } = this.getElementSize(
|
||||
width,
|
||||
height
|
||||
); // Destructuring assigment: http://es6-features.org/#ObjectMatchingShorthandNotation
|
||||
super.resizeElement(newWidth, newHeight);
|
||||
// Re-render the item to force it calculate a new font size.
|
||||
if (this.props.clockType === "digital") {
|
||||
// Replace the old element with the updated date.
|
||||
this.childElementRef.innerHTML = this.createClock().innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a element which contains a representation of a clock.
|
||||
* It choose between the clock types.
|
||||
* @return DOM Element.
|
||||
* @throws Error.
|
||||
*/
|
||||
private createClock(): HTMLElement | never {
|
||||
switch (this.props.clockType) {
|
||||
case "analogic":
|
||||
return this.createAnalogicClock();
|
||||
case "digital":
|
||||
return this.createDigitalClock();
|
||||
default:
|
||||
throw new Error("invalid clock type.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a element which contains a representation of an analogic clock.
|
||||
* @return DOM Element.
|
||||
*/
|
||||
private createAnalogicClock(): HTMLElement {
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
const colors = {
|
||||
watchFace: "#FFFFF0",
|
||||
watchFaceBorder: "#242124",
|
||||
mark: "#242124",
|
||||
handDark: "#242124",
|
||||
handLight: "#525252",
|
||||
secondHand: "#DC143C"
|
||||
};
|
||||
|
||||
const { width, height } = this.getElementSize(); // Destructuring assigment: http://es6-features.org/#ObjectMatchingShorthandNotation
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.className = "analogic-clock";
|
||||
div.style.width = `${width}px`;
|
||||
div.style.height = `${height}px`;
|
||||
|
||||
// SVG container.
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
// Auto resize SVG using the view box magic: https://css-tricks.com/scale-svg/
|
||||
svg.setAttribute("viewBox", "0 0 100 100");
|
||||
|
||||
// Clock face.
|
||||
const clockFace = document.createElementNS(svgNS, "g");
|
||||
clockFace.setAttribute("class", "clockface");
|
||||
const clockFaceBackground = document.createElementNS(svgNS, "circle");
|
||||
clockFaceBackground.setAttribute("cx", "50");
|
||||
clockFaceBackground.setAttribute("cy", "50");
|
||||
clockFaceBackground.setAttribute("r", "48");
|
||||
clockFaceBackground.setAttribute("fill", colors.watchFace);
|
||||
clockFaceBackground.setAttribute("stroke", colors.watchFaceBorder);
|
||||
clockFaceBackground.setAttribute("stroke-width", "2");
|
||||
clockFaceBackground.setAttribute("stroke-linecap", "round");
|
||||
// Insert the clockface background into the clockface group.
|
||||
clockFace.append(clockFaceBackground);
|
||||
|
||||
// Timezone complication.
|
||||
const city = this.getHumanTimezone();
|
||||
if (city.length > 0) {
|
||||
const timezoneComplication = document.createElementNS(svgNS, "text");
|
||||
timezoneComplication.setAttribute("text-anchor", "middle");
|
||||
timezoneComplication.setAttribute("font-size", "8");
|
||||
timezoneComplication.setAttribute(
|
||||
"transform",
|
||||
"translate(30 50) rotate(90)" // Rotate to counter the clock rotation.
|
||||
);
|
||||
timezoneComplication.setAttribute("fill", colors.mark);
|
||||
timezoneComplication.textContent = city;
|
||||
clockFace.append(timezoneComplication);
|
||||
}
|
||||
|
||||
// Marks group.
|
||||
const marksGroup = document.createElementNS(svgNS, "g");
|
||||
marksGroup.setAttribute("class", "marks");
|
||||
// Build the 12 hours mark.
|
||||
const mainMarkGroup = document.createElementNS(svgNS, "g");
|
||||
mainMarkGroup.setAttribute("class", "mark");
|
||||
mainMarkGroup.setAttribute("transform", "translate(50 50)");
|
||||
const mark1a = document.createElementNS(svgNS, "line");
|
||||
mark1a.setAttribute("x1", "36");
|
||||
mark1a.setAttribute("y1", "0");
|
||||
mark1a.setAttribute("x2", "46");
|
||||
mark1a.setAttribute("y2", "0");
|
||||
mark1a.setAttribute("stroke", colors.mark);
|
||||
mark1a.setAttribute("stroke-width", "5");
|
||||
const mark1b = document.createElementNS(svgNS, "line");
|
||||
mark1b.setAttribute("x1", "36");
|
||||
mark1b.setAttribute("y1", "0");
|
||||
mark1b.setAttribute("x2", "46");
|
||||
mark1b.setAttribute("y2", "0");
|
||||
mark1b.setAttribute("stroke", colors.watchFace);
|
||||
mark1b.setAttribute("stroke-width", "1");
|
||||
// Insert the 12 mark lines into their group.
|
||||
mainMarkGroup.append(mark1a, mark1b);
|
||||
// Insert the main mark into the marks group.
|
||||
marksGroup.append(mainMarkGroup);
|
||||
// Build the rest of the marks.
|
||||
for (let i = 1; i < 60; i++) {
|
||||
const mark = document.createElementNS(svgNS, "line");
|
||||
mark.setAttribute("y1", "0");
|
||||
mark.setAttribute("y2", "0");
|
||||
mark.setAttribute("stroke", colors.mark);
|
||||
mark.setAttribute("transform", `translate(50 50) rotate(${i * 6})`);
|
||||
|
||||
if (i % 5 === 0) {
|
||||
mark.setAttribute("x1", "38");
|
||||
mark.setAttribute("x2", "46");
|
||||
mark.setAttribute("stroke-width", i % 15 === 0 ? "2" : "1");
|
||||
} else {
|
||||
mark.setAttribute("x1", "42");
|
||||
mark.setAttribute("x2", "46");
|
||||
mark.setAttribute("stroke-width", "0.5");
|
||||
}
|
||||
|
||||
// Insert the mark into the marks group.
|
||||
marksGroup.append(mark);
|
||||
}
|
||||
|
||||
/* Clock hands */
|
||||
|
||||
// Hour hand.
|
||||
const hourHand = document.createElementNS(svgNS, "g");
|
||||
hourHand.setAttribute("class", "hour-hand");
|
||||
hourHand.setAttribute("transform", "translate(50 50)");
|
||||
// This will go back and will act like a border.
|
||||
const hourHandA = document.createElementNS(svgNS, "line");
|
||||
hourHandA.setAttribute("class", "hour-hand-a");
|
||||
hourHandA.setAttribute("x1", "0");
|
||||
hourHandA.setAttribute("y1", "0");
|
||||
hourHandA.setAttribute("x2", "30");
|
||||
hourHandA.setAttribute("y2", "0");
|
||||
hourHandA.setAttribute("stroke", colors.handLight);
|
||||
hourHandA.setAttribute("stroke-width", "4");
|
||||
hourHandA.setAttribute("stroke-linecap", "round");
|
||||
// This will go in front of the previous line.
|
||||
const hourHandB = document.createElementNS(svgNS, "line");
|
||||
hourHandB.setAttribute("class", "hour-hand-b");
|
||||
hourHandB.setAttribute("x1", "0");
|
||||
hourHandB.setAttribute("y1", "0");
|
||||
hourHandB.setAttribute("x2", "29.9");
|
||||
hourHandB.setAttribute("y2", "0");
|
||||
hourHandB.setAttribute("stroke", colors.handDark);
|
||||
hourHandB.setAttribute("stroke-width", "3.1");
|
||||
hourHandB.setAttribute("stroke-linecap", "round");
|
||||
// Append the elements to finish the hour hand.
|
||||
hourHand.append(hourHandA, hourHandB);
|
||||
|
||||
// Minute hand.
|
||||
const minuteHand = document.createElementNS(svgNS, "g");
|
||||
minuteHand.setAttribute("class", "minute-hand");
|
||||
minuteHand.setAttribute("transform", "translate(50 50)");
|
||||
// This will go back and will act like a border.
|
||||
const minuteHandA = document.createElementNS(svgNS, "line");
|
||||
minuteHandA.setAttribute("class", "minute-hand-a");
|
||||
minuteHandA.setAttribute("x1", "0");
|
||||
minuteHandA.setAttribute("y1", "0");
|
||||
minuteHandA.setAttribute("x2", "40");
|
||||
minuteHandA.setAttribute("y2", "0");
|
||||
minuteHandA.setAttribute("stroke", colors.handLight);
|
||||
minuteHandA.setAttribute("stroke-width", "2");
|
||||
minuteHandA.setAttribute("stroke-linecap", "round");
|
||||
// This will go in front of the previous line.
|
||||
const minuteHandB = document.createElementNS(svgNS, "line");
|
||||
minuteHandB.setAttribute("class", "minute-hand-b");
|
||||
minuteHandB.setAttribute("x1", "0");
|
||||
minuteHandB.setAttribute("y1", "0");
|
||||
minuteHandB.setAttribute("x2", "39.9");
|
||||
minuteHandB.setAttribute("y2", "0");
|
||||
minuteHandB.setAttribute("stroke", colors.handDark);
|
||||
minuteHandB.setAttribute("stroke-width", "1.5");
|
||||
minuteHandB.setAttribute("stroke-linecap", "round");
|
||||
const minuteHandPin = document.createElementNS(svgNS, "circle");
|
||||
minuteHandPin.setAttribute("r", "3");
|
||||
minuteHandPin.setAttribute("fill", colors.handDark);
|
||||
// Append the elements to finish the minute hand.
|
||||
minuteHand.append(minuteHandA, minuteHandB, minuteHandPin);
|
||||
|
||||
// Second hand.
|
||||
const secondHand = document.createElementNS(svgNS, "g");
|
||||
secondHand.setAttribute("class", "second-hand");
|
||||
secondHand.setAttribute("transform", "translate(50 50)");
|
||||
const secondHandBar = document.createElementNS(svgNS, "line");
|
||||
secondHandBar.setAttribute("x1", "0");
|
||||
secondHandBar.setAttribute("y1", "0");
|
||||
secondHandBar.setAttribute("x2", "46");
|
||||
secondHandBar.setAttribute("y2", "0");
|
||||
secondHandBar.setAttribute("stroke", colors.secondHand);
|
||||
secondHandBar.setAttribute("stroke-width", "1");
|
||||
secondHandBar.setAttribute("stroke-linecap", "round");
|
||||
const secondHandPin = document.createElementNS(svgNS, "circle");
|
||||
secondHandPin.setAttribute("r", "2");
|
||||
secondHandPin.setAttribute("fill", colors.secondHand);
|
||||
// Append the elements to finish the second hand.
|
||||
secondHand.append(secondHandBar, secondHandPin);
|
||||
|
||||
// Pin.
|
||||
const pin = document.createElementNS(svgNS, "circle");
|
||||
pin.setAttribute("cx", "50");
|
||||
pin.setAttribute("cy", "50");
|
||||
pin.setAttribute("r", "0.3");
|
||||
pin.setAttribute("fill", colors.handDark);
|
||||
|
||||
// Get the hand angles.
|
||||
const date = this.getOriginDate();
|
||||
const seconds = date.getSeconds();
|
||||
const minutes = date.getMinutes();
|
||||
const hours = date.getHours();
|
||||
const secAngle = (360 / 60) * seconds;
|
||||
const minuteAngle = (360 / 60) * minutes + (360 / 60) * (seconds / 60);
|
||||
const hourAngle = (360 / 12) * hours + (360 / 12) * (minutes / 60);
|
||||
// Set the clock time by moving the hands.
|
||||
hourHand.setAttribute("transform", `translate(50 50) rotate(${hourAngle})`);
|
||||
minuteHand.setAttribute(
|
||||
"transform",
|
||||
`translate(50 50) rotate(${minuteAngle})`
|
||||
);
|
||||
secondHand.setAttribute(
|
||||
"transform",
|
||||
`translate(50 50) rotate(${secAngle})`
|
||||
);
|
||||
|
||||
// Build the clock
|
||||
svg.append(clockFace, marksGroup, hourHand, minuteHand, secondHand, pin);
|
||||
// Rotate the clock to its normal position.
|
||||
svg.setAttribute("transform", "rotate(-90)");
|
||||
|
||||
/* Add the animation declaration to the container.
|
||||
* Since the animation keyframes need to know the
|
||||
* start angle, this angle is dynamic (current time),
|
||||
* and we can't edit keyframes through javascript
|
||||
* safely and with backwards compatibility, we need
|
||||
* to inject it.
|
||||
*/
|
||||
div.innerHTML = `
|
||||
<style>
|
||||
@keyframes rotate-hour {
|
||||
from {
|
||||
${prefixedCssRules(
|
||||
"transform",
|
||||
`translate(50px, 50px) rotate(${hourAngle}deg)`
|
||||
).join("\n")}
|
||||
}
|
||||
to {
|
||||
${prefixedCssRules(
|
||||
"transform",
|
||||
`translate(50px, 50px) rotate(${hourAngle + 360}deg)`
|
||||
).join("\n")}
|
||||
}
|
||||
}
|
||||
@keyframes rotate-minute {
|
||||
from {
|
||||
${prefixedCssRules(
|
||||
"transform",
|
||||
`translate(50px, 50px) rotate(${minuteAngle}deg)`
|
||||
).join("\n")}
|
||||
}
|
||||
to {
|
||||
${prefixedCssRules(
|
||||
"transform",
|
||||
`translate(50px, 50px) rotate(${minuteAngle + 360}deg)`
|
||||
).join("\n")}
|
||||
}
|
||||
}
|
||||
@keyframes rotate-second {
|
||||
from {
|
||||
${prefixedCssRules(
|
||||
"transform",
|
||||
`translate(50px, 50px) rotate(${secAngle}deg)`
|
||||
).join("\n")}
|
||||
}
|
||||
to {
|
||||
${prefixedCssRules(
|
||||
"transform",
|
||||
`translate(50px, 50px) rotate(${secAngle + 360}deg)`
|
||||
).join("\n")}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
// Add the clock to the container
|
||||
div.append(svg);
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a element which contains a representation of a digital clock.
|
||||
* @return DOM Element.
|
||||
*/
|
||||
private createDigitalClock(): HTMLElement {
|
||||
const element: HTMLDivElement = document.createElement("div");
|
||||
element.className = "digital-clock";
|
||||
|
||||
const { width } = this.getElementSize(); // Destructuring assigment: http://es6-features.org/#ObjectMatchingShorthandNotation
|
||||
|
||||
// Calculate font size to adapt the font to the item size.
|
||||
const baseTimeFontSize = 20; // Per 100px of width.
|
||||
const dateFontSizeMultiplier = 0.5;
|
||||
const tzFontSizeMultiplier = 6 / this.props.clockTimezone.length;
|
||||
const timeFontSize = (baseTimeFontSize * width) / 100;
|
||||
const dateFontSize =
|
||||
(baseTimeFontSize * dateFontSizeMultiplier * width) / 100;
|
||||
const tzFontSize = Math.min(
|
||||
(baseTimeFontSize * tzFontSizeMultiplier * width) / 100,
|
||||
(width / 100) * 10
|
||||
);
|
||||
|
||||
// Date calculated using the original timezone.
|
||||
const date = this.getOriginDate();
|
||||
|
||||
// Date.
|
||||
if (this.props.clockFormat === "datetime") {
|
||||
const dateElem: HTMLSpanElement = document.createElement("span");
|
||||
dateElem.className = "date";
|
||||
dateElem.textContent = humanDate(date, "default");
|
||||
dateElem.style.fontSize = `${dateFontSize}px`;
|
||||
if (this.props.color) dateElem.style.color = this.props.color;
|
||||
element.append(dateElem);
|
||||
}
|
||||
|
||||
// Time.
|
||||
const timeElem: HTMLSpanElement = document.createElement("span");
|
||||
timeElem.className = "time";
|
||||
timeElem.textContent = humanTime(date);
|
||||
timeElem.style.fontSize = `${timeFontSize}px`;
|
||||
if (this.props.color) timeElem.style.color = this.props.color;
|
||||
element.append(timeElem);
|
||||
|
||||
// City name.
|
||||
const city = this.getHumanTimezone();
|
||||
if (city.length > 0) {
|
||||
const tzElem: HTMLSpanElement = document.createElement("span");
|
||||
tzElem.className = "timezone";
|
||||
tzElem.textContent = city;
|
||||
tzElem.style.fontSize = `${tzFontSize}px`;
|
||||
if (this.props.color) tzElem.style.color = this.props.color;
|
||||
element.append(tzElem);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the current date using the timezone offset stored into the properties.
|
||||
* @return The current date.
|
||||
*/
|
||||
private getOriginDate(initialDate: Date | null = null): Date {
|
||||
const d = initialDate ? initialDate : new Date();
|
||||
const targetTZOffset = this.props.clockTimezoneOffset * 1000; // In ms.
|
||||
const localTZOffset = d.getTimezoneOffset() * 60 * 1000; // In ms.
|
||||
const utimestamp = d.getTime() + targetTZOffset + localTZOffset;
|
||||
|
||||
return new Date(utimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a human readable city name from the timezone text.
|
||||
* @param timezone Timezone text.
|
||||
*/
|
||||
public getHumanTimezone(timezone: string = this.props.clockTimezone): string {
|
||||
const [, city = ""] = timezone.split("/");
|
||||
return city.replace("_", " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a element size using the current size and the default values.
|
||||
* @return The size.
|
||||
*/
|
||||
private getElementSize(
|
||||
width: number = this.props.width,
|
||||
height: number = this.props.height
|
||||
): Size {
|
||||
switch (this.props.clockType) {
|
||||
case "analogic": {
|
||||
let diameter = 100; // Default value.
|
||||
|
||||
if (width > 0 && height > 0) {
|
||||
diameter = Math.min(width, height);
|
||||
} else if (width > 0) {
|
||||
diameter = width;
|
||||
} else if (height > 0) {
|
||||
diameter = height;
|
||||
}
|
||||
|
||||
return {
|
||||
width: diameter,
|
||||
height: diameter
|
||||
};
|
||||
}
|
||||
case "digital": {
|
||||
if (width > 0 && height > 0) {
|
||||
// The proportion of the clock should be (width = height / 2) aproximately.
|
||||
height = width / 2 < height ? width / 2 : height;
|
||||
} else if (width > 0) {
|
||||
height = width / 2;
|
||||
} else if (height > 0) {
|
||||
// The proportion of the clock should be (height * 2 = width) aproximately.
|
||||
width = height * 2;
|
||||
} else {
|
||||
width = 100; // Default value.
|
||||
height = 50; // Default value.
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error("invalid clock type.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import Clock, { clockPropsDecoder } from ".";
|
||||
|
||||
const genericRawProps = {
|
||||
id: 1,
|
||||
type: 19, // Clock item = 19
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null
|
||||
};
|
||||
|
||||
const positionRawProps = {
|
||||
x: 100,
|
||||
y: 50
|
||||
};
|
||||
|
||||
const sizeRawProps = {
|
||||
width: 100,
|
||||
height: 100
|
||||
};
|
||||
|
||||
const digitalClockProps = {
|
||||
clockType: "digital",
|
||||
clockFormat: "datetime",
|
||||
clockTimezone: "Madrid",
|
||||
clockTimezoneOffset: 60,
|
||||
showClockTimezone: true,
|
||||
color: "white"
|
||||
};
|
||||
|
||||
const linkedModuleProps = {
|
||||
// Agent props.
|
||||
agentId: null,
|
||||
agentName: null,
|
||||
// Module props.
|
||||
moduleId: null,
|
||||
moduleName: null
|
||||
};
|
||||
|
||||
describe("Clock item", () => {
|
||||
const clockInstance = new Clock(
|
||||
clockPropsDecoder({
|
||||
...genericRawProps,
|
||||
...positionRawProps,
|
||||
...sizeRawProps,
|
||||
...linkedModuleProps,
|
||||
...digitalClockProps
|
||||
})
|
||||
);
|
||||
|
||||
it("should have the digital-clock class", () => {
|
||||
expect(
|
||||
clockInstance.elementRef.getElementsByClassName("digital-clock").length
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
describe("getHumanTimezone function", () => {
|
||||
it("should return a better timezone", () => {
|
||||
expect(clockInstance.getHumanTimezone("America/New_York")).toBe(
|
||||
"New York"
|
||||
);
|
||||
expect(clockInstance.getHumanTimezone("Europe/Madrid")).toBe("Madrid");
|
||||
expect(clockInstance.getHumanTimezone("Asia/Tokyo")).toBe("Tokyo");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
@font-face {
|
||||
font-family: Alarm Clock;
|
||||
src: url(./alarm-clock.ttf);
|
||||
}
|
||||
|
||||
/* Digital clock */
|
||||
|
||||
.visual-console-item .digital-clock {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span {
|
||||
font-family: "Alarm Clock", "Courier New", Courier, monospace;
|
||||
font-size: 50px;
|
||||
|
||||
/* To improve legibility */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
text-shadow: rgba(0, 0, 0, 0.01) 0 0 1px;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span.date {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.visual-console-item .digital-clock > span.timezone {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
/* Analog clock */
|
||||
|
||||
.visual-console-item .analogic-clock .hour-hand {
|
||||
animation: rotate-hour 43200s infinite linear;
|
||||
}
|
||||
|
||||
.visual-console-item .analogic-clock .minute-hand {
|
||||
animation: rotate-minute 3600s infinite linear;
|
||||
}
|
||||
|
||||
.visual-console-item .analogic-clock .second-hand {
|
||||
animation: rotate-second 60s infinite linear;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import ColorCloud, { colorCloudPropsDecoder } from "./ColorCloud";
|
||||
|
||||
const genericRawProps = {
|
||||
id: 1,
|
||||
type: 20, // COlor cloud item = 20
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null
|
||||
};
|
||||
|
||||
const positionRawProps = {
|
||||
x: 100,
|
||||
y: 50
|
||||
};
|
||||
|
||||
const sizeRawProps = {
|
||||
width: 100,
|
||||
height: 100
|
||||
};
|
||||
|
||||
const colorCloudProps = {
|
||||
color: "rgb(100, 50, 245)"
|
||||
};
|
||||
|
||||
const linkedModuleProps = {
|
||||
// Agent props.
|
||||
agentId: null,
|
||||
agentName: null,
|
||||
// Module props.
|
||||
moduleId: null,
|
||||
moduleName: null
|
||||
};
|
||||
|
||||
describe("Color cloud item", () => {
|
||||
const colorCloudInstance = new ColorCloud(
|
||||
colorCloudPropsDecoder({
|
||||
...genericRawProps,
|
||||
...positionRawProps,
|
||||
...sizeRawProps,
|
||||
...linkedModuleProps,
|
||||
...colorCloudProps
|
||||
})
|
||||
);
|
||||
|
||||
it("should throw when using an invalid color into the props decoder", () => {
|
||||
expect(() =>
|
||||
colorCloudPropsDecoder({
|
||||
...genericRawProps,
|
||||
...positionRawProps,
|
||||
...sizeRawProps,
|
||||
...linkedModuleProps,
|
||||
color: null
|
||||
})
|
||||
).toThrowError(TypeError);
|
||||
});
|
||||
|
||||
it("should have the color-cloud class", () => {
|
||||
expect(
|
||||
colorCloudInstance.elementRef.getElementsByClassName("color-cloud").length
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
WithModuleProps,
|
||||
LinkedVisualConsoleProps,
|
||||
UnknownObject
|
||||
} from "../types";
|
||||
import { modulePropsDecoder, linkedVCPropsDecoder } from "../lib";
|
||||
import Item, { itemBasePropsDecoder, ItemType, ItemProps } from "../Item";
|
||||
|
||||
export type ColorCloudProps = {
|
||||
type: ItemType.COLOR_CLOUD;
|
||||
color: string;
|
||||
// TODO: Add the rest of the color cloud values?
|
||||
} & ItemProps &
|
||||
WithModuleProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the static graph props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function colorCloudPropsDecoder(
|
||||
data: UnknownObject
|
||||
): ColorCloudProps | never {
|
||||
// TODO: Validate the color.
|
||||
if (typeof data.color !== "string" || data.color.length === 0) {
|
||||
throw new TypeError("invalid color.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.COLOR_CLOUD,
|
||||
color: data.color,
|
||||
...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
|
||||
export default class ColorCloud extends Item<ColorCloudProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const container: HTMLDivElement = document.createElement("div");
|
||||
container.className = "color-cloud";
|
||||
|
||||
// Add the SVG.
|
||||
container.append(this.createSvgElement());
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
public createSvgElement(): SVGSVGElement {
|
||||
const gradientId = `grad_${this.props.id}`;
|
||||
// SVG container.
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
// Auto resize SVG using the view box magic: https://css-tricks.com/scale-svg/
|
||||
svg.setAttribute("viewBox", "0 0 100 100");
|
||||
|
||||
// Defs.
|
||||
const defs = document.createElementNS(svgNS, "defs");
|
||||
// Radial gradient.
|
||||
const radialGradient = document.createElementNS(svgNS, "radialGradient");
|
||||
radialGradient.setAttribute("id", gradientId);
|
||||
radialGradient.setAttribute("cx", "50%");
|
||||
radialGradient.setAttribute("cy", "50%");
|
||||
radialGradient.setAttribute("r", "50%");
|
||||
radialGradient.setAttribute("fx", "50%");
|
||||
radialGradient.setAttribute("fy", "50%");
|
||||
// Stops.
|
||||
const stop0 = document.createElementNS(svgNS, "stop");
|
||||
stop0.setAttribute("offset", "0%");
|
||||
stop0.setAttribute(
|
||||
"style",
|
||||
`stop-color:${this.props.color};stop-opacity:0.9`
|
||||
);
|
||||
const stop100 = document.createElementNS(svgNS, "stop");
|
||||
stop100.setAttribute("offset", "100%");
|
||||
stop100.setAttribute(
|
||||
"style",
|
||||
`stop-color:${this.props.color};stop-opacity:0`
|
||||
);
|
||||
// Circle.
|
||||
const circle = document.createElementNS(svgNS, "circle");
|
||||
circle.setAttribute("fill", `url(#${gradientId})`);
|
||||
circle.setAttribute("cx", "50%");
|
||||
circle.setAttribute("cy", "50%");
|
||||
circle.setAttribute("r", "50%");
|
||||
|
||||
// Append elements.
|
||||
radialGradient.append(stop0, stop100);
|
||||
defs.append(radialGradient);
|
||||
svg.append(defs, circle);
|
||||
|
||||
return svg;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
LinkedVisualConsoleProps,
|
||||
UnknownObject,
|
||||
WithModuleProps
|
||||
} from "../types";
|
||||
import {
|
||||
linkedVCPropsDecoder,
|
||||
modulePropsDecoder,
|
||||
decodeBase64,
|
||||
stringIsEmpty
|
||||
} from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type DonutGraphProps = {
|
||||
type: ItemType.DONUT_GRAPH;
|
||||
html: string;
|
||||
} & ItemProps &
|
||||
WithModuleProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the donut graph props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function donutGraphPropsDecoder(
|
||||
data: UnknownObject
|
||||
): DonutGraphProps | never {
|
||||
if (stringIsEmpty(data.html) && stringIsEmpty(data.encodedHtml)) {
|
||||
throw new TypeError("missing html content.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.DONUT_GRAPH,
|
||||
html: !stringIsEmpty(data.html)
|
||||
? data.html
|
||||
: decodeBase64(data.encodedHtml),
|
||||
...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class DonutGraph extends Item<DonutGraphProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "donut-graph";
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const scripts = element.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
setTimeout(() => {
|
||||
if (scripts[i].src.length === 0) eval(scripts[i].innerHTML.trim());
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
protected updateDomElement(element: HTMLElement): void {
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const aux = document.createElement("div");
|
||||
aux.innerHTML = this.props.html;
|
||||
const scripts = aux.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.length === 0) {
|
||||
eval(scripts[i].innerHTML.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { UnknownObject, WithModuleProps } from "../types";
|
||||
import {
|
||||
modulePropsDecoder,
|
||||
parseIntOr,
|
||||
decodeBase64,
|
||||
stringIsEmpty
|
||||
} from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type EventsHistoryProps = {
|
||||
type: ItemType.AUTO_SLA_GRAPH;
|
||||
maxTime: number | null;
|
||||
html: string;
|
||||
} & ItemProps &
|
||||
WithModuleProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the events history props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function eventsHistoryPropsDecoder(
|
||||
data: UnknownObject
|
||||
): EventsHistoryProps | never {
|
||||
if (stringIsEmpty(data.html) && stringIsEmpty(data.encodedHtml)) {
|
||||
throw new TypeError("missing html content.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.AUTO_SLA_GRAPH,
|
||||
maxTime: parseIntOr(data.maxTime, null),
|
||||
html: !stringIsEmpty(data.html)
|
||||
? data.html
|
||||
: decodeBase64(data.encodedHtml),
|
||||
...modulePropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class EventsHistory extends Item<EventsHistoryProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "events-history";
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const scripts = element.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.length === 0) {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
eval(scripts[i].innerHTML.trim());
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
protected updateDomElement(element: HTMLElement): void {
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const aux = document.createElement("div");
|
||||
aux.innerHTML = this.props.html;
|
||||
const scripts = aux.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.length === 0) {
|
||||
eval(scripts[i].innerHTML.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import Group, { groupPropsDecoder } from "./Group";
|
||||
|
||||
const genericRawProps = {
|
||||
id: 1,
|
||||
type: 11, // Group item = 11
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
isOnTop: false,
|
||||
parentId: null,
|
||||
aclGroupId: null
|
||||
};
|
||||
|
||||
const positionRawProps = {
|
||||
x: 100,
|
||||
y: 50
|
||||
};
|
||||
|
||||
const sizeRawProps = {
|
||||
width: 100,
|
||||
height: 100
|
||||
};
|
||||
|
||||
const groupRawProps = {
|
||||
imageSrc:
|
||||
"https://brutus.artica.lan:8081/uploads/-/system/project/avatar/1/1.png",
|
||||
groupId: 1
|
||||
};
|
||||
|
||||
describe("Group item", () => {
|
||||
const groupInstance = new Group(
|
||||
groupPropsDecoder({
|
||||
...genericRawProps,
|
||||
...positionRawProps,
|
||||
...sizeRawProps,
|
||||
...groupRawProps
|
||||
})
|
||||
);
|
||||
|
||||
it("should have the group class", () => {
|
||||
expect(
|
||||
groupInstance.elementRef.getElementsByClassName("group").length
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,80 @@
|
|||
import { LinkedVisualConsoleProps, UnknownObject } from "../types";
|
||||
import {
|
||||
linkedVCPropsDecoder,
|
||||
parseIntOr,
|
||||
notEmptyStringOr,
|
||||
stringIsEmpty,
|
||||
decodeBase64,
|
||||
parseBoolean
|
||||
} from "../lib";
|
||||
import Item, { ItemProps, itemBasePropsDecoder, ItemType } from "../Item";
|
||||
|
||||
export type GroupProps = {
|
||||
type: ItemType.GROUP_ITEM;
|
||||
groupId: number;
|
||||
imageSrc: string | null; // URL?
|
||||
statusImageSrc: string | null;
|
||||
showStatistics: boolean;
|
||||
html?: string | null;
|
||||
} & ItemProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
function extractHtml(data: UnknownObject): string | null {
|
||||
if (!stringIsEmpty(data.html)) return data.html;
|
||||
if (!stringIsEmpty(data.encodedHtml)) return decodeBase64(data.encodedHtml);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the group props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function groupPropsDecoder(data: UnknownObject): GroupProps | never {
|
||||
if (
|
||||
(typeof data.imageSrc !== "string" || data.imageSrc.length === 0) &&
|
||||
data.encodedHtml === null
|
||||
) {
|
||||
throw new TypeError("invalid image src.");
|
||||
}
|
||||
if (parseIntOr(data.groupId, null) === null) {
|
||||
throw new TypeError("invalid group Id.");
|
||||
}
|
||||
|
||||
const showStatistics = parseBoolean(data.showStatistics);
|
||||
const html = showStatistics ? extractHtml(data) : null;
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.GROUP_ITEM,
|
||||
groupId: parseInt(data.groupId),
|
||||
imageSrc: notEmptyStringOr(data.imageSrc, null),
|
||||
statusImageSrc: notEmptyStringOr(data.statusImageSrc, null),
|
||||
showStatistics,
|
||||
html,
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class Group extends Item<GroupProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "group";
|
||||
|
||||
if (!this.props.showStatistics && this.props.statusImageSrc !== null) {
|
||||
// Icon with status.
|
||||
element.style.background = `url(${this.props.statusImageSrc}) no-repeat`;
|
||||
element.style.backgroundSize = "contain";
|
||||
element.style.backgroundPosition = "center";
|
||||
} else if (this.props.showStatistics && this.props.html != null) {
|
||||
// Stats table.
|
||||
element.innerHTML = this.props.html;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { LinkedVisualConsoleProps, UnknownObject } from "../types";
|
||||
import { linkedVCPropsDecoder } from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type IconProps = {
|
||||
type: ItemType.ICON;
|
||||
imageSrc: string; // URL?
|
||||
} & ItemProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the icon props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function iconPropsDecoder(data: UnknownObject): IconProps | never {
|
||||
if (typeof data.imageSrc !== "string" || data.imageSrc.length === 0) {
|
||||
throw new TypeError("invalid image src.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.ICON,
|
||||
imageSrc: data.imageSrc,
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class Icon extends Item<IconProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "icon";
|
||||
element.style.background = `url(${this.props.imageSrc}) no-repeat`;
|
||||
element.style.backgroundSize = "contain";
|
||||
element.style.backgroundPosition = "center";
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { LinkedVisualConsoleProps, UnknownObject } from "../types";
|
||||
import { linkedVCPropsDecoder } from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type LabelProps = {
|
||||
type: ItemType.LABEL;
|
||||
} & ItemProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the label props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function labelPropsDecoder(data: UnknownObject): LabelProps | never {
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.LABEL,
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class Label extends Item<LabelProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "label";
|
||||
element.innerHTML = this.getLabelWithMacrosReplaced();
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override Item.createLabelDomElement
|
||||
* Create a new label for the visual console item.
|
||||
* @return Item label.
|
||||
*/
|
||||
public createLabelDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "visual-console-item-label";
|
||||
// Always return an empty label.
|
||||
return element;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
import { UnknownObject, Position, Size } from "../types";
|
||||
import { parseIntOr, notEmptyStringOr } from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
interface LineProps extends ItemProps {
|
||||
// Overrided properties.
|
||||
readonly type: ItemType.LINE_ITEM;
|
||||
label: null;
|
||||
isLinkEnabled: false;
|
||||
parentId: null;
|
||||
aclGroupId: null;
|
||||
// Custom properties.
|
||||
startPosition: Position;
|
||||
endPosition: Position;
|
||||
lineWidth: number;
|
||||
color: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the item props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function linePropsDecoder(data: UnknownObject): LineProps | never {
|
||||
const props: LineProps = {
|
||||
...itemBasePropsDecoder({ ...data, width: 1, height: 1 }), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.LINE_ITEM,
|
||||
label: null,
|
||||
isLinkEnabled: false,
|
||||
parentId: null,
|
||||
aclGroupId: null,
|
||||
// Initialize Position & Size.
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
// Custom properties.
|
||||
startPosition: {
|
||||
x: parseIntOr(data.startX, 0),
|
||||
y: parseIntOr(data.startY, 0)
|
||||
},
|
||||
endPosition: {
|
||||
x: parseIntOr(data.endX, 0),
|
||||
y: parseIntOr(data.endY, 0)
|
||||
},
|
||||
lineWidth: parseIntOr(data.lineWidth || data.borderWidth, 1),
|
||||
color: notEmptyStringOr(data.borderColor || data.color, null)
|
||||
};
|
||||
|
||||
/*
|
||||
* We need to enhance the props with the extracted size and position
|
||||
* of the box cause there are missing at the props update. A better
|
||||
* solution would be overriding the props setter to do it there, but
|
||||
* the language doesn't allow it while targetting ES5.
|
||||
* TODO: We need to figure out a more consistent solution.
|
||||
*/
|
||||
|
||||
return {
|
||||
...props,
|
||||
// Enhance the props extracting the box size and position.
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
...Line.extractBoxSizeAndPosition(props)
|
||||
};
|
||||
}
|
||||
|
||||
export default class Line extends Item<LineProps> {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public constructor(props: LineProps) {
|
||||
/*
|
||||
* We need to override the constructor cause we need to obtain
|
||||
* the
|
||||
* box size and position from the start and finish points
|
||||
* of the line.
|
||||
*/
|
||||
super({
|
||||
...props,
|
||||
...Line.extractBoxSizeAndPosition(props)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* To create the item's DOM representation.
|
||||
* @return Item.
|
||||
*/
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element: HTMLDivElement = document.createElement("div");
|
||||
element.className = "line";
|
||||
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
// SVG container.
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
// Set SVG size.
|
||||
svg.setAttribute(
|
||||
"width",
|
||||
(this.props.width + this.props.lineWidth).toString()
|
||||
);
|
||||
svg.setAttribute(
|
||||
"height",
|
||||
(this.props.height + this.props.lineWidth).toString()
|
||||
);
|
||||
const line = document.createElementNS(svgNS, "line");
|
||||
line.setAttribute(
|
||||
"x1",
|
||||
`${this.props.startPosition.x - this.props.x + this.props.lineWidth / 2}`
|
||||
);
|
||||
line.setAttribute(
|
||||
"y1",
|
||||
`${this.props.startPosition.y - this.props.y + this.props.lineWidth / 2}`
|
||||
);
|
||||
line.setAttribute(
|
||||
"x2",
|
||||
`${this.props.endPosition.x - this.props.x + this.props.lineWidth / 2}`
|
||||
);
|
||||
line.setAttribute(
|
||||
"y2",
|
||||
`${this.props.endPosition.y - this.props.y + this.props.lineWidth / 2}`
|
||||
);
|
||||
line.setAttribute("stroke", this.props.color || "black");
|
||||
line.setAttribute("stroke-width", this.props.lineWidth.toString());
|
||||
|
||||
svg.append(line);
|
||||
element.append(svg);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the size and position of the box from
|
||||
* the start and the finish of the line.
|
||||
* @param props Item properties.
|
||||
*/
|
||||
public static extractBoxSizeAndPosition(props: LineProps): Size & Position {
|
||||
return {
|
||||
width: Math.abs(props.startPosition.x - props.endPosition.x),
|
||||
height: Math.abs(props.startPosition.y - props.endPosition.y),
|
||||
x: Math.min(props.startPosition.x, props.endPosition.x),
|
||||
y: Math.min(props.startPosition.y, props.endPosition.y)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
import {
|
||||
LinkedVisualConsoleProps,
|
||||
UnknownObject,
|
||||
WithModuleProps
|
||||
} from "../types";
|
||||
import {
|
||||
linkedVCPropsDecoder,
|
||||
modulePropsDecoder,
|
||||
decodeBase64,
|
||||
stringIsEmpty
|
||||
} from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type ModuleGraphProps = {
|
||||
type: ItemType.MODULE_GRAPH;
|
||||
html: string;
|
||||
} & ItemProps &
|
||||
WithModuleProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the module graph props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function moduleGraphPropsDecoder(
|
||||
data: UnknownObject
|
||||
): ModuleGraphProps | never {
|
||||
if (stringIsEmpty(data.html) && stringIsEmpty(data.encodedHtml)) {
|
||||
throw new TypeError("missing html content.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.MODULE_GRAPH,
|
||||
html: !stringIsEmpty(data.html)
|
||||
? data.html
|
||||
: decodeBase64(data.encodedHtml),
|
||||
...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class ModuleGraph extends Item<ModuleGraphProps> {
|
||||
/**
|
||||
* @override Item.resizeElement.
|
||||
* Resize the DOM content container.
|
||||
* We need to override the resize function cause this item's height
|
||||
* is larger than the configured and the graph is over the label.
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
protected resizeElement(width: number): void {
|
||||
super.resizeElement(width, 0);
|
||||
}
|
||||
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "module-graph";
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Remove the overview graph.
|
||||
const legendP = element.getElementsByTagName("p");
|
||||
for (let i = 0; i < legendP.length; i++) {
|
||||
legendP[i].style.margin = "0px";
|
||||
}
|
||||
|
||||
// Remove the overview graph.
|
||||
const overviewGraphs = element.getElementsByClassName("overview_graph");
|
||||
for (let i = 0; i < overviewGraphs.length; i++) {
|
||||
overviewGraphs[i].remove();
|
||||
}
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const scripts = element.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.length === 0) {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
eval(scripts[i].innerHTML.trim());
|
||||
} catch (ignored) {} // eslint-disable-line no-empty
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
protected updateDomElement(element: HTMLElement): void {
|
||||
element.innerHTML = this.props.html;
|
||||
|
||||
// Remove the overview graph.
|
||||
const legendP = element.getElementsByTagName("p");
|
||||
for (let i = 0; i < legendP.length; i++) {
|
||||
legendP[i].style.margin = "0px";
|
||||
}
|
||||
|
||||
// Remove the overview graph.
|
||||
const overviewGraphs = element.getElementsByClassName("overview_graph");
|
||||
for (let i = 0; i < overviewGraphs.length; i++) {
|
||||
overviewGraphs[i].remove();
|
||||
}
|
||||
|
||||
// Hack to execute the JS after the HTML is added to the DOM.
|
||||
const aux = document.createElement("div");
|
||||
aux.innerHTML = this.props.html;
|
||||
const scripts = aux.getElementsByTagName("script");
|
||||
for (let i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.length === 0) {
|
||||
eval(scripts[i].innerHTML.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
import { arc as arcFactory } from "d3-shape";
|
||||
|
||||
import {
|
||||
LinkedVisualConsoleProps,
|
||||
UnknownObject,
|
||||
WithModuleProps
|
||||
} from "../types";
|
||||
import {
|
||||
linkedVCPropsDecoder,
|
||||
modulePropsDecoder,
|
||||
notEmptyStringOr,
|
||||
parseIntOr,
|
||||
parseFloatOr
|
||||
} from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type PercentileProps = {
|
||||
type: ItemType.PERCENTILE_BAR;
|
||||
percentileType:
|
||||
| "progress-bar"
|
||||
| "bubble"
|
||||
| "circular-progress-bar"
|
||||
| "circular-progress-bar-alt";
|
||||
valueType: "percent" | "value";
|
||||
minValue: number | null;
|
||||
maxValue: number | null;
|
||||
color: string | null;
|
||||
labelColor: string | null;
|
||||
value: number | null;
|
||||
unit: string | null;
|
||||
} & ItemProps &
|
||||
WithModuleProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw type value.
|
||||
* @param type Raw value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function extractPercentileType(type: any): PercentileProps["percentileType"] {
|
||||
switch (type) {
|
||||
case "progress-bar":
|
||||
case "bubble":
|
||||
case "circular-progress-bar":
|
||||
case "circular-progress-bar-alt":
|
||||
return type;
|
||||
default:
|
||||
case ItemType.PERCENTILE_BAR:
|
||||
return "progress-bar";
|
||||
case ItemType.PERCENTILE_BUBBLE:
|
||||
return "bubble";
|
||||
case ItemType.CIRCULAR_PROGRESS_BAR:
|
||||
return "circular-progress-bar";
|
||||
case ItemType.CIRCULAR_INTERIOR_PROGRESS_BAR:
|
||||
return "circular-progress-bar-alt";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw value type value.
|
||||
* @param type Raw value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function extractValueType(valueType: any): PercentileProps["valueType"] {
|
||||
switch (valueType) {
|
||||
case "percent":
|
||||
case "value":
|
||||
return valueType;
|
||||
default:
|
||||
return "percent";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the percentile props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function percentilePropsDecoder(
|
||||
data: UnknownObject
|
||||
): PercentileProps | never {
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.PERCENTILE_BAR,
|
||||
percentileType: extractPercentileType(data.percentileType || data.type),
|
||||
valueType: extractValueType(data.valueType),
|
||||
minValue: parseIntOr(data.minValue, null),
|
||||
maxValue: parseIntOr(data.maxValue, null),
|
||||
color: notEmptyStringOr(data.color, null),
|
||||
labelColor: notEmptyStringOr(data.labelColor, null),
|
||||
value: parseFloatOr(data.value, null),
|
||||
unit: notEmptyStringOr(data.unit, null),
|
||||
...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
const svgNS = "http://www.w3.org/2000/svg";
|
||||
|
||||
export default class Percentile extends Item<PercentileProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const colors = {
|
||||
background: "#000000",
|
||||
progress: this.props.color || "#F0F0F0",
|
||||
text: this.props.labelColor || "#444444"
|
||||
};
|
||||
// Progress.
|
||||
const progress = this.getProgress();
|
||||
// Main element.
|
||||
const element = document.createElement("div");
|
||||
// SVG container.
|
||||
const svg = document.createElementNS(svgNS, "svg");
|
||||
|
||||
switch (this.props.percentileType) {
|
||||
case "progress-bar":
|
||||
{
|
||||
const backgroundRect = document.createElementNS(svgNS, "rect");
|
||||
backgroundRect.setAttribute("fill", colors.background);
|
||||
backgroundRect.setAttribute("fill-opacity", "0.5");
|
||||
backgroundRect.setAttribute("width", "100");
|
||||
backgroundRect.setAttribute("height", "20");
|
||||
backgroundRect.setAttribute("rx", "5");
|
||||
backgroundRect.setAttribute("ry", "5");
|
||||
const progressRect = document.createElementNS(svgNS, "rect");
|
||||
progressRect.setAttribute("fill", colors.progress);
|
||||
progressRect.setAttribute("fill-opacity", "1");
|
||||
progressRect.setAttribute("width", `${progress}`);
|
||||
progressRect.setAttribute("height", "20");
|
||||
progressRect.setAttribute("rx", "5");
|
||||
progressRect.setAttribute("ry", "5");
|
||||
const text = document.createElementNS(svgNS, "text");
|
||||
text.setAttribute("text-anchor", "middle");
|
||||
text.setAttribute("alignment-baseline", "middle");
|
||||
text.setAttribute("font-size", "12");
|
||||
text.setAttribute("font-family", "arial");
|
||||
text.setAttribute("font-weight", "bold");
|
||||
text.setAttribute("transform", "translate(50 11)");
|
||||
text.setAttribute("fill", colors.text);
|
||||
|
||||
if (this.props.valueType === "value") {
|
||||
text.textContent = this.props.unit
|
||||
? `${this.props.value} ${this.props.unit}`
|
||||
: `${this.props.value}`;
|
||||
} else {
|
||||
text.textContent = `${progress}%`;
|
||||
}
|
||||
|
||||
// Auto resize SVG using the view box magic: https://css-tricks.com/scale-svg/
|
||||
svg.setAttribute("viewBox", "0 0 100 20");
|
||||
svg.append(backgroundRect, progressRect, text);
|
||||
}
|
||||
break;
|
||||
case "bubble":
|
||||
case "circular-progress-bar":
|
||||
case "circular-progress-bar-alt":
|
||||
{
|
||||
// Auto resize SVG using the view box magic: https://css-tricks.com/scale-svg/
|
||||
svg.setAttribute("viewBox", "0 0 100 100");
|
||||
|
||||
if (this.props.percentileType === "bubble") {
|
||||
// Create and append the circles.
|
||||
const backgroundCircle = document.createElementNS(svgNS, "circle");
|
||||
backgroundCircle.setAttribute("transform", "translate(50 50)");
|
||||
backgroundCircle.setAttribute("fill", colors.background);
|
||||
backgroundCircle.setAttribute("fill-opacity", "0.5");
|
||||
backgroundCircle.setAttribute("r", "50");
|
||||
const progressCircle = document.createElementNS(svgNS, "circle");
|
||||
progressCircle.setAttribute("transform", "translate(50 50)");
|
||||
progressCircle.setAttribute("fill", colors.progress);
|
||||
progressCircle.setAttribute("fill-opacity", "1");
|
||||
progressCircle.setAttribute("r", `${progress / 2}`);
|
||||
|
||||
svg.append(backgroundCircle, progressCircle);
|
||||
} else {
|
||||
// Create and append the circles.
|
||||
const arcProps = {
|
||||
innerRadius:
|
||||
this.props.percentileType === "circular-progress-bar" ? 30 : 0,
|
||||
outerRadius: 50,
|
||||
startAngle: 0,
|
||||
endAngle: Math.PI * 2
|
||||
};
|
||||
const arc = arcFactory();
|
||||
|
||||
const backgroundCircle = document.createElementNS(svgNS, "path");
|
||||
backgroundCircle.setAttribute("transform", "translate(50 50)");
|
||||
backgroundCircle.setAttribute("fill", colors.background);
|
||||
backgroundCircle.setAttribute("fill-opacity", "0.5");
|
||||
backgroundCircle.setAttribute("d", `${arc(arcProps)}`);
|
||||
const progressCircle = document.createElementNS(svgNS, "path");
|
||||
progressCircle.setAttribute("transform", "translate(50 50)");
|
||||
progressCircle.setAttribute("fill", colors.progress);
|
||||
progressCircle.setAttribute("fill-opacity", "1");
|
||||
progressCircle.setAttribute(
|
||||
"d",
|
||||
`${arc({
|
||||
...arcProps,
|
||||
endAngle: arcProps.endAngle * (progress / 100)
|
||||
})}`
|
||||
);
|
||||
|
||||
svg.append(backgroundCircle, progressCircle);
|
||||
}
|
||||
|
||||
// Create and append the text.
|
||||
const text = document.createElementNS(svgNS, "text");
|
||||
text.setAttribute("text-anchor", "middle");
|
||||
text.setAttribute("alignment-baseline", "middle");
|
||||
text.setAttribute("font-size", "16");
|
||||
text.setAttribute("font-family", "arial");
|
||||
text.setAttribute("font-weight", "bold");
|
||||
text.setAttribute("fill", colors.text);
|
||||
|
||||
if (this.props.valueType === "value") {
|
||||
// Show value and unit in 1 (no unit) or 2 lines.
|
||||
if (this.props.unit && this.props.unit.length > 0) {
|
||||
const value = document.createElementNS(svgNS, "tspan");
|
||||
value.setAttribute("x", "0");
|
||||
value.setAttribute("dy", "1em");
|
||||
value.textContent = `${this.props.value}`;
|
||||
const unit = document.createElementNS(svgNS, "tspan");
|
||||
unit.setAttribute("x", "0");
|
||||
unit.setAttribute("dy", "1em");
|
||||
unit.textContent = `${this.props.unit}`;
|
||||
text.append(value, unit);
|
||||
text.setAttribute("transform", "translate(50 33)");
|
||||
} else {
|
||||
text.textContent = `${this.props.value}`;
|
||||
text.setAttribute("transform", "translate(50 50)");
|
||||
}
|
||||
} else {
|
||||
// Percentage.
|
||||
text.textContent = `${progress}%`;
|
||||
text.setAttribute("transform", "translate(50 50)");
|
||||
}
|
||||
|
||||
svg.append(text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
element.append(svg);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private getProgress(): number {
|
||||
const minValue = this.props.minValue || 0;
|
||||
const maxValue = this.props.maxValue || 100;
|
||||
const value = this.props.value || 100;
|
||||
|
||||
if (value <= minValue) return 0;
|
||||
else if (value >= maxValue) return 100;
|
||||
else return ((value - minValue) / (maxValue - minValue)) * 100;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { UnknownObject } from "../types";
|
||||
import {
|
||||
stringIsEmpty,
|
||||
notEmptyStringOr,
|
||||
decodeBase64,
|
||||
parseIntOr
|
||||
} from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type ServiceProps = {
|
||||
type: ItemType.SERVICE;
|
||||
serviceId: number;
|
||||
imageSrc: string | null;
|
||||
statusImageSrc: string | null;
|
||||
encodedTitle: string | null;
|
||||
} & ItemProps;
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the service props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function servicePropsDecoder(data: UnknownObject): ServiceProps | never {
|
||||
if (data.imageSrc !== null) {
|
||||
if (
|
||||
typeof data.statusImageSrc !== "string" ||
|
||||
data.imageSrc.statusImageSrc === 0
|
||||
) {
|
||||
throw new TypeError("invalid status image src.");
|
||||
}
|
||||
} else {
|
||||
if (stringIsEmpty(data.encodedTitle)) {
|
||||
throw new TypeError("missing encode tittle content.");
|
||||
}
|
||||
}
|
||||
|
||||
if (parseIntOr(data.serviceId, null) === null) {
|
||||
throw new TypeError("invalid service id.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.SERVICE,
|
||||
serviceId: data.serviceId,
|
||||
imageSrc: notEmptyStringOr(data.imageSrc, null),
|
||||
statusImageSrc: notEmptyStringOr(data.statusImageSrc, null),
|
||||
encodedTitle: notEmptyStringOr(data.encodedTitle, null)
|
||||
};
|
||||
}
|
||||
|
||||
export default class Service extends Item<ServiceProps> {
|
||||
public createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "service";
|
||||
|
||||
if (this.props.statusImageSrc !== null) {
|
||||
element.style.background = `url(${this.props.statusImageSrc}) no-repeat`;
|
||||
element.style.backgroundSize = "contain";
|
||||
element.style.backgroundPosition = "center";
|
||||
} else if (this.props.encodedTitle !== null) {
|
||||
element.innerHTML = decodeBase64(this.props.encodedTitle);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
import {
|
||||
LinkedVisualConsoleProps,
|
||||
UnknownObject,
|
||||
WithModuleProps
|
||||
} from "../types";
|
||||
import {
|
||||
linkedVCPropsDecoder,
|
||||
parseIntOr,
|
||||
modulePropsDecoder,
|
||||
replaceMacros
|
||||
} from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type SimpleValueProps = {
|
||||
type: ItemType.SIMPLE_VALUE;
|
||||
valueType: "string" | "image";
|
||||
value: string;
|
||||
} & (
|
||||
| {
|
||||
processValue: "none";
|
||||
}
|
||||
| {
|
||||
processValue: "avg" | "max" | "min";
|
||||
period: number;
|
||||
}) &
|
||||
ItemProps &
|
||||
WithModuleProps &
|
||||
LinkedVisualConsoleProps;
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw value type.
|
||||
* @param valueType Raw value.
|
||||
*/
|
||||
const parseValueType = (
|
||||
valueType: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): SimpleValueProps["valueType"] => {
|
||||
switch (valueType) {
|
||||
case "string":
|
||||
case "image":
|
||||
return valueType;
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw process value.
|
||||
* @param processValue Raw value.
|
||||
*/
|
||||
const parseProcessValue = (
|
||||
processValue: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): SimpleValueProps["processValue"] => {
|
||||
switch (processValue) {
|
||||
case "none":
|
||||
case "avg":
|
||||
case "max":
|
||||
case "min":
|
||||
return processValue;
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the simple value props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function simpleValuePropsDecoder(
|
||||
data: UnknownObject
|
||||
): SimpleValueProps | never {
|
||||
if (typeof data.value !== "string" || data.value.length === 0) {
|
||||
throw new TypeError("invalid value");
|
||||
}
|
||||
|
||||
const processValue = parseProcessValue(data.processValue);
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.SIMPLE_VALUE,
|
||||
valueType: parseValueType(data.valueType),
|
||||
value: data.value,
|
||||
...(processValue === "none"
|
||||
? { processValue }
|
||||
: { processValue, period: parseIntOr(data.period, 0) }), // Object spread. It will merge the properties of the two objects.
|
||||
...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class SimpleValue extends Item<SimpleValueProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "simple-value";
|
||||
|
||||
if (this.props.valueType === "image") {
|
||||
const img = document.createElement("img");
|
||||
img.src = this.props.value;
|
||||
element.append(img);
|
||||
} else {
|
||||
// Add the value to the label and show it.
|
||||
let text = this.props.value;
|
||||
let label = this.getLabelWithMacrosReplaced();
|
||||
if (label.length > 0) {
|
||||
text = replaceMacros([{ macro: /\(?_VALUE_\)?/i, value: text }], label);
|
||||
}
|
||||
|
||||
element.innerHTML = text;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override Item.createLabelDomElement
|
||||
* Create a new label for the visual console item.
|
||||
* @return Item label.
|
||||
*/
|
||||
protected createLabelDomElement(): HTMLElement {
|
||||
const element = document.createElement("div");
|
||||
element.className = "visual-console-item-label";
|
||||
// Always return an empty label.
|
||||
return element;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import {
|
||||
WithModuleProps,
|
||||
LinkedVisualConsoleProps,
|
||||
UnknownObject
|
||||
} from "../types";
|
||||
|
||||
import {
|
||||
modulePropsDecoder,
|
||||
linkedVCPropsDecoder,
|
||||
notEmptyStringOr
|
||||
} from "../lib";
|
||||
import Item, { ItemType, ItemProps, itemBasePropsDecoder } from "../Item";
|
||||
|
||||
export type StaticGraphProps = {
|
||||
type: ItemType.STATIC_GRAPH;
|
||||
imageSrc: string; // URL?
|
||||
showLastValueTooltip: "default" | "enabled" | "disabled";
|
||||
statusImageSrc: string | null; // URL?
|
||||
lastValue: string | null;
|
||||
} & ItemProps &
|
||||
(WithModuleProps | LinkedVisualConsoleProps);
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw unknown value.
|
||||
* @param showLastValueTooltip Raw value.
|
||||
*/
|
||||
const parseShowLastValueTooltip = (
|
||||
showLastValueTooltip: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): StaticGraphProps["showLastValueTooltip"] => {
|
||||
switch (showLastValueTooltip) {
|
||||
case "default":
|
||||
case "enabled":
|
||||
case "disabled":
|
||||
return showLastValueTooltip;
|
||||
default:
|
||||
return "default";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* This will allow us to ensure the type safety.
|
||||
*
|
||||
* @param data Raw object.
|
||||
* @return An object representing the static graph props.
|
||||
* @throws Will throw a TypeError if some property
|
||||
* is missing from the raw object or have an invalid type.
|
||||
*/
|
||||
export function staticGraphPropsDecoder(
|
||||
data: UnknownObject
|
||||
): StaticGraphProps | never {
|
||||
if (typeof data.imageSrc !== "string" || data.imageSrc.length === 0) {
|
||||
throw new TypeError("invalid image src.");
|
||||
}
|
||||
|
||||
return {
|
||||
...itemBasePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
type: ItemType.STATIC_GRAPH,
|
||||
imageSrc: data.imageSrc,
|
||||
showLastValueTooltip: parseShowLastValueTooltip(data.showLastValueTooltip),
|
||||
statusImageSrc: notEmptyStringOr(data.statusImageSrc, null),
|
||||
lastValue: notEmptyStringOr(data.lastValue, null),
|
||||
...modulePropsDecoder(data), // Object spread. It will merge the properties of the two objects.
|
||||
...linkedVCPropsDecoder(data) // Object spread. It will merge the properties of the two objects.
|
||||
};
|
||||
}
|
||||
|
||||
export default class StaticGraph extends Item<StaticGraphProps> {
|
||||
protected createDomElement(): HTMLElement {
|
||||
const imgSrc = this.props.statusImageSrc || this.props.imageSrc;
|
||||
const element = document.createElement("div");
|
||||
element.className = "static-graph";
|
||||
element.style.background = `url(${imgSrc}) no-repeat`;
|
||||
element.style.backgroundSize = "contain";
|
||||
element.style.backgroundPosition = "center";
|
||||
|
||||
// Show last value in a tooltip.
|
||||
if (
|
||||
this.props.lastValue !== null &&
|
||||
this.props.showLastValueTooltip !== "disabled"
|
||||
) {
|
||||
element.className = "static-graph image forced_title";
|
||||
element.setAttribute("data-use_title_for_force_title", "1");
|
||||
element.setAttribute("data-title", this.props.lastValue);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
import {
|
||||
parseIntOr,
|
||||
stringIsEmpty,
|
||||
notEmptyStringOr,
|
||||
leftPad,
|
||||
prefixedCssRules,
|
||||
decodeBase64,
|
||||
humanDate,
|
||||
humanTime,
|
||||
replaceMacros
|
||||
} from "./lib";
|
||||
|
||||
describe("function parseIntOr", () => {
|
||||
it("should retrieve valid int or a default value", () => {
|
||||
expect(parseIntOr("Foo", null)).toBe(null);
|
||||
expect(parseIntOr("1a", null)).toBe(1);
|
||||
expect(parseIntOr("a1", null)).toBe(null);
|
||||
expect(parseIntOr("1", null)).toBe(1);
|
||||
expect(parseIntOr(false, null)).toBe(null);
|
||||
expect(parseIntOr(true, null)).toBe(null);
|
||||
expect(parseIntOr(1, null)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("function stringIsEmpty", () => {
|
||||
it("should check properly if a string is empry or not", () => {
|
||||
expect(stringIsEmpty()).toBe(true);
|
||||
expect(stringIsEmpty("")).toBe(true);
|
||||
expect(stringIsEmpty("foo")).toBe(false);
|
||||
expect(stringIsEmpty("bar")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("function notEmptyStringOr", () => {
|
||||
it("should retrieve not empty string or a default value", () => {
|
||||
expect(notEmptyStringOr("", null)).toBe(null);
|
||||
expect(notEmptyStringOr("Foo", null)).toBe("Foo");
|
||||
expect(notEmptyStringOr(1, 1)).toBe(1);
|
||||
expect(notEmptyStringOr(1, 0)).toBe(0);
|
||||
expect(notEmptyStringOr("", 0)).toBe(0);
|
||||
expect(notEmptyStringOr("Foo", "Bar")).toBe("Foo");
|
||||
expect(notEmptyStringOr(0, "Bar")).toBe("Bar");
|
||||
});
|
||||
});
|
||||
|
||||
describe("function leftPad", () => {
|
||||
it("should pad properly", () => {
|
||||
expect(leftPad(1, 2, 0)).toBe("01");
|
||||
expect(leftPad(1, 4, 0)).toBe("0001");
|
||||
expect(leftPad(1, 4, "0")).toBe("0001");
|
||||
expect(leftPad("1", 4, "0")).toBe("0001");
|
||||
expect(leftPad(10, 4, 0)).toBe("0010");
|
||||
expect(leftPad("bar", 6, "foo")).toBe("foobar");
|
||||
expect(leftPad("bar", 11, "foo")).toBe("foofoofobar");
|
||||
expect(leftPad("bar", 4, "foo")).toBe("fbar");
|
||||
expect(leftPad("bar", 2, "foo")).toBe("ar");
|
||||
expect(leftPad("bar", 3, "foo")).toBe("bar");
|
||||
});
|
||||
});
|
||||
|
||||
describe("function prefixedCssRules", () => {
|
||||
it("should add the prefixes to the rules", () => {
|
||||
const rules = prefixedCssRules("transform", "rotate(0)");
|
||||
expect(rules).toContainEqual("transform: rotate(0);");
|
||||
expect(rules).toContainEqual("-webkit-transform: rotate(0);");
|
||||
expect(rules).toContainEqual("-moz-transform: rotate(0);");
|
||||
expect(rules).toContainEqual("-ms-transform: rotate(0);");
|
||||
expect(rules).toContainEqual("-o-transform: rotate(0);");
|
||||
expect(rules).toHaveLength(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("function decodeBase64", () => {
|
||||
it("should decode the base64 without errors", () => {
|
||||
expect(decodeBase64("SGkgSSdtIGRlY29kZWQ=")).toEqual("Hi I'm decoded");
|
||||
expect(decodeBase64("Rk9PQkFSQkFa")).toEqual("FOOBARBAZ");
|
||||
expect(decodeBase64("eyJpZCI6MSwibmFtZSI6ImZvbyJ9")).toEqual(
|
||||
'{"id":1,"name":"foo"}'
|
||||
);
|
||||
expect(
|
||||
decodeBase64("PGRpdj5Cb3ggPHA+UGFyYWdyYXBoPC9wPjxociAvPjwvZGl2Pg==")
|
||||
).toEqual("<div>Box <p>Paragraph</p><hr /></div>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("humanDate function", () => {
|
||||
it("should return the date with padded 0's", () => {
|
||||
const expected = "01/02/0123";
|
||||
const date = new Date(`02/01/0123 12:00:00`);
|
||||
const digitalDate = humanDate(date);
|
||||
expect(digitalDate).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("humanTime function", () => {
|
||||
it("should return the time with padded 0's when hours/minutes/seconds are less than 10", () => {
|
||||
const expected = "01:02:03";
|
||||
const date = new Date(`01/01/1970 ${expected}`);
|
||||
const digitalTime = humanTime(date);
|
||||
expect(digitalTime).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replaceMacros function", () => {
|
||||
const macros = [
|
||||
{ macro: "_foo_", value: "foo" },
|
||||
{ macro: "_bar_", value: "bar" },
|
||||
{ macro: "_baz_", value: "baz" }
|
||||
];
|
||||
|
||||
it("should not replace anything if it doesn't find any macro", () => {
|
||||
const text = "Lorem Ipsum";
|
||||
expect(replaceMacros(macros, text)).toBe(text);
|
||||
});
|
||||
|
||||
it("should replace the macros", () => {
|
||||
const text = "Lorem _foo_ Ipsum _baz_";
|
||||
expect(replaceMacros(macros, text)).toBe("Lorem foo Ipsum baz");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,335 @@
|
|||
import {
|
||||
UnknownObject,
|
||||
Position,
|
||||
Size,
|
||||
WithAgentProps,
|
||||
WithModuleProps,
|
||||
LinkedVisualConsoleProps,
|
||||
LinkedVisualConsolePropsStatus
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Return a number or a default value from a raw value.
|
||||
* @param value Raw value from which we will try to extract a valid number.
|
||||
* @param defaultValue Default value to use if we cannot extract a valid number.
|
||||
* @return A valid number or the default value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function parseIntOr<T>(value: any, defaultValue: T): number | T {
|
||||
if (typeof value === "number") return value;
|
||||
if (typeof value === "string" && value.length > 0 && !isNaN(parseInt(value)))
|
||||
return parseInt(value);
|
||||
else return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a number or a default value from a raw value.
|
||||
* @param value Raw value from which we will try to extract a valid number.
|
||||
* @param defaultValue Default value to use if we cannot extract a valid number.
|
||||
* @return A valid number or the default value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function parseFloatOr<T>(value: any, defaultValue: T): number | T {
|
||||
if (typeof value === "number") return value;
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
value.length > 0 &&
|
||||
!isNaN(parseFloat(value))
|
||||
)
|
||||
return parseFloat(value);
|
||||
else return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string exists and it's not empty.
|
||||
* @param value Value to check.
|
||||
* @return The check result.
|
||||
*/
|
||||
export function stringIsEmpty(value?: string | null): boolean {
|
||||
return value == null || value.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a not empty string or a default value from a raw value.
|
||||
* @param value Raw value from which we will try to extract a non empty string.
|
||||
* @param defaultValue Default value to use if we cannot extract a non empty string.
|
||||
* @return A non empty string or the default value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function notEmptyStringOr<T>(value: any, defaultValue: T): string | T {
|
||||
return typeof value === "string" && value.length > 0 ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean from a raw value.
|
||||
* @param value Raw value from which we will try to extract the boolean.
|
||||
* @return A valid boolean value. false by default.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function parseBoolean(value: any): boolean {
|
||||
if (typeof value === "boolean") return value;
|
||||
else if (typeof value === "number") return value > 0;
|
||||
else if (typeof value === "string") return value === "1" || value === "true";
|
||||
else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad the current string with another string (multiple times, if needed)
|
||||
* until the resulting string reaches the given length.
|
||||
* The padding is applied from the start (left) of the current string.
|
||||
* @param value Text that needs to be padded.
|
||||
* @param length Length of the returned text.
|
||||
* @param pad Text to add.
|
||||
* @return Padded text.
|
||||
*/
|
||||
export function leftPad(
|
||||
value: string | number,
|
||||
length: number,
|
||||
pad: string | number = " "
|
||||
): string {
|
||||
if (typeof value === "number") value = `${value}`;
|
||||
if (typeof pad === "number") pad = `${pad}`;
|
||||
|
||||
const diffLength = length - value.length;
|
||||
if (diffLength === 0) return value;
|
||||
if (diffLength < 0) return value.substr(Math.abs(diffLength));
|
||||
|
||||
if (diffLength === pad.length) return `${pad}${value}`;
|
||||
if (diffLength < pad.length) return `${pad.substring(0, diffLength)}${value}`;
|
||||
|
||||
const repeatTimes = Math.floor(diffLength / pad.length);
|
||||
const restLength = diffLength - pad.length * repeatTimes;
|
||||
|
||||
let newPad = "";
|
||||
for (let i = 0; i < repeatTimes; i++) newPad += pad;
|
||||
|
||||
if (restLength === 0) return `${newPad}${value}`;
|
||||
return `${newPad}${pad.substring(0, restLength)}${value}`;
|
||||
}
|
||||
|
||||
/* Decoders */
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* @param data Raw object.
|
||||
* @return An object representing the position.
|
||||
*/
|
||||
export function positionPropsDecoder(data: UnknownObject): Position {
|
||||
return {
|
||||
x: parseIntOr(data.x, 0),
|
||||
y: parseIntOr(data.y, 0)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* @param data Raw object.
|
||||
* @return An object representing the size.
|
||||
* @throws Will throw a TypeError if the width and height are not valid numbers.
|
||||
*/
|
||||
export function sizePropsDecoder(data: UnknownObject): Size | never {
|
||||
if (
|
||||
data.width == null ||
|
||||
isNaN(parseInt(data.width)) ||
|
||||
data.height == null ||
|
||||
isNaN(parseInt(data.height))
|
||||
) {
|
||||
throw new TypeError("invalid size.");
|
||||
}
|
||||
|
||||
return {
|
||||
width: parseInt(data.width),
|
||||
height: parseInt(data.height)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* @param data Raw object.
|
||||
* @return An object representing the agent properties.
|
||||
*/
|
||||
export function agentPropsDecoder(data: UnknownObject): WithAgentProps {
|
||||
const agentProps: WithAgentProps = {
|
||||
agentId: parseIntOr(data.agent, null),
|
||||
agentName: notEmptyStringOr(data.agentName, null),
|
||||
agentAlias: notEmptyStringOr(data.agentAlias, null),
|
||||
agentDescription: notEmptyStringOr(data.agentDescription, null),
|
||||
agentAddress: notEmptyStringOr(data.agentAddress, null)
|
||||
};
|
||||
|
||||
return data.metaconsoleId != null
|
||||
? {
|
||||
metaconsoleId: data.metaconsoleId,
|
||||
...agentProps // Object spread: http://es6-features.org/#SpreadOperator
|
||||
}
|
||||
: agentProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* @param data Raw object.
|
||||
* @return An object representing the module and agent properties.
|
||||
*/
|
||||
export function modulePropsDecoder(data: UnknownObject): WithModuleProps {
|
||||
return {
|
||||
moduleId: parseIntOr(data.moduleId, null),
|
||||
moduleName: notEmptyStringOr(data.moduleName, null),
|
||||
moduleDescription: notEmptyStringOr(data.moduleDescription, null),
|
||||
...agentPropsDecoder(data) // Object spread: http://es6-features.org/#SpreadOperator
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid typed object from a raw object.
|
||||
* @param data Raw object.
|
||||
* @return An object representing the linked visual console properties.
|
||||
* @throws Will throw a TypeError if the status calculation properties are invalid.
|
||||
*/
|
||||
export function linkedVCPropsDecoder(
|
||||
data: UnknownObject
|
||||
): LinkedVisualConsoleProps | never {
|
||||
// Object destructuring: http://es6-features.org/#ObjectMatchingShorthandNotation
|
||||
const {
|
||||
metaconsoleId,
|
||||
linkedLayoutId: id,
|
||||
linkedLayoutAgentId: agentId
|
||||
} = data;
|
||||
|
||||
let linkedLayoutStatusProps: LinkedVisualConsolePropsStatus = {
|
||||
linkedLayoutStatusType: "default"
|
||||
};
|
||||
switch (data.linkedLayoutStatusType) {
|
||||
case "weight": {
|
||||
const weight = parseIntOr(data.linkedLayoutStatusTypeWeight, null);
|
||||
if (weight == null)
|
||||
throw new TypeError("invalid status calculation properties.");
|
||||
|
||||
if (data.linkedLayoutStatusTypeWeight)
|
||||
linkedLayoutStatusProps = {
|
||||
linkedLayoutStatusType: "weight",
|
||||
linkedLayoutStatusTypeWeight: weight
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "service": {
|
||||
const warningThreshold = parseIntOr(
|
||||
data.linkedLayoutStatusTypeWarningThreshold,
|
||||
null
|
||||
);
|
||||
const criticalThreshold = parseIntOr(
|
||||
data.linkedLayoutStatusTypeCriticalThreshold,
|
||||
null
|
||||
);
|
||||
if (warningThreshold == null || criticalThreshold == null) {
|
||||
throw new TypeError("invalid status calculation properties.");
|
||||
}
|
||||
|
||||
linkedLayoutStatusProps = {
|
||||
linkedLayoutStatusType: "service",
|
||||
linkedLayoutStatusTypeWarningThreshold: warningThreshold,
|
||||
linkedLayoutStatusTypeCriticalThreshold: criticalThreshold
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const linkedLayoutBaseProps = {
|
||||
linkedLayoutId: parseIntOr(id, null),
|
||||
linkedLayoutAgentId: parseIntOr(agentId, null),
|
||||
...linkedLayoutStatusProps // Object spread: http://es6-features.org/#SpreadOperator
|
||||
};
|
||||
|
||||
return metaconsoleId != null
|
||||
? {
|
||||
metaconsoleId,
|
||||
...linkedLayoutBaseProps // Object spread: http://es6-features.org/#SpreadOperator
|
||||
}
|
||||
: linkedLayoutBaseProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get a CSS rule with the most used prefixes.
|
||||
* @param ruleName Name of the CSS rule.
|
||||
* @param ruleValue Value of the CSS rule.
|
||||
* @return An array of rules with the prefixes applied.
|
||||
*/
|
||||
export function prefixedCssRules(
|
||||
ruleName: string,
|
||||
ruleValue: string
|
||||
): string[] {
|
||||
const rule = `${ruleName}: ${ruleValue};`;
|
||||
return [
|
||||
`-webkit-${rule}`,
|
||||
`-moz-${rule}`,
|
||||
`-ms-${rule}`,
|
||||
`-o-${rule}`,
|
||||
`${rule}`
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a base64 string.
|
||||
* @param input Data encoded using base64.
|
||||
* @return Decoded data.
|
||||
*/
|
||||
export function decodeBase64(input: string): string {
|
||||
return decodeURIComponent(escape(window.atob(input)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a date representation with the format 'd/m/Y'.
|
||||
* @param initialDate Date to be used instead of a generated one.
|
||||
* @param locale Locale to use if localization is required and available.
|
||||
* @example 24/02/2020.
|
||||
* @return Date representation.
|
||||
*/
|
||||
export function humanDate(date: Date, locale: string | null = null): string {
|
||||
if (locale && Intl && Intl.DateTimeFormat) {
|
||||
// Format using the user locale.
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
};
|
||||
return Intl.DateTimeFormat(locale, options).format(date);
|
||||
} else {
|
||||
// Use getDate, getDay returns the week day.
|
||||
const day = leftPad(date.getDate(), 2, 0);
|
||||
// The getMonth function returns the month starting by 0.
|
||||
const month = leftPad(date.getMonth() + 1, 2, 0);
|
||||
const year = leftPad(date.getFullYear(), 4, 0);
|
||||
|
||||
// Format: 'd/m/Y'.
|
||||
return `${day}/${month}/${year}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a time representation with the format 'hh:mm:ss'.
|
||||
* @param initialDate Date to be used instead of a generated one.
|
||||
* @example 01:34:09.
|
||||
* @return Time representation.
|
||||
*/
|
||||
export function humanTime(date: Date): string {
|
||||
const hours = leftPad(date.getHours(), 2, 0);
|
||||
const minutes = leftPad(date.getMinutes(), 2, 0);
|
||||
const seconds = leftPad(date.getSeconds(), 2, 0);
|
||||
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
interface Macro {
|
||||
macro: string | RegExp;
|
||||
value: string;
|
||||
}
|
||||
/**
|
||||
* Replace the macros of a text.
|
||||
* @param macros List of macros and their replacements.
|
||||
* @param text Text in which we need to replace the macros.
|
||||
*/
|
||||
export function replaceMacros(macros: Macro[], text: string): string {
|
||||
return macros.reduce(
|
||||
(acc, { macro, value }) => acc.replace(macro, value),
|
||||
text
|
||||
);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#visual-console-container {
|
||||
margin: 0px auto;
|
||||
position: relative;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.visual-console-item {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: initial;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
user-select: text;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
export interface UnknownObject {
|
||||
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface WithAgentProps {
|
||||
metaconsoleId?: number | null;
|
||||
agentId: number | null;
|
||||
agentName: string | null;
|
||||
agentAlias: string | null;
|
||||
agentDescription: string | null;
|
||||
agentAddress: string | null;
|
||||
}
|
||||
|
||||
export interface WithModuleProps extends WithAgentProps {
|
||||
moduleId: number | null;
|
||||
moduleName: string | null;
|
||||
moduleDescription: string | null;
|
||||
}
|
||||
|
||||
export type LinkedVisualConsolePropsStatus =
|
||||
| {
|
||||
linkedLayoutStatusType: "default";
|
||||
}
|
||||
| {
|
||||
linkedLayoutStatusType: "weight";
|
||||
linkedLayoutStatusTypeWeight: number;
|
||||
}
|
||||
| {
|
||||
linkedLayoutStatusType: "service";
|
||||
linkedLayoutStatusTypeWarningThreshold: number;
|
||||
linkedLayoutStatusTypeCriticalThreshold: number;
|
||||
};
|
||||
export type LinkedVisualConsoleProps = {
|
||||
metaconsoleId?: number | null;
|
||||
linkedLayoutId: number | null;
|
||||
linkedLayoutAgentId: number | null;
|
||||
} & LinkedVisualConsolePropsStatus;
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "es6",
|
||||
"strict": true,
|
||||
"alwaysStrict": true,
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
// "isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"pretty": true
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const path = require("path");
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const CleanWebpackPlugin = require("clean-webpack-plugin");
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
|
||||
const dev = process.env.NODE_ENV !== "production";
|
||||
const entry = path.join(__dirname, "src", "index.ts");
|
||||
const buildPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
process.env.BUILD_PATH && process.env.BUILD_PATH.length > 0
|
||||
? process.env.BUILD_PATH
|
||||
: "build"
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
mode: dev ? "development" : "production",
|
||||
entry, // Start from this file.
|
||||
output: {
|
||||
path: buildPath, // The files will be created here.
|
||||
// filename: dev ? "vc.[name].min.js" : "vc.[name].[chunkhash:8].min.js"
|
||||
filename: "vc.[name].min.js"
|
||||
},
|
||||
devtool: "source-map",
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json"]
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
// Loader for the Typescript compiler.
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: "awesome-typescript-loader"
|
||||
},
|
||||
// This loader builds a main CSS file from all the CSS imports across the files.
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: [
|
||||
// https://github.com/webpack-contrib/mini-css-extract-plugin
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
hot: true, // if you want HMR - we try to automatically inject hot reloading but if it's not working, add it to the config
|
||||
reloadAll: true // when desperation kicks in - this is a brute force HMR flag
|
||||
}
|
||||
},
|
||||
// https://webpack.js.org/loaders/css-loader
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
},
|
||||
// To post process CSS and add some things like prefixes to the rules. e.g.: -webkit-...
|
||||
// https://github.com/postcss/postcss-loader
|
||||
{
|
||||
loader: "postcss-loader",
|
||||
options: {
|
||||
plugins: () => [
|
||||
// To improve the support for old browsers.
|
||||
require("autoprefixer")({
|
||||
browsers: ["> 1%", "last 2 versions"]
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// To allow the use of file imports. The imported files are transformed into
|
||||
// data uris if they are small enough or it returns a path to the file.
|
||||
// https://webpack.js.org/loaders/url-loader
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
|
||||
loader: "url-loader",
|
||||
options: {
|
||||
limit: 10000,
|
||||
// name: "[name].[hash:8].[ext]"
|
||||
name: "[name].[ext]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
// This plugin will remove all files inside Webpack's output.path directory,
|
||||
// as well as all unused webpack assets after every successful rebuild.
|
||||
new CleanWebpackPlugin(),
|
||||
// Options for the plugin which extract the CSS files to build a main file.
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
// filename: dev ? "vc.[name].css" : "vc.[name].[contenthash:8].css",
|
||||
filename: "vc.[name].css",
|
||||
// Disable to remove warnings about conflicting order between imports.
|
||||
orderWarning: true
|
||||
})
|
||||
],
|
||||
// Static server which runs the playground on npm start.
|
||||
devServer: {
|
||||
open: true,
|
||||
contentBase: "playground"
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue